Skip to content

Commit 8e72209

Browse files
author
Phil Sturgeon
authored
Merge pull request #3 from wework/examples
Support "advanced example" from JSON Schema
2 parents 7983655 + aa2e610 commit 8e72209

25 files changed

+560
-36
lines changed

CHANGELOG.md

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Changelog
2+
All notable changes to this project will be documented in this file.
3+
4+
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5+
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6+
7+
## [Unreleased]
8+
9+
## [0.1.1] - 2018-04-09
10+
### Added
11+
* Convert `dependencies` to an allOf + oneOf OpenAPI-valid equivalent

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ A little NodeJS package to convert JSON Schema to [OpenAPI Schema Objects](https
77
* converts JSON Schema Draft 00 Wright (a.k.a draft v5) to OpenAPI 3.0 Schema Object
88
* switches `type: ['foo', 'null']` to `type: foo` and `nullable: true`
99
* supports deep structures with nested `allOf`s etc.
10-
* switches `patternProperties` to `x-patternProperties` in the Schema Object
10+
* switches `patternProperties` to `x-patternProperties`
11+
* converts `dependencies` to an allOf + oneOf OpenAPI-valid equivalent
1112

1213
## Installation
1314

index.js

+74-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
const structs = ['allOf', 'anyOf', 'oneOf', 'not', 'items', 'additionalProperties'];
22

3+
function InvalidTypeError(message) {
4+
this.name = 'InvalidTypeError';
5+
this.message = message;
6+
}
7+
8+
InvalidTypeError.prototype = new Error();
9+
310
function convert(schema, options) {
411
options = options || {};
512
options.cloneSchema = ! (options.cloneSchema === false);
@@ -8,12 +15,18 @@ function convert(schema, options) {
815
schema = JSON.parse(JSON.stringify(schema));
916
}
1017

11-
delete schema['$schema'];
18+
schema = removeRootKeywords(schema);
1219
schema = convertSchema(schema);
1320

1421
return schema;
1522
}
1623

24+
function removeRootKeywords(schema) {
25+
delete schema['$schema'];
26+
delete schema['id'];
27+
return schema;
28+
}
29+
1730
function convertSchema(schema) {
1831
let i = 0;
1932
let j = 0;
@@ -47,7 +60,10 @@ function convertSchema(schema) {
4760

4861
}
4962

63+
validateType(schema.type);
64+
5065
schema = convertTypes(schema);
66+
schema = convertDependencies(schema);
5167

5268
if (typeof schema['patternProperties'] === 'object') {
5369
schema = convertPatternProperties(schema);
@@ -56,11 +72,19 @@ function convertSchema(schema) {
5672
return schema;
5773
}
5874

75+
function validateType(type) {
76+
const validTypes = ['null', 'boolean', 'object', 'array', 'number', 'string', 'integer'];
77+
const types = Array.isArray(type) ? type : [type];
78+
types.forEach(type => {
79+
if (validTypes.indexOf(type) < 0 && type !== undefined)
80+
throw new InvalidTypeError('Type "' + type + '" is not a valid type');
81+
});
82+
}
83+
5984
function convertProperties(properties) {
60-
var key
61-
, property
62-
, props = {}
63-
;
85+
let key = {};
86+
let property = {};
87+
let props = {};
6488

6589
for (key in properties) {
6690
property = properties[key];
@@ -70,6 +94,51 @@ function convertProperties(properties) {
7094
return props;
7195
}
7296

97+
function convertDependencies(schema) {
98+
const deps = schema.dependencies;
99+
if (typeof deps !== 'object') {
100+
return schema;
101+
}
102+
103+
// Turns the dependencies keyword into an allOf of oneOf's
104+
// "dependencies": {
105+
// "post-office-box": ["street-address"]
106+
// },
107+
//
108+
// becomes
109+
//
110+
// "allOf": [
111+
// {
112+
// "oneOf": [
113+
// {"not": {"required": ["post-office-box"]}},
114+
// {"required": ["post-office-box", "street-address"]}
115+
// ]
116+
// }
117+
//
118+
119+
delete schema['dependencies'];
120+
if (!Array.isArray(schema.allOf)) {
121+
schema.allOf = [];
122+
}
123+
124+
for (const key in deps) {
125+
const foo = {
126+
'oneOf': [
127+
{
128+
'not': {
129+
'required': [key]
130+
}
131+
},
132+
{
133+
'required': [].concat(key, deps[key])
134+
}
135+
]
136+
};
137+
schema.allOf.push(foo);
138+
}
139+
return schema;
140+
}
141+
73142
function convertTypes(schema) {
74143
var newType;
75144

package-lock.json

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "json-schema-to-openapi-schema",
3-
"version": "0.1.0",
3+
"version": "0.1.1",
44
"description": "Converts a JSON Schema to OpenAPI Schema Object",
55
"main": "index.js",
66
"scripts": {
File renamed without changes.
File renamed without changes.

test/complex_schemas.js

-28
This file was deleted.

test/complex_schemas.test.js

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'use strict';
2+
3+
const convert = require('../');
4+
const should = require('should');
5+
const getSchema = require('./helpers').getSchema;
6+
7+
['basic', 'address', 'calendar'].forEach(test => {
8+
it(`converts ${test}/openapi.json`, () => {
9+
const schema = getSchema(test + '/json-schema.json');
10+
const result = convert(schema);
11+
const expected = getSchema(test + '/openapi.json');
12+
13+
should(result).deepEqual(expected, 'converted');
14+
});
15+
16+
it(`converting ${test}/openapi.json in place`, () => {
17+
const schema = getSchema(test + '/json-schema.json');
18+
const result = convert(schema, { cloneSchema: false });
19+
const expected = getSchema(test + '/openapi.json');
20+
21+
should(schema).deepEqual(result, 'changed schema in place');
22+
should(result).deepEqual(expected, 'converted');
23+
});
24+
});

test/helpers.js

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const fs = require('fs');
2+
const join = require('path').join;
3+
4+
const getSchema = file => {
5+
const path = join(__dirname, 'schemas', file);
6+
return JSON.parse(fs.readFileSync(path));
7+
};
8+
9+
module.exports = { getSchema };

test/invalid_types.test.js

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use strict';
2+
3+
const convert = require('../');
4+
const should = require('should');
5+
const getSchema = require('./helpers').getSchema;
6+
7+
it('dateTime is invalid type', () => {
8+
const schema = { type: 'dateTime' };
9+
should.throws(() => { convert(schema); }, /InvalidTypeError/);
10+
});
11+
12+
13+
it('foo is invalid type', () => {
14+
const schema = { type: 'foo' };
15+
should.throws(() => { convert(schema); }, /InvalidTypeError/);
16+
});
17+
18+
it('invalid type inside complex schema', () => {
19+
const schema = getSchema('invalid/json-schema.json');
20+
should.throws(() => { convert(schema); }, /InvalidTypeError.*invalidtype/);
21+
});
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

test/schemas/address/json-schema.json

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-06/schema#",
3+
"description": "An Address following the convention of http://microformats.org/wiki/hcard",
4+
"type": "object",
5+
"properties": {
6+
"post-office-box": { "type": "string" },
7+
"extended-address": { "type": "string" },
8+
"street-address": { "type": "string" },
9+
"locality":{ "type": "string" },
10+
"region": { "type": "string" },
11+
"postal-code": { "type": "string" },
12+
"country-name": { "type": "string"}
13+
},
14+
"required": ["locality", "region", "country-name"],
15+
"dependencies": {
16+
"post-office-box": ["street-address"],
17+
"extended-address": ["street-address"]
18+
}
19+
}

test/schemas/address/openapi.json

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"description": "An Address following the convention of http://microformats.org/wiki/hcard",
3+
"type": "object",
4+
"properties": {
5+
"post-office-box": { "type": "string" },
6+
"extended-address": { "type": "string" },
7+
"street-address": { "type": "string" },
8+
"locality":{ "type": "string" },
9+
"region": { "type": "string" },
10+
"postal-code": { "type": "string" },
11+
"country-name": { "type": "string"}
12+
},
13+
"required": ["locality", "region", "country-name"],
14+
"allOf": [
15+
{
16+
"oneOf": [
17+
{"not": {"required": ["post-office-box"]}},
18+
{"required": ["post-office-box", "street-address"]}
19+
]
20+
},
21+
{
22+
"oneOf": [
23+
{"not": {"required": ["extended-address"]}},
24+
{"required": ["extended-address", "street-address"]}
25+
]
26+
}
27+
]
28+
}
File renamed without changes.
+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-06/schema#",
3+
"description": "A representation of an event",
4+
"type": "object",
5+
"required": [ "dtstart", "summary" ],
6+
"properties": {
7+
"dtstart": {
8+
"format": "date-time",
9+
"type": "string",
10+
"description": "Event starting time"
11+
},
12+
"dtend": {
13+
"format": "date-time",
14+
"type": "string",
15+
"description": "Event ending time"
16+
},
17+
"summary": { "type": "string" },
18+
"location": { "type": "string" },
19+
"url": { "type": "string", "format": "uri" },
20+
"duration": {
21+
"format": "time",
22+
"type": "string",
23+
"description": "Event duration"
24+
},
25+
"rdate": {
26+
"format": "date-time",
27+
"type": "string",
28+
"description": "Recurrence date"
29+
},
30+
"rrule": {
31+
"type": "string",
32+
"description": "Recurrence rule"
33+
},
34+
"category": { "type": "string" },
35+
"description": { "type": "string" },
36+
"geo": { "$ref": "http://json-schema.org/geo" }
37+
}
38+
}

test/schemas/calendar/openapi.json

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"description": "A representation of an event",
3+
"type": "object",
4+
"required": [ "dtstart", "summary" ],
5+
"properties": {
6+
"dtstart": {
7+
"format": "date-time",
8+
"type": "string",
9+
"description": "Event starting time"
10+
},
11+
"dtend": {
12+
"format": "date-time",
13+
"type": "string",
14+
"description": "Event ending time"
15+
},
16+
"summary": { "type": "string" },
17+
"location": { "type": "string" },
18+
"url": { "type": "string", "format": "uri" },
19+
"duration": {
20+
"format": "time",
21+
"type": "string",
22+
"description": "Event duration"
23+
},
24+
"rdate": {
25+
"format": "date-time",
26+
"type": "string",
27+
"description": "Recurrence date"
28+
},
29+
"rrule": {
30+
"type": "string",
31+
"description": "Recurrence rule"
32+
},
33+
"category": { "type": "string" },
34+
"description": { "type": "string" },
35+
"geo": { "$ref": "http://json-schema.org/geo" }
36+
}
37+
}

0 commit comments

Comments
 (0)