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)

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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'

    View Slide

  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

    View Slide

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

    View Slide

  8. View Slide

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

    View Slide

  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";

    }

    View Slide

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

    View Slide

  12. コードを見返す
    my $age = '25';

    my $pet = WWW::OpenAPIClient::Object::Pet->new(age => $age);

    $api_instance->add_pet(pet => $pet);

    25で送っとるやん

    View Slide

  13. コードを見返す
    my $age = '25';

    my $pet = WWW::OpenAPIClient::Object::Pet->new(age => $age);

    $api_instance->add_pet(pet => $pet);

    25で送っとるやん
    よく見たら '25'
    だな....

    View Slide

  14. コードを見返す
    my $age = '25';

    my $pet = WWW::OpenAPIClient::Object::Pet->new(age => $age);

    $api_instance->add_pet(pet => $pet);

    25で送っとるやん
    よく見たら '25'
    だな....
    これPerlのJSON問題じゃん!!!!

    View Slide

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

    answer => 42,

    answer_string => '42',

    );

    say encode_json(\%data);

    #
    実行結果:

    # {"answer":42, "answer_string":"42"}

    View Slide

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

    View Slide

  17. こうすると解決!!!
    my $age = '25';

    my $pet = WWW::OpenAPIClient::Object::Pet->new(age => $age + 0);

    $api_instance->add_pet(pet => $pet);

    View Slide

  18. めでたしめでたし

    View Slide

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

    View Slide

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

    View Slide

  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);

    View Slide

  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});

    View Slide

  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;

    }

    View Slide

  24. 周辺をみてみると
    Open API の型情報とオブジェクト名の対応をハッシュで持ってる
    これを使えば強制できるのでは...!?
    __PACKAGE__->openapi_types( {

    'id' => 'int',

    'age' => 'int',

    'name' => 'string',

    'photo_urls' => 'ARRAY[string]',

    'tags' => 'ARRAY[Tag]',

    'status' => 'string'

    } );

    View Slide

  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;

    View Slide

  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;

    }

    View Slide

  27. View Slide

  28. View Slide

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

    View Slide

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

    View Slide