AWS API GatewayにインポートするSwaggerではパスパラメータはメソッド毎に定義するべき

AWS APIGateway の REST API で生成した JavaScript SDK で、パスパラメータ付きのメソッド呼び出しが失敗していました。API は コンソールで Swagger のYaml をインポートしていましたが、その定義が良くなかったようです。

//APIクライアント生成
const api = apigClientFactory.newClient({
    "apiKey": "XXXXXX...",});

// GET <endpoint>/data/{id} を呼び出す
const response = await api.dataIdGet(
    {id:"xxx"}, {});
    // GET <endpoint>/data が呼び出されてしまう

直接 SDKのソースを見ると、このメソッドのリソースパスを生成する箇所で、明らかにパスパラメータが展開されていませんでした。

parseParametersToObjectの第2引数が空の配列になっている(以下のここが空っぽのコメント部)ため、GET /data/ を実行してしまう。

apiGateway-js-sdk/apigClient.js:

apigClient.dataIdGet = function (params, body,
    additionalParams)
{
  if(additionalParams === undefined) {
      additionalParams = {};
  }
  apiGateway.core.utils
    .assertParametersDefined(params, [], ['body']);
  var dataIdGetRequest = {
    verb: 'get'.toUpperCase(),
    path: pathComponent + uritemplate('/data/{id}')
      .expand(apiGateway.core.utils
      .parseParametersToObject(params, [ ])),
                           //ここが空っぽ ↑
    headers: apiGateway.core.utils
      .parseParametersToObject(params, []),
    queryParams: apiGateway.core.utils
      .parseParametersToObject(params, []),
    body: body
  };
  return apiGatewayClient.makeRequest(
    dataIdGetRequest, authType,
    additionalParams, config.apiKey);
};

とりあえずこの配列にパスパラメータ名 "id" を入れてやれば正しく呼び出せるようになりました(以下)。

  var dataIdGetRequest = {
    verb: 'get'.toUpperCase(),
    path: pathComponent + uritemplate('/data/{id}')
      .expand(apiGateway.core.utils
      .parseParametersToObject(params, ['id'])),
                     //パスパラメータ名を定義↑

しかしこれでは根本的に解決しません。SDKが更新されるたびに編集しなおすなんてやってられませんからね。

で、調べてみると、パスパラメータをリソース/data/{id}に対して定義していたのがいけなかったと判明しました。Swagger的には問題ないはずなのですが、API Gatewayにインポートする場合、「パスパラメータはメソッドに対して定義する必要がある」ようです。

どちらの場合でも、API Gateway のコンソールではパスパラメータとして認識されていますが、デプロイ済みのAPIで生成するJavaScriptのSDKでは違いが出るみたいです(他言語のSDKでどうなっているのかは不明)。もしかするとAWS側のバグなのかもしれません。

ES2017でオブジェクトの全プロパティ値を得るには Object.values が使えるのに

ジブン向けに書いています。保守的にもほどがあるっちゅう話です。

JavaScriptでプログラミングしていると、 オブジェクトのプロパティの値の配列を取り出したい ってことがしょっちゅうあります。

現在最新の ECMAScript 2017 では、 オブジェクトの全プロパティの値を配列として得るために、Object.valuesが使えるのですが、ワタシは従来(ES2015以降)の方法に手がなじんでしまっており、いざという時に「実績ないからなー」的な不安感から使ってこなかったんですね。これって、どこかで無理やりこじ開けないとイケナイやつです。

従来 Object.keys を使って以下のようにしていました。長いですね。

let values = Object.keys(obj).map(function(key) { return obj[key];});

ECMAScript 2015では、アロー関数が使えるので、以下のようにさらに簡潔に書けます。

let values = Object.keys(obj).map(key => obj[key]);

しかし今やECMAScript 2017では、 Object.value を使って、以下のように書けるのです。腱鞘炎すら治りそうな勢いですよ。

let values = Object.values(obj);

どんどん Object.values を使いましょう > ジブン

テンプレートリテラルの展開時のエラーについて調べてみた

JavaScript で、テンプレートリテラルからエラーが投入される状況を確認してみました。

基本は、Node.js 8.10 LTS を使用しましたが、一部ChromeのJavaScriptでも確認しています。

結果として確認できたのは、下記2点。

  1. 定義済みオブジェクトの未定義キーを参照してもエラーにならない
  2. 未定義変数や未定義オブジェクト(=undefined)のキーを参照するとエラー

上記動作はJavaScript的に普通の動き。strictモードでも非strictモードでも同様です。「テンプレートリテラルだから」と特別なことはないようです。

以降、それぞれのケースを確認した結果です。

定義済みオブジェクトの未定義キーを参照する

定義済みオブジェクトの未定義キーを参照しても、エラーは投入されずundefinedと評価されます。

"use strict";
try {
    let foo = { bar:0 };
    console.log(`${foo.baz}`);//bazは未定義
    //出力>undefined
} catch(err) {
    console.error(`${err.name}: ${err.message}`);
}

未定義変数を参照する

未定義の変数を参照すると、実行時エラーReferenceErrorが投入されます。

try {
    console.log(`${foo}`);//fooは未定義
} catch(err) {
    console.error(`${err.name}: ${err.message}`);
    //出力>ReferenceError: foo is not defined
}

※ ブラウザでは以下のようにすればエラーになりません。ブラウザのJavaScriptのグローバル変数は window オブジェクトのキーとして定義されるためですね。

    console.log(`${window.foo}`);
    //出力>undefined

未定義オブジェクトのキーを(無理やり)参照

未定義オブジェクト(=undefined)のキーを参照しようとするとTypeErrorが投入されます。

try {
    let foo = {};
    console.log(`${foo.bar.baz}`);//barが未定義
} catch(err) {
    console.error(`${err.name}: ${err.message}`);
    //出力>TypeError: Cannot read property 'baz' of undefined
}

未定義の関数を呼び出す

定義済みオブジェクトの未定義のキーを関数呼び出しするとTypeErrorが投入されます。

try {
    let foo = { bar:0 };
    console.log(`${foo.baz()}`);//bazは未定義
} catch(err) {
    console.error(`${err.name}: ${err.message}`);
    //出力>TypeError: foo.baz is not a function
}

関数以外を関数呼び出し

定義済みオブジェクトの関数でないメンバを呼び出そうとすると、TypeErrorが投入されます。

try {
    let foo = { bar:0 };
    console.log(`${foo.bar()}`);//barは関数ではない
} catch(err) {
    console.error(`${err.name}: ${err.message}`);
    //出力>TypeError: foo.bar is not a function
}

Gitサブモジュールを移動する一番簡単な方法は?

少なくとも私の環境 Git for Windows 2.7.0-windows.1 で、Git のサブモジュールのパスを移動するには、おなじみ git mv だけで事足りてます。

昔は大変だったんだよね

Gitのサブモジュールは扱いにくいと言われてきました。

場所を移動したり、削除したりする時、手操作でいろいろやらないとリポジトリが変な状態になってしまったりして困ってました。

今回、サブモジュールを別のディレクトリに移す必要が生じて極度の緊張。
怖れをなしてインターネッツで検索すると、それ見たことか「いろんなファイルをいじり倒さないといけません」と。

直感的には git mv でお願いしたい

その手順があまりにややこしくてげんなりしました。

「直感的には git mv なんだがなあ・・・」と愚痴が出たので、モノは試しとやってみたら大成功。

Gitのversionは、、、

$ git version
git version 2.17.0.windows.1

最近のGitでは改善されてたようですね。

いつから有効なのかは不明です。
少なくとも最新版にすれば問題ないと思います。

サブモジュールの移動例

以下は、既存のサブモジュールを別ディレクトリに移動する例。.gitsubmodulesもきちんと更新されますよ。

$ git submodule #移動前のサブモジュールを確認(hashは省略)
 ********** org-dir/sub-mod (***********)

$ mkdir dst-dir/ # 移動先ディレクトリの作成

$ git mv org-dir/sub-mod dst-dir/ # 移動(warningは無視)
warning: LF will be replaced by CRLF in .gitmodules.
The file will have its original line endings in your
working directory.

$ git submodule #移動後のサブモジュールの確認(hashは省略)
 ********** dst-dir/sub-mod (***********)

$ git status # 変更点の確認
On branch mv-sub-mod
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   .gitmodules
        renamed:    org-dir/sub-mod -> dst-dir/sub-mod
        
$ git diff --staged
diff --git a/.gitmodules b/.gitmodules
index *******..******* *******
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,4 +1,4 @@
 [submodule "sub-mod"]
-   path = org-dir/sub-mod
+   path = dst-dir/sub-mod
    url = ssh://git@xxx.xxx.xxx.xxx/yyy/zzz.git
    branch = mv-sub-mod
(略)

サブモジュールの名前変更

サブモジュールのディレクトリ名を変えるのも同じく可能です。

$ git submodule
 ********** org-dir/sub-mod (***********)

$ mkdir dst-dir/

$ git mv org-dir/sub-mod dst-dir/submod2
()
$ git status # 変更点の確認
On branch mv-sub-mod
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   .gitmodules
        renamed:    org-dir/sub-mod -> dst-dir/submod2

ということで

とりあえず最近のGitでサブモジュールの場所を移動したり名前を変えたりするには、git mv で充分ですって話でした。.gitsubmodulesもちゃんと更新してくれます。

StackEdit5で使えなくなった目次生成機能の代替手段

StackEdit5では目次を自動的に作ってくれる[TOC]マーカーが使えなくなっていますが、なんとか代替手段はあるようです。

[TOC]マーカー削除の理由は?

GitHubのIssueに書かれてました、、、

他に良いサービスがあるので[TOC]マーカーの機能は陳腐化しちゃったんだよね・・・他のを使ってください・・・

ということらしいです。なら仕方がない。代替手段を試してみる。

編集中に目次を利用する方法

StackEdit5の右上にある[#]メニュー内に「Table of contents」という項目があり、これを選ぶと目次が表示されます。

GitHubのIssue#1248 で紹介されています。

Publish時に[TOC]マーカーを目次に変換する方法

また、StackEditコミュニティのHowToに、[TOC]マーカーを目次に置き換える方法が紹介されています。

HTMLへの発行(Publish)時のテンプレートで変換するようなので「プレビュー画面でリアルタイムに」は無理なのでしょう(まだ試していません)。

まとめ

便利な機能だったし削除は残念ですが、一応代替手段が提供されているので穏便に(笑)。

発行時の目次生成は試していませんが、またそのうち。

ワタシはStackEditを、主にBloggerへの投稿のために使用しているのですが、そもそも「目次が必要なほど長いのは書かない」ほうが良いのかも?とも思いはじめました。

参考リンク

  1. [TOC] in the document no longer works - Issue #1248 - benweet/stackedit
  2. Replace [TOC] markers with tables of contents in your exports - How To - StackEdit community

AWS API Gatewayから SVG(content-type: image/svg+xml)を返却する

WEBクライアントのJavaScriptからAWS API GatewayのGETメソッドを呼び出して、SVGファイルを取得する。

Lambdaを定義

以下のLambda「svgImageGet」を定義する。パラメータに応じたSVGファイルをS3からダウンロードしてAPI Gatewayへ返す関数。

const AWS = require("aws-sdk");
const S3 = new AWS.S3();
exports.handler = async (event) => {
    let responseBody = { svg: null };
    let params = {
        Bucket: "myBucket",
        Key: `svg/${event.bodyName}.svg`,
    };
    let s3obj = await (new Promise((resolve, reject) => {
        S3.getObject(params, (err, data) => {
            if(err) {
             reject(err);
            } else {
          resolve(data);
         }
        });
    }));
    responseBody.svg = s3obj.Body.toString("utf-8");
    return responseBody;
};

API Gateway の設定

前提: 任意のAPIにCORSが有効化されたメソッド:/svg-file GETが定義されている状態。

以降このGETメソッドに対する設定。

「メソッドリクエスト」の設定

「メソッドリクエスト」の「URL クエリ文字列パラメータ」にパラメータbodyNameを追加。

「統合リクエスト」の設定

「統合リクエスト」で上記Lambdaに紐づけて、content-typeapplication/jsonの「本文マッピングテンプレート」を追加。内容は以下。

{
    "bodyName" : "$input.params('bodyName')"
}

上記$input.params('bodyName')が「メソッドリクエスト」の「URL クエリ文字列パラメータ」を指しており、このJSON全体が、Lambdaの第一引数に渡される。

「統合レスポンス」の設定

「統合レスポンス」の「本文マッピングテンプレート」で、「リクエスト本文のパススルー」から「テンプレートが定義されていない場合 (推奨)」を選んでおき(デフォルト)content-typeimage/svg+xml
のマッピングテンプレートを追加する。テンプレートの内容は以下とする。

#set($inputRoot = $input.path('$'))
$inputRoot.svg

テンプレートの $inputRoot.svg が、LambdaのresponseBody.svgに対応している。

「メソッドレスポンス」の設定

HTTPのステータス 200 を開き、「200のレスポンス本文」のContent-Typeをimage/svg+xmlとする。モデルは空(Empty)で良い。

API Gatewayの呼び出し

これまで設定してきたAPIを任意のステージへデプロイした上で、JavaScriptのSDKをダウンロードする。

このSDKはHTMLのSCRIPTタグで読み込んでおき、以下の関数を実行すれば、SVGドキュメントを取得できる。

/* globals Promise, apigClientFactory */
"use strict";

let apigClient = apigClientFactory.newClient({
    apiKey: "apiKey",
});

/**
 * @async
 * @param {string} bodyName ファイルボディ名
 * @returns {Promise<xmldoc>} SVGドキュメントを返す。
 */
async function svgFileGet(bodyName) {
    let response = await apigClient.svgFileGet(
        { bodyName: bodyName }, {}, {});
    if(response == null) {
        throw new Error("Null response");
    } else if(!("status" in response)) {
        throw new Error("No status in response");
    } else if(response.status !== 200) {
        throw new Error("Request fail");
    } else if(!("data" in response)) {
        throw new Error("No data in response");
    }
    let parser = new DOMParser();
    return parser.parseFromString(
      response.data, "image/svg+xml");
};

JavaScript(ES2015)で変数を文字列リテラルに展開するとか、ヒアドキュメント的なもの

JavaScriptで文字列内に変数など(実際には式)を展開したいとき、ES2015(ES6)では、文字列をバッククォート(`` - バックティックともいうらしい)で囲む、テンプレートリテラルという記法が使用できます。

文字列中に式を展開できる

テンプレートリテラルには、${式}という形式のプレースホルダを含ませられ、評価時に式が展開されるというもの。

let value = "Template Literal";
console.log(`Hello! ${value}`);

なんだかPerlやPHPみたいですね。

複数行文字列をそのまま書ける

さらに、テンプレートリテラルの中には改行文字を含められるそうで、ヒアドキュメントみたいなことが可能。

let world = `Template
String`;

console.log(
`Hello!
${world}`
);

簡素で良いですね。

タグ付けされたテンプレートリテラルとは?

タグ付けされたテンプレートリテラルとは、テンプレートリテラルの展開内容を処理する機能(と記法?)です。

テンプレートリテラルの評価の途中で展開内容を独自の関数で受け取れて、その展開結果をオーバーライドできます。

MDNによくわからないサンプルコードが掲載されていますので、以降で説明。

var a = 5;
var b = 10;

function tag(strings, ...values) {
  console.log(strings[0]); // "Hello "
  console.log(strings[1]); // " world"
  console.log(values[0]);  // 15
  console.log(values[1]);  // 50

  return "Bazinga!";
}

tag`Hello ${ a + b } world ${ a * b}`;
// "Bazinga!"

最初に関数tagが定義されています。

function tag(strings, ...values) {
・
・
・

tagの第一引数はstrings。第2引数のvalueは、呼び出し時に指定された第2引数から最後までを配列として受けとります。

tag`Hello ${ a + b } world ${ a * b}`;
// "Bazinga!"

最終行でtagを呼び出しており、これがタグ付けされたテンプレートリテラルという記法のようです。

コメント部分であたかも"Bazinga!"とコンソールに表示されそうに書かれていますが、出ません。あくまでも テンプレートリテラルの評価値が"Bazinga!"です。

この呼び出しでは、事前にテンプレートリテラル内のプレースホルダとそれ以外の部分に分割され、

  1. Hello - 固定文字列
  2. ${ a + b } - プレースホルダ
  3. world - 固定文字列
  4. ${ a * b} - プレースホルダ

プレースホルダ以外のHello, worldが一つの配列にまとめられて第一引数に与えられ、プレースホルダ(${ a + b }, ${ a * b})の展開結果が第2引数以降へ与えられます。

通常の関数呼び出しで表現すれば以下のようになります。

tag([`Hello `, ` world `],
	`${ a + b }`, `${ a * b}`);

互換性は?

ES2015/ES6での標準仕様。
Node.jsではv6.0以降で使えるから、今やほとんどの環境で互換性を気にする必要はなさそう。

正式名称?

一般的には「テンプレートリテラル」と呼ばれているようだけど、MDNでは「テンプレート文字列」となっている。(URLも「template-string」)。

自分をアップデートする必要性

これ、以前どこかで見かけて、なんとなく知っていたのだけど、習慣的に+でつなげたり、["a","b"].join("")などとしていました。不便だなあとは思っていましたが、どこかで「そういうものだ」とあきらめてました。

なんでもどんどん便利になっていくのが普通なのだから、定期的に知識や技術をアップデートする必要がありますね。