CLOVER🍀

That was when it all began.

Mustache.javaでバインドした変数が見つからなかった場合に、例外を投げる

もともとは、Mustache.javaでバインドした変数が見つからなかった場合にどうなるのかという挙動を確認しようと思ったところから。

デフォルトでは、バインドした変数がなくてもエラーとならず、単にその部分が空になるだけのようです。
つまり、こういうテンプレートがあって

{{name}}

実際に「name」に対応する変数がなくてもエラーとはなりません。

この点の確認と、仮に変数がなかったらエラーとする(例外を投げる)にはどうすればいいのかということで、ちょっと調べてみました。

準備

まずは、Maven依存関係から。

        <dependency>
          <groupId>com.github.spullara.mustache.java</groupId>
          <artifactId>compiler</artifactId>
          <version>0.9.1</version>
        </dependency>

また、テストコードを書くので、JUnitとAssertJも加えます。

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.1.0</version>
            <scope>test</scope>
        </dependency>

デフォルトの動作確認

最初に、以降のコードについては以下のimport文があることを前提にします。

import java.io.StringReader;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.github.mustachejava.Binding;
import com.github.mustachejava.Code;
import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.Mustache;
import com.github.mustachejava.MustacheException;
import com.github.mustachejava.MustacheFactory;
import com.github.mustachejava.TemplateContext;
import com.github.mustachejava.reflect.GuardedBinding;
import com.github.mustachejava.reflect.MissingWrapper;
import com.github.mustachejava.reflect.ReflectionObjectHandler;
import com.github.mustachejava.util.Wrapper;
import org.junit.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.ThrowableAssert.catchThrowable;

で、確認。

    @Test
    public void testMissingVariable() {
        String template = "{{name}}";

        Map<String, Object> scope = new HashMap<>();
        scope.put("naming", "磯野 カツオ");

        MustacheFactory mf = new DefaultMustacheFactory();
        Mustache mustache = mf.compile(new StringReader(template), "example");

        StringWriter writer = new StringWriter();
        mustache.execute(writer, scope);
        writer.flush();

        assertThat(writer.toString())
                .isEqualTo("");
    }

テンプレートにはこう書いていますが

        String template = "{{name}}";

実際にバインドする時の名前を「naming」としています。

        Map<String, Object> scope = new HashMap<>();
        scope.put("naming", "磯野 カツオ");

結果としては、その部分がなかったことになります。

        assertThat(writer.toString())
                .isEqualTo("");

例外を投げる場合

ドキュメントには時に記載がありませんが、このようなケースで例外を投げようと思ったらテストケースを参考に実装すればよさそうです。
https://github.com/spullara/mustache.java/blob/master/compiler/src/test/java/com/github/mustachejava/FailOnMissingTest.java

まんまですが、こんな感じ。

    @Test
    public void testMissingVariableFailure() {
        String template = "{{name}}";

        Map<String, Object> scope = new HashMap<>();
        scope.put("naming", "磯野 カツオ");

        ReflectionObjectHandler roh = new ReflectionObjectHandler() {
            @Override
            public Binding createBinding(String name, final TemplateContext tc, Code code) {
                return new GuardedBinding(this, name, tc, code) {
                    @Override
                    protected synchronized Wrapper getWrapper(String name, List<Object> scopes) {
                        Wrapper wrapper = super.getWrapper(name, scopes);
                        if (wrapper instanceof MissingWrapper) {
                            throw new MustacheException(name + " not found in " + tc);
                        }
                        return wrapper;
                    }
                };
            }
        };

        DefaultMustacheFactory mf = new DefaultMustacheFactory();
        mf.setObjectHandler(roh);
        Mustache mustache = mf.compile(new StringReader(template), "example");

        Throwable thrown = catchThrowable(() -> {
            StringWriter writer = new StringWriter();
            mustache.execute(writer, scope);
            writer.flush();
        });

        assertThat(thrown)
                .isInstanceOf(MustacheException.class)
                .hasMessage("Failed to get value for name @[example:1]");
    }

ObjectHandlerなるものを、実装すればよいみたいです。今回は、ReflectionObjectHandlerですが。

        ReflectionObjectHandler roh = new ReflectionObjectHandler() {
            @Override
            public Binding createBinding(String name, final TemplateContext tc, Code code) {
                return new GuardedBinding(this, name, tc, code) {
                    @Override
                    protected synchronized Wrapper getWrapper(String name, List<Object> scopes) {
                        Wrapper wrapper = super.getWrapper(name, scopes);
                        if (wrapper instanceof MissingWrapper) {
                            throw new MustacheException(name + " not found in " + tc);
                        }
                        return wrapper;
                    }
                };
            }
        };

あと、ObjectHandlerを設定するには、MustacheFactoryの型のままだとダメなようなので、DefaultMustacheFactoryで扱います。

        DefaultMustacheFactory mf = new DefaultMustacheFactory();
        mf.setObjectHandler(roh);

とりあえず、挙動の確認と、設定方法の確認はできましたよっと