Slide 1

Slide 1 text

OpenAPI Generator Perl Clientでも型チューニングした い!! 吉祥寺.pm #30 2022/07/28 八雲アナグラ(AnaTofuZ)

Slide 2

Slide 2 text

こんにちは 八雲アナグラ(@AnaTofuZ) 株式会社はてな ノベルチーム Perl TypeScript golang なんとPerlの話をします

Slide 3

Slide 3 text

スキーマ駆動開発 みなさんスキーマ書いていますか DB GraphQL REST API REST APIのスキーマといえばOpenAPI Specificationですね

Slide 4

Slide 4 text

OpenAPI Specification REST APIの設計の仕様 REST APIの実装とは別にスキーマを定義する スキーマをもとにコード生成するツールが充実 RESTのパラメーターには型を定義することができる プリミティブ型 string, number, integer, boolean 型に加えてフォーマットを指定できるものもある stringのフォーマット date, date-time,... 独自定義型

Slide 5

Slide 5 text

OpenAPIで/pet にPOSTする /pet に対してのPOSTを定義している requestBodyは #/components/requestBodies/Pet 具体的な値を型として定義することができる /pet: post: responses: '200': description: Successful operation '405': description: Invalid input requestBody: $ref: '#/components/requestBodies/Pet'

Slide 6

Slide 6 text

OpenAPIで/pet にPOSTするrequestBodyの内容 送るPetObjectには型が存在する 配列も送ることができる Pet: type: object properties: id: type: integer format: int64 name: type: string example: doggie age: type: integer format: int64 photoUrls: type: array items: type: string

Slide 7

Slide 7 text

OpenAPI と Perl クライアント/サーバーサイドの作成ツールはPerlのコードを生成できるものもある 有名所 OpenAPI Generator OpenAPI::Client 今日はOpenAPI Generatorの話をします

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

OpenAPI Generator Java製便利ツール 最近ではDockerでも動くので便利 Swagger Codegenからのforkプロジェクト OpenAPI仕様からmustacheなテンプレートをもとにコード生成する Perlはクライアントライブラリを生成できる 主要コミッタの William Cheng さんはPerlが書ける

Slide 10

Slide 10 text

PerlClient API Clientのインスタンスを作って、APIに対応する use WWW::OpenAPIClient::PetApi; my $api_instance = WWW::OpenAPIClient::PetApi->new( # Configure OAuth2 access token for authorization: petstore_auth access_token => 'YOUR_ACCESS_TOKEN', ); my $age = "25"; my $pet = WWW::OpenAPIClient::Object::Pet->new(age => $age); eval { $api_instance->add_pet(pet => $pet); }; if ($@) { warn "Exception when calling PetApi->add_pet: $@\n"; }

Slide 11

Slide 11 text

突然のサーバーエラー golangで書いたサーバーに対してリクエストしたらエラーが発生した parsing body body from \"\" failed, because json: cannot unmarshal string into Go struct field AddPet.age of type int64 ageで死んでいるけど、ちゃんとageには25って数値を与えたはず...

Slide 12

Slide 12 text

コードを見返す my $age = '25'; my $pet = WWW::OpenAPIClient::Object::Pet->new(age => $age); $api_instance->add_pet(pet => $pet); 25で送っとるやん

Slide 13

Slide 13 text

コードを見返す my $age = '25'; my $pet = WWW::OpenAPIClient::Object::Pet->new(age => $age); $api_instance->add_pet(pet => $pet); 25で送っとるやん よく見たら '25' だな....

Slide 14

Slide 14 text

コードを見返す my $age = '25'; my $pet = WWW::OpenAPIClient::Object::Pet->new(age => $age); $api_instance->add_pet(pet => $pet); 25で送っとるやん よく見たら '25' だな.... これPerlのJSON問題じゃん!!!!

Slide 15

Slide 15 text

PerlのJSON問題 Perlは数値を文字列で使うか数値で使うかは文脈で決定する 評価された文脈によって内部構造が更新される くわしくはPerl内部構造の深遠に迫る Perlで生きている分には良いのだけど、型が必要な世界に行くときに注意が必要 JSONに変換する際はこの内部構造の状態を見て文字列/数値を判断する my %data = ( answer => 42, answer_string => '42', ); say encode_json(\%data); # 実行結果: # {"answer":42, "answer_string":"42"}

Slide 16

Slide 16 text

PerlのJSON問題の代表的な解決 数値か文字列のコンテキストで値を変化させずに評価すればいい 数値 +0 か *1 をする 文字列 '' を文字列結合する 真偽値 JSON::true , JSON::false か、 \1 , \0 を使う \1 は1へのリファレンス(ポインタのようなもの)

Slide 17

Slide 17 text

こうすると解決!!! my $age = '25'; my $pet = WWW::OpenAPIClient::Object::Pet->new(age => $age + 0); $api_instance->add_pet(pet => $pet);

Slide 18

Slide 18 text

めでたしめでたし

Slide 19

Slide 19 text

みたいな話をしていたところ ???「テンプレート側いじれば良いんじゃない」

Slide 20

Slide 20 text

みたいな話をしていたところ ???「テンプレート側いじれば良いんじゃない」 たしかにね

Slide 21

Slide 21 text

JSON化の流れを追う OpenAPI Generatorで作ったClientはオブジェクトを引数で取る REST APIなので最終的にはJSONに変換される この例だと $pet がJSONに変換されるはず であるならばこのオブジェクトのコードを見てみるとわかるかもしれない my $pet = WWW::OpenAPIClient::Object::Pet->new(age => $age); $api_instance->add_pet(pet => $pet);

Slide 22

Slide 22 text

生成されたコードをみてみる オブジェクトのプロパティを Class::Accessor を使ってハッシュ形式で管理している 例えば attribute_map はPerlの内部表現と外に出すときの表現の対応をもっている snake_case(Perl)がcamelCase(OpenAPI)に このキーをアクセサとして生やしている __PACKAGE__->attribute_map( { 'id' => 'id', 'age' => 'age', 'name' => 'name', 'photo_urls' => 'photoUrls', 'tags' => 'tags', 'status' => 'status' } ); __PACKAGE__->mk_accessors(keys %{__PACKAGE__->attribute_map});

Slide 23

Slide 23 text

json変換部分 TO_JSON といういかにもなメソッドがある Perlはオブジェクトを直接JSON化できない それでもなんとかオブジェクトから変換したい時用のJSONモジュールのAPI attribute_map の中身をなんか $_data に素朴に詰めている 素朴なので型とか一切みてない... # used by JSON for serialization sub TO_JSON { my $self = shift; my $_data = {}; foreach my $_key (keys %{$self->attribute_map}) { if (defined $self->{$_key}) { $_data->{$self->attribute_map->{$_key}} = $self->{$_key}; } } return $_data; }

Slide 24

Slide 24 text

周辺をみてみると Open API の型情報とオブジェクト名の対応をハッシュで持ってる これを使えば強制できるのでは...!? __PACKAGE__->openapi_types( { 'id' => 'int', 'age' => 'int', 'name' => 'string', 'photo_urls' => 'ARRAY[string]', 'tags' => 'ARRAY[Tag]', 'status' => 'string' } );

Slide 25

Slide 25 text

というわけで気合でどうにかするように 型をチェックしながら強制的にコンテキストを操作!!! if ( grep( /^$type$/, ('int', 'double'))) { # https://metacpan.org/pod/JSON#simple-scalars # numify it, ensuring it will be dumped as a number return $data + 0; } elsif ($type eq 'string') { # https://metacpan.org/pod/JSON#simple-scalars # stringified return $data . q(); } elsif ($type eq 'boolean') { # https://metacpan.org/pod/JSON#JSON::true,-JSON::false,-JSON::null return $data ? \1 : \0;

Slide 26

Slide 26 text

というわけで気合でどうにかするように Date型も正しい値を返すように!!! } elsif ($type eq 'DATE') { if (ref($data) eq 'DateTime') { # https://metacpan.org/pod/DateTime#$dt-%3Eymd($optional_separator),-$dt-%3Emdy(...),-$dt-%3Edmy(...) return $data->ymd; } return $data .q(); } elsif ($type eq 'DATE_TIME') { # the date-time notation as defined by RFC 3339, section 5.6, for example, 2017-07-21T17:32:28Z if (ref($data) eq 'DateTime') { # https://metacpan.org/pod/DateTime#$dt-%3Erfc3339 return $data->rfc3339; } return $data .q(); } else { # hash (model), In this case, the TO_JSON of the $data object is executed return $data; }

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

無事にmergeされました 微妙にミスっていてPR mergeしていただいた後に追加PRを出したりしましたが、無事 mergeされました 次のOpenAPI Generatorからリリースされます

Slide 30

Slide 30 text

まとめ REST APIのスキーマとしてOpenAPI Specificationがある OpenAPI Specificationは型を書くことができる OpenAPI Generatorはスキーマをもとにクライアントを作れる PerlとJSONの型のマッピングをいい感じになんとかした