サーバレスで GraphQL したかったので AWS AppSync に入門してみた。スキーマを書いてデータソースとリゾルバを追加して……というフローでずいぶんさくさくと進むんだけど、タイトルに書いたとおり時刻をエポックミリ秒として扱おうとすると困ってしまった。
つまり以下のようなスキーマで、データソースに格納されたエポックミリ秒(例えば 1503932400000
)を JSON にして返そうとすると:
type Entry { createdAt: Int! }
こんな感じのエラーになってしまった。
{ "data": { "getEvent": { "createdAt": null } }, "errors": [ { "path": [ "getEvent", "createdAt" ], "locations": null, "message": "Can't serialize value (/getEvent/createdAt) : Expected type 'Int' but was 'Long'." } ] }
なるほどね、と思ったものの GraphQL に Long 型はない。調べてみると、数値が 2147483648
以上になるとリゾルバの内部で Long 型として扱われるように見える。
これは型が Int のフィールドにローカルのリゾルバ(type が None のもの)をアタッチし、request mapping template を
{ "version": "2017-02-28", "payload": 2147483648 ## または 2147483647 }
のようにして確かめることができる。
ミリ秒をあきらめてエポック秒にすればこの境界を越えないわけだけど、せっかくなのでこういう風に解決してみた:
createdAt
フィールドが引数を取り、文字列型を返すようにする。
type Entry { createdAt(format: String, timezone: String): String! }
上記 createdAt
フィールドにローカルのリゾルバをアタッチし、request mapping template を以下のようにする。
#set($format = $util.defaultIfNull($ctx.args.format, "yyyy-MM-dd HH:mm:ss")) #set($timezone = $util.defaultIfNull($ctx.args.timezone, "+09:00")) { "version": "2017-02-28", "payload": "${util.time.epochMilliSecondsToFormatted($ctx.source.createdAtEpochMillis, $format, $timezone)}" }
クライアント側からはパーズしやすいフォーマットで受け取るか、受け取った文字列をそのまま表示するなどすればよい。楽しいですね。
参考: Resolver Mapping Template Context Reference - AWS AppSync