-
Notifications
You must be signed in to change notification settings - Fork 91
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add
collectFromComposedSchemas()
and getExamplesForSchema()
…
… to utilities (#708) Signed-off-by: Dan Hudlow <[email protected]>
- Loading branch information
Showing
7 changed files
with
474 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
56 changes: 56 additions & 0 deletions
56
packages/utilities/src/utils/collect-from-composed-schemas.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
/** | ||
* Copyright 2024 IBM Corporation. | ||
* SPDX-License-Identifier: Apache2.0 | ||
*/ | ||
|
||
/** | ||
* Returns an array of items collected by the provided `collector(schema) => item[]` function for a | ||
* simple or composite schema, and deduplicates primitives in the result. The collector function is | ||
* not run for `null` or `undefined` schemas. | ||
* @param {object} schema simple or composite OpenAPI 3.x schema object | ||
* @param {Function} collector a `(schema) => item[]` function to collect items from each simple schema | ||
* @param {boolean} includeSelf collect from the provided schema in addition to its composed schemas (defaults to `true`) | ||
* @param {boolean} includeNot collect from schemas composed with `not` (defaults to `false`) | ||
* @returns {Array} collected items | ||
*/ | ||
function collectFromComposedSchemas( | ||
schema, | ||
collector, | ||
includeSelf = true, | ||
includeNot = false | ||
) { | ||
const items = []; | ||
|
||
if (schema === undefined || schema === null) { | ||
return items; | ||
} | ||
|
||
if (includeSelf) { | ||
items.push(...collector(schema)); | ||
} | ||
|
||
if (includeNot) { | ||
items.push( | ||
...collectFromComposedSchemas(schema.not, collector, true, true) | ||
); | ||
} | ||
|
||
for (const applicatorType of ['allOf', 'oneOf', 'anyOf']) { | ||
if (Array.isArray(schema[applicatorType])) { | ||
for (const applicatorSchema of schema[applicatorType]) { | ||
items.push( | ||
...collectFromComposedSchemas( | ||
applicatorSchema, | ||
collector, | ||
true, | ||
includeNot | ||
) | ||
); | ||
} | ||
} | ||
} | ||
|
||
return [...new Set(items)]; // de-duplicate | ||
} | ||
|
||
module.exports = collectFromComposedSchemas; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
/** | ||
* Copyright 2024 IBM Corporation. | ||
* SPDX-License-Identifier: Apache2.0 | ||
*/ | ||
|
||
/** | ||
* @private | ||
*/ | ||
const collectFromComposedSchemas = require('./collect-from-composed-schemas'); | ||
|
||
/** | ||
* Returns an array of examples for a simple or composite schema. For each composed schema, if | ||
* `schema.examples` is present (and an array), `schema.example` is ignored. | ||
* @param {object} schema simple or composite OpenAPI 3.x schema object | ||
* @returns {Array} examples | ||
*/ | ||
function getExamplesForSchema(schema) { | ||
return collectFromComposedSchemas(schema, s => { | ||
if (Array.isArray(s.examples)) { | ||
return s.examples; | ||
} else if (s.example !== undefined) { | ||
return [s.example]; | ||
} | ||
|
||
return []; | ||
}); | ||
} | ||
|
||
module.exports = getExamplesForSchema; |
40 changes: 16 additions & 24 deletions
40
packages/utilities/src/utils/get-property-names-for-schema.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,46 +1,38 @@ | ||
/** | ||
* Copyright 2017 - 2023 IBM Corporation. | ||
* Copyright 2017 - 2024 IBM Corporation. | ||
* SPDX-License-Identifier: Apache2.0 | ||
*/ | ||
|
||
/** | ||
* @private | ||
*/ | ||
const isObject = require('./is-object'); | ||
/** | ||
* @private | ||
*/ | ||
const collectFromComposedSchemas = require('./collect-from-composed-schemas'); | ||
|
||
/** | ||
* Returns an array of property names for a simple or composite schema, | ||
* optionally filtered by a lambda function. | ||
* @param {object} schema simple or composite OpenAPI 3.0 schema object | ||
* @param {Function} propertyFilter a `(schema) => boolean` function to perform filtering | ||
* @param {object} schema simple or composite OpenAPI 3.x schema object | ||
* @param {Function} propertyFilter a `(propertyName, propertySchema) => boolean` function to perform filtering | ||
* @returns {Array} property names | ||
*/ | ||
function getPropertyNamesForSchema(schema, propertyFilter = () => true) { | ||
const propertyNames = []; | ||
|
||
if (!isObject(schema)) { | ||
return propertyNames; | ||
} | ||
return collectFromComposedSchemas(schema, s => { | ||
const propertyNames = []; | ||
|
||
if (isObject(schema.properties)) { | ||
for (const propertyName of Object.keys(schema.properties)) { | ||
if (propertyFilter(propertyName, schema.properties[propertyName])) { | ||
propertyNames.push(propertyName); | ||
if (isObject(s.properties)) { | ||
for (const propertyName of Object.keys(s.properties)) { | ||
if (propertyFilter(propertyName, s.properties[propertyName])) { | ||
propertyNames.push(propertyName); | ||
} | ||
} | ||
} | ||
} | ||
|
||
for (const applicatorType of ['allOf', 'oneOf', 'anyOf']) { | ||
if (Array.isArray(schema[applicatorType])) { | ||
for (const applicatorSchema of schema[applicatorType]) { | ||
propertyNames.push( | ||
...getPropertyNamesForSchema(applicatorSchema, propertyFilter) | ||
); | ||
} | ||
} | ||
} | ||
|
||
return [...new Set(propertyNames)]; // de-duplicate | ||
return propertyNames; | ||
}); | ||
} | ||
|
||
module.exports = getPropertyNamesForSchema; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
189 changes: 189 additions & 0 deletions
189
packages/utilities/test/collect-from-composed-schemas.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
/** | ||
* Copyright 2024 IBM Corporation. | ||
* SPDX-License-Identifier: Apache2.0 | ||
*/ | ||
|
||
const { collectFromComposedSchemas } = require('../src'); | ||
|
||
describe('Utility function: collectFromComposedSchemas()', () => { | ||
it('should return `[]` for `undefined` or `null`', async () => { | ||
expect(collectFromComposedSchemas(undefined, () => ['foo'])).toEqual([]); | ||
expect(collectFromComposedSchemas(null, () => ['foo'])).toEqual([]); | ||
}); | ||
|
||
it('should not run collector for `undefined` or `null`', async () => { | ||
expect(() => | ||
collectFromComposedSchemas(undefined, () => { | ||
throw new Error(); | ||
}) | ||
).not.toThrow(); | ||
expect(() => | ||
collectFromComposedSchemas(null, () => { | ||
throw new Error(); | ||
}) | ||
).not.toThrow(); | ||
}); | ||
|
||
it('should collect once from a simple schema', async () => { | ||
const schemaFoo = { foo: Math.random() }; | ||
const collectedFrom = []; | ||
|
||
collectFromComposedSchemas(schemaFoo, s => { | ||
collectedFrom.push(s); | ||
|
||
return []; | ||
}); | ||
|
||
expect(collectedFrom.length).toEqual(1); | ||
expect(collectedFrom[0]).toEqual(schemaFoo); | ||
}); | ||
|
||
it('should collect from a composed schema', async () => { | ||
const composedSchema = { | ||
foo: Math.random(), | ||
allOf: [ | ||
{ | ||
foo: Math.random(), | ||
}, | ||
], | ||
oneOf: [ | ||
{ | ||
foo: Math.random(), | ||
}, | ||
], | ||
anyOf: [ | ||
{ | ||
foo: Math.random(), | ||
}, | ||
], | ||
}; | ||
|
||
expect( | ||
collectFromComposedSchemas(composedSchema, s => [s.foo]).sort() | ||
).toEqual( | ||
[ | ||
composedSchema.foo, | ||
composedSchema.allOf[0].foo, | ||
composedSchema.oneOf[0].foo, | ||
composedSchema.anyOf[0].foo, | ||
].sort() | ||
); | ||
}); | ||
|
||
it('should collect from a deeply composed schema', async () => { | ||
const deeplyComposedSchema = { | ||
foo: Math.random(), | ||
allOf: [ | ||
{ | ||
foo: Math.random(), | ||
oneOf: [ | ||
{ | ||
foo: Math.random(), | ||
anyOf: [ | ||
{ | ||
foo: Math.random(), | ||
}, | ||
], | ||
}, | ||
], | ||
}, | ||
], | ||
}; | ||
|
||
expect( | ||
collectFromComposedSchemas(deeplyComposedSchema, s => [s.foo]).sort() | ||
).toEqual( | ||
[ | ||
deeplyComposedSchema.foo, | ||
deeplyComposedSchema.allOf[0].foo, | ||
deeplyComposedSchema.allOf[0].oneOf[0].foo, | ||
deeplyComposedSchema.allOf[0].oneOf[0].anyOf[0].foo, | ||
].sort() | ||
); | ||
}); | ||
|
||
it('should de-duplicate primitive items collected in a composed schema', async () => { | ||
const value = Math.random(); | ||
|
||
expect( | ||
collectFromComposedSchemas( | ||
{ | ||
foo: value, | ||
allOf: [ | ||
{ | ||
foo: value + 0, | ||
}, | ||
], | ||
}, | ||
s => [s.foo] | ||
) | ||
).toEqual([value]); | ||
}); | ||
|
||
it('should not deduplicate non-primitive examples for composed schema', async () => { | ||
expect( | ||
collectFromComposedSchemas( | ||
{ | ||
foo: {}, | ||
allOf: [ | ||
{ | ||
foo: {}, | ||
}, | ||
], | ||
}, | ||
s => [s.foo] | ||
) | ||
).toEqual([{}, {}]); | ||
}); | ||
|
||
it('should not collect from self if includeSelf = false', async () => { | ||
const composedSchema = { | ||
foo: Math.random(), | ||
allOf: [ | ||
{ | ||
foo: Math.random(), | ||
}, | ||
], | ||
}; | ||
|
||
expect( | ||
collectFromComposedSchemas(composedSchema, s => [s.foo], false).sort() | ||
).toEqual([composedSchema.allOf[0].foo].sort()); | ||
}); | ||
|
||
it('should not collect from `not` if includeNot = false', async () => { | ||
const composedSchema = { | ||
foo: Math.random(), | ||
not: { | ||
foo: Math.random(), | ||
}, | ||
}; | ||
|
||
expect( | ||
collectFromComposedSchemas( | ||
composedSchema, | ||
s => [s.foo], | ||
true, | ||
false | ||
).sort() | ||
).toEqual([composedSchema.foo].sort()); | ||
}); | ||
|
||
it('should collect from `not` if includeNot = true', async () => { | ||
const composedSchema = { | ||
foo: Math.random(), | ||
not: { | ||
foo: Math.random(), | ||
}, | ||
}; | ||
|
||
expect( | ||
collectFromComposedSchemas( | ||
composedSchema, | ||
s => [s.foo], | ||
true, | ||
true | ||
).sort() | ||
).toEqual([composedSchema.foo, composedSchema.not.foo].sort()); | ||
}); | ||
}); |
Oops, something went wrong.