LESSON 30分

ストーリー

高橋アーキテクト
OpenAPIの基本構造は分かった。次はスキーマの詳細だ

高橋アーキテクトがバリデーションエラーのログを見せた。

高橋アーキテクト
このAPIは、“年齢”フィールドに -5 が送られてもエラーにならなかった。スキーマで制約を定義していなかったからだ
あなた
スキーマに制約を書いておけば、自動でバリデーションしてくれるんですか?
高橋アーキテクト
そうだ。OpenAPIのスキーマは JSON Schema ベースだから、型、範囲、パターン、必須チェックなどを宣言的に定義できる

データ型と制約

基本データ型

# 文字列
name:
  type: string
  minLength: 1
  maxLength: 100
  example: "田中太郎"

# 数値
age:
  type: integer
  minimum: 0
  maximum: 150
  example: 25

price:
  type: number
  format: double
  minimum: 0
  exclusiveMinimum: true
  example: 1500.50

# 真偽値
isActive:
  type: boolean
  default: true

# 日付・時刻
createdAt:
  type: string
  format: date-time
  example: "2025-01-15T09:00:00Z"

birthDate:
  type: string
  format: date
  example: "1990-05-15"

文字列の制約

# 正規表現パターン
zipCode:
  type: string
  pattern: "^\\d{3}-\\d{4}$"
  example: "100-0001"

phoneNumber:
  type: string
  pattern: "^0\\d{1,4}-\\d{1,4}-\\d{4}$"
  example: "03-1234-5678"

# format による制約
email:
  type: string
  format: email
  example: "user@example.com"

url:
  type: string
  format: uri
  example: "https://example.com"

uuid:
  type: string
  format: uuid
  example: "550e8400-e29b-41d4-a716-446655440000"

列挙型(Enum)

status:
  type: string
  enum: [todo, in_progress, in_review, done]
  description: |
    タスクのステータス:
    * `todo` - 未着手
    * `in_progress` - 進行中
    * `in_review` - レビュー中
    * `done` - 完了

priority:
  type: string
  enum: [low, medium, high, critical]
  default: medium

複合型の定義

オブジェクトとネスト

components:
  schemas:
    Task:
      type: object
      required: [id, title, status, createdAt]
      properties:
        id:
          type: string
          example: "tsk_789"
        title:
          type: string
          minLength: 1
          maxLength: 200
        description:
          type: string
          maxLength: 5000
          nullable: true
        status:
          type: string
          enum: [todo, in_progress, in_review, done]
        priority:
          type: string
          enum: [low, medium, high, critical]
          default: medium
        assignee:
          $ref: "#/components/schemas/UserSummary"
        labels:
          type: array
          items:
            $ref: "#/components/schemas/Label"
          maxItems: 10
        dueDate:
          type: string
          format: date-time
          nullable: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    UserSummary:
      type: object
      required: [id, name]
      properties:
        id:
          type: string
        name:
          type: string

    Label:
      type: object
      required: [id, name, color]
      properties:
        id:
          type: string
        name:
          type: string
        color:
          type: string
          pattern: "^#[0-9a-fA-F]{6}$"

配列の制約

tags:
  type: array
  items:
    type: string
    minLength: 1
    maxLength: 50
  minItems: 0
  maxItems: 20
  uniqueItems: true
  example: ["frontend", "urgent", "bug"]

高度なスキーマ機能

oneOf / anyOf / allOf

# allOf: 複数のスキーマを結合(AND)
CreateTaskRequest:
  allOf:
    - $ref: "#/components/schemas/TaskBase"
    - type: object
      required: [title]
      properties:
        assigneeId:
          type: string

# oneOf: いずれか1つに合致(XOR)
NotificationTarget:
  oneOf:
    - $ref: "#/components/schemas/EmailTarget"
    - $ref: "#/components/schemas/SlackTarget"
    - $ref: "#/components/schemas/WebhookTarget"
  discriminator:
    propertyName: type
    mapping:
      email: "#/components/schemas/EmailTarget"
      slack: "#/components/schemas/SlackTarget"
      webhook: "#/components/schemas/WebhookTarget"

# anyOf: 1つ以上に合致(OR)
SearchResult:
  anyOf:
    - $ref: "#/components/schemas/UserResult"
    - $ref: "#/components/schemas/TaskResult"

再利用可能なレスポンス

components:
  responses:
    NotFound:
      description: "リソースが見つかりません"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            error:
              code: "NOT_FOUND"
              message: "指定されたリソースが見つかりません"
              traceId: "req_abc123"

    ValidationError:
      description: "バリデーションエラー"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ValidationErrorResponse"

    Unauthorized:
      description: "認証エラー"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            error:
              code: "UNAUTHENTICATED"
              message: "認証が必要です"
              traceId: "req_def456"

  schemas:
    ErrorResponse:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message, traceId]
          properties:
            code:
              type: string
            message:
              type: string
            traceId:
              type: string

    ValidationErrorResponse:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message, details, traceId]
          properties:
            code:
              type: string
              example: "VALIDATION_ERROR"
            message:
              type: string
            details:
              type: array
              items:
                type: object
                properties:
                  field:
                    type: string
                  message:
                    type: string
            traceId:
              type: string

まとめ

ポイント内容
基本データ型string, integer, number, boolean + format
文字列制約minLength, maxLength, pattern, format
数値制約minimum, maximum, exclusiveMinimum
列挙型enum で許容値を限定
複合型allOf(結合), oneOf(排他), anyOf(論理和)
再利用$ref でスキーマ・レスポンスを共有

チェックリスト

  • 基本データ型と制約(minLength, pattern, enum等)を使いこなせる
  • ネストされたオブジェクトのスキーマを定義できる
  • allOf, oneOf, anyOf の違いと使い分けを理解した
  • 再利用可能なレスポンス定義のパターンを把握した

次のステップへ

スキーマ定義とバリデーションを学びました。

次のセクションでは、OpenAPIからのコード生成とドキュメント自動化を学びます。 仕様書を書くだけで、実装の大部分が自動化される世界を体験しましょう。


推定読了時間: 30分