aglioとdrakovでAPI仕様書管理ことはじめ

1. はじめに

最近では Swagger のほうが主流なのかなと思いつつも

agliodrakov とは何かや、Blueprint によるAPI仕様の書き方については言及しない。

導入手順のみまとめる。

2. プロジェクト準備

$ mkdir aglio-sample
$ cd aglio-sample/

2.1. nodeバージョン

最新の node のバージョンでは aglio のインストールでエラーが出るので古いバージョン(8系)を使用する。

Macの場合

$ nodenv local 8.17.0
$ node -v
v8.17.0
$ nodenv version
8.17.0 (set by /Users/siwa32/projects/samples/aglio-sample/.node-version)

Windowsの場合

$ nodist local 8.17.0
$ node -v
v8.17.0
$ nodist
  (x64)
> 8.17.0  (C:\projects\samples\aglio-demo\.node-version: 8.17.0)
  10.20.1
  14.3.0  (global: 14.3.0)

node のバージョン切り替えは Macは nodenv、Windowsは nodist を使用する。

nodeの最新バージョンによるエラーの例については下記おまけ参照

2.2. 初期化

$ npm init

3. aglio インストール

$ npm install aglio
$ npx aglio
Usage: node_modules/.bin/aglio [options] -i infile [-o outfile -s]

オプション:
  -i, --input           Input file
  -o, --output          Output file
  -t, --theme           Theme name or layout file        [デフォルト: "default"]
  -f, --filter          Sanitize input from Windows    [真偽] [デフォルト: true]
  -s, --server          Start a local live preview server
  -h, --host            Address to bind local preview server to
                                                       [デフォルト: "127.0.0.1"]
  -p, --port            Port for local preview server         [デフォルト: 3000]
  -v, --version         Display version number               [デフォルト: false]
  -c, --compile         Compile the blueprint file           [デフォルト: false]
  -n, --include-path    Base directory for relative includes
  --verbose             Show verbose information and stack traces
                                                             [デフォルト: false]
  --theme-variables     Color scheme name or path to custom variables
                                                         [デフォルト: "default"]
  --theme-condense-nav  Condense navigation links      [真偽] [デフォルト: true]
  --theme-full-width    Use full window width         [真偽] [デフォルト: false]
  --theme-template      Template name or path to custom template
                                                         [デフォルト: "default"]
  --theme-style         Layout style name or path to custom stylesheet
  --theme-emoji         Enable support for emoticons   [真偽] [デフォルト: true]

例:
  node_modules/.bin/aglio -i example.apib   Render to HTML
  -o output.html
  node_modules/.bin/aglio -i example.apib   Start preview server
  -s
  node_modules/.bin/aglio                   Theme colors
  --theme-variables flatly -i example.apib
  -s
  node_modules/.bin/aglio                   Disable options
  --no-theme-condense-nav -i example.apib
  -s

See https://github.com/danielgtaylor/aglio#readme for more information

4. サンプル用意

サンプル用のAPI仕様書を用意する。

今回は API Blueprint の公式サイトからサンプル(02. Resource and Actions \| API Blueprint)をそのまま拝借した。
example.apib
 1FORMAT: 1A
 2
 3# Resource and Actions API
 4This API example demonstrates how to define a resource with multiple actions.
 5
 6
 7# /message
 8This is our [resource](http://www.w3.org/TR/di-gloss/#def-resource). It is
 9defined by its
10[URI](http://www.w3.org/TR/di-gloss/#def-uniform-resource-identifier) or, more
11precisely, by its [URI Template](http://tools.ietf.org/html/rfc6570).
12
13This resource has no actions specified but we will fix that soon.
14
15## GET
16Here we define an action using the `GET` [HTTP request method](http://www.w3schools.com/tags/ref_httpmethods.asp) for our resource `/message`.
17
18As with every good action it should return a
19[response](http://www.w3.org/TR/di-gloss/#def-http-response). A response always
20bears a status code. Code 200 is great as it means all is green. Responding
21with some data can be a great idea as well so let's add a plain text message to
22our response.
23
24+ Response 200 (text/plain)
25
26        Hello World!
27
28## PUT
29OK, let's add another action. This time to put new data to our resource
30(essentially an update action). We will need to send something in a
31[request](http://www.w3.org/TR/di-gloss/#def-http-request) and then send a
32response back confirming the posting was a success (_HTTP Status Code 204 ~
33Resource updated successfully, no content is returned_).
34
35+ Request (text/plain)
36
37        All your base are belong to us.
38
39+ Response 204

5. 表示用プレビューサーバ起動

$ npx aglio -i example.apib -s
Server started on http://127.0.0.1:3000/
Rendering example.apib
  • i オプション : API仕様ファイル

  • s オプション : プレビューサーバー起動

サーバーが起動するので http://127.0.0.1:3000/ にアクセする。

99c23a7c
図 1. ブラウザでの表示

仕様書ファイルを更新すると自動的に検知して反映してくれる。

$ npx aglio -i example.apib -s
Server started on http://127.0.0.1:3000/
Rendering example.apib
Socket connected
Refresh web page in browser
Updated example.apib
Rendering example.apib
Refresh web page in browser

サーバを終了するには Ctrl + c

6. htmlに変換

API仕様書からHTMLに変換する。

$ npx aglio -i example.apib -o example.html
  • i オプション : API仕様ファイル

  • o オプション : 出力するhtmlファイル

$ ll example*
-rw-r--r--  1 siwa32  staff   1.3K  5 31 02:20 example.apib
-rw-r--r--  1 siwa32  staff    24K  5 31 02:33 example.html

7. モックサーバ

drakov でモックサーバを立ち上げる。

drakov をインストールする。

$ npm install drakov
$ npx drakov
[INFO] No configuration files found
[INFO] Loading configuration from CLI
Usage:
  ./drakov -f <path to blueprint> [-p <server port|3000>]

Example:
  ./drakov -f ./*.md -p 3000

オプション:
  --sourceFiles, -f    Glob expression to select spec files.              [必須]
  --serverPort, -p     Specifies the port to be listened by Drakov server
                                                              [デフォルト: 3000]
  --staticPaths, -s    A list of comma delimited paths to use for static file
                       proxying
  --pathDelimiter, -d  Delimiter for mount point in static path (defaults is
                       "=")
  --stealthmode        Run silent (no console output)
  --disableCORS        Disable CORS header
  --sslKeyFile         Key File for SSL connections
  --sslCrtFile         Certificate File for SSL connections
  --delay              Add a delay to the response (in milliseconds)
  --method             Add method to Access-Control-Allow-Methods response
                       header
  --header             Add header to Access-Control-Allow-Headers response
                       header
  --public             Allow external requests               [デフォルト: false]
  --autoOptions        Automatically respond to OPTIONS requests for routes in
                       spec files
  --config             Load configuration from a Javascript file, must export an
                       object
  --discover, -D       List all available endpoints under `/drakov`. If value of
                       argument is a module name, it will be required and called
                       to create a middleware function       [デフォルト: false]
  --watch              Reload Drakov when change detected in list of source
                       files
  --debugMode          Enables DEBUG mode. Mismatch requests will be dumped
  --ignoreHeader       Ignore the HTTP header in API blueprints

必須の引数が見つかりません: f

モックサーバ起動

$ npx drakov -f "example.apib"
[INFO] No configuration files found
[INFO] Loading configuration from CLI
   DRAKOV STARTED
[LOG] Setup Route: GET /message
[LOG] Setup Route: PUT /message
   Drakov 1.0.4      Listening on port 3000

ブラウザで http://127.0.0.1:3000/message にアクセスしてみる。

API仕様に定義したレスポンスが返る。

3cb307fb
図 2. /messageのレスポンス

ワイルドカードを使用して複数ファイルを指定する事も可能

$ l *.apib
example.apib   example2.apib
$ npx drakov -f "*.apib"
[INFO] No configuration files found
[INFO] Loading configuration from CLI
   DRAKOV STARTED
[LOG] Setup Route: GET /message
[LOG] Setup Route: PUT /message
[LOG] Setup Route: GET /coupons/:id Retrieve a Coupon
   Drakov 1.0.4      Listening on port 3000

"" で囲ってあげないと最初に見つけたファイルしか認識してくれないようです。

$ npx drakov -f *.apib
[INFO] No configuration files found
[INFO] Loading configuration from CLI
   DRAKOV STARTED
[LOG] Setup Route: GET /message
[LOG] Setup Route: PUT /message
   Drakov 1.0.4      Listening on port 3000

aglio もポートが3000なので、ポートを変えたい場合は -p オプションを使用してポートを指定することができる。

$ npx drakov -f "*.apib" -p 3333

localhost以外からアクセスを可能にする場合は --public オプションを使用する。

$ npx drakov -f "*.apib" --public

--watch オプションをつけると仕様書ファイルの変更にリアルタイムに追随してくれる。

$ npx drakov -f "*.apib" --watch

8. 【おまけ】nodeのバージョンが新しい場合

node14.3.0 で aglio をインストールしようとしたときのエラー

$ node -v
v14.3.0
$ npm init
$ npm install aglio

...(snip)...

In file included from ../src/annotation.cc:1:
../src/protagonist.h:22:43: error: no member named 'Handle' in namespace 'v8'
    OptionsResult* ParseOptionsObject(v8::Handle<v8::Object>, bool);
                                      ~~~~^
../src/protagonist.h:22:60: error: expected '(' for function-style cast or type construction
    OptionsResult* ParseOptionsObject(v8::Handle<v8::Object>, bool);
                                                 ~~~~~~~~~~^
../src/protagonist.h:22:61: error: expected expression
    OptionsResult* ParseOptionsObject(v8::Handle<v8::Object>, bool);
                                                            ^
../src/protagonist.h:22:67: error: expected '(' for function-style cast or type construction
    OptionsResult* ParseOptionsObject(v8::Handle<v8::Object>, bool);
                                                              ~~~~^
../src/protagonist.h:29:30: error: no template named 'Handle' in namespace 'v8'
        static void Init(v8::Handle<v8::Object> target);
                         ~~~~^
../src/protagonist.h:47:30: error: no template named 'Handle' in namespace 'v8'
        static void Init(v8::Handle<v8::Object> target);
                         ~~~~^
../src/protagonist.h:68:30: error: no template named 'Handle' in namespace 'v8'
        static void Init(v8::Handle<v8::Object> target);
                         ~~~~^
../src/annotation.cc:18:29: error: no template named 'Handle'
void SourceAnnotation::Init(Handle<Object> exports)
                            ^
../src/annotation.cc:26:38: error: too few arguments to function call, single argument 'context' was not specified
    constructor.Reset(t->GetFunction());
                      ~~~~~~~~~~~~~~ ^
/Users/siwa32/Library/Caches/node-gyp/14.3.0/include/node/v8.h:6404:3: note: 'GetFunction' declared here
  V8_WARN_UNUSED_RESULT MaybeLocal<Function> GetFunction(
  ^
/Users/siwa32/Library/Caches/node-gyp/14.3.0/include/node/v8config.h:422:31: note: expanded from macro 'V8_WARN_UNUSED_RESULT'
#define V8_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
                              ^
../src/annotation.cc:27:88: error: too few arguments to function call, single argument 'context' was not specified
    exports->Set(Nan::New<String>("SourceAnnotation").ToLocalChecked(), t->GetFunction());
                                                                        ~~~~~~~~~~~~~~ ^
/Users/siwa32/Library/Caches/node-gyp/14.3.0/include/node/v8.h:6404:3: note: 'GetFunction' declared here
  V8_WARN_UNUSED_RESULT MaybeLocal<Function> GetFunction(
  ^
/Users/siwa32/Library/Caches/node-gyp/14.3.0/include/node/v8config.h:422:31: note: expanded from macro 'V8_WARN_UNUSED_RESULT'
#define V8_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
                              ^
../src/annotation.cc:44:48: error: too few arguments to function call, single argument 'context' was not specified
    return v8_wrap(annotationObject)->ToObject();
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^
/Users/siwa32/Library/Caches/node-gyp/14.3.0/include/node/v8.h:2819:3: note: 'ToObject' declared here
  V8_WARN_UNUSED_RESULT MaybeLocal<Object> ToObject(
  ^
/Users/siwa32/Library/Caches/node-gyp/14.3.0/include/node/v8config.h:422:31: note: expanded from macro 'V8_WARN_UNUSED_RESULT'
#define V8_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
                              ^
4 warnings and 11 errors generated.
make: *** [Release/obj.target/protagonist/src/annotation.o] Error 1
gyp ERR! build error
gyp ERR! stack Error: `make` failed with exit code: 2
gyp ERR! stack     at ChildProcess.onExit (/usr/local/lib/node_modules/npm/node_modules/node-gyp/lib/build.js:194:23)
gyp ERR! stack     at ChildProcess.emit (events.js:315:20)
gyp ERR! stack     at Process.ChildProcess._handle.onexit (internal/child_process.js:276:12)
gyp ERR! System Darwin 19.4.0
gyp ERR! command "/usr/local/Cellar/node/14.3.0/bin/node" "/usr/local/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js" "rebuild"
gyp ERR! cwd /Users/siwa32/projects/samples/aglio-error/node_modules/protagonist
gyp ERR! node -v v14.3.0
gyp ERR! node-gyp -v v5.1.0
gyp ERR! not ok

> aglio-theme-olio@1.6.3 postinstall /Users/siwa32/projects/samples/aglio-error/node_modules/aglio-theme-olio
> node scripts/setup-cache.js

...(snip)...