Skip to content

Commit 035a16f

Browse files
author
Phil Sturgeon
authored
Merge pull request #8 from wework/json-schema-walker
Use JSON Schema Walker
2 parents 8e72209 + 6b8ac3e commit 035a16f

File tree

7 files changed

+163
-94
lines changed

7 files changed

+163
-94
lines changed

.circleci/config.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
version: 2
2+
shared: &shared
3+
docker:
4+
- image: circleci/node:latest
5+
steps:
6+
- checkout
7+
- restore_cache:
8+
key: dependency-cache-{{ checksum "package.json" }}
9+
- run:
10+
name: Update npm
11+
command: 'sudo npm install -g npm@latest'
12+
- run:
13+
name: Install npm dependencies
14+
command: npm install
15+
- save_cache:
16+
key: dependency-cache-{{ checksum "package.json" }}
17+
paths:
18+
- node_modules
19+
- run:
20+
name: Tests
21+
command: npm test
22+
# - run:
23+
# name: Report Coverage
24+
# command: npm run coverage
25+
26+
jobs:
27+
node-latest:
28+
<<: *shared
29+
30+
node-10:
31+
docker:
32+
- image: circleci/node:10
33+
<<: *shared
34+
35+
node-9:
36+
docker:
37+
- image: circleci/node:9
38+
<<: *shared
39+
40+
node-8:
41+
docker:
42+
- image: circleci/node:8
43+
<<: *shared
44+
45+
workflows:
46+
version: 2
47+
commit:
48+
jobs:
49+
- node-latest
50+
- node-10
51+
- node-9
52+
- node-8

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# JSON Schema to OpenAPI Schema
1+
# JSON Schema to OpenAPI Schema
22

33
A little NodeJS package to convert JSON Schema to [OpenAPI Schema Objects](https://swagger.io/specification/#schemaObject).
44

@@ -72,11 +72,11 @@ _† Draft v5 is also known as Draft Wright 00, as the drafts are often named af
7272

7373
## TODO
7474

75-
- [ ] Support later JSON Schema drafts through a yet-to-be-released json-schema-migrator package
75+
- [ ] Support later JSON Schema drafts via [cloudflare/json-schema-transformer] when it adds that functionality
7676

7777
## Converting Back
7878

79-
To convert the other way, check out [openapi-schema-to-json-schema](https://github.com/mikunn/openapi-schema-to-json-schema), which this package was based on.
79+
To convert the other way, check out [openapi-schema-to-json-schema], which this package was based on.
8080

8181
**NOTE**: `$ref`s are not dereferenced. Use a dereferencer such as [json-schema-ref-parser](https://www.npmjs.com/package/json-schema-ref-parser) prior to using this package.
8282

@@ -98,3 +98,5 @@ npm test
9898
[mikunn]: https://github.com/mikunn
9999
[Phil Sturgeon]: https://github.com/philsturgeon
100100
[openapi-schema-to-json-schema]: https://github.com/mikunn/openapi-schema-to-json-schema
101+
[link-contributors]: https://github.com/wework/json-schema-to-openapi-schema/graphs/contributors
102+
[cloudflare/json-schema-transformer]: https://github.com/cloudflare/json-schema-tools/blob/master/workspaces/json-schema-transform/README.md

index.js

Lines changed: 31 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const structs = ['allOf', 'anyOf', 'oneOf', 'not', 'items', 'additionalProperties'];
1+
const schemaWalker = require('@cloudflare/json-schema-walker');
22

33
function InvalidTypeError(message) {
44
this.name = 'InvalidTypeError';
@@ -7,61 +7,27 @@ function InvalidTypeError(message) {
77

88
InvalidTypeError.prototype = new Error();
99

10-
function convert(schema, options) {
11-
options = options || {};
12-
options.cloneSchema = ! (options.cloneSchema === false);
10+
function convert(schema, options = {}) {
11+
const { cloneSchema = true } = options;
1312

14-
if (options.cloneSchema) {
13+
if (cloneSchema) {
1514
schema = JSON.parse(JSON.stringify(schema));
1615
}
1716

18-
schema = removeRootKeywords(schema);
19-
schema = convertSchema(schema);
20-
17+
const vocab = schemaWalker.getVocabulary(schema, schemaWalker.vocabularies.DRAFT_04);
18+
schemaWalker.schemaWalk(schema, convertSchema, null, vocab);
2119
return schema;
2220
}
2321

24-
function removeRootKeywords(schema) {
22+
function stripIllegalKeywords(schema) {
2523
delete schema['$schema'];
24+
delete schema['$id'];
2625
delete schema['id'];
2726
return schema;
2827
}
2928

30-
function convertSchema(schema) {
31-
let i = 0;
32-
let j = 0;
33-
let struct = null;
34-
35-
for (i; i < structs.length; i++) {
36-
struct = structs[i];
37-
38-
if (Array.isArray(schema[struct])) {
39-
for (j; j < schema[struct].length; j++) {
40-
schema[struct][j] = convertSchema(schema[struct][j]);
41-
}
42-
} else if (typeof schema[struct] === 'object') {
43-
schema[struct] = convertSchema(schema[struct]);
44-
}
45-
}
46-
47-
if (typeof schema.properties === 'object') {
48-
schema.properties = convertProperties(schema.properties);
49-
50-
if (Array.isArray(schema.required)) {
51-
schema.required = cleanRequired(schema.required, schema.properties);
52-
53-
if (schema.required.length === 0) {
54-
delete schema.required;
55-
}
56-
}
57-
if (Object.keys(schema.properties).length === 0) {
58-
delete schema.properties;
59-
}
60-
61-
}
62-
63-
validateType(schema.type);
64-
29+
function convertSchema(schema, path, parent, parentPath) {
30+
schema = stripIllegalKeywords(schema);
6531
schema = convertTypes(schema);
6632
schema = convertDependencies(schema);
6733

@@ -81,19 +47,6 @@ function validateType(type) {
8147
});
8248
}
8349

84-
function convertProperties(properties) {
85-
let key = {};
86-
let property = {};
87-
let props = {};
88-
89-
for (key in properties) {
90-
property = properties[key];
91-
props[key] = convertSchema(property);
92-
}
93-
94-
return props;
95-
}
96-
9750
function convertDependencies(schema) {
9851
const deps = schema.dependencies;
9952
if (typeof deps !== 'object') {
@@ -140,38 +93,40 @@ function convertDependencies(schema) {
14093
}
14194

14295
function convertTypes(schema) {
143-
var newType;
144-
14596
if (schema.type === undefined) {
14697
return schema;
14798
}
14899

149-
// type needs to be a string, not an array
150-
if (schema.type instanceof Array && schema.type.includes('null')) {
151-
var numTypes = schema.type.length;
100+
validateType(schema.type);
152101

153-
schema.nullable = true;
102+
if (Array.isArray(schema.type)) {
154103

155-
// if it was just type: ['null'] for some reason
156-
switch (numTypes) {
157-
case 1:
158-
// Didn't know what else to do
159-
newType = 'string';
104+
if (schema.type.length > 2 || !schema.type.includes('null')) {
105+
throw new Error('Type of ' + schema.type.join(',') + ' is too confusing for OpenAPI to understand. Found in ' + JSON.stringify(schema));
106+
}
107+
108+
switch (schema.type.length) {
109+
case 0:
110+
delete schema.type;
160111
break;
161112

162-
case 2:
163-
newType = schema.type.find(function(element) {
164-
return element !== 'null';
165-
});
113+
case 1:
114+
if (schema.type === 'null') {
115+
schema.nullable = true;
116+
}
117+
else {
118+
schema.type = schema.type[0];
119+
}
166120
break;
167121

168122
default:
169-
throw 'type cannot be an array, and you have ' + numTypes + ' types';
123+
schema.type = schema.type.find(type => type !== 'null');
124+
schema.nullable = true;
170125
}
171126
}
172-
173-
if (newType) {
174-
schema.type = newType;
127+
else if (schema.type === 'null') {
128+
delete schema.type;
129+
schema.nullable = true;
175130
}
176131

177132
return schema;
@@ -185,19 +140,4 @@ function convertPatternProperties(schema) {
185140
return schema;
186141
}
187142

188-
function cleanRequired(required, properties) {
189-
var i = 0;
190-
191-
required = required || [];
192-
properties = properties || {};
193-
194-
for (i; i < required.length; i++) {
195-
if (properties[required[i]] === undefined) {
196-
required.splice(i, 1);
197-
}
198-
}
199-
200-
return required;
201-
}
202-
203143
module.exports = convert;

package-lock.json

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,8 @@
1515
"mocha": "^5.0.0",
1616
"nyc": "^11.6.0",
1717
"should": "^13.2.0"
18+
},
19+
"dependencies": {
20+
"@cloudflare/json-schema-walker": "^0.1.1"
1821
}
1922
}

test/nullable.test.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,26 @@ it('adds `nullable: true` for `type: [string, null]`', () => {
1717
});
1818
});
1919

20+
21+
it('supports nullables inside sub-schemas', () => {
22+
const schema = {
23+
$schema: 'http://json-schema.org/draft-04/schema#',
24+
oneOf: [
25+
{ type: 'string' },
26+
{ type: 'null' }
27+
]
28+
};
29+
30+
const result = convert(schema);
31+
32+
should(result).deepEqual({
33+
oneOf: [
34+
{ type: 'string' },
35+
{ nullable: true }
36+
]
37+
});
38+
});
39+
2040
it('does not add nullable for non null types', () => {
2141
const schema = {
2242
$schema: 'http://json-schema.org/draft-04/schema#',

test/subschema.test.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
'use strict';
2+
3+
const convert = require('../');
4+
const should = require('should');
5+
6+
it('strips $id from all subschemas not just root`', () => {
7+
const schema = {
8+
$id: "https://foo/bla",
9+
$schema: "http://json-schema.org/draft-06/schema#",
10+
type: "object",
11+
properties: {
12+
foo: {
13+
$id: "/properties/foo",
14+
type: "array",
15+
items: {
16+
$id: "/properties/foo/items",
17+
type: "object",
18+
properties: {
19+
id: {
20+
$id: "/properties/foo/items/properties/id",
21+
type: "string",
22+
}
23+
}
24+
}
25+
}
26+
}
27+
};
28+
29+
const result = convert(schema);
30+
31+
should(result).deepEqual({
32+
type: "object",
33+
properties: {
34+
foo: {
35+
type: "array",
36+
items: {
37+
type: "object",
38+
properties: {
39+
id: {
40+
type: "string",
41+
}
42+
}
43+
}
44+
}
45+
}
46+
});
47+
});

0 commit comments

Comments
 (0)