Skip to content

Commit

Permalink
feat: add maxProperties object sampler support (#158)
Browse files Browse the repository at this point in the history
* feat: add maxProperties object sampler support

* chore: refactor naming and use const when possible
  • Loading branch information
adamaltman authored Mar 18, 2024
1 parent f5b65c7 commit a04d433
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 7 deletions.
41 changes: 35 additions & 6 deletions src/samplers/object.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -33,5 +36,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) {
const 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;
}
25 changes: 24 additions & 1 deletion test/unit/object.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
});

0 comments on commit a04d433

Please sign in to comment.