💨

Postman Spec Hub が OpenAPI 2.0 / 3.1 に対応したので試してみた

に公開

過去新しいくリリースされたPostman SpecHubを以下の記事で試してみました。
https://zenn.dev/kameoncloud/articles/b536274e56205d

OpenAPI3.0 / Async2.0 準拠のAPI諸元(設計図)を管理することが可能でそこから必要に応じてコレクション(テスト用APIコール)を作成できる機能です。

2025年11月5日にSpecHubが新たに OpenAPI2.0 / 3.1 の諸元に対応しました。2.0は今から新規で作成することは少ないかと思いますのでこの記事では3.1を試していきます。

OpenAPI 3.1

OpenAPI 3.1 では3.0に加えて主に以下の変更が入っています。

JSON Schema 2020-12にフル準拠。
3.0でももちろんJSONには対応していたのですがあくまで独自解釈で対応している、という状態でしたが、3.1ではJSON Schema 2020-12に正式対応したことにより表現が統一されました。

条件によるバリデーション
上記によりPostされるボディなどに対して条件によるバリデーションが接敵出るようになりました。
if / then / else 構文を用いて条件によって必要なパラメータ条件を変動させることが可能になりました。

schema:
  $schema: "https://json-schema.org/draft/2020-12/schema"
  type: object
  properties:
    status:
      type: string
      enum: [pending, paid]
    notes:
      type: ["string", "null"]
    payment:
      type: ["object", "null"]
      properties:
        method:
          type: string
          enum: [card, bank]
        transactionId:
          type: string
      required: ["method"]
  required: ["status"]

  # 🔍 ここが3.1で使える if / then / else 構文
  if:
    properties:
      status: { const: paid }
  then:
    required: ["payment"]
  else:
    properties:
      payment: { type: "null" }

  unevaluatedProperties: false

例えばこのサンプルですとstatuspaidの場合、paymentパラメータは必須になります。一方statuspaid以外の場合、paymentパラメータはnull固定になります。

null の取り扱いの明確化
3.0ではnullable: trueとしてnullを取り扱っていましたが、3.1ではtype: ["string","null"]としてユニオン型で定義されるようになりました。

詳細はこちらの公式サイトを参照してください。
https://learn.openapis.org/upgrading/v3.0-to-v3.1.html?utm_source=chatgpt.com

さっそくやってみる

ではさっそくOpenAPI3.1ベースのAPI諸元をyamlで作成して投入してみます。

新たに選択できるようになったOpenAPI3.1を選択します。
以下のyamlを貼り付けて保存します。

openapi: 3.1.0
info:
  title: Sample 3.1-only Features API
  version: 1.0.2
  description: >
    OpenAPI 3.1 で追加された機能を使ったデモAPI。
    - JSON Schema 2020-12準拠
    - if/then/else, unevaluatedProperties, dependentSchemas
    - webhooks, components.pathItems
    - ユニオン型によるnull許容
jsonSchemaDialect: "https://json-schema.org/draft/2020-12/schema"

servers:
  - url: https://kameda.requestcatcher.com/

paths:
  /orders/{orderId}:
    $ref: "#/components/pathItems/orderById"

  /orders:
    post:
      summary: Create an order
      description: 新しい注文を作成します。
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $schema: "https://json-schema.org/draft/2020-12/schema"
              $id: "https://kameda.requestcatcher.com/schemas/create-order"
              type: object
              properties:
                status:
                  type: string
                  enum: [pending, paid]
                notes:
                  type: ["string", "null"]
                payment:
                  type: ["object", "null"]
                  properties:
                    method:
                      type: string
                      enum: [card, bank]
                    transactionId:
                      type: string
                  required: ["method"]
              required: ["status"]
              if:
                properties:
                  status: { const: paid }
              then:
                required: ["payment"]
              else:
                properties:
                  payment: { type: "null" }
              unevaluatedProperties: false
            examples:
              pending:
                summary: 注文保留状態
                value:
                  {
                    "status": "pending",
                    "notes": "call me before shipping",
                    "payment": null
                  }
              paid_card:
                summary: 支払い完了(カード)
                value:
                  {
                    "status": "paid",
                    "notes": "please deliver soon",
                    "payment": {
                      "method": "card",
                      "transactionId": "tr_abc123"
                    }
                  }
              paid_bank:
                summary: 支払い完了(銀行振込)
                value:
                  {
                    "status": "paid",
                    "notes": null,
                    "payment": {
                      "method": "bank",
                      "transactionId": "bank_20251105_001"
                    }
                  }
              minimal:
                summary: 最小構成(status のみ)
                value:
                  { "status": "pending" }
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Order"
        "400":
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

components:
  pathItems:
    orderById:
      parameters:
        - name: orderId
          in: path
          required: true
          description: Order identifier
          schema:
            type: string
      get:
        summary: Get order by ID
        responses:
          "200":
            description: OK
            content:
              application/json:
                schema:
                  $ref: "#/components/schemas/Order"
          "404":
            description: Not Found
            content:
              application/json:
                schema:
                  $ref: "#/components/schemas/Error"

  schemas:
    Order:
      $schema: "https://json-schema.org/draft/2020-12/schema"
      $id: "https://kameda.requestcatcher.com/schemas/order"
      type: object
      properties:
        id: { type: string }
        status:
          type: string
          enum: [pending, paid, shipped, cancelled]
        notes:
          type: ["string", "null"]
        delivery:
          type: object
          properties:
            method:
              type: string
              enum: [pickup, courier]
            address:
              type: ["object", "null"]
              properties:
                line1: { type: string }
                city: { type: string }
                postalCode: { type: string }
              required: ["line1", "city", "postalCode"]
          required: ["method"]
      required: ["id", "status"]
      dependentSchemas:
        delivery:
          properties:
            delivery:
              type: object
              if:
                properties:
                  method: { const: courier }
              then:
                required: ["address"]
              else:
                properties:
                  address: { type: "null" }
      unevaluatedProperties: false

    Error:
      $schema: "https://json-schema.org/draft/2020-12/schema"
      type: object
      properties:
        code: { type: integer }
        message: { type: string }
        cause: { type: ["string", "null"] }
      required: ["code", "message"]
      unevaluatedProperties: false

webhooks:
  orderCreated:
    post:
      summary: Webhook when an order is created
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $schema: "https://json-schema.org/draft/2020-12/schema"
              allOf:
                - $ref: "#/components/schemas/Order"
              unevaluatedProperties: false
      responses:
        "200":
          description: Acknowledge

Generate Collectonをクリックします。

APIコールが一つの複数のサンプルが作成されました。

ボディでは以下の様に条件ごとにパラメータが変わっており、その条件分岐ごとにサンプルが自動で作成されています。

Discussion