Search code examples
javajinjava

How to make JinJava throw error if it can't find a binding


I need to use JinJava to process small templates that can contain different tokens. For example:

Hello,{{ user }}...

where a binding user is required because without it the output does not make any sense.

My class does not know what tokens template uses and it gets a list of bindings from remote services.

Therefore, there may be a case when a binding that is specified in a template does not exist. JinJava's default behavior is to use null that is converted to empty string in the output string. It does not work in my case, I need an exception to be thrown if any of bindings is missed.

My current solution is to use a custom Filter

/**
* A filter for JinJava that verifies that a value is not null and if the value is null, writes a fatal error to interpreter's log
*/
public class RequiredFilter implements Filter {
    @Override
    public Object filter(Object var, JinjavaInterpreter interpreter, String... args) {
        if (var == null) {
            interpreter.addError(new TemplateError(TemplateError.ErrorType.FATAL, TemplateError.ErrorReason.MISSING, "Value of a required key is null", "", 1, null));
        }
        return var;
    }

    @Override
    public String getName() {
        return "required";
    }
}

And make clients to explicitly specify this function for all tokens in templates. The example above now looks like:

Hello, {{ user|required }}...

and if the binding user does not exist, I get my exception. However, the requirement of a custom function looks strange.

Is there a way to set up JinJava to achieve the similar behavior and do not bother client by the requirement to specify the filter required every time?


Solution

  • Based on your solution with filters of tokens i did it by registering custom Tags for that purpose.

    public static String fillTemplateTagsWithProperties(
            String template, Map<String, String> properties) throws IllegalArgumentException
    {
        if (template == null) throw new IllegalArgumentException("template is null");
    
        Jinjava jinjava = new Jinjava();
        properties.forEach((k, v) -> jinjava.getGlobalContext().registerTag(new Tag() {
            @Override
            public String interpret(TagNode tagNode, JinjavaInterpreter interpreter) {
                if (v == null) {
                    interpreter.addError(new TemplateError(
                            TemplateError.ErrorType.FATAL,
                            TemplateError.ErrorReason.MISSING,
                            "Value of a required key is null",
                            k, 1, null));
                }
                return v;
            }
            @Override
            public String getEndTagName() {
                return null;
            }
            @Override
            public String getName() {
                return k;
            }
        }));
        RenderResult renderResult = jinjava.renderForResult(template, properties);
        if (renderResult.getErrors().size() == 0) {
            return renderResult.getOutput();
        } else {
            throw new IllegalArgumentException(renderResult.getErrors().stream()
                    .map(Object::toString)
                    .collect(Collectors.joining(",")));
        }
    }
    
    @Test
    public void jinjavaWithCustomTags(){
        String template = "Hallo {% hallo %}\n" +
                "I am {% unrendered %}";
    
        Map<String, String> props = new HashMap<>();
        props.put("hallo", "Welt");
        //props.put("unrendered", "[try null here]");
    
        System.out.println(fillTemplateTagsWithProperties(template, props));
    
    }
    

    which outputs:

    Hallo Welt
    I am [try null here]
    

    or throws:

    ### java.lang.IllegalArgumentException: TemplateError{severity=FATAL, reason=SYNTAX_ERROR, message=UnknownTagException: Syntax error in '{% unrendered %}': Unknown tag: unrendered, fieldName=null, lineno=2, item=OTHER}