Upgrade to Pro — share decks privately, control downloads, hide ads and more …

OpenAPI Generator Perl Clientでも型チューニングしたい!!

OpenAPI Generator Perl Clientでも型チューニングしたい!!

吉祥寺.pm30【オンライン】  の登壇資料です
https://kichijojipm.connpass.com/event/254162/

AnaTofuZ

July 28, 2022
Tweet

More Decks by AnaTofuZ

Other Decks in Technology

Transcript

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

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

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

  4. OpenAPI Specification REST APIの設計の仕様 REST APIの実装とは別にスキーマを定義する スキーマをもとにコード生成するツールが充実 RESTのパラメーターには型を定義することができる プリミティブ型 string,

    number, integer, boolean 型に加えてフォーマットを指定できるものもある stringのフォーマット date, date-time,... 独自定義型
  5. 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'
  6. 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
  7. OpenAPI と Perl クライアント/サーバーサイドの作成ツールはPerlのコードを生成できるものもある 有名所 OpenAPI Generator OpenAPI::Client 今日はOpenAPI Generatorの話をします

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

    Cheng さんはPerlが書ける
  10. 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"; }
  11. 突然のサーバーエラー golangで書いたサーバーに対してリクエストしたらエラーが発生した parsing body body from \"\" failed, because json:

    cannot unmarshal string into Go struct field AddPet.age of type int64 ageで死んでいるけど、ちゃんとageには25って数値を与えたはず...
  12. コードを見返す my $age = '25'; my $pet = WWW::OpenAPIClient::Object::Pet->new(age =>

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

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

    $age); $api_instance->add_pet(pet => $pet); 25で送っとるやん よく見たら '25' だな.... これPerlのJSON問題じゃん!!!!
  15. PerlのJSON問題 Perlは数値を文字列で使うか数値で使うかは文脈で決定する 評価された文脈によって内部構造が更新される くわしくはPerl内部構造の深遠に迫る Perlで生きている分には良いのだけど、型が必要な世界に行くときに注意が必要 JSONに変換する際はこの内部構造の状態を見て文字列/数値を判断する my %data = (

    answer => 42, answer_string => '42', ); say encode_json(\%data); # 実行結果: # {"answer":42, "answer_string":"42"}
  16. PerlのJSON問題の代表的な解決 数値か文字列のコンテキストで値を変化させずに評価すればいい 数値 +0 か *1 をする 文字列 '' を文字列結合する

    真偽値 JSON::true , JSON::false か、 \1 , \0 を使う \1 は1へのリファレンス(ポインタのようなもの)
  17. こうすると解決!!! my $age = '25'; my $pet = WWW::OpenAPIClient::Object::Pet->new(age =>

    $age + 0); $api_instance->add_pet(pet => $pet);
  18. めでたしめでたし

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

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

  21. JSON化の流れを追う OpenAPI Generatorで作ったClientはオブジェクトを引数で取る REST APIなので最終的にはJSONに変換される この例だと $pet がJSONに変換されるはず であるならばこのオブジェクトのコードを見てみるとわかるかもしれない my

    $pet = WWW::OpenAPIClient::Object::Pet->new(age => $age); $api_instance->add_pet(pet => $pet);
  22. 生成されたコードをみてみる オブジェクトのプロパティを 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});
  23. 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; }
  24. 周辺をみてみると Open API の型情報とオブジェクト名の対応をハッシュで持ってる これを使えば強制できるのでは...!? __PACKAGE__->openapi_types( { 'id' => 'int',

    'age' => 'int', 'name' => 'string', 'photo_urls' => 'ARRAY[string]', 'tags' => 'ARRAY[Tag]', 'status' => 'string' } );
  25. というわけで気合でどうにかするように 型をチェックしながら強制的にコンテキストを操作!!! 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;
  26. というわけで気合でどうにかするように 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; }
  27. None
  28. None
  29. 無事にmergeされました 微妙にミスっていてPR mergeしていただいた後に追加PRを出したりしましたが、無事 mergeされました 次のOpenAPI Generatorからリリースされます

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