From 59b762aea70e45a52ea0ee6a93da6cfe6bc23099 Mon Sep 17 00:00:00 2001 From: Adam Altman Date: Sat, 16 Mar 2024 11:42:05 -0500 Subject: [PATCH 1/2] feat: add maxProperties object sampler support --- src/samplers/object.js | 26 ++++++++++++++++++++++++++ test/unit/object.spec.js | 25 ++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/samplers/object.js b/src/samplers/object.js index 890057f..863bb4c 100644 --- a/src/samplers/object.js +++ b/src/samplers/object.js @@ -33,5 +33,31 @@ export function sampleObject(schema, options = {}, spec, context) { res[`${String(propertyName)}1`] = traverse(schema.additionalProperties, options, spec, {depth: depth + 1 }).value; res[`${String(propertyName)}2`] = traverse(schema.additionalProperties, options, spec, {depth: depth + 1 }).value; } + + // Strictly enforce maxProperties constraint + if (schema && typeof schema.properties === 'object' && schema.maxProperties !== undefined && Object.keys(res).length > schema.maxProperties) { + let filteredResult = {}; + let propertiesAdded = 0; + + // Always include required properties first, if present + const requiredProperties = (Array.isArray(schema.required) ? schema.required : []); + requiredProperties.forEach(propName => { + if (res[propName] !== undefined) { + filteredResult[propName] = res[propName]; + propertiesAdded++; + } + }); + + // Add other properties until maxProperties is reached + Object.keys(res).forEach(propName => { + if (propertiesAdded < schema.maxProperties && !filteredResult.hasOwnProperty(propName)) { + filteredResult[propName] = res[propName]; + propertiesAdded++; + } + }); + + res = filteredResult; + } + return res; } diff --git a/test/unit/object.spec.js b/test/unit/object.spec.js index 537c629..a413ebb 100644 --- a/test/unit/object.spec.js +++ b/test/unit/object.spec.js @@ -182,5 +182,28 @@ describe('sampleObject', () => { fooId: 'fb4274c7-4fcd-4035-8958-a680548957ff', barId: '3c966637-4898-4972-9a9d-baefa6cd6c89' }); - }) + }); + + it('respects maxProperties by including no more than 2 properties in the sampled object', () => { + // Define a schema with four properties and a maxProperties limit of two + const schema = { + type: 'object', + properties: { + name: { type: 'string' }, + email: { type: 'string', format: 'email' }, + age: { type: 'integer', minimum: 0 }, + phone: { type: 'string' } + }, + required: ['name', 'email'], // 'name' and 'email' are required + maxProperties: 2 + }; + + const result = sampleObject(schema); + + expect(Object.keys(result).length).to.be.at.most(2); + + // Assert that if 'name' and 'email' are required, they are included + expect(result).to.have.property('name'); + expect(result).to.have.property('email'); + }); }); From 1839561609fcd6c4b107955552607b25d66902aa Mon Sep 17 00:00:00 2001 From: Adam Altman Date: Sun, 17 Mar 2024 09:24:44 -0500 Subject: [PATCH 2/2] chore: refactor naming and use const when possible --- src/samplers/object.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/samplers/object.js b/src/samplers/object.js index 863bb4c..93d232a 100644 --- a/src/samplers/object.js +++ b/src/samplers/object.js @@ -4,15 +4,18 @@ export function sampleObject(schema, options = {}, spec, context) { const depth = (context && context.depth || 1); if (schema && typeof schema.properties === 'object') { - let requiredKeys = (Array.isArray(schema.required) ? schema.required : []); - let requiredKeyDict = requiredKeys.reduce((dict, key) => { - dict[key] = true; - return dict; - }, {}); + + // Prepare for skipNonRequired option + const requiredProperties = Array.isArray(schema.required) ? schema.required : []; + const requiredPropertiesMap = {}; + + for (const requiredProperty of requiredProperties) { + requiredPropertiesMap[requiredProperty] = true; + } Object.keys(schema.properties).forEach(propertyName => { // skip before traverse that could be costly - if (options.skipNonRequired && !requiredKeyDict.hasOwnProperty(propertyName)) { + if (options.skipNonRequired && !requiredPropertiesMap.hasOwnProperty(propertyName)) { return; } @@ -36,11 +39,11 @@ export function sampleObject(schema, options = {}, spec, context) { // Strictly enforce maxProperties constraint if (schema && typeof schema.properties === 'object' && schema.maxProperties !== undefined && Object.keys(res).length > schema.maxProperties) { - let filteredResult = {}; + const filteredResult = {}; let propertiesAdded = 0; // Always include required properties first, if present - const requiredProperties = (Array.isArray(schema.required) ? schema.required : []); + const requiredProperties = Array.isArray(schema.required) ? schema.required : []; requiredProperties.forEach(propName => { if (res[propName] !== undefined) { filteredResult[propName] = res[propName];