WADLを読み込んで REST Client になる TypeScript のソースコードを吐き出すツールを作ってみた

wadl2ts というWADLを読み込んで REST Client になる TypeScript のソースコードを吐き出すツールを作ってみました。 というか作り始めようとしました。簡単なサンプルレベルが動いたので記事にしようかと。ソースは github に上げています。

サーバ側

まず REST サービス のサーバ側が用意します。以前の記事と同様に Java の Jersey です。 どうせなら、Node.js の Web フレームワークにしようかと思っったのですが、WADL をレスポンスしてくれるヤツがあるのか分からないので諦めました。

package app;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.xml.bind.annotation.XmlRootElement;

@Path("calculator")
public class Calculator {

    @Path("add")
    @GET
    @Produces(MediaType.APPLICATION_XML)
    public Result add(@QueryParam("arg1") int arg1, @QueryParam("arg2") int arg2) {
        Result r = new Result();
        r.value = arg1 + arg2;
        return r;
    }

    @XmlRootElement
    public static class Result {
        public Integer value;
    }
}

リクエストパラメータを2つ受け取り、足し算してエンティティに詰めてレスポンスするだけです。 以下のURLにアクセスすると、

http://localhost:8080/wadl2ts-example/rest/calculator/add?arg1=1&arg2=2

以下のレスポンスが返ります。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><result><value>3</value></result>

以下のURLにアクセスすると、

http://localhost:8080/wadl2ts-example/rest/application.wadl

以下のような WADL が取得できます。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<application xmlns="http://wadl.dev.java.net/2009/02">
    <doc xmlns:jersey="http://jersey.java.net/" jersey:generatedBy="Jersey: 2.15 2015-01-12 22:32:50"/>
    <doc xmlns:jersey="http://jersey.java.net/" jersey:hint="This is simplified WADL with user and core resources only. To get full WADL with extended resources use the query parameter detail. Link: http://localhost:8080/wadl2ts-example/rest/application.wadl?detail=true"/>
    <grammars>
        <include href="application.wadl/xsd0.xsd">
            <doc title="Generated" xml:lang="en"/>
        </include>
    </grammars>
    <resources base="http://localhost:8080/wadl2ts-example/rest/">
        <resource path="calculator">
            <resource path="add">
                <method id="add" name="GET">
                    <request>
                        <param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="arg1" style="query" type="xs:int"/>
                        <param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="arg2" style="query" type="xs:int"/>
                    </request>
                    <response>
                        <ns2:representation xmlns:ns2="http://wadl.dev.java.net/2009/02" xmlns="" element="result" mediaType="application/xml"/>
                    </response>
                </method>
            </resource>
        </resource>
    </resources>
</application>

xsd の方は、

<?xml version="1.0" standalone="yes"?>
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">

  <xs:element name="result" type="result"/>

  <xs:complexType name="result">
    <xs:sequence>
      <xs:element name="value" type="xs:int" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema>

クライアント側

まずは、 wadl2ts のセットアップ。 npm のモジュールとして登録しておくのが良かったのかしれませんが、完成度が低すぎなので登録してません。 github からプロジェクトを落とします。 そしてプロジェクト配下で以下のコマンドで依存ライブラリを install します。

npm install

で 以下のコマンドを叩くと TypeScript のコードが生成されます。

bin/wadl2ts client.ts client http://localhost:8080/wadl2ts-example/rest/application.wadl

第一引数が、生成するファイル名。第二引数が生成するモジュール名。第三引数が、WADLのURL です。 以下の様なコードが生成されます。

/// <reference path="typings/jquery/jquery.d.ts" />
/// <reference path="typings/x2js/xml2json.d.ts" />

module client {

export interface Result {
  value: number;
}

export var baseUri = 'http://localhost:8080/wadl2ts-example/rest/';
export var x2js = new X2JS();
export module calculator {
  export module add {
    export function get(arg1: number, arg2: number, callback: (response: Result) => void): void {
      $.ajax({dataType: 'xml', type: 'GET', url: baseUri + 'calculator/add', data: {arg1: arg1, arg2: arg2, }, success: (res: any)=>{callback(<Result>((<any>x2js.xml2json(res)).result));}});
    }
  }
}

}

改行が少ないので見づらいですが、これが REST Client のコードになります。 そして、これを利用する側のコードです。

HTML

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>wadl2ts example</title>

    <!-- Bootstrap -->
    <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" rel="stylesheet">

    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->
  </head>
  <body>
    <h1>wadl2ts example</h1>
    <form id="form">
      <input id="arg1" type="text" />
      +
      <input id="arg2" type="text" />
      <button type="submit" class="btn btn-default">add</button>
    </form>
    <h2 id="result"></h2>

    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
    <script src="xml2json.min.js"></script>
    <script src="client.js"></script>
    <script src="index.js"></script>
  </body>
</html>

TypeScript

/// <reference path="client.ts" />

$(function(){
  $('#form').submit(()=>{
    var arg1 = $('#arg1').val();
    var arg2 = $('#arg2').val();
    client.calculator.add.get(arg1, arg2, (result)=>{
      $('#result').text(result.value);
    });
    return false;
  });
});

7-9 行目が生成したコードを利用している部分です。画面から入力された2つの数字を引数で指定し、それがリクエストパラメータになります。 そして、コールバック関数でレスポンスを受け取り画面に表示させます。 IDEで補完が利くし、静的に解決できるわけです。よし。

まとめ

とりあえず簡単なサンプルが動いたというだけで、全然未完成です。WADL や XSD の仕様もまだよく理解してないので。。 あと、今回は レスポンスを XML としましたが、みんな大好き JSON/JSONP をちゃんと利用できるようにしないと使いものにならないなと思います。 そもそも Jersey2 系で JSON Schema をレスポンスしてくれると良いのですが。。