GeekFactory

int128.hatenablog.com

Swagger Codegenにおけるカスタムバリデーションの追加

Swagger(OpenAPI)では必須チェック(required: true)や数値型チェック(type: integer)などが用意されており、コード生成時にバリデーションコードを出力できます。しかし、複雑なバリデーションルールを定義するには正規表現に頼らざるを得ないため、YAMLの保守性が悪化する問題を抱えています。そこで本稿では、Bean Validation Annotationのようなカスタムバリデータを用意し、コード生成時に適切なアノテーションを出力する方法を提案します。

Swaggerでは x- で始まる属性はVendor Extensionsと呼ばれており、APIやモデルを自由に拡張できるようになっています。ここではVendor Extensionsを利用してISBNコードのバリデーションを追加する例を考えます。

まず、Swagger YAMLに独自の属性を追加します。下記の例では x-validations 属性にバリデーションルールのマップを持たせています。

definitions:
  Pet:
    properties:
      code:
        type: string
        x-validations:
          isbn13: true

次に、Swagger CodegenのMustacheテンプレートをカスタマイズします。テンプレートの中では vendorExtensions プロパティでVendor Extensionsにアクセスできます。下記の例では独自のアノテーションを出力しています。

  {{#vendorExtensions.x-validations.isbn13}}
    @ISBN13
  {{/vendorExtensions.x-validations.isbn13}}

Spring MVCテンプレートの場合は、上記のコードを pojo.mustache に追加するとモデルに独自のアノテーションが付くようになります。コード生成を実行すると下記のようなモデルが出力されます。

public class Pet   {
    
  private Long id = null;

    
  private String name = null;

    @ISBN13
  private String code = null;

実際に動く例は下記のリポジトリを参照してください。

github.com

Circle CIでCloud Functionをデプロイする

以前に Circle CIでCloud Functionをデプロイする - GeekFactory というエントリを書きましたが、gcloudコマンドでデプロイする方が簡単でした。gcloudコマンドでデプロイする手順を説明します。

GCPの設定

デプロイに使用するサービスアカウントを作成します。ロールは Project Editor を割り当てておきます。Cloud Functionsはまだベータ版なので専用のロールが用意されていないようです。

サービスアカウントを作成したら秘密鍵をダウンロードします。後からCircleCIで設定できるように、BASE64エンコードしておきます。

base64 service-account-xxxxxxxx.json

次に、デプロイで使用するストレージバケットを作成します。名前は何でも構いません。

CircleCIの設定

下記の内容で .circleci/config.yml を作成します。

version: 2
jobs:
  build:
    docker:
      - image: google/cloud-sdk
    working_directory: ~/repo
    steps:
      - checkout
      - run: echo "$GOOGLE_AUTH" | base64 -i --decode > "$HOME/gcp-key.json"
      - run: gcloud auth activate-service-account --key-file "$HOME/gcp-key.json"
      - run: gcloud --quiet config set project "$GOOGLE_PROJECT_ID"
      - run: gcloud beta functions deploy 関数名 --stage-bucket バケット名 --trigger-http

gcloud beta functions deploy コマンドの使い方は Deploying from Your Local Machine  |  Cloud Functions Documentation  |  Google Cloud Platform を参照してください。

下記の環境変数を設定します。

ビルドが成功すればデプロイ完了です。

Spring Security OAuthにおけるアクセストークン取得失敗時の例外

Spring Security OAuthでアクセストークンの取得に失敗した場合のリトライを設計するため、エラーケースと例外の対応を調べてみました。

アクセストークンの取得をリトライするには以下の2つの方法があります。

  1. アクセストークン取得(AccessTokenProvider#obtainAccessToken)をリトライする。
  2. ClientHttpRequestInterceptor でHTTPリクエストをリトライする。

前者の場合、失敗の原因にかかわらず OAuth2AccessDeniedException が発生するので、 OAuth2AccessDeniedException に対してリトライを行えばOKです。後者の場合は下記を参考にしてください。(7/28追記)

以下のテストケースに記載があります。

テストケースからは下記の仕様が読み取れます。

エラーケース 例外
ステータスコード400で "{"error":"access_denied"} が返された場合 UserDeniedAuthorizationException
ステータスコード403で "{"error":"access_denied"} が返された場合 OAuth2AccessDeniedException
400番台のステータスコードが返された場合 HttpClientErrorException
500番台のステータスコードが返された場合 HttpServerErrorException

これだけでは足りないので実際に検証した結果が下記です。

エラーケース 例外
接続に失敗した場合 ConnectException
Content-Typeとリクエストボディが合致しない場合 OAuth2AccessDeniedException 未調査

上記を踏まえると、リトライの対象例外は ConnectExceptionHttpServerErrorException にするとよさそうです。リトライの実装方法は下記で書いているのでご参考まで。

int128.hatenablog.com