diff --git a/.gitignore b/.gitignore index 0f193c2e..582ed7b5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ # Jekyll products .sass-cache/ _site/ +node_modules/ diff --git a/.travis.yml b/.travis.yml index c0bdbed8..12841e3a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,25 @@ language: ruby dist: trusty sudo: false +node_js: +- node +before_script: +- npm install ajv-cli +- PATH="./node_modules/.bin/:$PATH" script: - bundle exec jekyll build +- ajv test -s schema -d "_includes/person.json" --valid +- ajv test -s schema -d "_includes/example1/schema*.json" --valid +- ajv test -s _includes/example1/schema1.json -d _includes/example1/instance.json --valid +- ajv test -s _includes/example1/schema2.json -d _includes/example1/instance.json --valid +- ajv test -s _includes/example1/schema3.json -d _includes/example1/instance.json --valid +- ajv test -s _includes/example1/schema4.json -d _includes/example1/instance.json --valid +- ajv test -s _includes/example1/schema5.json -d _includes/example1/instance.json --valid +- ajv test -s schema -d _includes/example1/set_schema.json --valid +- ajv test -s _includes/example1/set_schema.json -d _includes/example1/set_instance.json -r geo --valid +- ajv test -s schema -d "_includes/example2/schema*.json" --valid +- ajv test -s schema -d "_includes/example2/entry_schema*.json" --valid +- ajv test -s schema -d "_includes/example2/storage_schema*.json" --valid +- ajv test -s _includes/example2/schema1.json -d _includes/example2/instance.json --valid +- ajv test -s _includes/example2/schema2.json -d _includes/example2/instance.json -r _includes/example2/entry_schema3.json --valid +- ajv test -s schema -d address -d calendar -d card -d geo --valid \ No newline at end of file diff --git a/_config.yml b/_config.yml index ad96e174..71a91b24 100644 --- a/_config.yml +++ b/_config.yml @@ -31,10 +31,10 @@ header_pages: - examples.md - implementations.md -collections: - docs: - output: true - permalink: "/:title:output_ext" +exclude: +- README.md +- Gemfile +- node_modules gems: - jekyll-relative-links diff --git a/_includes/example1/instance.json b/_includes/example1/instance.json new file mode 100644 index 00000000..3cf40ec6 --- /dev/null +++ b/_includes/example1/instance.json @@ -0,0 +1,6 @@ +{ + "id": 1, + "name": "A green door", + "price": 12.50, + "tags": ["home", "green"] +} \ No newline at end of file diff --git a/_includes/example1/schema1.json b/_includes/example1/schema1.json new file mode 100644 index 00000000..bde74825 --- /dev/null +++ b/_includes/example1/schema1.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "title": "Product", + "description": "A product from Acme's catalog", + "type": "object" +} \ No newline at end of file diff --git a/_includes/example1/schema2.json b/_includes/example1/schema2.json new file mode 100644 index 00000000..593db4ce --- /dev/null +++ b/_includes/example1/schema2.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "title": "Product", + "description": "A product from Acme's catalog", + "type": "object", + "properties": { + "id": { + "description": "The unique identifier for a product", + "type": "integer" + } + }, + "required": ["id"] +} \ No newline at end of file diff --git a/_includes/example1/schema3.json b/_includes/example1/schema3.json new file mode 100644 index 00000000..cd8e32fd --- /dev/null +++ b/_includes/example1/schema3.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "title": "Product", + "description": "A product from Acme's catalog", + "type": "object", + "properties": { + "id": { + "description": "The unique identifier for a product", + "type": "integer" + }, + "name": { + "description": "Name of the product", + "type": "string" + } + }, + "required": ["id", "name"] +} \ No newline at end of file diff --git a/_includes/example1/schema4.json b/_includes/example1/schema4.json new file mode 100644 index 00000000..5d0a5490 --- /dev/null +++ b/_includes/example1/schema4.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "title": "Product", + "description": "A product from Acme's catalog", + "type": "object", + "properties": { + "id": { + "description": "The unique identifier for a product", + "type": "integer" + }, + "name": { + "description": "Name of the product", + "type": "string" + }, + "price": { + "type": "number", + "exclusiveMinimum": 0 + } + }, + "required": ["id", "name", "price"] +} \ No newline at end of file diff --git a/_includes/example1/schema5.json b/_includes/example1/schema5.json new file mode 100644 index 00000000..c17d9db2 --- /dev/null +++ b/_includes/example1/schema5.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "title": "Product", + "description": "A product from Acme's catalog", + "type": "object", + "properties": { + "id": { + "description": "The unique identifier for a product", + "type": "integer" + }, + "name": { + "description": "Name of the product", + "type": "string" + }, + "price": { + "type": "number", + "exclusiveMinimum": 0 + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "uniqueItems": true + } + }, + "required": ["id", "name", "price"] +} \ No newline at end of file diff --git a/_includes/example1/set_instance.json b/_includes/example1/set_instance.json new file mode 100644 index 00000000..b847cede --- /dev/null +++ b/_includes/example1/set_instance.json @@ -0,0 +1,31 @@ +[ + { + "id": 2, + "name": "An ice sculpture", + "price": 12.50, + "tags": ["cold", "ice"], + "dimensions": { + "length": 7.0, + "width": 12.0, + "height": 9.5 + }, + "warehouseLocation": { + "latitude": -78.75, + "longitude": 20.4 + } + }, + { + "id": 3, + "name": "A blue mouse", + "price": 25.50, + "dimensions": { + "length": 3.1, + "width": 1.0, + "height": 1.0 + }, + "warehouseLocation": { + "latitude": 54.4, + "longitude": -32.7 + } + } +] \ No newline at end of file diff --git a/_includes/example1/set_schema.json b/_includes/example1/set_schema.json new file mode 100644 index 00000000..93366142 --- /dev/null +++ b/_includes/example1/set_schema.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "title": "Product set", + "type": "array", + "items": { + "title": "Product", + "type": "object", + "properties": { + "id": { + "description": "The unique identifier for a product", + "type": "number" + }, + "name": { + "type": "string" + }, + "price": { + "type": "number", + "exclusiveMinimum": 0 + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "uniqueItems": true + }, + "dimensions": { + "type": "object", + "properties": { + "length": {"type": "number"}, + "width": {"type": "number"}, + "height": {"type": "number"} + }, + "required": ["length", "width", "height"] + }, + "warehouseLocation": { + "description": "Coordinates of the warehouse with the product", + "$ref": "http://json-schema.org/geo" + } + }, + "required": ["id", "name", "price"] + } +} \ No newline at end of file diff --git a/_includes/example2/entry_schema1.json b/_includes/example2/entry_schema1.json new file mode 100644 index 00000000..d6c7beff --- /dev/null +++ b/_includes/example2/entry_schema1.json @@ -0,0 +1,24 @@ +{ + "id": "http://some.site.somewhere/entry-schema#", + "$schema": "http://json-schema.org/draft-06/schema#", + "description": "schema for an fstab entry", + "type": "object", + "required": [ "storage" ], + "properties": { + "storage": { + "type": "object", + "oneOf": [ + { "$ref": "#/definitions/diskDevice" }, + { "$ref": "#/definitions/diskUUID" }, + { "$ref": "#/definitions/nfs" }, + { "$ref": "#/definitions/tmpfs" } + ] + } + }, + "definitions": { + "diskDevice": {}, + "diskUUID": {}, + "nfs": {}, + "tmpfs": {} + } +} \ No newline at end of file diff --git a/_includes/example2/entry_schema2.json b/_includes/example2/entry_schema2.json new file mode 100644 index 00000000..50dfc582 --- /dev/null +++ b/_includes/example2/entry_schema2.json @@ -0,0 +1,34 @@ +{ + "id": "http://some.site.somewhere/entry-schema#", + "$schema": "http://json-schema.org/draft-06/schema#", + "description": "schema for an fstab entry", + "type": "object", + "required": [ "storage" ], + "properties": { + "storage": { + "type": "object", + "oneOf": [ + { "$ref": "#/definitions/diskDevice" }, + { "$ref": "#/definitions/diskUUID" }, + { "$ref": "#/definitions/nfs" }, + { "$ref": "#/definitions/tmpfs" } + ] + }, + "fstype": { + "enum": [ "ext3", "ext4", "btrfs" ] + }, + "options": { + "type": "array", + "minItems": 1, + "items": { "type": "string" }, + "uniqueItems": true + }, + "readonly": { "type": "boolean" } + }, + "definitions": { + "diskDevice": {}, + "diskUUID": {}, + "nfs": {}, + "tmpfs": {} + } +} \ No newline at end of file diff --git a/_includes/example2/entry_schema3.json b/_includes/example2/entry_schema3.json new file mode 100644 index 00000000..5ae25a98 --- /dev/null +++ b/_includes/example2/entry_schema3.json @@ -0,0 +1,83 @@ +{ + "id": "http://some.site.somewhere/entry-schema#", + "$schema": "http://json-schema.org/draft-06/schema#", + "description": "schema for an fstab entry", + "type": "object", + "required": [ "storage" ], + "properties": { + "storage": { + "type": "object", + "oneOf": [ + { "$ref": "#/definitions/diskDevice" }, + { "$ref": "#/definitions/diskUUID" }, + { "$ref": "#/definitions/nfs" }, + { "$ref": "#/definitions/tmpfs" } + ] + }, + "fstype": { + "enum": [ "ext3", "ext4", "btrfs" ] + }, + "options": { + "type": "array", + "minItems": 1, + "items": { "type": "string" }, + "uniqueItems": true + }, + "readonly": { "type": "boolean" } + }, + "definitions": { + "diskDevice": { + "properties": { + "type": { "enum": [ "disk" ] }, + "device": { + "type": "string", + "pattern": "^/dev/[^/]+(/[^/]+)*$" + } + }, + "required": [ "type", "device" ], + "additionalProperties": false + }, + "diskUUID": { + "properties": { + "type": { "enum": [ "disk" ] }, + "label": { + "type": "string", + "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$" + } + }, + "required": [ "type", "label" ], + "additionalProperties": false + }, + "nfs": { + "properties": { + "type": { "enum": [ "nfs" ] }, + "remotePath": { + "type": "string", + "pattern": "^(/[^/]+)+$" + }, + "server": { + "type": "string", + "oneOf": [ + { "format": "hostname" }, + { "format": "ipv4" }, + { "format": "ipv6" } + ] + } + }, + "required": [ "type", "server", "remotePath" ], + "additionalProperties": false + }, + "tmpfs": { + "properties": { + "type": { "enum": [ "tmpfs" ] }, + "sizeInMB": { + "type": "integer", + "minimum": 16, + "maximum": 512 + } + }, + "required": [ "type", "sizeInMB" ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/_includes/example2/instance.json b/_includes/example2/instance.json new file mode 100644 index 00000000..acc5c4a8 --- /dev/null +++ b/_includes/example2/instance.json @@ -0,0 +1,31 @@ +{ + "/": { + "storage": { + "type": "disk", + "device": "/dev/sda1" + }, + "fstype": "btrfs", + "readonly": true + }, + "/var": { + "storage": { + "type": "disk", + "label": "8f3ba6f4-5c70-46ec-83af-0d5434953e5f" + }, + "fstype": "ext4", + "options": [ "nosuid" ] + }, + "/tmp": { + "storage": { + "type": "tmpfs", + "sizeInMB": 64 + } + }, + "/var/www": { + "storage": { + "type": "nfs", + "server": "my.nfs.server", + "remotePath": "/exports/mypath" + } + } +} \ No newline at end of file diff --git a/_includes/example2/schema1.json b/_includes/example2/schema1.json new file mode 100644 index 00000000..616f1ac7 --- /dev/null +++ b/_includes/example2/schema1.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "type": "object", + "properties": { + "/": {} + }, + "patternProperties": { + "^(/[^/]+)+$": {} + }, + "additionalProperties": false, + "required": [ "/" ] +} \ No newline at end of file diff --git a/_includes/example2/schema2.json b/_includes/example2/schema2.json new file mode 100644 index 00000000..167a7710 --- /dev/null +++ b/_includes/example2/schema2.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "type": "object", + "properties": { + "/": { "$ref": "http://some.site.somewhere/entry-schema#" } + }, + "patternProperties": { + "^(/[^/]+)+$": { "$ref": "http://some.site.somewhere/entry-schema#" } + }, + "additionalProperties": false, + "required": [ "/" ] +} \ No newline at end of file diff --git a/_includes/example2/storage_schema1.json b/_includes/example2/storage_schema1.json new file mode 100644 index 00000000..7bf92b14 --- /dev/null +++ b/_includes/example2/storage_schema1.json @@ -0,0 +1,11 @@ +{ + "properties": { + "type": { "enum": [ "disk" ] }, + "device": { + "type": "string", + "pattern": "^/dev/[^/]+(/[^/]+)*$" + } + }, + "required": [ "type", "device" ], + "additionalProperties": false +} \ No newline at end of file diff --git a/_includes/example2/storage_schema2.json b/_includes/example2/storage_schema2.json new file mode 100644 index 00000000..2c77835b --- /dev/null +++ b/_includes/example2/storage_schema2.json @@ -0,0 +1,11 @@ +{ + "properties": { + "type": { "enum": [ "disk" ] }, + "label": { + "type": "string", + "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$" + } + }, + "required": [ "type", "label" ], + "additionalProperties": false +} \ No newline at end of file diff --git a/_includes/example2/storage_schema3.json b/_includes/example2/storage_schema3.json new file mode 100644 index 00000000..66aea6e1 --- /dev/null +++ b/_includes/example2/storage_schema3.json @@ -0,0 +1,19 @@ +{ + "properties": { + "type": { "enum": [ "nfs" ] }, + "remotePath": { + "type": "string", + "pattern": "^(/[^/]+)+$" + }, + "server": { + "type": "string", + "oneOf": [ + { "format": "hostname" }, + { "format": "ipv4" }, + { "format": "ipv6" } + ] + } + }, + "required": [ "type", "server", "remotePath" ], + "additionalProperties": false +} \ No newline at end of file diff --git a/_includes/example2/storage_schema4.json b/_includes/example2/storage_schema4.json new file mode 100644 index 00000000..7caa3cd6 --- /dev/null +++ b/_includes/example2/storage_schema4.json @@ -0,0 +1,12 @@ +{ + "properties": { + "type": { "enum": [ "tmpfs" ] }, + "sizeInMB": { + "type": "integer", + "minimum": 16, + "maximum": 512 + } + }, + "required": [ "type", "sizeInMB" ], + "additionalProperties": false +} \ No newline at end of file diff --git a/_includes/person.json b/_includes/person.json new file mode 100644 index 00000000..6e128524 --- /dev/null +++ b/_includes/person.json @@ -0,0 +1,18 @@ +{ + "title": "Person", + "type": "object", + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "age": { + "description": "Age in years", + "type": "integer", + "minimum": 0 + } + }, + "required": ["firstName", "lastName"] +} \ No newline at end of file diff --git a/example/address.json b/example/address.json index 67f7a92e..dd124d65 100644 --- a/example/address.json +++ b/example/address.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-04/schema#", + "$schema": "http://json-schema.org/draft-06/schema#", "description": "An Address following the convention of http://microformats.org/wiki/hcard", "type": "object", "properties": { diff --git a/example/calendar.json b/example/calendar.json index b1a6f112..b716e8b5 100644 --- a/example/calendar.json +++ b/example/calendar.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-04/schema#", + "$schema": "http://json-schema.org/draft-06/schema#", "description": "A representation of an event", "type": "object", "required": [ "dtstart", "summary" ], @@ -33,6 +33,6 @@ }, "category": { "type": "string" }, "description": { "type": "string" }, - "geo": { "$ref": "http: //json-schema.org/geo" } + "geo": { "$ref": "http://json-schema.org/geo" } } } diff --git a/example/card.json b/example/card.json index acdc3d07..59dbbc93 100644 --- a/example/card.json +++ b/example/card.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-04/schema#", + "$schema": "http://json-schema.org/draft-06/schema#", "description": "A representation of a person, company, organization, or place", "type": "object", "required": ["familyName", "givenName"], diff --git a/example/geo.json b/example/geo.json index 2f335265..4a0610ed 100644 --- a/example/geo.json +++ b/example/geo.json @@ -1,9 +1,10 @@ { - "$schema": "http://json-schema.org/draft-04/schema#", + "id": "http://json-schema.org/geo", + "$schema": "http://json-schema.org/draft-06/schema#", "description": "A geographical coordinate", "type": "object", "properties": { "latitude": { "type": "number" }, "longitude": { "type": "number" } } -} +} \ No newline at end of file diff --git a/example1.md b/example1.md index 9c2451e3..026afc0c 100644 --- a/example1.md +++ b/example1.md @@ -10,12 +10,7 @@ Let's pretend we're interacting with a JSON based product catalog. This catalog An example product in this API is: ```json -{ - "id": 1, - "name": "A green door", - "price": 12.50, - "tags": ["home", "green"] -} +{% include example1/instance.json %} ``` While generally straightforward, that example leaves some open questions. For example, one may ask: @@ -33,19 +28,14 @@ Starting the schema To start a schema definition, let's begin with a basic JSON schema: ```json -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Product", - "description": "A product from Acme's catalog", - "type": "object" -} +{% include example1/schema1.json %} ``` The above schema has four properties called *keywords*. The *title* and *description* keywords are descriptive only, in that they do not add constraints to the data being validated. The intent of the schema is stated with these two keywords (that is, this schema describes a product). The *type* keyword defines the first constraint on our JSON data: it has to be a JSON Object. -Finally, the *$schema* keyword states that this schema is written according to the draft v4 specification. +Finally, the *$schema* keyword states that this schema is written according to the draft-06 specification. Defining the properties ----------------------- @@ -59,19 +49,7 @@ Next let's answer our previous questions about this API, starting with id. In JSON Schema terms, we can update our schema to: ```json -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Product", - "description": "A product from Acme's catalog", - "type": "object", - "properties": { - "id": { - "description": "The unique identifier for a product", - "type": "integer" - } - }, - "required": ["id"] -} +{% include example1/schema2.json %} ``` ### Is name required? @@ -79,57 +57,20 @@ In JSON Schema terms, we can update our schema to: *name* is a string value that describes a product. Since there isn't much to a product without a name, it also is required. Adding this gives us the schema: ```json -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Product", - "description": "A product from Acme's catalog", - "type": "object", - "properties": { - "id": { - "description": "The unique identifier for a product", - "type": "integer" - }, - "name": { - "description": "Name of the product", - "type": "string" - } - }, - "required": ["id", "name"] -} +{% include example1/schema3.json %} ``` ### Can price be 0? -According to Acme's docs, there are no free products. In JSON schema a number can have a minimum. By default this minimum is inclusive, so we need to specify *exclusiveMinimum*. Therefore we can update our schema with *price*: +According to Acme's docs, there are no free products. So we need to specify *exclusiveMinimum*. If we wanted to include 0 as a valid price, we would have specified *minimum* instead. Therefore, we can update our schema with *price*: ```json -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Product", - "description": "A product from Acme's catalog", - "type": "object", - "properties": { - "id": { - "description": "The unique identifier for a product", - "type": "integer" - }, - "name": { - "description": "Name of the product", - "type": "string" - }, - "price": { - "type": "number", - "minimum": 0, - "exclusiveMinimum": true - } - }, - "required": ["id", "name", "price"] -} +{% include example1/schema4.json %} ``` ### Are all tags strings? -Finally, we come to the *tags* property. Unlike the previous properties, tags have many values, and is represented as a JSON array. According to Acme's docs, all tags must be strings, but you aren't required to specify tags. We simply leave *tags* out of the list of required properties. +Finally, we come to the *tags* property. Unlike the previous properties, tags have many values, and is represented as a JSON array. If, according to Acme's docs, all tags must be strings, but you aren't required to specify tags; we would simply leave *tags* out of the list of required properties. However, Acme's docs add two constraints: @@ -139,36 +80,7 @@ However, Acme's docs add two constraints: The first constraint can be added with *minItems*, and the second one by specifying *uniqueItems* as being true: ```json -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Product", - "description": "A product from Acme's catalog", - "type": "object", - "properties": { - "id": { - "description": "The unique identifier for a product", - "type": "integer" - }, - "name": { - "description": "Name of the product", - "type": "string" - }, - "price": { - "type": "number", - "minimum": 0, - "exclusiveMinimum": true - }, - "tags": { - "type": "array", - "items": { - "type": "string" - }, - "minItems": 1, - "uniqueItems": true - } - }, - "required": ["id", "name", "price"] -} +{% include example1/schema5.json %} ``` Summary @@ -183,86 +95,11 @@ And also, since JSON Schema defines a reference schema for a geographic location ### Set of products: ```json -[ - { - "id": 2, - "name": "An ice sculpture", - "price": 12.50, - "tags": ["cold", "ice"], - "dimensions": { - "length": 7.0, - "width": 12.0, - "height": 9.5 - }, - "warehouseLocation": { - "latitude": -78.75, - "longitude": 20.4 - } - }, - { - "id": 3, - "name": "A blue mouse", - "price": 25.50, - "dimensions": { - "length": 3.1, - "width": 1.0, - "height": 1.0 - }, - "warehouseLocation": { - "latitude": 54.4, - "longitude": -32.7 - } - } -] +{% include example1/set_instance.json %} ``` ### Set of products schema: ```json -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Product set", - "type": "array", - "items": { - "title": "Product", - "type": "object", - "properties": { - "id": { - "description": "The unique identifier for a product", - "type": "number" - }, - "name": { - "type": "string" - }, - "price": { - "type": "number", - "minimum": 0, - "exclusiveMinimum": true - }, - "tags": { - "type": "array", - "items": { - "type": "string" - }, - "minItems": 1, - "uniqueItems": true - }, - "dimensions": { - "type": "object", - "properties": { - "length": {"type": "number"}, - "width": {"type": "number"}, - "height": {"type": "number"} - }, - "required": ["length", "width", "height"] - }, - "warehouseLocation": { - "description": "Coordinates of the warehouse with the product", - "$ref": "http://json-schema.org/geo" - } - }, - "required": ["id", "name", "price"] - } -} +{% include example1/set_schema.json %} ``` - diff --git a/example2.md b/example2.md index d0b1efa1..af537a5e 100644 --- a/example2.md +++ b/example2.md @@ -5,43 +5,13 @@ title: Building a mount point schema This example shows a possible JSON representation of a hypothetical machine's mount points as represented in an `/etc/fstab` file. -An entry in the fstab file can have many different forms. Here is a possible representation of a full fstab: +An entry in an fstab file can have many different forms. Here is a possible representation of a full fstab: ```json -{ - "/": { - "storage": { - "type": "disk", - "device": "/dev/sda1" - }, - "fstype": "btrfs", - "readonly": true - }, - "/var": { - "storage": { - "type": "disk", - "label": "8f3ba6f4-5c70-46ec-83af-0d5434953e5f" - }, - "fstype": "ext4", - "options": [ "nosuid" ] - }, - "/tmp": { - "storage": { - "type": "tmpfs", - "sizeInMB": 64 - } - }, - "/var/www": { - "storage": { - "type": "nfs", - "server": "my.nfs.server", - "remotePath": "/exports/mypath" - } - } -} +{% include example2/instance.json %} ``` -Not all constraints to an fstab file can be modeled using JSON Schema alone; however, it can already represent a good number of them. We will add constraints one after the other until we get to a satisfactory result. +Not all constraints to an fstab file can be modeled using JSON Schema alone; however, it can represent a good number of them. We will add constraints one after the other until we get to a satisfactory result. Base schema ----------- @@ -52,21 +22,10 @@ We will start with a base schema expressing the following constraints: - the member names (or property names) of this object must all be valid, absolute paths; - there must be an entry for the root filesystem (ie, `/`). -We also want the schema to be regarded as a draft v4 schema, we must therefore specify *$schema*: +We also want the schema to be regarded as a draft v6 schema, we must therefore specify *$schema*: ```json -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "properties": { - "/": {} - }, - "patternProperties": { - "^(/[^/]+)+$": {} - }, - "additionalProperties": false, - "required": [ "/" ] -} +{% include example2/schema1.json %} ``` Note how the valid paths constraint is enforced here: @@ -85,30 +44,7 @@ The entry schema - starting out Here again we will proceed step by step. We will start with the global structure of our schema, which will be as such: ```json -{ - "id": "http://some.site.somewhere/entry-schema#", - "$schema": "http://json-schema.org/draft-04/schema#", - "description": "schema for an fstab entry", - "type": "object", - "required": [ "storage" ], - "properties": { - "storage": { - "type": "object", - "oneOf": [ - { "$ref": "#/definitions/diskDevice" }, - { "$ref": "#/definitions/diskUUID" }, - { "$ref": "#/definitions/nfs" }, - { "$ref": "#/definitions/tmpfs" } - ] - } - }, - "definitions": { - "diskDevice": {}, - "diskUUID": {}, - "nfs": {}, - "tmpfs": {} - } -} +{% include example2/entry_schema1.json %} ``` You should already be familiar with some of the constraints: @@ -138,40 +74,7 @@ Let's now extend this skeleton to add constraints to these three properties. Not With these added constraints, the schema now looks like this: ```json -{ - "id": "http://some.site.somewhere/entry-schema#", - "$schema": "http://json-schema.org/draft-04/schema#", - "description": "schema for an fstab entry", - "type": "object", - "required": [ "storage" ], - "properties": { - "storage": { - "type": "object", - "oneOf": [ - { "$ref": "#/definitions/diskDevice" }, - { "$ref": "#/definitions/diskUUID" }, - { "$ref": "#/definitions/nfs" }, - { "$ref": "#/definitions/tmpfs" } - ] - }, - "fstype": { - "enum": [ "ext3", "ext4", "btrfs" ] - }, - "options": { - "type": "array", - "minItems": 1, - "items": { "type": "string" }, - "uniqueItems": true - }, - "readonly": { "type": "boolean" } - }, - "definitions": { - "diskDevice": {}, - "diskUUID": {}, - "nfs": {}, - "tmpfs": {} - } -} +{% include example2/entry_schema2.json %} ``` For now, all definitions are empty (an empty JSON Schema validates all instances). We will write schemas for individual definitions below, and fill these schemas into the entry schema. @@ -182,17 +85,7 @@ The *diskDevice* storage type This storage type has two required properties, *type* and *device*. The type can only be *disk*, and the device must be an absolute path starting with */dev*. No other properties are allowed: ```json -{ - "properties": { - "type": { "enum": [ "disk" ] }, - "device": { - "type": "string", - "pattern": "^/dev/[^/]+(/[^/]+)*$" - } - }, - "required": [ "type", "device" ], - "additionalProperties": false -} +{% include example2/storage_schema1.json %} ``` You will have noted that we need not specify that *type* must be a string: the constraint described by *enum* is enough. @@ -203,17 +96,7 @@ The *diskUUID* storage type This storage type has two required properties, *type* and *label*. The type can only be *disk*, and the label must be a valid UUID. No other properties are allowed: ```json -{ - "properties": { - "type": { "enum": [ "disk" ] }, - "label": { - "type": "string", - "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$" - } - }, - "required": [ "type", "label" ], - "additionalProperties": false -} +{% include example2/storage_schema2.json %} ``` The *nfs* storage type @@ -224,25 +107,7 @@ This storage type has three required properties: *type*, *server* and *remotePat For the constraints on *server*, we use a new keyword: *format*. While it is not required that *format* be supported, we will suppose that it is here: ```json -{ - "properties": { - "type": { "enum": [ "nfs" ] }, - "remotePath": { - "type": "string", - "pattern": "^(/[^/]+)+$" - }, - "server": { - "type": "string", - "oneOf": [ - { "format": "host-name" }, - { "format": "ipv4" }, - { "format": "ipv6" } - ] - } - }, - "required": [ "type", "server", "remotePath" ], - "additionalProperties": false -} +{% include example2/storage_schema3.json %} ``` The *tmpfs* storage type @@ -251,18 +116,7 @@ The *tmpfs* storage type This storage type has two required properties: *type* and *sizeInMB*. The size can only be an integer. What is more, we will require that the size be between 16 and 512, inclusive: ```json -{ - "properties": { - "type": { "enum": [ "tmpfs" ] }, - "sizeInMB": { - "type": "integer", - "minimum": 16, - "maximum": 512 - } - }, - "required": [ "type", "sizeInMB" ], - "additionalProperties": false -} +{% include example2/storage_schema4.json %} ``` The full entry schema @@ -271,89 +125,7 @@ The full entry schema The resulting schema is quite large: ```json -{ - "id": "http://some.site.somewhere/entry-schema#", - "$schema": "http://json-schema.org/draft-04/schema#", - "description": "schema for an fstab entry", - "type": "object", - "required": [ "storage" ], - "properties": { - "storage": { - "type": "object", - "oneOf": [ - { "$ref": "#/definitions/diskDevice" }, - { "$ref": "#/definitions/diskUUID" }, - { "$ref": "#/definitions/nfs" }, - { "$ref": "#/definitions/tmpfs" } - ] - }, - "fstype": { - "enum": [ "ext3", "ext4", "btrfs" ] - }, - "options": { - "type": "array", - "minItems": 1, - "items": { "type": "string" }, - "uniqueItems": true - }, - "readonly": { "type": "boolean" } - }, - "definitions": { - "diskDevice": { - "properties": { - "type": { "enum": [ "disk" ] }, - "device": { - "type": "string", - "pattern": "^/dev/[^/]+(/[^/]+)*$" - } - }, - "required": [ "type", "device" ], - "additionalProperties": false - }, - "diskUUID": { - "properties": { - "type": { "enum": [ "disk" ] }, - "label": { - "type": "string", - "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$" - } - }, - "required": [ "type", "label" ], - "additionalProperties": false - }, - "nfs": { - "properties": { - "type": { "enum": [ "nfs" ] }, - "remotePath": { - "type": "string", - "pattern": "^(/[^/]+)+$" - }, - "server": { - "type": "string", - "oneOf": [ - { "format": "host-name" }, - { "format": "ipv4" }, - { "format": "ipv6" } - ] - } - }, - "required": [ "type", "server", "remotePath" ], - "additionalProperties": false - }, - "tmpfs": { - "properties": { - "type": { "enum": [ "tmpfs" ] }, - "sizeInMB": { - "type": "integer", - "minimum": 16, - "maximum": 512 - } - }, - "required": [ "type", "sizeInMB" ], - "additionalProperties": false - } - } -} +{% include example2/entry_schema3.json %} ``` Plugging this into our main schema @@ -362,18 +134,7 @@ Plugging this into our main schema Now that all possible entries have been described, we can refer to the entry schema from our main schema. We will, again, use a JSON Reference here: ```json -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "properties": { - "/": { "$ref": "http://some.site.somewhere/entry-schema#" } - }, - "patternProperties": { - "^(/[^/]+)+$": { "$ref": "http://some.site.somewhere/entry-schema#" } - }, - "additionalProperties": false, - "required": [ "/" ] -} +{% include example2/schema2.json %} ``` Wrapping up @@ -388,7 +149,7 @@ This is only an example for learning purposes. Some additional constraints could - it makes no sense for `/` to be mounted on a tmpfs filesystem; - it makes no sense to specify the filesystem type if the storage is either NFS or tmpfs. -As an exercise, you can always try and add these constraints. It would probably require splitting the schema further. +As an exercise, you can always try to add these constraints. It would probably require splitting the schema further. ### Not all constraints can be expressed @@ -402,6 +163,4 @@ While this is not a concern if you know that the schema you write will be used b - *format* support is optional, and as such other tools may ignore this keyword: this can lead to a different validation outcome for the same data; - it uses regular expressions: care should be taken not to use any advanced features (such as lookarounds), since they may not be supported at the other end; -- it uses *$schema* to express the need to use draft v4 syntax, but not all tools support draft v4 (in fact, most don't support it). - - +- it uses *$schema* to express the need to use draft v6 compliant processing, but not all tools support draft v6. diff --git a/examples.md b/examples.md index 1c9dc474..f36ebfb4 100644 --- a/examples.md +++ b/examples.md @@ -6,24 +6,7 @@ title: Examples Here is a basic example of a JSON Schema: ```json -{ - "title": "Person", - "type": "object", - "properties": { - "firstName": { - "type": "string" - }, - "lastName": { - "type": "string" - }, - "age": { - "description": "Age in years", - "type": "integer", - "minimum": 0 - } - }, - "required": ["firstName", "lastName"] -} +{% include person.json%} ``` Example schemas