diff --git a/.github/workflows/validate-examples.yaml b/.github/workflows/validate-examples.yaml new file mode 100644 index 0000000000..dc13c37ffb --- /dev/null +++ b/.github/workflows/validate-examples.yaml @@ -0,0 +1,27 @@ +name: validate-examples + +# Author: @MikeRalphson +# Issue: https://github.com/OAI/OpenAPI-Specification/issues/1739 + +# +# This workflow validates files in the examples/v2 and /v3 directories +# + +# run this on push to any branch and creation of pull-requests +on: [push, pull_request] + +jobs: + validate-examples: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 # checkout repo content + - uses: actions/setup-node@v1 # setup Node.js + with: + node-version: '12.x' + - name: Install dependencies + run: npm i + - name: Validate examples + run: node scripts/validateExamples/index.js + diff --git a/.github/workflows/validate-schemas.yaml b/.github/workflows/validate-schemas.yaml new file mode 100644 index 0000000000..f89490c214 --- /dev/null +++ b/.github/workflows/validate-schemas.yaml @@ -0,0 +1,31 @@ +name: validate-schemas + +# Author: @MikeRalphson +# Issue: https://github.com/OAI/OpenAPI-Specification/issues/1739 + +# +# This workflow validates files in the schemas directory against their +# appropriate meta-schemas +# + +# run this on push to any branch and creation of pull-requests +on: [push, pull_request] + +jobs: + mdv: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 # checkout repo content + - uses: actions/setup-node@v1 # setup Node.js + with: + node-version: '12.x' + - name: Install dependencies + run: npm i + - name: Validate schemas + run: | + node scripts/validateSchema/index.js schemas/v1.2/apiDeclaration.json schemas/jsonSchema/draft-04/metaschema.json + node scripts/validateSchema/index.js schemas/v2.0/schema.json schemas/jsonSchema/draft-04/metaschema.json + node scripts/validateSchema/index.js schemas/v3.0/schema.yaml schemas/jsonSchema/draft-04/metaschema.json + diff --git a/package.json b/package.json index 08cbfdd010..27936d4f2a 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "chai": "^4.3.1", "mdv": "^1.0.7", "mocha": "^8.3.0", + "node-readfiles": "^0.2.0", "yaml": "^1.8.3" }, "keywords": [ diff --git a/schemas/jsonSchema/draft-04/metaschema.json b/schemas/jsonSchema/draft-04/metaschema.json new file mode 100644 index 0000000000..bcbb84743e --- /dev/null +++ b/schemas/jsonSchema/draft-04/metaschema.json @@ -0,0 +1,149 @@ +{ + "id": "http://json-schema.org/draft-04/schema#", + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#" } + }, + "positiveInteger": { + "type": "integer", + "minimum": 0 + }, + "positiveIntegerDefault0": { + "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ] + }, + "simpleTypes": { + "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1, + "uniqueItems": true + } + }, + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "$schema": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": {}, + "multipleOf": { + "type": "number", + "minimum": 0, + "exclusiveMinimum": true + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "boolean", + "default": false + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "boolean", + "default": false + }, + "maxLength": { "$ref": "#/definitions/positiveInteger" }, + "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { + "anyOf": [ + { "type": "boolean" }, + { "$ref": "#" } + ], + "default": {} + }, + "items": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/schemaArray" } + ], + "default": {} + }, + "maxItems": { "$ref": "#/definitions/positiveInteger" }, + "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "maxProperties": { "$ref": "#/definitions/positiveInteger" }, + "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "required": { "$ref": "#/definitions/stringArray" }, + "additionalProperties": { + "anyOf": [ + { "type": "boolean" }, + { "$ref": "#" } + ], + "default": {} + }, + "definitions": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "properties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/stringArray" } + ] + } + }, + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + }, + "type": { + "anyOf": [ + { "$ref": "#/definitions/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/definitions/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "format": { "type": "string" }, + "allOf": { "$ref": "#/definitions/schemaArray" }, + "anyOf": { "$ref": "#/definitions/schemaArray" }, + "oneOf": { "$ref": "#/definitions/schemaArray" }, + "not": { "$ref": "#" } + }, + "dependencies": { + "exclusiveMaximum": [ "maximum" ], + "exclusiveMinimum": [ "minimum" ] + }, + "default": {} +} diff --git a/scripts/validateExamples/index.js b/scripts/validateExamples/index.js new file mode 100644 index 0000000000..5167517d5b --- /dev/null +++ b/scripts/validateExamples/index.js @@ -0,0 +1,57 @@ +#!/usr/bin/env node +'use strict'; + +const fs = require('fs'); +const util = require('util'); + +const yaml = require('yaml'); +const rf = require('node-readfiles'); +const jsonschema = require('@hyperjump/json-schema'); + +const schema = {}; +schema["v2.0"] = yaml.parse(fs.readFileSync('./schemas/v2.0/schema.json','utf8')); +schema["v3.0"] = yaml.parse(fs.readFileSync('./schemas/v3.0/schema.yaml','utf8')); + +jsonschema.add(schema["v2.0"]); +jsonschema.add(schema["v3.0"]); + +async function main(path,schema,propName) { + return new Promise(async function(resolve,reject){ + let files = await rf(path, { readContents: false, filenameFormat: rf.FULL_PATH }); + files = files.sort(); + for (let file of files) { + const instanceStr = fs.readFileSync(file,'utf8'); + let instanceObj; + try { + instanceObj = yaml.parse(instanceStr,{prettyErrors:true}); + } + catch (ex) { + process.exitCode = 1; + console.warn(file,ex.message); + } + if (instanceObj && instanceObj[propName]) { + console.log('Validating',file); + try { + const schemaObj = await jsonschema.get(schema.id); + const result = await jsonschema.validate(schemaObj, instanceObj, jsonschema.DETAILED); + if (!result.valid) { + process.exitCode = 1; + console.warn(file,util.inspect(result.errors, {depth:null})); + } + } + catch (ex) { + process.exitCode = 1; + console.warn(file,ex.message); + } + } + } + resolve(files); + }); +} + +async function validateExamples(){ + await main('./examples/v2.0/',schema["v2.0"],'swagger'); + await main('./examples/v3.0/',schema["v3.0"],'openapi'); +} + +validateExamples(); diff --git a/scripts/validateSchema/index.js b/scripts/validateSchema/index.js new file mode 100644 index 0000000000..b8eb15cdce --- /dev/null +++ b/scripts/validateSchema/index.js @@ -0,0 +1,33 @@ +#!/usr/bin/env node +'use strict'; + +const fs = require('fs'); +const util = require('util'); + +const yaml = require('yaml'); +const jsonschema = require('@hyperjump/json-schema'); + +const schema = yaml.parse(fs.readFileSync(process.argv[2],'utf8')); +const metaSchema = yaml.parse(fs.readFileSync(process.argv[3],'utf8')); + +async function main() { + jsonschema.add(metaSchema); + const msObj = await jsonschema.get(metaSchema.id); + let result; + try { + result = await jsonschema.validate(msObj, schema, jsonschema.DETAILED); + } + catch (ex) { + result = { valid: false, errors: [ ex ] }; + } + if (!result.valid) { + console.warn(util.inspect(result.errors, {depth:null})); + process.exit(1); + } + else { + console.log('OK'); + } +} + +console.log('Checking',process.argv[2]); +main();