Go の ogen で Convenient errors を使う
はじめに
ogen の自動生成を使っていてずっと気になっていたログ。
INFO convenient Convenient errors are not available
INFO レベルだしまあいいかと放置していましたが、ようやくこれが何か知りました。
この Convenient errors を使えば ogen でのエラーハンドリングを共通化することができます。
ogen のエラーハンドリングってなんかイケテナイんだよなあと思っていたことも同時に解消しました。
この記事を通して少しでもみなさんの ogen ライフが快適になれば幸いです。
ogen
OpenAPI からコードを自動生成することができ、以下のような特徴があります。
- 生成されたコードで refrect や interface{} を使わない
- 独自のルーティング機構
- 標準の OpenAPI v3 で完結
- Optional や Nullable に対応
- OpenTelemetry に対応
以前私が oapi-codegen と比較した記事もあるのでそちらもご覧ください。
事前準備
バージョン
go version
go version go1.22.4 darwin/arm64
ogen version
v1.2.1
※ 手元にインストールせずに利用するので実行時のバージョンタグを記載
openapi.yaml
コード自動生成に利用する OpenAPI は以下のようなものとなります。
openapi.yaml
openapi: 3.0.3
info:
title: Sample Go ogen Convenient errors
description: |-
This is the Sample Go ogen APP API documentation.
termsOfService: https://localhost:8080
contact:
email: kotaro.otaka@example.com
license:
name: Apache 2.0
version: 0.0.1
externalDocs:
description: Find out more about Swagger
url: http://swagger.io
servers:
- url: http://localhost:8080
tags:
- name: Sample
description: Sample
paths:
/:
get:
tags:
- Sample
summary: Sample
description: Sample
operationId: getSample
parameters:
- name: status
in: query
description: status
required: true
schema:
type: integer
example: 200
responses:
"200":
description: OK
"400":
description: Bad Request
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"403":
description: Forbidden
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"404":
description: Not Found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"500":
description: Internal Server Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
post:
tags:
- Sample
summary: Sample
description: Sample
operationId: postSampl
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
status:
type: integer
description: status
example: 200
required:
- status
responses:
"200":
description: OK
"400":
description: Bad Request
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"403":
description: Forbidden
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"404":
description: Not Found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"500":
description: Internal Server Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
components:
schemas:
Error:
type: object
properties:
message:
type: string
required:
- message
サンプルコードのため以下のような API 設計としています。
- リクエストにてレスポンスのステータスコードを指定
- GET と POST で機能を提供
Convenient errors なしの実装
ogen はレスポンスの型を強力にサポートしてくれるので各APIごとにステータスコードそれぞれ型を提供してくれます。
そのためこのように丁寧にレスポンスを実装してあげる必要があります。
各 API ごとにレスポンスの分岐コードが発生するのでかなり冗長です。
また、処理の結果はエラーなのにメソッドの返り値ではエラーを返さない( 第二戻り値が nil ) となるので Go っぽいコードではなくなります。
※ WithErrorHandler() を使って感じの共通化したエラーハンドラーがかけるかもしれないです。ジェネリクスとか使って。知っている方いらっしゃいましたら教えてください。
Convenient errors ありの実装
Convenient erros を利用するために openapi.yaml を以下のように書き換えます。
ステータスコード 500 で定義していたものを default
にします。
- "500":
+ default:
description: Internal Server Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
この変更をすべてのAPIに対して行います。
また、この default で参照する定義はすべて同じものである必要があります。
公式サイトの記載
- Every operation defines the same default response
- This response defines only one application/json media
openapi.yaml を修正することによって自動生成コードに変化が現れます。
-
interface
として NewError() メソッドが必要となる - エラーハンドリング部分に下記実装が追加される
+if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) +}
また、コード自動生成実行時のログも変化し以下のような出力となります。
INFO convenient Generating convenient error response
この実装を使うことで以下のようにエラーハンドリングを共通化して実装することができます。
Convinient errors なしの場合よりコードの量が多くなっていますが、 API の数が多くなればなるほど共通化の恩恵を受けることができるでしょう。
ただし、デメリットとして openapi.yaml で定義していないステータスコードも返る可能性がでてきます。
その点は気をつけていきたいです。
おわりに
Convenient errors を使うと ogen その名の通り便利になります。
ogen のエラーハンドリングで冗長なコードを書いているなと思ったらぜひ使ってみてください。
今回実装したコードは以下に置いておきます。
Discussion