openapi-enforcer
Advanced tools
Comparing version 1.14.3 to 1.15.0
@@ -7,2 +7,14 @@ # Change Log | ||
## 1.15.0 | ||
### Added | ||
- **Partial TypeScript Support** | ||
Added some type support for the enforcer. It's a start. | ||
- **Added Schema Hooks** | ||
Developers can now add hooks before and after serialization, deserialization, and validation. See the documentation on the [schema component](https://byu-oit.github.io/openapi-enforcer/api/components/schema). | ||
## 1.14.3 | ||
@@ -9,0 +21,0 @@ |
@@ -437,2 +437,83 @@ --- | ||
## Hook | ||
`Schema.hook ( type, handler ) : void` | ||
Add a hook to schema deserialize, serialize, and validate functonality. | ||
**Parameters:** | ||
| Parameter | Description | | ||
| --------- | ----------- | | ||
| type | The type of hook to add. Expects on of the following values: `"afterDeserialize"`, `"afterSerialize"`, `"afterValidate"`, `"beforeDeserialize"`, `"beforeSerialize"`, or `"beforeValidate"` | | ||
| handler | The function to call for the hook. Every hook function must run synchronously, receives the parameters `value`, `schema`, and `exception`, and should return an object with the following structure: `{ done: false, hasException: false, value: 'whatever' }`. The `hasException` property is optional and allows for performance optimization. If your hook adds an exception then set the value to `true`, otherwise return it as `false`. | | ||
In the case that an exception exists your hook function will not be run. It's possible another hook may run and specify that the process is `done: true`, also, preventing your hook from running. | ||
**Returns** undefined | ||
**Example** | ||
```js | ||
// define a custom beforeDeserialize hook | ||
function zeroDateHook (value, schema, exception) { | ||
if (schema.type === 'string' && schema.format === 'date' && value === '0000-00-00') { | ||
// custom deserialize functionality example | ||
return { | ||
done: true, // if done then don't allow any other deserialize functions to run, we are done | ||
hasException: false, // we didn't add any exception messages with this hook | ||
value: new Date(0) | ||
} | ||
} else if (schema.type === 'string' && schema.format === 'date-time' && value === '0000-00-00') { | ||
// exception example | ||
exception.message('Zero dates are not allowed for date-time formats.') | ||
return { | ||
done: true, | ||
hasException: true, | ||
value | ||
} | ||
} else { | ||
// no changes example | ||
// we didn't make any changes so pass along the unchanged value and let other deserializers work on it | ||
return { | ||
done: false, | ||
hasException: false, | ||
value | ||
} | ||
} | ||
} | ||
Enforcer.v2_0.Schema.hook('beforeDeserialize', hook) | ||
Enforcer.v3_0.Schema.hook('beforeDeserialize', hook) | ||
``` | ||
## Unhook | ||
`Schema.unhook ( type, handler ) : void` | ||
Remove a previously added hook. | ||
**Parameters:** | ||
| Parameter | Description | | ||
| --------- | ----------- | | ||
| type | The type of hook to remove. Expects on of the following values: `"afterDeserialize"`, `"afterSerialize"`, `"afterValidate"`, `"beforeDeserialize"`, `"beforeSerialize"`, or `"beforeValidate"` | | ||
| handler | The function to unhook. | | ||
**Returns** undefined | ||
**Example** | ||
```js | ||
function myHook (value, schema, exception) { ... } | ||
// add the hook | ||
Enforcer.v2_0.Schema.hook('beforeDeserialize', myHook) | ||
Enforcer.v3_0.Schema.hook('beforeDeserialize', myHook) | ||
// remove the hook | ||
Enforcer.v2_0.Schema.unhook('beforeDeserialize', myHook) | ||
Enforcer.v3_0.Schema.unhook('beforeDeserialize', myHook) | ||
``` | ||
## Value | ||
@@ -439,0 +520,0 @@ |
@@ -0,0 +0,0 @@ ;(function () { |
{ | ||
"name": "openapi-enforcer", | ||
"version": "1.14.3", | ||
"version": "1.15.0", | ||
"description": "Library for validating, parsing, and formatting data against open api schemas.", | ||
@@ -22,2 +22,3 @@ "main": "index.js", | ||
}, | ||
"types": "openapi-enforcer.d.ts", | ||
"keywords": [ | ||
@@ -49,3 +50,3 @@ "openapi", | ||
"devDependencies": { | ||
"@gi60s/markdown-docs": "0.1.2", | ||
"@gi60s/markdown-docs": "^0.1.2", | ||
"chai": "^4.2.0", | ||
@@ -52,0 +53,0 @@ "chokidar-cli": "^1.2.3", |
@@ -319,2 +319,10 @@ /** | ||
}; | ||
const hooks = scope.hooks = { | ||
afterDeserialize: [], | ||
afterSerialize: [], | ||
afterValidate: [], | ||
beforeDeserialize: [], | ||
beforeSerialize: [], | ||
beforeValidate: [], | ||
} | ||
scope.dataTypeConstructors = function () { | ||
@@ -370,2 +378,14 @@ return Array.from(constructors.values()); | ||
hook: function (type, handler) { | ||
if (!hooks.hasOwnProperty(type)) throw Error('Invalid hook type. Choose one of: ' + Object.keys(hooks).join(', ')) | ||
if (typeof handler !== 'function') throw Error('Invalid hook handler. Expected a function. Received: ' + util.smart(handler)) | ||
hooks[type].push(handler) | ||
}, | ||
unhook: function (type, handler) { | ||
const handlers = hooks[type] || [] | ||
const index = handlers.indexOf(handler) | ||
if (index !== -1) handlers.splice(index, 1) | ||
}, | ||
Value: Value | ||
@@ -372,0 +392,0 @@ } |
@@ -18,2 +18,3 @@ /** | ||
'use strict'; | ||
const hooks = require('./hooks') | ||
const schemaUtil = require('./util'); | ||
@@ -31,3 +32,3 @@ const util = require('../util'); | ||
function runDeserialize(exception, map, schema, originalValue, options) { | ||
const { serialize, value } = Value.getAttributes(originalValue); | ||
let { serialize, value } = Value.getAttributes(originalValue); | ||
if (!serialize) return originalValue; | ||
@@ -48,4 +49,8 @@ | ||
if (value === null && (schema.nullable || schema['x-nullable'])) return value; | ||
const hookResult = hooks.runHooks(schema, 'beforeDeserialize', value, exception) | ||
value = hookResult.value | ||
if (hookResult.done) return value | ||
if (value === null && (schema.nullable || schema['x-nullable'])) return hooks.after(schema, 'afterDeserialize', value, exception); | ||
if (schema.allOf) { | ||
@@ -59,3 +64,3 @@ const exception2 = exception.at('allOf'); | ||
}); | ||
return Object.assign(value, result); | ||
return hooks.after(schema, 'afterDeserialize', Object.assign(value, result), exception) | ||
} else { | ||
@@ -78,3 +83,3 @@ return runDeserialize(exception2.at('0'), map, schema.allOf[0], originalValue, options); | ||
} | ||
return result; | ||
return hooks.after(schema, 'afterDeserialize', result, exception); | ||
@@ -88,3 +93,3 @@ } else if (type === 'array') { | ||
} | ||
return value; | ||
return hooks.after(schema, 'afterDeserialize', value, exception); | ||
} else { | ||
@@ -114,3 +119,3 @@ exception.message('Expected an array. Received: ' + util.smart(value)); | ||
} | ||
return value; | ||
return hooks.after(schema, 'afterDeserialize', value, exception); | ||
} else { | ||
@@ -127,3 +132,3 @@ exception.message('Expected an object. Received: ' + util.smart(value)); | ||
// although this will never run right now, it is here in case a custom type definition for booleans is created | ||
return dataType.deserialize({ | ||
const result = dataType.deserialize({ | ||
exception, | ||
@@ -133,2 +138,3 @@ schema, | ||
}); | ||
return hooks.after(schema, 'afterDeserialize', result, exception) | ||
} else if (typeofValue !== 'boolean') { | ||
@@ -140,3 +146,3 @@ if (!options.strict) { | ||
} else if (typeofValue === 'number') { | ||
return !!value; | ||
return hooks.after(schema, 'afterDeserialize', !!value, exception) | ||
} | ||
@@ -146,3 +152,3 @@ } | ||
} else { | ||
return value; | ||
return hooks.after(schema, 'afterDeserialize', value, exception) | ||
} | ||
@@ -152,3 +158,3 @@ | ||
if (dataType && dataType.deserialize) { | ||
return dataType.deserialize({ | ||
const result = dataType.deserialize({ | ||
exception, | ||
@@ -158,9 +164,10 @@ schema, | ||
}); | ||
return hooks.after(schema, 'afterDeserialize', result, exception) | ||
} else if (typeofValue !== 'number' || !util.isInteger(value)) { | ||
if (!options.strict && typeofValue === 'string' && rxInteger.test(value)) { | ||
return +value; | ||
return hooks.after(schema, 'afterDeserialize', +value, exception) | ||
} | ||
exception.message('Expected an integer. Received: ' + util.smart(value)); | ||
} else { | ||
return value; | ||
return hooks.after(schema, 'afterDeserialize', value, exception) | ||
} | ||
@@ -170,3 +177,3 @@ | ||
if (dataType && dataType.deserialize) { | ||
return dataType.deserialize({ | ||
const result = dataType.deserialize({ | ||
exception, | ||
@@ -176,9 +183,10 @@ schema, | ||
}); | ||
return hooks.after(schema, 'afterDeserialize', result, exception) | ||
} else if (typeofValue !== 'number') { | ||
if (!options.strict && typeofValue === 'string' && rxNumber.test(value)) { | ||
return +value; | ||
return hooks.after(schema, 'afterDeserialize', +value, exception) | ||
} | ||
exception.message('Expected a number. Received: ' + util.smart(value)); | ||
} else { | ||
return value; | ||
return hooks.after(schema, 'afterDeserialize', value, exception) | ||
} | ||
@@ -188,3 +196,3 @@ | ||
if (dataType && dataType.deserialize) { | ||
return dataType.deserialize({ | ||
const result = dataType.deserialize({ | ||
exception, | ||
@@ -194,6 +202,7 @@ schema, | ||
}); | ||
return hooks.after(schema, 'afterDeserialize', result, exception) | ||
} if (typeofValue !== 'string') { | ||
exception.message('Expected a string. Received: ' + util.smart(value)); | ||
} else { | ||
return value; | ||
return hooks.after(schema, 'afterDeserialize', value, exception) | ||
} | ||
@@ -203,4 +212,4 @@ } | ||
} else { | ||
return value | ||
return hooks.after(schema, 'afterDeserialize', value, exception) | ||
} | ||
} |
@@ -18,2 +18,3 @@ /** | ||
'use strict'; | ||
const hooks = require('./hooks') | ||
const schemaUtil = require('./util'); | ||
@@ -26,3 +27,3 @@ const util = require('../util'); | ||
function runSerialize(exception, map, schema, originalValue) { | ||
const { serialize, value } = Value.getAttributes(originalValue); | ||
let { serialize, value } = Value.getAttributes(originalValue); | ||
if (!serialize) return originalValue; | ||
@@ -42,4 +43,8 @@ | ||
if (value === null && (schema.nullable || schema['x-nullable'])) return value; | ||
const hookResult = hooks.runHooks(schema, 'beforeSerialize', value, exception) | ||
value = hookResult.value | ||
if (hookResult.done) return value | ||
if (value === null && (schema.nullable || schema['x-nullable'])) return hooks.after(schema, 'afterSerialize', value, exception); | ||
if (schema.allOf) { | ||
@@ -52,9 +57,10 @@ const result = {}; | ||
}); | ||
return Object.assign(value, result); | ||
return hooks.after(schema, 'afterSerialize', Object.assign(value, result), exception); | ||
} else if (schema.anyOf || schema.oneOf) { | ||
let result; | ||
if (schema.discriminator) { | ||
const { name, key, schema: subSchema } = schema.discriminate(value, true) | ||
if (subSchema) { | ||
Object.assign(value, runSerialize(exception, map, subSchema, originalValue)); | ||
result = Object.assign(value, runSerialize(exception, map, subSchema, originalValue)); | ||
} else { | ||
@@ -64,5 +70,5 @@ exception.message('Discriminator property "' + key + '" as "' + name + '" did not map to a schema'); | ||
} else { | ||
return schemaUtil.anyOneOf(schema, originalValue, exception, map, runSerialize, true); | ||
result = schemaUtil.anyOneOf(schema, originalValue, exception, map, runSerialize, true); | ||
} | ||
return value; | ||
return hooks.after(schema, 'afterSerialize', result, exception); | ||
@@ -76,3 +82,3 @@ } else if (type === 'array') { | ||
} | ||
return value; | ||
return hooks.after(schema, 'afterSerialize', value, exception); | ||
} else { | ||
@@ -102,3 +108,3 @@ exception.message('Expected an array. Received: ' + util.smart(value)); | ||
} | ||
return value; | ||
return hooks.after(schema, 'afterSerialize', value, exception); | ||
} else { | ||
@@ -122,3 +128,3 @@ exception.message('Expected an object. Received: ' + util.smart(originalValue)); | ||
} | ||
return result; | ||
return hooks.after(schema, 'afterSerialize', result, exception); | ||
@@ -134,3 +140,3 @@ } else if (type === 'integer') { | ||
} | ||
return result; | ||
return hooks.after(schema, 'afterSerialize', result, exception); | ||
@@ -146,3 +152,3 @@ } else if (type === 'number') { | ||
} | ||
return result; | ||
return hooks.after(schema, 'afterSerialize', result, exception); | ||
@@ -158,8 +164,8 @@ } else if (type === 'string') { | ||
} | ||
return result; | ||
return hooks.after(schema, 'afterSerialize', result, exception); | ||
} | ||
} else { | ||
return value; | ||
return hooks.after(schema, 'afterSerialize', value, exception); | ||
} | ||
} |
@@ -18,2 +18,3 @@ /** | ||
'use strict'; | ||
const hooks = require('./hooks') | ||
const Exception = require('../exception'); | ||
@@ -26,3 +27,3 @@ const util = require('../util'); | ||
function runValidate(exception, map, schema, originalValue, options) { | ||
const { validate, value } = Value.getAttributes(originalValue); | ||
let { validate, value } = Value.getAttributes(originalValue); | ||
if (!validate) return originalValue; | ||
@@ -44,4 +45,8 @@ | ||
const hookResult = hooks.runHooks(schema, 'beforeValidate', value, exception) | ||
value = hookResult.value | ||
if (hookResult.done) return value | ||
// if nullable and null then skip all other validation | ||
if (value === null && (schema.nullable || schema['x-nullable'])) return; | ||
if (value === null && (schema.nullable || schema['x-nullable'])) return hooks.after(schema, 'afterValidate', value, exception); | ||
@@ -62,3 +67,4 @@ if (schema.allOf) { | ||
} else { | ||
runValidate(exception.at(value[key]), map, subSchema, value, options); | ||
const child = exception.at(value[key]) | ||
runValidate(child, map, subSchema, value, options); | ||
} | ||
@@ -77,3 +83,5 @@ } else { | ||
} | ||
if (!valid) exception.push(anyOfException); | ||
if (!valid) { | ||
exception.push(anyOfException); | ||
} | ||
} | ||
@@ -87,3 +95,4 @@ | ||
} else { | ||
runValidate(exception.at(value[key]), map, subSchema, value, options); | ||
const child = exception.at(value[key]) | ||
runValidate(child, map, subSchema, value, options); | ||
} | ||
@@ -104,3 +113,5 @@ } else { | ||
} | ||
if (valid !== 1) exception.push(oneOfException); | ||
if (valid !== 1) { | ||
exception.push(oneOfException); | ||
} | ||
} | ||
@@ -111,3 +122,5 @@ | ||
runValidate(child, map, schema, value, options); | ||
if (!child.hasException) exception.message('Value should not validate against schema'); | ||
if (!child.hasException) { | ||
exception.message('Value should not validate against schema'); | ||
} | ||
@@ -281,2 +294,4 @@ } else if (type === 'array') { | ||
} | ||
hooks.after(schema, 'afterValidate', value, exception, true); | ||
} | ||
@@ -283,0 +298,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
1583122
184
20949