티스토리 뷰

회사에서 Handlebars.java 와 관련된 이슈가 공유가 되었다.

Handlebars 가 Javascript 버전과 Java 버전의 #with helper 결과가 동일하지 않습니다.

우선 이 이슈 버그를 해결한 코드는 필자의 github 저장소 https://github.com/powerumc/handlebars.java.bug-fix 에 커밋 되어 있고, 원본 저장소의 이슈 번호 #314, Pull Request #315 에 등록 되었다.

Handlebars vs Handlebars.java

이 테스트에서 사용되는 handlebars 데이터는 다음과 같습니다.

{ "company": { "ko": "쿠팡",
               "en": "Coupang" }}

그리고 handlebars 템플릿은 다음과 같다.

{{#with company}}
  우리 회사는 {{ko}}
  My company is {{company.en}}
{{/with}}
- Javascript handlebars 결과
  우리 회사는 쿠팡
  My company is 
- Handlebars.java 결과
  우리 회사는 쿠팡
  My company is Coupang

문제 원인

Handlebars.java 를 반나절 정도 분석하고 나니 대충 (정말 대충) 어떻게 흘러가는 지 조금 이해가 되었다.

문제는 Handlebars.java 에서는 #with helper 에서 value 값을 resolving 하지 못하면 parent object (=context) 에서 찾는다. #with helper 에게 전달된 데이터를 model 이라고 하면 전달된 데이터 전체를 context 로 불린다. 그래서 model 의 parent 는 context 가 된다.

위의 상황을 보면 My company is {{company.en}} 와 같은 코드의 model 에서 {#with company}}company.en을 찾지 못해서 context 에서 company.en을 찾게 된다.

문제 해결 방법

문제의 원인을 파악했으니 코드를 디버깅해 알겠지만 튜닝 포인트가 매우 다양하다. #with helper 전체를 뜯어 고칠 수 도 있고, 내부적으로 CompositeValueResolver 를 튜닝하거나, 그 외에 다양한 방법으로 고칠 수 있다.

가장 간단하게 이 버그를 픽스하기 위해 또 반나절 정도를 적용해 보고, 가장 적은 코드로 튜닝할 수 있는 코드를 만들었다.

Options.java 코드에서 wrap(final Object) 메서드를 다음과 같이 픽스하였다. 딱 세 줄만 추가해 주면 된다.

public Context wrap(final Object model) {
  if (model == context) {
    return context;
  }
  if (model == context.model()) {
    return context;
  }
  if (model instanceof Context) {
    return (Context) model;
  }
  if (model.getClass() == LinkedHashMap.class) {  // 이 코드부터...
    return Context.newContext(model);
  }                                               // 여기까지 추가...
    return Context.newContext(context, model);
  }

그럼 아래의 이슈가 되는 테스트 코드가 무사히 통과하고,Handlebars.java 의 모든 테스트도 통과한다.

public class Issue314 extends AbstractTest {

  @Test
  public void withHelperSpec() throws IOException {
    String context = "{ obj: { context: { one: 1, two: 2 } } }";

    shouldCompileTo("{{#with obj}}{{context.one}}{{/with}}",     context, "1");
    shouldCompileTo("{{#with obj}}{{obj.context.one}}{{/with}}", context, "");
 }
}


댓글