Skip to content

Commit 6906c8e

Browse files
chore: add required simplification (#61)
1 parent 1f9fa41 commit 6906c8e

File tree

12 files changed

+246
-23
lines changed

12 files changed

+246
-23
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ We have split out the different simplification processes into separate small fun
1313
- [Simplification of items](./docs/SimplifyItems.md)
1414
- [Simplification of properties](./docs/SimplifyProperties.md)
1515
- [Simplification of additional properties](./docs/SimplifyAdditionalProperties.md)
16+
- [Simplification of required](./docs/SimplifyRequired.md)

docs/SimplifyRequired.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Determining the required properties for the model
2+
3+
In order to determine all the possible required properties a schema can have, we both merge and use existing definitions, however we need to define a precedence for JSON Schema keywords for in which order the required are merged or determined.
4+
5+
## Precedence
6+
7+
The precedence of keywords are in which order we merge or determine `required` in. The following are the precedence for determining the array of required:
8+
9+
`required` --> `allOf` --> `oneOf` --> `anyOf` --> `then` --> `else`

src/models/CommonModel.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,4 +118,29 @@ export class CommonModel extends CommonSchema<CommonModel> {
118118
mergeTo.originalSchema = originalSchema;
119119
return mergeTo;
120120
}
121-
}
121+
122+
/**
123+
* Retrieves data from originalSchema by given key
124+
*
125+
* @param key given key
126+
* @returns {any}
127+
*/
128+
getFromSchema<K extends keyof Schema>(key: K) {
129+
let schema = this.originalSchema || {};
130+
if (typeof schema === 'boolean') schema = {};
131+
return schema[key];
132+
}
133+
134+
/**
135+
* Checks if given property name is required in object
136+
*
137+
* @param propertyName given property name
138+
* @returns {boolean}
139+
*/
140+
isRequired(propertyName: string): boolean {
141+
if (this.required === undefined) {
142+
return false;
143+
}
144+
return this.required.includes(propertyName);
145+
}
146+
}

src/models/CommonSchema.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ export class CommonSchema<T> {
1010
additionalProperties?: boolean | T;
1111
$ref?: string;
1212

13+
required?: string[];
14+
1315
/**
1416
* Function to transform nested schemas into type of generic extended class
1517
*

src/models/Schema.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ export class Schema extends CommonSchema<Schema | boolean> {
2121
uniqueItems?: boolean;
2222
maxProperties?: number;
2323
minProperties?: number;
24-
required?: string[];
2524
allOf?: (Schema | boolean)[];
2625
oneOf?: (Schema | boolean)[];
2726
anyOf?: (Schema | boolean)[];
@@ -40,7 +39,7 @@ export class Schema extends CommonSchema<Schema | boolean> {
4039
contentMediaType?: string; //Enum?
4140
definitions?: { [key: string]: Schema | boolean; };
4241
description?: string;
43-
default?: string;
42+
default?: any;
4443
readOnly?: boolean;
4544
writeOnly?: boolean;
4645
examples?: Object[];

src/simplification/Simplifier.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
21
import { CommonModel, Schema } from '../models';
32
import simplifyProperties from './SimplifyProperties';
43
import simplifyEnums from './SimplifyEnums';
5-
import simplifyTypes from './SimplifyTypes';
64
import simplifyItems from './SimplifyItems';
75
import simplifyExtend from './SimplifyExtend';
6+
import simplifyRequired from './SimplifyRequired';
7+
import simplifyTypes from './SimplifyTypes';
88
import { SimplificationOptions } from '../models/SimplificationOptions';
99
import simplifyAdditionalProperties from './SimplifyAdditionalProperties';
1010
import { isModelObject } from './Utils';
@@ -58,11 +58,10 @@ export class Simplifier {
5858
if (typeof schema !== 'boolean' && this.seenSchemas.has(schema)) {
5959
return [this.seenSchemas.get(schema)!];
6060
}
61+
6162
model.originalSchema = Schema.toSchema(schema);
62-
const simplifiedTypes = simplifyTypes(schema);
63-
if (simplifiedTypes !== undefined) {
64-
model.type = simplifiedTypes;
65-
}
63+
model.type = simplifyTypes(schema);
64+
6665
if (typeof schema !== 'boolean') {
6766
this.seenSchemas.set(schema, model);
6867
//All schemas of type object MUST have ids, for now lets make it simple
@@ -115,6 +114,11 @@ export class Simplifier {
115114
model.enum = enums;
116115
}
117116
}
117+
118+
const required = simplifyRequired(schema);
119+
if (required !== undefined) {
120+
model.required = required;
121+
}
118122
}
119123

120124
//Always ensure the model representing the input schema to be in index 0.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { Schema } from 'models/Schema';
2+
3+
type Output = string[] | undefined;
4+
5+
/**
6+
* Find the required array for a simplified version of a schema
7+
*
8+
* @param schema to find the simplified required array for
9+
* @param seenSchemas already seen schemas, this is to avoid circular schemas
10+
*/
11+
export default function simplifyRequired(schema: Schema | boolean, seenSchemas: Set<any> = new Set()): Output {
12+
// use Set, because we don't need cache any reference to previous simplified values
13+
if (
14+
typeof schema === 'boolean' ||
15+
seenSchemas.has(schema)
16+
) {
17+
return undefined;
18+
}
19+
seenSchemas.add(schema);
20+
21+
let required: Output = schema.required;
22+
const addRequired = (r: Output) => {
23+
if (r !== undefined) {
24+
required = required || [];
25+
required.push(...r);
26+
}
27+
};
28+
const handler = (schemas: (Schema | boolean)[] = []) => {
29+
schemas.forEach((schema) => {
30+
addRequired(simplifyRequired(schema, seenSchemas));
31+
});
32+
};
33+
34+
handler(schema.allOf);
35+
handler(schema.oneOf);
36+
handler(schema.anyOf);
37+
38+
schema.then && addRequired(simplifyRequired(schema.then, seenSchemas));
39+
schema.else && addRequired(simplifyRequired(schema.else, seenSchemas));
40+
41+
// remove duplication
42+
if (Array.isArray(required)) {
43+
return [...new Set(required)];
44+
}
45+
return undefined;
46+
}

src/simplification/SimplifyTypes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,4 +116,4 @@ export default function simplifyTypes(schema: Schema | boolean, seenSchemas: Map
116116
}
117117

118118
return types;
119-
}
119+
}

test/models/CommonModel.spec.ts

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -288,15 +288,15 @@ describe('CommonModel', function() {
288288
const doc: Schema = { };
289289
let doc1 = CommonModel.toCommonModel(doc);
290290
let doc2 = CommonModel.toCommonModel(doc);
291-
doc2.items = [{type: "string"}];
291+
doc2.items = [CommonModel.toCommonModel({type: "string"})];
292292
doc1 = CommonModel.mergeCommonModels(doc1, doc2, doc);
293293
expect(doc1.items).toEqual(doc2.items[0]);
294294
});
295295
test('should be merged when only left side is defined', function() {
296296
const doc: Schema = { };
297297
let doc1 = CommonModel.toCommonModel(doc);
298298
let doc2 = CommonModel.toCommonModel(doc);
299-
doc1.items = [{type: "string"}, {type: "number"}];
299+
doc1.items = [CommonModel.toCommonModel({type: "string"}), CommonModel.toCommonModel({type: "number"})];
300300
doc1 = CommonModel.mergeCommonModels(doc1, doc2, doc);
301301
expect(doc1.items).toEqual({type: ["string", "number"], originalSchema: {}});
302302
});
@@ -312,17 +312,17 @@ describe('CommonModel', function() {
312312
const doc: Schema = { };
313313
let doc1 = CommonModel.toCommonModel(doc);
314314
let doc2 = CommonModel.toCommonModel(doc);
315-
doc2.items = {type: "string"};
316-
doc1.items = {type: "number"};
315+
doc2.items = CommonModel.toCommonModel({type: "string"});
316+
doc1.items = CommonModel.toCommonModel({type: "number"});
317317
doc1 = CommonModel.mergeCommonModels(doc1, doc2, doc);
318318
expect(doc1.items).toEqual({type: ["number", "string"], originalSchema: {}});
319319
});
320320
test('should be merged when both sides are defined as array of schemas with different lengths', function() {
321321
const doc: Schema = { };
322322
let doc1 = CommonModel.toCommonModel(doc);
323323
let doc2 = CommonModel.toCommonModel(doc);
324-
doc2.items = [{type: "string"}, {type: ["boolean"]}];
325-
doc1.items = [{type: ["number"]}];
324+
doc2.items = [CommonModel.toCommonModel({type: "string"}), CommonModel.toCommonModel({type: "boolean"})];
325+
doc1.items = [CommonModel.toCommonModel({type: "number"})];
326326
doc1 = CommonModel.mergeCommonModels(doc1, doc2, doc);
327327
expect(doc1.items).toEqual({"originalSchema": {}, "type": ["number", "string", "boolean"]});
328328
});
@@ -340,25 +340,25 @@ describe('CommonModel', function() {
340340
const doc: Schema = { };
341341
let doc1 = CommonModel.toCommonModel(doc);
342342
let doc2 = CommonModel.toCommonModel(doc);
343-
doc2.properties = {"testProp": {type: "string"}};
343+
doc2.properties = {"testProp": CommonModel.toCommonModel({type: "string"})};
344344
doc1 = CommonModel.mergeCommonModels(doc1, doc2, doc);
345345
expect(doc1.properties).toEqual(doc2.properties);
346346
});
347347
test('should be merged when both sides are defined', function() {
348348
const doc: Schema = { };
349349
let doc1 = CommonModel.toCommonModel(doc);
350350
let doc2 = CommonModel.toCommonModel(doc);
351-
doc2.properties = {"testProp": {type: "string"}};
352-
doc1.properties = {"testProp2": {type: "number"}};
351+
doc2.properties = {"testProp": CommonModel.toCommonModel({type: "string"})};
352+
doc1.properties = {"testProp2": CommonModel.toCommonModel({type: "number"})};
353353
doc1 = CommonModel.mergeCommonModels(doc1, doc2, doc);
354354
expect(doc1.properties).toEqual({"testProp": {type: "string"}, "testProp2": {type: "number"}});
355355
});
356356
test('should be merged together when both sides are defined', function() {
357357
const doc: Schema = { };
358358
let doc1 = CommonModel.toCommonModel(doc);
359359
let doc2 = CommonModel.toCommonModel(doc);
360-
doc2.properties = {"testProp": {type: "string"}};
361-
doc1.properties = {"testProp": {type: "number"}};
360+
doc2.properties = {"testProp": CommonModel.toCommonModel({type: "string"})};
361+
doc1.properties = {"testProp": CommonModel.toCommonModel({type: "number"})};
362362
doc1 = CommonModel.mergeCommonModels(doc1, doc2, doc);
363363
expect(doc1.properties).toEqual({"testProp": {type: ["number", "string"], originalSchema: {}}});
364364
});
@@ -371,4 +371,26 @@ describe('CommonModel', function() {
371371
});
372372
});
373373
});
374+
375+
describe('helpers', function() {
376+
describe('getFromSchema', function() {
377+
test('should work', function() {
378+
const doc = { type: "string", description: "Some description" };
379+
const d = CommonModel.toCommonModel(doc);
380+
d.originalSchema = doc;
381+
const desc = d.getFromSchema('description');
382+
expect(desc).toEqual(doc.description);
383+
});
384+
});
385+
386+
describe('isRequired', function() {
387+
test('check that property is required', function() {
388+
const doc = { type: "object", properties: { prop: { type: "string" } } };
389+
const d = CommonModel.toCommonModel(doc);
390+
d.required = ["prop"];
391+
expect(d.isRequired("prop")).toEqual(true);
392+
expect(d.isRequired("propX")).toEqual(false);
393+
});
394+
});
395+
});
374396
});

test/processors/JsonSchemaInputProcessor/commonInputModel/applying_conditional_schemas.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
}
4848
}
4949
},
50+
"required": ["country"],
5051
"originalSchema": {
5152
"$schema": "http://json-schema.org/draft-07/schema#",
5253
"type": "object",

0 commit comments

Comments
 (0)