Skip to content

Commit f8c8ec3

Browse files
committed
auto sample generation is disable for non-required properties only
1 parent 5e8f42d commit f8c8ec3

File tree

12 files changed

+137
-56
lines changed

12 files changed

+137
-56
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ Available options:
5252
Don't include `readOnly` object properties
5353
- **skipWriteOnly** - `boolean`
5454
Don't include `writeOnly` object properties
55-
- **disableAutoGeneration** - `boolean`
56-
Don't auto generate sample when the schema hasn't explicit example nor default value
55+
- **disableNonRequiredAutoGen** - `boolean`
56+
Don't auto generate sample for non-required object properties when the schema hasn't explicit example nor default value
5757
- **quiet** - `boolean`
5858
Don't log console warning messages
5959
- **spec** - whole specification where the schema is taken from. Required only when schema may contain `$ref`. **spec** must not contain any external references

src/samplers/array.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export function sampleArray(schema, options = {}, spec, context) {
77
arrayLength = Math.max(arrayLength, schema.items.length);
88
}
99

10-
let itemSchemaGetter = itemNumber => {
10+
const itemSchemaGetter = itemNumber => {
1111
if (Array.isArray(schema.items)) {
1212
return schema.items[itemNumber] || {};
1313
}
@@ -18,9 +18,13 @@ export function sampleArray(schema, options = {}, spec, context) {
1818
if (!schema.items) return res;
1919

2020
for (let i = 0; i < arrayLength; i++) {
21-
let itemSchema = itemSchemaGetter(i);
22-
let { value: sample } = traverse(itemSchema, options, spec, {depth: depth + 1});
21+
let { value: sample } = traverse(itemSchemaGetter(i), options, spec, {depth: depth + 1});
2322
res.push(sample);
2423
}
25-
return res;
24+
25+
if (!options.omissible || res.some(item => item !== null)) {
26+
return res;
27+
}
28+
29+
return null;
2630
}

src/samplers/boolean.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
export function sampleBoolean(schema, options) {
2-
if (options.disableAutoGeneration) {
1+
export function sampleBoolean(schema, options={}) {
2+
if (options.omissible) {
33
return null;
44
}
55
return true; // let be optimistic :)

src/samplers/number.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
export function sampleNumber(schema, options={}) {
2-
if (options.disableAutoGeneration) {
2+
if (options.omissible) {
33
return null;
44
}
55

6-
let res;
76
if (schema.maximum && schema.minimum) {
8-
res = schema.exclusiveMinimum ? Math.floor(schema.minimum) + 1 : schema.minimum;
7+
let res = schema.exclusiveMinimum ? Math.floor(schema.minimum) + 1 : schema.minimum;
98
if ((schema.exclusiveMaximum && res >= schema.maximum) ||
109
((!schema.exclusiveMaximum && res > schema.maximum))) {
1110
res = (schema.maximum + schema.minimum) / 2;

src/samplers/object.js

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,28 @@ export function sampleObject(schema, options = {}, spec, context) {
44
const depth = (context && context.depth || 1);
55

66
if (schema && typeof schema.properties === 'object') {
7-
let requiredKeys = (Array.isArray(schema.required) ? schema.required : []);
8-
let requiredKeyDict = requiredKeys.reduce((dict, key) => {
7+
const requiredKeys = (Array.isArray(schema.required) ? schema.required : []);
8+
const requiredKeyDict = requiredKeys.reduce((dict, key) => {
99
dict[key] = true;
1010
return dict;
1111
}, {});
1212

1313
Object.keys(schema.properties).forEach(propertyName => {
14+
const isRequired = requiredKeyDict.hasOwnProperty(propertyName);
1415
// skip before traverse that could be costly
15-
if (options.skipNonRequired && !requiredKeyDict.hasOwnProperty(propertyName)) {
16+
if (options.skipNonRequired && !isRequired) {
1617
return;
1718
}
1819

19-
const sample = traverse(schema.properties[propertyName], options, spec, { propertyName, depth: depth + 1 });
20+
const propertyOmissible = options.disableNonRequiredAutoGen && !isRequired;
21+
22+
const sample = traverse(
23+
schema.properties[propertyName],
24+
Object.assign({}, options, { omissible: propertyOmissible }),
25+
spec,
26+
{ propertyName, depth: depth + 1 }
27+
);
28+
2029
if (options.skipReadOnly && sample.readOnly) {
2130
return;
2231
}
@@ -25,18 +34,18 @@ export function sampleObject(schema, options = {}, spec, context) {
2534
return;
2635
}
2736

28-
if (sample.value || !options.disableAutoGeneration){
37+
if (sample.value || !propertyOmissible) {
2938
res[propertyName] = sample.value;
3039
}
3140
});
3241
}
3342

34-
if (!options.disableAutoGeneration && schema && typeof schema.additionalProperties === 'object') {
43+
if (!options.disableNonRequiredAutoGen && schema && typeof schema.additionalProperties === 'object') {
3544
res.property1 = traverse(schema.additionalProperties, options, spec, {depth: depth + 1 }).value;
3645
res.property2 = traverse(schema.additionalProperties, options, spec, {depth: depth + 1 }).value;
3746
}
3847

39-
if (Object.keys(res).length > 0 || !options.disableAutoGeneration) {
48+
if (Object.keys(res).length > 0 || !options.omissible) {
4049
return res;
4150
}
4251

src/samplers/string.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ const stringFormats = {
7878
};
7979

8080
export function sampleString(schema, options, spec, context) {
81-
if (options && options.disableAutoGeneration) {
81+
if (options && options.omissible) {
8282
return null;
8383
}
8484
let format = schema.format || 'default';

src/traverse.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export function traverse(schema, options, spec, context) {
9292
example = schema.default;
9393
} else if (schema.const !== undefined) {
9494
example = schema.const;
95-
} else if (schema.enum !== undefined && schema.enum.length) {
95+
} else if (schema.enum !== undefined && schema.enum.length && !options.disableNonRequiredAutoGen) {
9696
example = schema.enum[0];
9797
} else if (schema.examples !== undefined && schema.examples.length) {
9898
example = schema.examples[0];

test/integration.spec.js

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -406,9 +406,10 @@ describe('Integration', function() {
406406
expect(result).to.equal(expected);
407407
});
408408

409-
it('should use explicit example', function() { // TODO: array
409+
it('should skip non-required properties without example if disableNonRequiredAutoGen=true', () => {
410410
var obj = {
411411
withExample: 'Example',
412+
withExampleArray: [3],
412413
};
413414
schema = {
414415
type: 'object',
@@ -425,19 +426,41 @@ describe('Integration', function() {
425426
withoutExampleObject: {
426427
type: 'object',
427428
},
429+
withoutExampleArray: {
430+
type: 'array',
431+
items: { type: 'string'}
432+
},
428433
withExample: {
429434
type: 'string',
430435
example: 'Example'
436+
},
437+
withExampleArray: {
438+
type: 'array',
439+
items: { type: 'number', default: 3 }
431440
}
432441
},
433442
additionalProperties: {
434443
type: 'number'
435444
}
436445
};
437-
result = OpenAPISampler.sample(schema, { disableAutoGeneration: true });
446+
result = OpenAPISampler.sample(schema, { disableNonRequiredAutoGen: true });
447+
expected = obj;
448+
expect(result).to.deep.equal(obj);
449+
});
450+
451+
it('should return empty object if disableNonRequiredAutoGen=true and no explicit example', () => {
452+
var obj = {};
453+
schema = {
454+
type: 'object',
455+
properties: {
456+
withoutExampleString: { type: 'string' },
457+
}
458+
};
459+
result = OpenAPISampler.sample(schema, { disableNonRequiredAutoGen: true });
438460
expected = obj;
439461
expect(result).to.deep.equal(obj);
440462
});
463+
441464
});
442465

443466
describe('Detection', function() {

test/unit/array.spec.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,45 @@ describe('sampleArray', () => {
2222
res = sampleArray({items: [{type: 'number'}, {type: 'string'}, {}]});
2323
expect(res).to.deep.equal([0, 'string', null]);
2424
});
25+
26+
describe('disableNonRequiredAutoGen', () => {
27+
28+
it('should return null if omissible=true and primitive type item has no example', () => {
29+
res = sampleArray({ items: { type: 'string' } }, { omissible: true, disableNonRequiredAutoGen: true });
30+
expect(res).to.be.null;
31+
});
32+
33+
it('should return null if omssible=true and object type item has no example', () => {
34+
res = sampleArray({
35+
items: {
36+
type: 'object',
37+
properties: {
38+
a: { type: 'string' },
39+
},
40+
}
41+
}, { omissible: true, disableNonRequiredAutoGen: true });
42+
expect(res).to.be.null;
43+
});
44+
45+
it('should return valid array samples if omissible=false and primitive type item has no example', () => {
46+
// the sample must be valid to schema and show the array item type when the array is not omitted
47+
res = sampleArray({ items: { type: 'string' }, minItems: 2 }, { disableNonRequiredAutoGen: true });
48+
expect(res).to.deep.equal(['string', 'string']);
49+
});
50+
51+
it('should return array of empty object if omssible=false and object type item has no example', () => {
52+
// the sample must be valid to schema and show the array item type when the array is not omitted
53+
res = sampleArray({
54+
items: {
55+
type: 'object',
56+
properties: {
57+
a: { type: 'string' },
58+
},
59+
}
60+
}, { disableNonRequiredAutoGen: true });
61+
expect(res).to.deep.equal([{}]);
62+
});
63+
64+
});
65+
2566
});

test/unit/number.spec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ describe('sampleNumber', () => {
5454
expect(res).to.equal(3);
5555
});
5656

57-
it('should return null if disableAutoGeneration is true', () => {
58-
res = sampleNumber({}, { disableAutoGeneration: true });
57+
it('should return null if it is omissible', () => {
58+
res = sampleNumber({}, { omissible: true });
5959
expect(res).to.be.null;
6060
});
6161
});

0 commit comments

Comments
 (0)