casid / jte

Secure and speedy templates for Java and Kotlin.
https://jte.gg
Apache License 2.0
748 stars 56 forks source link

Automatically call `.toString()` when outputting unknown types? #302

Closed milgner closed 9 months ago

milgner commented 9 months ago

I was passing a Kotlin value class into my template and trying to render it. Unfortunately, compilation failed because it couldn't find any matching overload for writeUserContent. Would it be possible to have a fallback that automatically uses the result of .toString() if the type is unknown? I like using domain-specific value classes (as outlined in the Arrow documentation here) a lot and peppering the templates with a lot of .toString() invocations would be quite cumbersome.


JteindexGenerated.kt:14:14 None of the following functions can be called with the arguments supplied: 
public open fun writeUserContent(p0: Content!): Unit defined in gg.jte.html.HtmlTemplateOutput
public open fun writeUserContent(p0: Boolean): Unit defined in gg.jte.html.HtmlTemplateOutput
public open fun writeUserContent(p0: Boolean!): Unit defined in gg.jte.html.HtmlTemplateOutput
public open fun writeUserContent(p0: Byte): Unit defined in gg.jte.html.HtmlTemplateOutput
public open fun writeUserContent(p0: Char): Unit defined in gg.jte.html.HtmlTemplateOutput
public open fun writeUserContent(p0: Char!): Unit defined in gg.jte.html.HtmlTemplateOutput
public open fun writeUserContent(p0: Double): Unit defined in gg.jte.html.HtmlTemplateOutput
public open fun writeUserContent(p0: Enum<*>!): Unit defined in gg.jte.html.HtmlTemplateOutput
public open fun writeUserContent(p0: Float): Unit defined in gg.jte.html.HtmlTemplateOutput
public open fun writeUserContent(p0: Int): Unit defined in gg.jte.html.HtmlTemplateOutput
public open fun writeUserContent(p0: Long): Unit defined in gg.jte.html.HtmlTemplateOutput
public open fun writeUserContent(p0: Number!): Unit defined in gg.jte.html.HtmlTemplateOutput
public open fun writeUserContent(p0: Short): Unit defined in gg.jte.html.HtmlTemplateOutput
public open fun writeUserContent(p0: String!): Unit defined in gg.jte.html.HtmlTemplateOutput
casid commented 9 months ago

Welcome, @milgner.

This is actually a feature to prevent leaking sensitive internal information in templates.

milgner commented 9 months ago

Oh, I see :sweat_smile: That makes a lot of sense. One possible work-around I found is using an extension function:

fun HtmlTemplateOutput.writeUserContent(valueObject: MyValueClass) = writeUserContent(valueObject.toString())

and then in the template

@import my.writeUserContent

This works at the cost of having to explicitly specify the import - which still looks a lot better than having to put .toString() into the template body.

Thanks for the quick reply!

casid commented 9 months ago

You could also let your Domain classes implement gg.jte.Content, but I think it is a bit smelly to have domain classes implement a framework-specific interface.

The extension method sounds like a great solution!

Typografikon commented 6 months ago

My idea was to implement custom StringOutput:

package some;

import some.domain.Image;

import gg.jte.output.*;

public class CustomStringOutput extends StringOutput /* implements TemplateOutput */ {

    public CustomStringOutput() {
        super();
    }

    void writeUserContent(Object value) {
        if (value != null) {
            writeUserContent(value.toString());
        }
    }

    void writeUserContent(Image value) {
        if (value != null) {
            writeUserContent(value.toString());
        }
    }

}

And then use it:

TemplateOutput jteOutput = new CustomStringOutput();
jteTemplateEngine.render(template, model, jteOutput);
String out = jteOutput.toString();

But I still got

Uncaught exception gg.jte.TemplateException: Failed to compile template, error at index.jte:151 /tmp/cz.typografikon.website.lecebka.Run/gg/jte/generated/ondemand/JteindexGenerated.java:45: error: no suitable method found for writeUserContent(Image) jteOutput.writeUserContent(image); ^ method TemplateOutput.writeUserContent(String) is not applicable (argument mismatch; Image cannot be converted to String)

Any suggestions? Thanks!