LoginSignup
5
3

More than 5 years have passed since last update.

Rubyの prmd gem でYAMLのスキーマバリデーションをする

Posted at

Prmd

Prmd は JSON Schema の scaffold, 正当性チェック, ドキュメント生成を行うツールです。

interagent/prmd - GitHub

インストール

$ gem install prmd

サンプル

名前と年齢を保持する Person を定義してバリデーションしてみます。

init

テンプレートを生成します

$ prmd init --yaml person -o person.yml
  • 生成されたファイル
person.yml
---
"$schema": http://json-schema.org/draft-04/hyper-schema
title: FIXME - Person
description: FIXME
stability: prototype
strictProperties: true
type:
- object
definitions:
  id:
    description: unique identifier of person
    readOnly: true
    format: uuid
    type:
    - string
  name:
    description: unique name of person
    readOnly: true
    type:
    - string
  identity:
    anyOf:
    - "$ref": "/schemata/person#/definitions/id"
    - "$ref": "/schemata/person#/definitions/name"
  created_at:
    description: when person was created
    format: date-time
    type:
    - string
  updated_at:
    description: when person was updated
    format: date-time
    type:
    - string
links:
- description: Create a new person.
  href: "/persons"
  method: POST
  rel: create
  schema:
    properties: {}
    type:
    - object
  title: Create
- description: Delete an existing person.
  href: "/persons/{(%2Fschemata%2Fperson%23%2Fdefinitions%2Fidentity)}"
  method: DELETE
  rel: destroy
  title: Delete
- description: Info for existing person.
  href: "/persons/{(%2Fschemata%2Fperson%23%2Fdefinitions%2Fidentity)}"
  method: GET
  rel: self
  title: Info
- description: List existing persons.
  href: "/persons"
  method: GET
  rel: instances
  title: List
- description: Update an existing person.
  href: "/persons/{(%2Fschemata%2Fperson%23%2Fdefinitions%2Fidentity)}"
  method: PATCH
  rel: update
  schema:
    properties: {}
    type:
    - object
  title: Update
properties:
  created_at:
    "$ref": "/schemata/person#/definitions/created_at"
  id:
    "$ref": "/schemata/person#/definitions/id"
  name:
    "$ref": "/schemata/person#/definitions/name"
  updated_at:
    "$ref": "/schemata/person#/definitions/updated_at"
id: schemata/person

生成したテンプレートを編集

person.yml
---
id: schemata/person
"$schema": http://json-schema.org/draft-04/hyper-schema
title: Person
description: 
stability: prototype
strictProperties: true
type:
- object
definitions:
  name:
    description: name of person
    type:
    - string
  age:
    description: age of person
    pattern: ^[0-9]{1,3}$
    type:
    - string
    - "null"
  identity:
    "$ref": "/schemata/person#/definitions/name"
required: ["name"]
properties:
  name:
    "$ref": "/schemata/person#/definitions/name"
  age:
    "$ref": "/schemata/person#/definitions/age"

METAファイルを作成

meta.yml
---
"$schema": http://json-schema.org/draft-04/hyper-schema
title: Sample API JSON Schema
description: sample schema
id: sample
links:
- href: https://example.com
  rel: self

結合する

YAMLを結合します

$ prmd combine --meta meta.yml person.yml > schema.json
  • 出力結果を確認
schema.json
{
  "$schema": "http://json-schema.org/draft-04/hyper-schema",
  "type": [
    "object"
  ],
  "definitions": {
    "person": {
      "$schema": "http://json-schema.org/draft-04/hyper-schema",
      "title": "Person",
      "description": "人",
      "stability": "prototype",
      "strictProperties": true,
      "type": [
        "object"
      ],
      "definitions": {
        "name": {
          "description": "name of person",
          "type": [
            "string"
          ]
        },
        "age": {
          "description": "age of person",
          "pattern": "^[0-9]{1,3}$",
          "type": [
            "string",
            "null"
          ]
        },
        "identity": {
          "$ref": "#/definitions/person/definitions/name"
        }
      },
      "required": [
        "name"
      ],
      "properties": {
        "name": {
          "$ref": "#/definitions/person/definitions/name"
        },
        "age": {
          "$ref": "#/definitions/person/definitions/age"
        }
      }
    }
  },
  "properties": {
    "person": {
      "$ref": "#/definitions/person"
    }
  },
  "title": "Sample API JSON Schema",
  "description": "sample schema",
  "id": "sample",
  "links": [
    {
      "href": "https://example.com",
      "rel": "self"
    }
  ]
}

ドキュメントを生成

$ prmd doc schema.json > schema.md
  • 出力結果を確認
schema.md
## <a name="resource-person">Person</a>

Stability: `prototype`### Attributes

| Name | Type | Description | Example |
| ------- | ------- | ------- | ------- |
| **age** | *string* | age of person<br/> **pattern:** `^[0-9]{1,3}$` | `"example"` |
| **name** | *string* | name of person | `"example"` |

JSON Schema としての正当性をチェックする

$ prmd verify schema.json

わざとエラーを発生させてみます。

age:
  description: age of person
  pattern: ^\[0-9]{1,3}$
  type:
  # number を不正な値 invalid に変更する
  - invalid
$ prmd verify schema.json
schema.json: #/definitions/person/definitions/age/type: failed schema #/properties/type: No subschema in "anyOf" matched.
schema.json: #: failed schema #: Not all subschemas of "allOf" matched.
schema.json: #/definitions/person/definitions/age/type: failed schema #/properties/type: No subschema in "anyOf" matched.
schema.json: #/definitions/person: failed schema #/properties/definitions/additionalProperties: Not all subschemas of "allOf" matched.
schema.json: #/definitions/person/definitions/age/type: failed schema #/properties/type: No subschema in "anyOf" matched.
schema.json: #/definitions/person/definitions/age: failed schema #/properties/definitions/additionalProperties: Not all subschemas of "allOf" matched.

json-schema gem でバリデーションを実行する

  • テストデータ1 : 正常系
person1.yml
person:
  name: tanaka1
  age: "34"
  • テストデータ2 : ageのパターンエラー
person2.yml
person:
  name: tanaka
  age: "1000"
  • テストデータ3 : name の null エラー
person3.yml
person:
  name:
  age: "20"
  • テストデータ4 : 正常系(age は null 許容)
person4.yml
person:
  name: suzuki
  age:
  • テストデータ5 : name の必須エラー
person5.yml
person:
  age:
  • テストコード
test.rb
require "json-schema"
require 'pp'
require 'yaml'
require 'json'

schema = JSON.parse(File.read('schema.json'))
jsons = (1..5).map{|e|YAML.load_file("person#{e}.yml").to_json}
jsons.each do |json|
  begin
    pp json
    pp JSON::Validator.validate!(schema, json)
  rescue JSON::Schema::ValidationError => e
    pp e.message
  end
end
  • 実行結果
$ ruby test.rb
"{\"person\":{\"name\":\"tanaka1\",\"age\":\"34\"}}"
true
"{\"person\":{\"name\":\"tanaka\",\"age\":\"1000\"}}"
"The property '#/person/age' value \"1000\" did not match the regex '^[0-9]{1,3}$'"
"{\"person\":{\"name\":null,\"age\":\"20\"}}"
"The property '#/person/name' of type NilClass did not match one or more of the following types: string"
"{\"person\":{\"name\":\"suzuki\",\"age\":null}}"
true
"{\"person\":{\"age\":null}}"
"The property '#/person' did not contain a required property of 'name'"

外部資料

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3