openapi-enforcer
Advanced tools
Comparing version 0.9.5 to 0.10.0
@@ -27,15 +27,14 @@ /** | ||
* Convert value into a binary octet string. | ||
* @param {string} errPrefix | ||
* @param {boolean, number, string, Buffer} value | ||
* @returns {string} | ||
* @returns {{ error: string|null, value: string|null}} | ||
* @throws {Error} | ||
*/ | ||
exports.binary = function(errPrefix, value) { | ||
exports.binary = function(value) { | ||
const type = typeof value; | ||
if (type === 'boolean') { | ||
return '0000000' + (value ? '1' : '0'); | ||
return formatted(null, '0000000' + (value ? '1' : '0')); | ||
} else if (type === 'number' && !isNaN(value)) { | ||
return decToBin(value); | ||
return formatted(null, decToBin(value)); | ||
@@ -49,8 +48,7 @@ } else if (type === 'string' || value instanceof Buffer) { | ||
} | ||
return binary; | ||
return formatted(null, binary); | ||
} else { | ||
throw Error(`${errPrefix} Cannot convert to binary. | ||
The value must be a boolean, number, string, or buffer. | ||
Received: ${smart(value)}`); | ||
return formatted('Cannot convert to binary. The value must be a boolean, ' + | ||
'number, string, or buffer. Received: ' + smart(value)); | ||
} | ||
@@ -61,8 +59,7 @@ }; | ||
* Convert a value to a boolean. | ||
* @param {string} errPrefix | ||
* @param {*} value | ||
* @returns {boolean} | ||
* @returns {{ error: null, value: boolean}} | ||
*/ | ||
exports.boolean = function(errPrefix, value) { | ||
return !!value; | ||
exports.boolean = function(value) { | ||
return formatted(null, !!value); | ||
}; | ||
@@ -72,11 +69,10 @@ | ||
* Convert to base64 encoded string. | ||
* @param {string} errPrefix | ||
* @param {boolean, number, string, buffer} value | ||
* @returns {string} | ||
* @returns {{ error: string|null, value: string|null}} | ||
*/ | ||
exports.byte = function(errPrefix, value) { | ||
exports.byte = function(value) { | ||
const type = typeof value; | ||
if (type === 'boolean') { | ||
return value ? 'AQ==' : 'AA=='; | ||
return formatted(null, value ? 'AQ==' : 'AA=='); | ||
@@ -87,14 +83,13 @@ } else if (type === 'number' && !isNaN(value)) { | ||
for (let i = 0; i < binary.length; i += 8) bytes.push(parseInt(binary.substr(i, 8), 2)); | ||
return Buffer.from(bytes).toString('base64'); | ||
return formatted(null, Buffer.from(bytes).toString('base64')); | ||
} else if (type === 'string') { | ||
return Buffer.from(value, 'utf8').toString('base64'); | ||
return formatted(null, Buffer.from(value, 'utf8').toString('base64')); | ||
} else if (value instanceof Buffer) { | ||
return value.toString('base64'); | ||
return formatted(null, value.toString('base64')); | ||
} else { | ||
throw Error(`${errPrefix} Cannot convert to byte. | ||
The value must be a boolean, number, string, or buffer. | ||
Received: ${smart(value)}`); | ||
return formatted('Cannot convert to byte. The value must be a boolean, ' + | ||
'number, string, or buffer. Received: ' + smart(value)); | ||
} | ||
@@ -105,8 +100,12 @@ }; | ||
* Take a number, date value, or a date string and convert to date format. | ||
* @param {string} errPrefix | ||
* @param {Date, string, number} value | ||
* @returns {string} | ||
* @returns {{ error: string|null, value: string|null}} | ||
*/ | ||
exports.date = function(errPrefix, value) { | ||
return exports.dateTime(errPrefix, value).substr(0, 10); | ||
exports.date = function(value) { | ||
const data = exports.dateTime(value); | ||
if (data.error) { | ||
data.error = data.error.replace('date-time', 'date'); | ||
return data; | ||
} | ||
return formatted(null, data.value.substr(0, 10)); | ||
}; | ||
@@ -116,7 +115,6 @@ | ||
* Take a number, date value, or a date string and convert to ISO date format. | ||
* @param {string} errPrefix | ||
* @param {Date, string, number} value | ||
* @returns {string} | ||
* @returns {{ error: string|null, value: string|null}} | ||
*/ | ||
exports.dateTime = function(errPrefix, value) { | ||
exports.dateTime = function(value) { | ||
const type = typeof value; | ||
@@ -126,17 +124,16 @@ const isString = type === 'string'; | ||
if (isString && rx.dateTime.test(value)) { | ||
return new Date(value).toISOString(); | ||
return formatted(null, new Date(value).toISOString()); | ||
} else if (isString && rx.date.test(value)) { | ||
return new Date(value + 'T00:00:00.000Z').toISOString(); | ||
return formatted(null, new Date(value + 'T00:00:00.000Z').toISOString()); | ||
} else if (util.isDate(value)) { | ||
return value.toISOString(); | ||
return formatted(null, value.toISOString()); | ||
} else if (type === 'number') { | ||
return new Date(value).toISOString(); | ||
return formatted(null, new Date(value).toISOString()); | ||
} else { | ||
throw Error(`${errPrefix} Cannot convert to date. | ||
The value must be a Date, a number, or a date string. | ||
Received: ${smart(value)}`); | ||
return formatted('Cannot convert to date-time. The value must be a Date, ' + | ||
'a number, or a date string. Received: ' + smart(value)); | ||
} | ||
@@ -148,12 +145,10 @@ }; | ||
* Convert a value to an integer. | ||
* @param {string} errPrefix | ||
* @param {*} value | ||
* @returns {number} | ||
* @returns {{ error: string|null, value: number|null}} | ||
*/ | ||
exports.integer = function(errPrefix, value) { | ||
exports.integer = function(value) { | ||
const result = +value; | ||
if (!isNaN(result)) return Math.round(result); | ||
throw Error(`${errPrefix} Cannot convert to integer. | ||
The value must be numeric. | ||
Received: ${smart(value)}`); | ||
if (!isNaN(result)) return formatted(null, Math.round(result)); | ||
return formatted('Cannot convert to integer. The value must be numeric. ' + | ||
'Received: ' + smart(value)); | ||
}; | ||
@@ -163,12 +158,10 @@ | ||
* Convert a value to a number. | ||
* @param {string} errPrefix | ||
* @param {string, number, boolean} value | ||
* @returns {number} | ||
* @returns {{ error: string|null, value: number|null}} | ||
*/ | ||
exports.number = function(errPrefix, value) { | ||
exports.number = function(value) { | ||
const result = +value; | ||
if (!isNaN(result)) return result; | ||
throw Error(`${errPrefix} Cannot convert to number. | ||
The value must be numeric. | ||
Received: ${smart(value)}`); | ||
if (!isNaN(result)) return formatted(null, result); | ||
return formatted('Cannot convert to number. The value must be numeric. ' + | ||
'Received: ' + smart(value)); | ||
}; | ||
@@ -178,7 +171,6 @@ | ||
* Convert a value to a string. | ||
* @param {string} errPrefix | ||
* @param {string, number, boolean, object, date} value | ||
* @returns {string} | ||
* @returns {{ error: string|null, value: string|null}} | ||
*/ | ||
exports.string = function(errPrefix, value) { | ||
exports.string = function(value) { | ||
switch (typeof value) { | ||
@@ -188,10 +180,9 @@ case 'boolean': | ||
case 'string': | ||
return String(value); | ||
return formatted(null, String(value)); | ||
case 'object': | ||
if (util.isDate(value)) return value.toISOString(); | ||
return JSON.stringify(value); | ||
if (util.isDate(value)) return formatted(null, value.toISOString()); | ||
return formatted(null, JSON.stringify(value)); | ||
} | ||
throw Error(`${errPrefix} Cannot convert to string. | ||
The value must be a string, a number, or a boolean, and Object, or a Date. | ||
Received: ${smart(value)}`); | ||
return formatted('Cannot convert to string. The value must be a string, ' + | ||
'a number, or a boolean, and Object, or a Date. Received: ' + smart(value)); | ||
}; | ||
@@ -203,2 +194,9 @@ | ||
return mod === 0 ? binary : zeros.substr(mod) + binary; | ||
} | ||
function formatted(err, value) { | ||
return { | ||
error: err ? err : null, | ||
value: err ? null : value | ||
} | ||
} |
@@ -18,5 +18,7 @@ /** | ||
'use strict'; | ||
const format = require('./format'); | ||
const Exception = require('./exception'); | ||
const populate = require('./populate'); | ||
const util = require('./util'); | ||
const random = require('./random'); | ||
const traverse = require('./traverse'); | ||
const validate = require('./validate'); | ||
@@ -30,11 +32,41 @@ | ||
const staticDefaults = { | ||
deserialize: { | ||
throw: true | ||
}, | ||
errors: { | ||
prefix: '' | ||
}, | ||
populate: { | ||
copy: false, | ||
defaults: true, | ||
ignoreMissingRequired: true, | ||
replacement: 'handlebar', | ||
templateDefaults: true, | ||
templates: true, | ||
throw: true, | ||
variables: true | ||
}, | ||
random: { | ||
skipInvalid: false, | ||
throw: true | ||
}, | ||
request: { | ||
throw: true | ||
}, | ||
serialize: { | ||
throw: true | ||
} | ||
}; | ||
/** | ||
* Produce an open api enforcer instance. | ||
* @param {object, string} definition The open api definition object or a string representing the version to use. | ||
* @param {object} [options] The default options. | ||
* @constructor | ||
*/ | ||
function OpenApiEnforcer(definition) { | ||
function OpenApiEnforcer(definition, options) { | ||
// make sure that this is called as a new instance | ||
if (!(this instanceof OpenApiEnforcer)) return new OpenApiEnforcer(definition); | ||
if (!(this instanceof OpenApiEnforcer)) return new OpenApiEnforcer(definition, options); | ||
@@ -58,6 +90,4 @@ // if the definition was passed in as a version number then rebuild the definition object | ||
const version = new Version(this, definition); | ||
version.defaults = Version.defaults; | ||
// normalize defaults | ||
const defaults = Version.defaults; | ||
// build path parser functions | ||
@@ -110,2 +140,11 @@ const pathParsers = {}; | ||
// normalize defaults | ||
const defaults = {}; | ||
Object.keys(staticDefaults) | ||
.forEach(key => { | ||
defaults[key] = Object.assign({}, staticDefaults[key], | ||
OpenApiEnforcer.defaults && OpenApiEnforcer.defaults[key], | ||
options && options[key]); | ||
}); | ||
// store protected properties | ||
@@ -124,14 +163,19 @@ store.set(this, { | ||
* @param {*} value | ||
* @returns {{ errors:string[], value:* }} | ||
* @param {object} options | ||
* @param {boolean} [options.throw=true] If true then throw errors if found, otherwise return errors array. | ||
* @returns {*|{ exception: OpenAPIException, value:* }} | ||
*/ | ||
OpenApiEnforcer.prototype.deserialize = function(schema, value) { | ||
OpenApiEnforcer.prototype.deserialize = function(schema, value, options) { | ||
const exception = new Exception('One or more errors occurred during deserialization'); | ||
const data = store.get(this); | ||
const errors = []; | ||
const version = data.version; | ||
const result = version.serial.deserialize(errors, '', schema, value); | ||
const hasErrors = errors.length; | ||
return { | ||
errors: hasErrors ? errors.map(v => v.trim()) : null, | ||
value: hasErrors ? null : result | ||
}; | ||
// normalize options | ||
options = Object.assign({}, data.defaults.deserialize, options); | ||
// run version specific deserialization | ||
const result = version.serial.deserialize(exception, schema, value); | ||
// determine how to handle deserialization data | ||
return util.errorHandler(options.throw, exception, result); | ||
}; | ||
@@ -143,8 +187,9 @@ | ||
* @param {*} value | ||
* @param {object} [options] | ||
* @param {string} [options.prefix=''] | ||
* @returns {string[]|undefined} | ||
*/ | ||
OpenApiEnforcer.prototype.errors = function(schema, value) { | ||
OpenApiEnforcer.prototype.errors = function(schema, value, options) { | ||
const data = store.get(this); | ||
const version = data.version; | ||
const options = Object.assign({ prefix: '' }, arguments[2]); | ||
const v = { | ||
@@ -154,5 +199,7 @@ definition: data.definition, | ||
errors: [], | ||
options: data.defaults.validate, | ||
options: version.defaults.validate, | ||
version: version | ||
}; | ||
options = Object.assign({}, data.defaults.errors, options); | ||
validate(v, options.prefix, 0, schema, value); | ||
@@ -176,2 +223,5 @@ return v.errors.length > 0 ? v.errors : null; | ||
// if the parser was not found then they have a bad path | ||
if (!parsers) return; | ||
// find the right parser | ||
@@ -194,20 +244,31 @@ const length = parsers.length; | ||
* Populate an object or an array using default, x-template, x-variable, and a parameter map. | ||
* @param {object} config | ||
* @param {object} config.schema | ||
* @param {object} [config.params={}] | ||
* @param {object} [config.options] | ||
* @param {*} [config.value] | ||
* @returns {*} | ||
* @param {object} schema | ||
* @param {object} [params={}] | ||
* @param {*} [value=undefined] | ||
* @param {object} options | ||
* @param {boolean} [options.copy] | ||
* @param {boolean} [options.ignoreMissingRequired] | ||
* @param {boolean} [options.oneOf] | ||
* @param {string} [options.replacement] | ||
* @param {boolean} [options.serialize] | ||
* @param {boolean} [options.templateDefaults] | ||
* @param {boolean} [options.templates] | ||
* @param {boolean} [options.throw=true] If true then throw errors if found, otherwise return errors array. | ||
* @param {boolean} [options.variables] | ||
* @returns {{ exception: OpenAPIException|null, value: *}|*} | ||
*/ | ||
OpenApiEnforcer.prototype.populate = function (config) { | ||
const options = config.options | ||
? Object.assign({}, OpenApiEnforcer.defaults.populate, config.options) | ||
: OpenApiEnforcer.defaults.populate; | ||
OpenApiEnforcer.prototype.populate = function (schema, params, value, options) { | ||
const data = store.get(this); | ||
const version = data.version; | ||
const exception = new Exception('One or more errors occurred during population'); | ||
// normalize options | ||
options = Object.assign({}, data.defaults.populate, options, version.defaults.populate); | ||
// initialize variables | ||
const initialValueProvided = config.hasOwnProperty('value'); | ||
const version = store.get(this).version; | ||
const v = { | ||
context: this, | ||
injector: populate.injector[options.replacement], | ||
map: config.params || {}, | ||
map: params || {}, | ||
options: options, | ||
@@ -219,11 +280,10 @@ schemas: version.schemas, | ||
// produce start value | ||
const value = v.options.copy && initialValueProvided | ||
? util.copy(config.value) | ||
: config.value; | ||
if (v.options.copy) value = util.copy(value); | ||
// begin population | ||
const root = { root: value }; | ||
populate.populate(v, '<root>', config.schema, root, 'root'); | ||
populate.populate(v, exception, schema, root, 'root'); | ||
return root.root; | ||
// determine how to handle deserialization data | ||
return util.errorHandler(options.throw, exception, root.root); | ||
}; | ||
@@ -234,6 +294,16 @@ | ||
* @param {object} schema | ||
* @returns {*} | ||
* @param {object} [options] | ||
* @param {boolean} [options.skipInvalid=false] | ||
* @param {boolean} [options.throw=true] | ||
* @returns {{ exception: OpenAPIException|null, value: *}|*} | ||
*/ | ||
OpenApiEnforcer.prototype.random = function(schema) { | ||
return store.get(this).version.random(schema); | ||
OpenApiEnforcer.prototype.random = function(schema, options) { | ||
if (!options) options = {}; | ||
if (!options.hasOwnProperty('skipInvalid')) options.skipInvalid = false; | ||
if (!options.hasOwnProperty('throw')) options.throw = true; | ||
const result = random.util.traverse(schema, | ||
store.get(this).version, options, | ||
Object.assign({}, staticDefaults.random, options)); | ||
return util.errorHandler(options.throw, result.exception, result.value); | ||
}; | ||
@@ -249,33 +319,45 @@ | ||
* @param {string} [req.path] | ||
* @param {object} [options] | ||
* @param {boolean} [options.throw] | ||
* @returns {object} | ||
*/ | ||
OpenApiEnforcer.prototype.request = function(req) { | ||
OpenApiEnforcer.prototype.request = function(req, options) { | ||
const data = store.get(this); | ||
const exception = new Exception('One or more problems exist with the request'); | ||
options = Object.assign({}, data.defaults.request, options); | ||
// normalize input parameter | ||
if (typeof req === 'string') req = { path: req }; | ||
if (typeof req !== 'object') throw Error('Invalid request. Must be a string or an object. Received: ' + req); | ||
if (!req || typeof req !== 'object') throw Error('Invalid request. Must be a string or an object. Received: ' + util.smart(req)); | ||
req = Object.assign({}, req); | ||
if (req.body !== undefined && typeof req.body !== 'object' && typeof req.body !== 'string') throw Error('Invalid request body. Must be a string or an object. Received: ' + req.body); | ||
if (req.cookies && typeof req.cookies !== 'object') throw Error('Invalid request cookies. Must be an object. Received: ' + req.cookies); | ||
if (req.headers && typeof req.headers !== 'object') throw Error('Invalid request headers. Must be an object. Received: ' + req.headers); | ||
if (typeof req.path !== 'string') throw Error('Invalid request path. Must be a string. Received: ' + req.path); | ||
if (!req.method) req.method = 'get'; | ||
if (typeof req.method !== 'string') throw Error('Invalid request method. Must be a string. Received: ' + req.method); | ||
if (req.hasOwnProperty('cookies') && (!req.cookies || typeof req.cookies !== 'object')) throw Error('Invalid request cookies. Must be an object. Received: ' + util.smart(req.cookies)); | ||
if (req.hasOwnProperty('headers') && (!req.headers || typeof req.headers !== 'object')) throw Error('Invalid request headers. Must be an object. Received: ' + util.smart(req.headers)); | ||
if (req.hasOwnProperty('path') && typeof req.path !== 'string') throw Error('Invalid request path. Must be a string. Received: ' + util.smart(req.path)); | ||
if (req.hasOwnProperty('method') && typeof req.method !== 'string') throw Error('Invalid request method. Must be a string. Received: ' + util.smart(req.method)); | ||
if (!req.hasOwnProperty('path')) req.path = '/'; | ||
if (!req.hasOwnProperty('method')) req.method = 'get'; | ||
// build request path and query | ||
const pathAndQuery = req.path.split('?'); | ||
req.path = pathAndQuery[0]; | ||
req.query = pathAndQuery[1]; | ||
req.path = util.edgeSlashes(req.path, true, false); | ||
req.path = util.edgeSlashes(pathAndQuery[0], true, false); | ||
const query = pathAndQuery[1]; | ||
// get the defined open api path or call next middleware | ||
const path = this.path(req.path); | ||
if (!path) return { statusCode: 404, errors: ['Not found'] }; | ||
if (!path) { | ||
exception.push('Path not found'); | ||
exception.meta = { statusCode: 404 }; | ||
return util.errorHandler(options.throw, exception); | ||
} | ||
// validate that the path supports the method | ||
const method = req.method.toLowerCase(); | ||
if (!path.schema[method]) return { statusCode: 405, errors: ['Method not allowed'] }; | ||
if (!path.schema[method]) { | ||
exception.push('Method not allowed'); | ||
exception.meta = { statusCode: 405 }; | ||
return util.errorHandler(options.throw, exception); | ||
} | ||
// parse and validate request input | ||
const result = store.get(this).version.parseRequestParameters(path.schema, { | ||
const parsed = store.get(this).version.parseRequestParameters(path.schema, exception, { | ||
body: req.body, | ||
@@ -286,34 +368,24 @@ cookie: req.cookies || {}, | ||
path: path.params, | ||
query: req.query || '' | ||
query: query || '' | ||
}); | ||
const responses = path.schema[method].responses; | ||
const produces = path.schema[method].produces; | ||
const value = result.value; | ||
const returnValue = { | ||
errors: result.errors, | ||
path: path.path, | ||
request: value | ||
? { | ||
cookies: value.cookie, | ||
headers: value.header, | ||
path: value.path, | ||
query: value.query | ||
} | ||
: null, | ||
response: (config) => { | ||
const data = responseData(this, produces, responses, config); | ||
return { | ||
data: data, | ||
errors: config => responseErrors(this, responses, data, config), | ||
example: config => responseExample(this, responses, data, config), | ||
populate: config => responsePopulate(this, responses, data, config), | ||
serialize: config => responseSerialize(this, responses, data, config) | ||
} | ||
}, | ||
schema: path.schema, | ||
statusCode: result.errors ? 400 : 200 | ||
}; | ||
if (value && value.hasOwnProperty('body')) returnValue.request.body = value.body; | ||
return returnValue; | ||
// if no errors then generate the return value, otherwise add 400 code | ||
let result = null; | ||
if (parsed.exception) { | ||
exception.meta = { statusCode: 400 }; | ||
} else { | ||
const value = parsed.value; | ||
result = { | ||
path: path.path, | ||
cookies: value.cookie, | ||
headers: value.header, | ||
params: value.path, | ||
query: value.query, | ||
response: config => responseFactory(this, path, method, config), | ||
schema: path.schema | ||
}; | ||
if (value && value.hasOwnProperty('body')) result.body = value.body; | ||
} | ||
return util.errorHandler(options.throw, exception, result); | ||
}; | ||
@@ -323,23 +395,24 @@ | ||
* Validate and serialize a response. | ||
* @param {{ code: string, contentType: string, path: string, method: string }} options The request object. | ||
* @param {string} options | ||
* @param {string|number} options.code | ||
* @param {string} options.contentType | ||
* @param {string} options.path | ||
* @param {string} options.method | ||
* @returns {{data: function, example: function, populate: function, serialize: function}} | ||
*/ | ||
OpenApiEnforcer.prototype.response = function(options) { | ||
if (!options || typeof options !== 'object') throw Error('Invalid options. Must be a string or an object. Received: ' + util.smart(options)); | ||
options = Object.assign({}, { method: 'get' }, options); | ||
//if (typeof options.code !== 'string' && typeof options.code !== 'number') throw Error('Invalid code. Must be a string or a number. Received: ' + util.smart(options.code)); | ||
//if (typeof options.contentType !== 'string') throw Error('Invalid contentType. Must be a string. Received: ' + util.smart(options.contentType)); | ||
if (typeof options.path !== 'string') throw Error('Invalid path. Must be a string. Received: ' + util.smart(options.path)); | ||
if (options.hasOwnProperty('method') && typeof options.method !== 'string') throw Error('Invalid method. Must be a string. Received: ' + util.smart(options.method)); | ||
const path = this.path(options.path); | ||
if (!path) throw Error('Invalid request path. The path is not defined in the specification: ' + req.path); | ||
if (!path) throw Error('Invalid path. The path is not defined in the specification: ' + options.path); | ||
options = Object.assign({}, { method: 'get' }, options); | ||
const method = options.method.toLowerCase(); | ||
if (!path.schema[method]) throw Error('Invalid method for request path. The method is not defined in the specification: ' + method.toUpperCase() + ' ' + req.path); | ||
if (!path.schema[method]) throw Error('Invalid method for request path. The method is not defined in the specification: ' + method.toUpperCase() + ' ' + options.path); | ||
const responses = path.schema[method].responses; | ||
const produces = path.schema[method].produces; | ||
const data = responseData(this, produces, responses, options); | ||
return { | ||
data: data, | ||
errors: config => responseErrors(this, responses, data, config), | ||
example: config => responseExample(this, responses, data, config), | ||
populate: config => responsePopulate(this, responses, data, config), | ||
serialize: config => responseSerialize(this, responses, data, config) | ||
}; | ||
return responseFactory(this, path, method, options); | ||
}; | ||
@@ -352,9 +425,41 @@ | ||
* @param {*} value | ||
* @param {object} options | ||
* @param {boolean} [options.throw=true] If true then throw errors if found, otherwise return errors array. | ||
* @returns {*} | ||
*/ | ||
OpenApiEnforcer.prototype.serialize = function(schema, value) { | ||
return serialize('', schema, value); | ||
OpenApiEnforcer.prototype.serialize = function(schema, value, options) { | ||
const exception = new Exception('One or more errors occurred during serialization'); | ||
const data = store.get(this); | ||
const version = data.version; | ||
// normalize options | ||
options = Object.assign({}, data.defaults.serialize, options); | ||
// run version specific deserialization | ||
const result = version.serial.serialize(exception, schema, value); | ||
// determine how to handle serialization data | ||
return util.errorHandler(options.throw, exception, result); | ||
}; | ||
/** | ||
* Get an object that will allow a simplified execution context for a recurring schema and options. | ||
* @param {object} schema | ||
* @param {object} [options] | ||
* @returns {{deserialize: (function(*=): (*|{errors: string[], value: *})), errors: (function(*=): string[]), populate: (function(*=, *=, *=): *), random: (function(): *), serialize: (function(*=, *=): {errors, value}), validate: (function(*=): void)}} | ||
*/ | ||
OpenApiEnforcer.prototype.schema = function(schema, options) { | ||
const context = this; | ||
if (!options || typeof options !== 'object') options = {}; | ||
return { | ||
deserialize: (value) => context.deserialize(schema, value, options.deserialize), | ||
errors: (value) => context.errors(schema, value, options.errors), | ||
populate: (params, value) => context.populate(schema, params, value, options.populate), | ||
random: () => context.random(schema), | ||
serialize: (value) => context.serialize(schema, value, options.serialize), | ||
validate: (value) => context.validate(schema, value) | ||
} | ||
}; | ||
/** | ||
* Check a value against a schema for errors and throw any errors encountered. | ||
@@ -378,16 +483,6 @@ * @param {object} schema | ||
// expose an interface for updating defaults | ||
OpenApiEnforcer.defaults = util.copy(staticDefaults); | ||
OpenApiEnforcer.defaults = { | ||
populate: { | ||
copy: false, | ||
defaults: true, | ||
ignoreMissingRequired: true, | ||
oneOf: true, | ||
replacement: 'handlebar', | ||
serialize: false, | ||
templateDefaults: true, | ||
templates: true, | ||
variables: true | ||
} | ||
}; | ||
OpenApiEnforcer.Exception = Exception; | ||
@@ -439,2 +534,15 @@ | ||
function responseFactory(context, path, method, options) { | ||
const responses = path.schema[method].responses; | ||
const produces = path.schema[method].produces; | ||
const data = responseData(context, produces, responses, options); | ||
return { | ||
data: data, | ||
errors: config => responseErrors(context, responses, data, config), | ||
example: config => responseExample(context, responses, data, config), | ||
populate: config => responsePopulate(context, responses, data, config), | ||
serialize: config => responseSerialize(context, responses, data, config) | ||
}; | ||
} | ||
function responseExample(context, responses, data, options) { | ||
@@ -470,11 +578,3 @@ if (data.error) throw Error(data.error); | ||
// populate body | ||
if (data.schema) { | ||
const options = { | ||
schema: data.schema, | ||
params: config.params, | ||
options: config.options | ||
}; | ||
if (config.hasOwnProperty('body')) options.value = config.body; | ||
result.body = context.populate(options); | ||
} | ||
if (data.schema) result.body = context.populate(data.schema, config.params, config.body, config.options); | ||
@@ -496,9 +596,3 @@ // populate headers | ||
if (schema) { | ||
const options = { | ||
schema: schema, | ||
params: config.params, | ||
options: config.options | ||
}; | ||
if (headers.hasOwnProperty(header)) options.value = headers[header]; | ||
const value = context.populate(options); | ||
const value = context.populate(schema, config.params, headers[header], config.options); | ||
if (value !== undefined) headers[header] = value; | ||
@@ -524,3 +618,3 @@ } | ||
if (config.hasOwnProperty('body')) { | ||
result.body = context.serialize(data.schema, config.body); | ||
result.body = context.serialize(data.schema, config.body, config.options); | ||
} | ||
@@ -536,3 +630,3 @@ | ||
let value = schema && schema.schema | ||
? serialize('', schema.schema, headers[name]) | ||
? context.serialize(schema.schema, headers[name], config.options) | ||
: String(headers[name]); | ||
@@ -548,41 +642,1 @@ value = version.serializeResponseHeader(schema, value); | ||
function serialize(prefix, schema, value) { | ||
const type = util.schemaType(schema); | ||
switch (type) { | ||
case 'array': | ||
if (Array.isArray(value)) return value.map((v, i) => serialize(prefix + '/' + i, schema.items || {}, v)); | ||
break; | ||
case 'boolean': | ||
case 'integer': | ||
case 'number': | ||
return format[type](prefix, value); | ||
case 'object': | ||
if (value && typeof value === 'object') { | ||
const result = {}; | ||
const additionalProperties = schema.additionalProperties; | ||
const properties = schema.properties || {}; | ||
Object.keys(value).forEach(key => { | ||
if (properties.hasOwnProperty(key)) { | ||
result[key] = serialize(prefix + '/' + key, properties[key], value[key]); | ||
} else if (additionalProperties) { | ||
result[key] = serialize(prefix + '/' + key, additionalProperties, value[key]); | ||
} | ||
}); | ||
return result; | ||
} | ||
return value; | ||
case 'string': | ||
default: | ||
switch (schema.format) { | ||
case 'binary': | ||
case 'byte': | ||
case 'date': | ||
case 'date-time': | ||
return format[schema.format](prefix, value); | ||
} | ||
return format.string(prefix, value); | ||
} | ||
} |
@@ -18,3 +18,4 @@ /** | ||
'use strict'; | ||
const format = require('./format'); | ||
const parse = require('./parse'); | ||
const traverse = require('./traverse'); | ||
const util = require('./util'); | ||
@@ -28,13 +29,22 @@ | ||
exports.populate = function(v, prefix, schema, object, property) { | ||
exports.populate = function(v, exception, schema, object, property) { | ||
const options = v.options; | ||
const type = util.schemaType(schema); | ||
let value = object[property]; | ||
if (type === 'array') { | ||
if (schema.allOf) { | ||
schema.allOf.forEach((schema, index) => exports.populate(v, exception.nest('/allOf/' + index), schema, object, property)); | ||
} else if (schema.oneOf && options.oneOf && schema.discriminator) { | ||
const discriminator = v.version.getDiscriminatorSchema(schema, value); | ||
if (discriminator) exports.populate(v, exception.nest('/oneOf'), discriminator, object, property); | ||
} else if (type === 'array') { | ||
let value = object[property]; | ||
if (value !== undefined && !Array.isArray(object[property])) { | ||
throw Error(prefix + ': Provided value must be an array. Received: ' + util.smart(value)); | ||
exception.push('Provided value must be an array. Received: ' + util.smart(value)); | ||
return; | ||
} | ||
apply(v, schema, type, object, property); | ||
apply(v, exception, schema, type, object, property); | ||
@@ -44,3 +54,3 @@ value = object[property]; | ||
value.forEach((item, index) => { | ||
exports.populate(v, prefix + '/' + index, schema.items, value, index) | ||
exports.populate(v, exception.nest('/' + index), schema.items, value, index) | ||
}); | ||
@@ -50,60 +60,49 @@ } | ||
} else if (type === 'object') { | ||
const value = object[property]; | ||
if (value !== undefined && (!value || typeof value !== 'object')) { | ||
throw Error(prefix + ': Provided value must be a non-null object. Received: ' + util.smart(value)); | ||
exception.push('Provided value must be a non-null object. Received: ' + util.smart(value)); | ||
return; | ||
} | ||
// if allOf then apply each item | ||
if (schema.allOf) { | ||
schema.allOf.forEach(schema => exports.populate(v, prefix, schema, object, property)); | ||
apply(v, exception, schema, type, object, property); | ||
value = object[property]; | ||
// populate oneOf as described by the discriminator | ||
} else if (options.oneOf && schema.oneOf && schema.discriminator) { | ||
const discriminator = v.version.getDiscriminatorSchema(schema, value); | ||
if (discriminator) exports.populate(v, prefix, discriminator, object, property); | ||
// if not ignoring required then these values may not actually populate | ||
const required = options.ignoreMissingRequired ? null : schema.required || []; | ||
const target = required ? Object.assign({}, value) : value || {}; | ||
} else { | ||
apply(v, schema, type, object, property); | ||
const value = object[property]; | ||
// populate for additional properties | ||
const additionalProperties = schema.additionalProperties; | ||
if (additionalProperties) { | ||
const properties = schema.properties || {}; | ||
Object.keys(target).forEach(key => { | ||
// if not ignoring required then these values may not actually populate | ||
const required = options.ignoreMissingRequired ? null : schema.required || []; | ||
const target = required ? Object.assign({}, value) : value || {}; | ||
// if enforcing required then remove this property from the remaining required list | ||
if (required) { | ||
const index = required.indexOf(key); | ||
if (index !== -1) required.splice(index, 1); | ||
} | ||
// populate for additional properties | ||
const additionalProperties = schema.additionalProperties; | ||
if (additionalProperties) { | ||
const properties = schema.properties || {}; | ||
Object.keys(target).forEach(key => { | ||
// populate the property | ||
if (!properties.hasOwnProperty(key)) { | ||
exports.populate(v, exception.nest('/' + key), additionalProperties, target, key); | ||
} | ||
}); | ||
} | ||
// if enforcing required then remove this property from the remaining required list | ||
if (required) { | ||
const index = required.indexOf(key); | ||
if (index !== -1) required.splice(index, 1); | ||
} | ||
// populate for known properties | ||
const properties = schema.properties; | ||
if (properties) { | ||
Object.keys(properties).forEach(key => { | ||
exports.populate(v, exception.nest('/' + key), properties[key], target, key); | ||
}); | ||
} | ||
// populate the property | ||
if (!properties.hasOwnProperty(key)) { | ||
exports.populate(v, prefix + '/' + key, additionalProperties, target, key); | ||
} | ||
}); | ||
} | ||
// if enforcing required and it was fulfilled then update the object's property with the target | ||
// if not enforcing required then the object's property is already the target | ||
if ((value !== undefined || Object.keys(target).length) && (!required || !required.length)) { | ||
object[property] = target; | ||
} | ||
// populate for known properties | ||
const properties = schema.properties; | ||
if (properties) { | ||
Object.keys(properties).forEach(key => { | ||
exports.populate(v, prefix + '/' + key, properties[key], target, key); | ||
}); | ||
} | ||
// if enforcing required and it was fulfilled then update the object's property with the target | ||
// if not enforcing required then the object's property is already the target | ||
if ((value !== undefined || Object.keys(target).length) && (!required || !required.length)) { | ||
object[property] = target; | ||
} | ||
} | ||
} else { | ||
apply(v, schema, type, object, property); | ||
apply(v, exception, schema, type, object, property); | ||
} | ||
@@ -115,3 +114,3 @@ | ||
function apply(v, schema, type, object, property) { | ||
function apply(v, exception, schema, type, object, property) { | ||
if (object[property] === undefined) { | ||
@@ -122,17 +121,16 @@ const options = v.options; | ||
const value = map[schema['x-variable']]; | ||
if (options.serialize) { | ||
const form = util.schemaFormat(schema); | ||
object[property] = format[form]('', value); | ||
} else { | ||
object[property] = value; | ||
} | ||
if (value !== undefined) object[property] = value; | ||
} else if (options.templates && type === 'string' && schema.hasOwnProperty('x-template')) { | ||
object[property] = v.injector(schema['x-template'], map); | ||
const value = v.injector(schema['x-template'], map); | ||
object[property] = parseStringValue(exception, schema, value); | ||
} else if (options.defaults && schema.hasOwnProperty('default')) { | ||
const value = schema.default; | ||
object[property] = options.templateDefaults && typeof value === 'string' | ||
? v.injector(value, map) | ||
: value; | ||
const defaultValue = schema.default; | ||
if (defaultValue !== undefined) { | ||
const value = options.templateDefaults && typeof defaultValue === 'string' | ||
? v.injector(defaultValue, map) | ||
: defaultValue; | ||
object[property] = parseStringValue(exception, schema, value); | ||
} | ||
} | ||
@@ -155,3 +153,3 @@ } | ||
const property = match[1]; | ||
result += value.substring(offset, match.index) + (data.hasOwnProperty(property) ? data[property] : match[0]); | ||
result += value.substring(offset, match.index) + (data[property] !== undefined ? data[property] : match[0]); | ||
offset = match.index + match[0].length; | ||
@@ -161,2 +159,123 @@ } | ||
}; | ||
} | ||
function parseStringValue(exception, schema, value) { | ||
if (typeof value !== 'string' || util.schemaType(schema) !== 'string') return value; | ||
let result; | ||
switch (util.schemaFormat(schema)) { | ||
case 'byte': | ||
case 'binary': | ||
case 'date': | ||
case 'date-time': | ||
result = parse[schema.format](value); | ||
return result.error | ||
? exception.push(result.error) | ||
: result.value; | ||
default: | ||
return value; | ||
} | ||
} | ||
function populate(version, schema, params, value, options) { | ||
if (!params) params = {}; | ||
// normalize options | ||
options = Object.assign({}, data.defaults.populate, options, version.defaults.populate); | ||
// produce start value | ||
if (options.copy) value = util.copy(value); | ||
// acquire injector | ||
const injector = populate.injector[options.replacement]; | ||
const result = traverse({ | ||
schema: schema, | ||
value: value, | ||
version: version, | ||
handler: data => { | ||
const schema = data.schema; | ||
const type = data.type; | ||
// if there is no value then attempt to populate one | ||
if (data.value === undefined) { | ||
if (options.variables && schema.hasOwnProperty('x-variable') && params.hasOwnProperty(schema['x-variable'])) { | ||
const value = params[schema['x-variable']]; | ||
if (value !== undefined) data.value = value; | ||
} else if (options.templates && type === 'string' && schema.hasOwnProperty('x-template')) { | ||
const value = injector(schema['x-template'], params); | ||
data.value = parseStringValue(exception, schema, value); | ||
} else if (options.defaults && schema.hasOwnProperty('default')) { | ||
const defaultValue = schema.default; | ||
if (defaultValue !== undefined) { | ||
const value = options.templateDefaults && typeof defaultValue === 'string' | ||
? injector(defaultValue, params) | ||
: defaultValue; | ||
data.value = parseStringValue(exception, schema, value); | ||
} | ||
} | ||
} | ||
// if there is still no value and schema is for an object then try to populate its properties | ||
if (type === 'object') { | ||
} | ||
// if still no value, maybe go deeper | ||
if (data.value === undefined) { | ||
switch (data.type) { | ||
case 'anyOf': | ||
case 'oneOf': | ||
schemas = data.type === 'anyOf' ? schema.anyOf : schema.oneOf; | ||
index = Math.floor(Math.random() * schemas.length); | ||
data.schema = schemas[index]; | ||
data.again(); | ||
break; | ||
case 'allOf': | ||
// if (type === 'object') { | ||
// const merged = {}; | ||
// const schemas = schema.allOf; | ||
// const length = schemas.length; | ||
// let hasValue; | ||
// for (let i = 0; i < length; i++) { | ||
// const schema = schemas[i]; | ||
// if (!schema.type || util.schemaType(schema) === 'object') { | ||
// if (schema.properties) | ||
// } else { | ||
// message('Invalid schema type at index: ' + i); | ||
// } | ||
// } | ||
// | ||
// const merged = schema.allOf.reduce((p, c) => { | ||
// Object.assign(p, { properties: }) | ||
// }, {}) | ||
// } else { | ||
// message('Unable to populate "allOf" except for type "object"') | ||
// } | ||
data.value = undefined; // TODO: future functionality | ||
break; | ||
case 'not': | ||
message('Unable to populate "not" except for objects') | ||
break; | ||
default: | ||
} | ||
} | ||
// if still no value then go deeper on arrays or objects | ||
if (data.value === undefined) { | ||
if (type === 'array') { | ||
} else if (type === 'object') { | ||
} | ||
} | ||
} | ||
}); | ||
} |
@@ -18,2 +18,3 @@ /** | ||
'use strict'; | ||
const traverse = require('./traverse'); | ||
const util = require('./util'); | ||
@@ -54,3 +55,3 @@ | ||
// generate random array | ||
exports.array = function(schema) { | ||
exports.array = function(exception, schema) { | ||
return common(schema, () => { | ||
@@ -65,3 +66,3 @@ const config = {}; | ||
while (array.length < length) { | ||
const value = this.byType(itemSchema || randomSchema()); | ||
const value = this.byType(exception, itemSchema || randomSchema()); | ||
if (schema.uniqueItems) { | ||
@@ -71,3 +72,3 @@ const match = array.find(v => util.same(v, value)); | ||
duplicates++; | ||
if (duplicates > 5) throw Error('Cannot generate example due to too narrowly scoped schema constraints: ' + JSON.stringify(schema)); | ||
if (duplicates > 5) exception('Cannot generate example due to too narrowly scoped schema constraints: ' + JSON.stringify(schema)); | ||
} else { | ||
@@ -86,8 +87,8 @@ duplicates = 0; | ||
// generate random buffer | ||
exports.binary = function(schema) { | ||
return exports.byte(schema); | ||
exports.binary = function(exception, schema) { | ||
return exports.byte(exception, schema); | ||
}; | ||
// generate random boolean | ||
exports.boolean = function(schema) { | ||
exports.boolean = function(exception, schema) { | ||
return common(schema, () => { | ||
@@ -99,3 +100,3 @@ return chooseOne([false, true]); | ||
// generate random buffer | ||
exports.byte = function(schema) { | ||
exports.byte = function(exception, schema) { | ||
return common(schema, () => { | ||
@@ -113,18 +114,18 @@ const config = {}; | ||
// generate random value that matches schema type | ||
exports.byType = function(schema) { | ||
exports.byType = function(exception, schema) { | ||
const type = util.schemaType(schema); | ||
switch (type) { | ||
case 'array': return this.array(schema); | ||
case 'boolean': return this.boolean(schema); | ||
case 'integer': return this.integer(schema); | ||
case 'number': return this.number(schema); | ||
case 'object': return this.object(schema); | ||
// case 'array': return this.array(exception, schema); | ||
case 'boolean': return this.boolean(exception, schema); | ||
case 'integer': return this.integer(exception, schema); | ||
case 'number': return this.number(exception, schema); | ||
// case 'object': return this.object(exception, schema); | ||
case 'string': | ||
switch (schema.format) { | ||
case 'binary': return this.binary(schema); | ||
case 'byte': return this.byte(schema); | ||
case 'date': return this.date(schema); | ||
case 'date-time': return this.dateTime(schema); | ||
case 'binary': return this.binary(exception, schema); | ||
case 'byte': return this.byte(exception, schema); | ||
case 'date': return this.date(exception, schema); | ||
case 'date-time': return this.dateTime(exception, schema); | ||
} | ||
return this.string(schema); | ||
return this.string(exception, schema); | ||
} | ||
@@ -134,4 +135,4 @@ }; | ||
// generate random date object | ||
exports.date = function(schema) { | ||
const d = exports.dateTime(schema); | ||
exports.date = function(exception, schema) { | ||
const d = exports.dateTime(exception, schema); | ||
d.setUTCHours(0, 0, 0, 0); | ||
@@ -142,3 +143,3 @@ return d; | ||
// generate random date object | ||
exports.dateTime = function(schema) { | ||
exports.dateTime = function(exception, schema) { | ||
schema = Object.assign({}, schema); | ||
@@ -154,3 +155,3 @@ if (schema.enum) schema.enum = schema.enum.map(v => +(new Date(v))); | ||
// generate random integer | ||
exports.integer = function(schema) { | ||
exports.integer = function(exception, schema) { | ||
return randomNumber(0, 500, true, schema); | ||
@@ -160,3 +161,3 @@ }; | ||
// generate random number | ||
exports.number = function(schema) { | ||
exports.number = function(exception, schema) { | ||
return randomNumber(0, 500, false, schema); | ||
@@ -166,3 +167,3 @@ }; | ||
// generate random object | ||
exports.object = function(schema) { | ||
exports.object = function(exception, schema) { | ||
const result = {}; | ||
@@ -185,3 +186,3 @@ const max = schema.hasOwnProperty('maxProperties') ? schema.maxProperties : Number.MAX_SAFE_INTEGER; | ||
if (requiredKeys.length > max) { | ||
throw Error('Cannot generate example due to too many required properties: ' + JSON.stringify(schema)); | ||
exception('Cannot generate example due to too many required properties'); | ||
} | ||
@@ -191,3 +192,3 @@ | ||
requiredKeys.forEach(key => { | ||
result[key] = this.byType(schema.properties[key] || randomSchema()); | ||
result[key] = this.byType(exception, schema.properties[key] || randomSchema()); | ||
}); | ||
@@ -203,3 +204,3 @@ remaining -= requiredKeys.length; | ||
remaining--; | ||
result[key] = this.byType(schema.properties[key] || randomSchema()); | ||
result[key] = this.byType(exception, schema.properties[key] || randomSchema()); | ||
} | ||
@@ -216,3 +217,3 @@ } | ||
remaining--; | ||
result[key] = this.byType(schema.additionalProperties === true | ||
result[key] = this.byType(exception, schema.additionalProperties === true | ||
? randomSchema() | ||
@@ -227,7 +228,7 @@ : schema.additionalProperties); | ||
exports.string = function(schema) { | ||
exports.string = function(exception, schema) { | ||
return common(schema, () => { | ||
let result = chooseOne(stringChoices); | ||
if (schema.pattern) { | ||
throw Error('Cannot generate example for string due to pattern requirement'); | ||
exception('Cannot generate example for string due to pattern requirement'); | ||
} | ||
@@ -244,5 +245,12 @@ if (schema.hasOwnProperty('minLength')) { | ||
exports.util = { | ||
chooseOne: chooseOne, | ||
randomNumber: randomNumber, | ||
randomSchema: randomSchema, | ||
traverse: randomTraverse | ||
}; | ||
function chooseOne(choices) { | ||
@@ -355,2 +363,128 @@ const index = Math.floor(Math.random() * choices.length); | ||
} | ||
} | ||
function randomTraverse(schema, version, options) { | ||
const random = exports; | ||
return traverse({ | ||
exception: 'Unable to generate random value', | ||
schema: schema, | ||
version: version, | ||
handler: data => { | ||
const schema = data.schema; | ||
let index; | ||
let schemas; | ||
switch (data.modifier) { | ||
case 'anyOf': | ||
case 'oneOf': | ||
schemas = data.modifier === 'anyOf' ? schema.anyOf : schema.oneOf; | ||
index = Math.floor(Math.random() * schemas.length); | ||
data.schema = schemas[index]; | ||
data.again(); | ||
return; | ||
case 'allOf': | ||
if (!options.skipInvalid) { | ||
data.exception('Random value generator does not work for "allOf" directive'); | ||
} | ||
return; | ||
case 'not': | ||
data.data.hasInvalid = true; | ||
data.exception('Random value generator does not work for "not" directive'); | ||
return; | ||
} | ||
if (data.type === 'array') { | ||
data.stop(); // I'll do manual traversal | ||
const config = {}; | ||
config.minimum = schema.hasOwnProperty('minItems') ? schema.minItems : 0; | ||
config.maximum = schema.hasOwnProperty('maxItems') ? schema.maxItems : config.minimum + 5; | ||
const length = random.util.randomNumber(0, 0, true, config); | ||
const array = []; | ||
const itemSchema = schema.items; | ||
let duplicates = 0; | ||
let arrayLength; | ||
while ((arrayLength = array.length) < length) { | ||
const itemParams = data.traverse(data.exception.nest(arrayLength), itemSchema || random.util.randomSchema()); | ||
const value = itemParams.value; | ||
if (schema.uniqueItems) { | ||
const match = array.find(v => util.same(v, value)); | ||
if (match) { | ||
duplicates++; | ||
if (duplicates > 5) { | ||
if (!options.skipInvalid) { | ||
data.exception('Cannot generate example due to too narrowly scoped schema constraints'); | ||
} | ||
break; | ||
} | ||
} else { | ||
duplicates = 0; | ||
array.push(value); | ||
} | ||
} else { | ||
array.push(value); | ||
} | ||
} | ||
data.value = array; | ||
} else if (data.type === 'object') { | ||
const result = {}; | ||
const max = schema.hasOwnProperty('maxProperties') ? schema.maxProperties : Number.MAX_SAFE_INTEGER; | ||
let remaining = max; | ||
if (schema.properties) { | ||
// separate properties by optional or required | ||
const optionalKeys = []; | ||
const requiredKeys = []; | ||
Object.keys(schema.properties).forEach(key => { | ||
schema.properties[key].required ? requiredKeys.push(key) : optionalKeys.push(key); | ||
}); | ||
// check for too many required properties | ||
if (requiredKeys.length > max) { | ||
data.exception('Cannot generate example due to too many required properties'); | ||
} | ||
// populate required properties | ||
requiredKeys.forEach(key => { | ||
result[key] = exports.byType(data.exception, schema.properties[key] || randomSchema()); | ||
}); | ||
remaining -= requiredKeys.length; | ||
// populate optional properties until max properties reached | ||
let length = optionalKeys.length; | ||
while (remaining && length) { | ||
const index = Math.floor(Math.random() * length); | ||
const key = optionalKeys.splice(index, 1)[0]; | ||
length--; | ||
remaining--; | ||
result[key] = exports.byType(data.exception, schema.properties[key] || randomSchema()); | ||
} | ||
} | ||
if (schema.additionalProperties && remaining) { | ||
let count = 0; | ||
let index = 1; | ||
while (count < 3 && remaining) { | ||
const key = 'additionalProperty' + index++; | ||
if (!result.hasOwnProperty(key)) { | ||
count++; | ||
remaining--; | ||
result[key] = exports.byType(data.exception, schema.additionalProperties === true | ||
? randomSchema() | ||
: schema.additionalProperties); | ||
} | ||
} | ||
} | ||
data.value = result; | ||
} else { | ||
data.value = random.byType(data.exception, schema); | ||
} | ||
} | ||
}); | ||
} |
@@ -18,4 +18,6 @@ /** | ||
'use strict'; | ||
const rxMediaType = /^([\s\S]+?)\/([\s\S]+?)(?:\+([\s\S]+?))?$/; | ||
const Exception = require('./exception'); | ||
const rxMediaType = /^([\s\S]+?)\/(?:([\s\S]+?)\+)?([\s\S]+?)$/; | ||
exports.arrayPushMany = function(target, source) { | ||
@@ -62,4 +64,21 @@ source.forEach(item => target.push(item)); | ||
exports.errorHandler = function(useThrow, exception, value) { | ||
const hasErrors = Exception.hasException(exception); | ||
if (hasErrors && useThrow) { | ||
const err = Error(exception); | ||
err.code = 'OPEN_API_EXCEPTION'; | ||
Object.assign(err, exception.meta); | ||
throw err; | ||
} else if (useThrow) { | ||
return value; | ||
} else { | ||
return { | ||
error: hasErrors ? exception : null, | ||
value: hasErrors ? null : value | ||
}; | ||
} | ||
}; | ||
/** | ||
* Provide an accept media type string and possible matches and get the match. | ||
* Provide an accept media / mime type string and possible matches and get the match. | ||
* @param {string} input | ||
@@ -78,6 +97,6 @@ * @param {string[]} store | ||
return { | ||
extension: match[3] || '*', | ||
extension: match[2] || '*', | ||
index: index, | ||
quality: +((q && q[1]) || 1), | ||
subType: match[2], | ||
subType: match[3], | ||
type: match[1] | ||
@@ -95,4 +114,4 @@ } | ||
const type = match[1]; | ||
const subType = match[2]; | ||
const extension = match[3] || '*'; | ||
const subType = match[3]; | ||
const extension = match[2] || '*'; | ||
const typeMatch = ((accept.type === type || accept.type === '*' || type === '*') && | ||
@@ -158,3 +177,3 @@ (accept.subType === subType || accept.subType === '*' || subType === '*') && | ||
exports.queryParams = function (name, value) { | ||
exports.queryParamsByName = function (name, value) { | ||
const rx = RegExp('(?:^|&)' + name + '=([^&]*)', 'g'); | ||
@@ -167,2 +186,19 @@ const results = []; | ||
exports.queryParamNames = function(value, objValue) { | ||
const retObject = arguments.length >= 2; | ||
const names = {}; | ||
const boolean = !!objValue; | ||
value.split('&').forEach(pair => { | ||
const kv = pair.split('='); | ||
const name = kv[0]; | ||
if (name) names[name] = boolean; | ||
}); | ||
return retObject ? names : Object.keys(names); | ||
}; | ||
exports.randomNumber = function(integer, min, max) { | ||
if (!min) min = Math.random(); | ||
if (!max) max = Math.random(); | ||
}; | ||
/** | ||
@@ -224,6 +260,5 @@ * Do a deep equal on two values. | ||
exports.schemaType = function(schema) { | ||
if (schema.type) return schema.type; | ||
if (schema.type) return schema.type; // TODO: remove this function - validator will require all types be specified | ||
if (schema.items) return 'array'; | ||
if (schema.properties || schema.additionalProperties || schema.allOf || schema.anyOf || schema.oneOf) return 'object'; | ||
return 'string'; | ||
if (schema.properties || schema.additionalProperties) return 'object'; | ||
}; | ||
@@ -265,3 +300,3 @@ | ||
exports.smart = function(value) { | ||
if (typeof value === 'string') return "'" + value.replace(/'/g, "\\'") + "'"; | ||
if (typeof value === 'string') return '"' + value.replace(/"/g, '\\"') + '"'; | ||
if (value instanceof Date) return value.toISOString(); | ||
@@ -268,0 +303,0 @@ return String(value); |
@@ -18,2 +18,3 @@ /** | ||
'use strict'; | ||
const Exception = require('../exception'); | ||
const random = require('../random'); | ||
@@ -120,2 +121,3 @@ const serial = require('./serialize'); | ||
* @param {object} schema The path schema object. | ||
* @param {OpenAPIException} exception | ||
* @param {object} req | ||
@@ -128,6 +130,5 @@ * @param {object|string} req.body | ||
* @param {string} req.query | ||
* @returns {{ errors: Array<string>|null, value: null|{ body: string|object, cookie: object, header: object, path: object, query: object }}} | ||
* @returns {{ exception: OpenAPIException|null, value: null|{ body: string|object, cookie: object, header: object, path: object, query: object }}} | ||
*/ | ||
Version.prototype.parseRequestParameters = function(schema, req) { | ||
const errors = []; | ||
Version.prototype.parseRequestParameters = function(schema, exception, req) { | ||
const mSchema = schema[req.method]; | ||
@@ -164,6 +165,9 @@ const result = { | ||
const deserializeValidate = (schema, value, at, name) => { | ||
let data = this.enforcer.deserialize(schema, value); | ||
if (!data.errors) data.errors = this.enforcer.errors(schema, data.value); | ||
if (data.errors) errors.push('Invalid value for ' + at + ' parameter "' + name + '":\n\t' + data.errors.join('\n\t')); | ||
return data.value; | ||
const deserializeException = exception.nest('Invalid value for ' + at + ' parameter "' + name + '"'); | ||
const result = serial.deserialize(deserializeException, schema, value); | ||
if (!Exception.hasException(deserializeException)) { | ||
const errors = this.enforcer.errors(schema, result); | ||
if (errors) errors.forEach(error => deserializeException.push(error)) | ||
} | ||
return result; | ||
}; | ||
@@ -174,17 +178,18 @@ | ||
if (paramMap.body.required && req.body === undefined) { | ||
errors.push('Missing required body parameter "' + paramMap.body.name + '"'); | ||
exception.push('Missing required body parameter "' + paramMap.body.name + '"'); | ||
} else if (paramMap.body.schema && req.body !== undefined) { | ||
const schema = paramMap.body.schema; | ||
const typed = this.enforcer.deserialize(schema, req.body); | ||
if (typed.errors) { | ||
errors.push('Invalid request body:\n\t' + typed.errors.join('\n\t')); | ||
} else { | ||
const validationErrors = this.enforcer.errors(schema, typed.value); | ||
if (validationErrors) { | ||
errors.push('Invalid request body":\n\t' + validationErrors.join('\n\t')); | ||
const paramException = exception.nest('Invalid request body'); | ||
const value = serial.deserialize(paramException, schema, req.body); | ||
if (!Exception.hasException(paramException)) { | ||
const errors = this.enforcer.errors(schema, value); | ||
if (errors) { | ||
errors.forEach(error => paramException.push(error)); | ||
} else { | ||
result.body = typed.value; | ||
result.body = value; | ||
} | ||
} | ||
} else if (req.body !== undefined) { | ||
result.body = req.body; | ||
} | ||
@@ -221,3 +226,3 @@ } | ||
} else if (definition.required) { | ||
errors.push('Missing required ' + at + ' parameter "' + name + '"'); | ||
exception.push('Missing required ' + at + ' parameter "' + name + '"'); | ||
} | ||
@@ -232,3 +237,3 @@ }); | ||
const definition = paramMap.query[name]; | ||
const values = util.queryParams(name, req.query); | ||
const values = util.queryParamsByName(name, req.query); | ||
const store = result.query; | ||
@@ -250,9 +255,16 @@ | ||
} else if (definition.required) { | ||
errors.push('Missing required query parameter "' + name + '"'); | ||
exception.push('Missing required query parameter "' + name + '"'); | ||
} | ||
}); | ||
const hasErrors = errors.length; | ||
// look for any query parameters that are not allowed | ||
util.queryParamNames(req.query).forEach(name => { | ||
if (!paramMap.query.hasOwnProperty(name)) { | ||
exception.push('Unexpected query parameter "' + name + '" not permitted'); | ||
} | ||
}); | ||
const hasErrors = Exception.hasException(exception); | ||
return { | ||
errors: hasErrors ? errors : null, | ||
exception: hasErrors ? exception : null, | ||
value: hasErrors ? null : result | ||
@@ -297,2 +309,15 @@ }; | ||
populate: { | ||
oneOf: false | ||
}, | ||
traverse: { | ||
allOf: true, | ||
anyOf: false, | ||
array: true, | ||
oneOf: false, | ||
not: false, | ||
object: true | ||
}, | ||
validate: { | ||
@@ -299,0 +324,0 @@ depth: Number.MAX_VALUE, // validate to full depth |
@@ -18,2 +18,3 @@ /** | ||
'use strict'; | ||
const format = require('../format'); | ||
const parse = require('../parse'); | ||
@@ -23,14 +24,13 @@ const util = require('../util'); | ||
exports.deserialize = deserialize; | ||
exports.serialize = function(schema, value) { | ||
exports.serialize = serialize; | ||
}; | ||
function deserialize(errors, prefix, schema, value) { | ||
function deserialize(exception, schema, value) { | ||
if (schema.allOf) { | ||
const result = {}; | ||
schema.allOf.forEach((schema, index) => { | ||
const v = deserialize(errors, prefix + '/allOf/' + index, schema, value); | ||
const v = deserialize(exception.nest('/allOf/' + index), schema, value); | ||
Object.assign(result, v) | ||
}); | ||
return result; | ||
} else { | ||
@@ -42,6 +42,6 @@ const type = util.schemaType(schema); | ||
if (Array.isArray(value)) return schema.items | ||
? value.map((v,i) => deserialize(errors, prefix + '/' + i, schema.items, v)) | ||
? value.map((v,i) => deserialize(exception.nest('/' + i), schema.items, v)) | ||
: value; | ||
errors.push(prefix + ' Expected an array. Received: ' + value); | ||
break; | ||
exception.push('Expected an array. Received: ' + util.smart(value)); | ||
return; | ||
@@ -52,3 +52,3 @@ case 'boolean': | ||
result = parse[type](value); | ||
if (result.error) errors.push(prefix + ' ' + result.error); | ||
if (result.error) exception.push(result.error); | ||
return result.value; | ||
@@ -67,3 +67,3 @@ | ||
} | ||
if (result.error) errors.push(prefix + ' ' + result.error); | ||
if (result.error) exception.push(result.error); | ||
return result.value; | ||
@@ -78,5 +78,5 @@ | ||
if (properties.hasOwnProperty(key)) { | ||
result[key] = deserialize(errors, prefix + '/' + key, properties[key], value[key]); | ||
result[key] = deserialize(exception.nest('/' + key), properties[key], value[key]); | ||
} else if (additionalProperties) { | ||
result[key] = deserialize(errors, prefix + '/' + key, additionalProperties, value[key]); | ||
result[key] = deserialize(exception.nest('/' + key), additionalProperties, value[key]); | ||
} | ||
@@ -86,10 +86,73 @@ }); | ||
} | ||
errors.push(prefix + ' Expected an object. Received: ' + value); | ||
exception.push('Expected an object. Received: ' + util.smart(value)); | ||
return; | ||
default: | ||
errors.push(prefix + ' Unknown schema type'); | ||
exception.push('Unknown schema type'); | ||
return; | ||
} | ||
} | ||
} | ||
function serialize(exception, schema, value) { | ||
if (schema.allOf) { | ||
const result = {}; | ||
schema.allOf.forEach((schema, index) => { | ||
const v = serialize(exception.nest('/allOf/' + index), schema, value); | ||
Object.assign(result, v) | ||
}); | ||
return result; | ||
} else { | ||
const type = util.schemaType(schema); | ||
let result; | ||
switch (type) { | ||
case 'array': | ||
if (Array.isArray(value)) { | ||
return schema.items | ||
? value.map((v, i) => serialize(exception.nest('/' + i), schema.items || {}, v)) | ||
: value; | ||
} | ||
exception.push('Expected an array. Received: ' + util.smart(value)); | ||
return; | ||
case 'boolean': | ||
case 'integer': | ||
case 'number': | ||
result = format[type](value); | ||
if (result.error) exception.push(result.error); | ||
return result.value; | ||
case 'string': | ||
default: | ||
switch (schema.format) { | ||
case 'binary': | ||
case 'byte': | ||
case 'date': | ||
case 'date-time': | ||
result = format[schema.format](value); | ||
break; | ||
default: | ||
result = format.string(value); | ||
} | ||
if (result.error) exception.push(result.error); | ||
return result.value; | ||
case 'object': | ||
if (value && typeof value === 'object') { | ||
const result = {}; | ||
const additionalProperties = schema.additionalProperties; | ||
const properties = schema.properties || {}; | ||
Object.keys(value).forEach(key => { | ||
if (properties.hasOwnProperty(key)) { | ||
result[key] = serialize(exception.nest('/' + key), properties[key], value[key]); | ||
} else if (additionalProperties) { | ||
result[key] = serialize(exception.nest('/' + key), additionalProperties, value[key]); | ||
} | ||
}); | ||
return result; | ||
} | ||
return value; | ||
} | ||
} | ||
} |
@@ -18,2 +18,3 @@ /** | ||
'use strict'; | ||
const Exception = require('../exception'); | ||
const params = require('./param-style'); | ||
@@ -160,2 +161,3 @@ const Random = require('../random'); | ||
* @param {object} schema The path schema object. | ||
* @param {OpenAPIException} exception | ||
* @param {object} req | ||
@@ -168,6 +170,5 @@ * @param {object|string} req.body | ||
* @param {string} req.query | ||
* @returns {{ error: Array<string>|null, value: null|{ body: string|object, cookie: object, header: object, path: object, query: object, responses: object }}} | ||
* @returns {{ exception: OpenAPIException|null, value: null|{ body: string|object, cookie: object, header: object, path: object, query: object, responses: object }}} | ||
*/ | ||
Version.prototype.parseRequestParameters = function(schema, req) { | ||
const errors = []; | ||
Version.prototype.parseRequestParameters = function(schema, exception, req) { | ||
const mSchema = schema[req.method]; | ||
@@ -207,11 +208,11 @@ const paramTypes = ['cookie', 'header', 'path', 'query']; | ||
const schema = content[key].schema; | ||
const typed = this.enforcer.deserialize(schema, req.body); | ||
if (typed.errors) { | ||
errors.push('Invalid request body:\n\t' + typed.errors.join('\n\t')); | ||
} else { | ||
const validationErrors = this.enforcer.errors(schema, typed.value); | ||
if (validationErrors) { | ||
errors.push('Invalid request body":\n\t' + validationErrors.join('\n\t')); | ||
const bodyException = exception.nest('Invalid request body'); | ||
const value = serial.deserialize(exception, schema, req.body); | ||
if (!Exception.hasException(bodyException)) { | ||
const errors = this.enforcer.errors(schema, value); | ||
if (errors) { | ||
errors.forEach(error => bodyException.push(error)); | ||
} else { | ||
result.body = typed.value; | ||
result.body = value; | ||
} | ||
@@ -222,6 +223,9 @@ } | ||
} else if (mSchema.requestBody.required && req.body === undefined) { | ||
errors.push('Missing required request body'); | ||
exception.push('Missing required request body'); | ||
} | ||
} | ||
// look for any query parameters that are not allowed | ||
const definedQueryParams = util.queryParamNames(req.query, false); | ||
// parse and validate cookie, headers, path, and query | ||
@@ -239,3 +243,6 @@ paramTypes.forEach(paramType => { | ||
const at = definition.in; | ||
if (values.hasOwnProperty(name) || at === 'query') { | ||
const inQuery = at === 'query'; | ||
if (definedQueryParams.hasOwnProperty(name)) definedQueryParams[name] = true; | ||
if (values.hasOwnProperty(name) || inQuery) { | ||
const at = definition.in; | ||
@@ -252,5 +259,9 @@ const style = definition.style || defaultStyle(at); | ||
// throw Error because it's a problem with the swagger | ||
if (at !== 'query') throw Error('The deepObject style only works with query parameters. Error at ' + at + ' parameter "' + name + '"'); | ||
if (!inQuery) throw Error('The deepObject style only works with query parameters. Error at ' + at + ' parameter "' + name + '"'); | ||
if (type !== 'object') throw Error('The deepObject style only works with objects but the parameter schema type is ' + type); | ||
parsed = params.deepObject(name, req.query); | ||
parsed = params.deepObject(name, req.query, definedQueryParams); | ||
if (parsed.value) { | ||
Object.keys(parsed.value) | ||
.forEach(key => definedQueryParams[name + '[' + key + ']'] = true); | ||
} | ||
break; | ||
@@ -260,5 +271,5 @@ | ||
// throw Error because it's a problem with the swagger | ||
if (at !== 'cookie' && at !== 'query') throw Error('The form style only works with cookie and query parameters. Error at ' + at + ' parameter "' + name + '"'); | ||
if (at === 'query') { | ||
const results = util.queryParams(name, req.query); | ||
if (at !== 'cookie' && !inQuery) throw Error('The form style only works with cookie and query parameters. Error at ' + at + ' parameter "' + name + '"'); | ||
if (inQuery) { | ||
const results = util.queryParamsByName(name, req.query); | ||
if (!results) return; | ||
@@ -279,3 +290,3 @@ if (type === 'array') { | ||
} | ||
parsed = params.form(type, explode, name, value); | ||
parsed = params.form(type, explode, name, value, inQuery && definedQueryParams); | ||
break; | ||
@@ -297,6 +308,6 @@ | ||
// throw Error because it's a problem with the swagger | ||
if (at !== 'query' || (type !== 'object' && type !== 'array')) throw Error('The pipeDelimited style only works with query parameters for the schema type array or object. Error at ' + at + ' parameter "' + name + '"'); | ||
queryValue = util.queryParams(name, req.query); | ||
if (!inQuery || (type !== 'object' && type !== 'array')) throw Error('The pipeDelimited style only works with query parameters for the schema type array or object. Error at ' + at + ' parameter "' + name + '"'); | ||
queryValue = util.queryParamsByName(name, req.query); | ||
if (!queryValue) return; | ||
parsed = params.pipeDelimited(type, queryValue.pop()); | ||
parsed = params.pipeDelimited(type, queryValue.pop(), inQuery && definedQueryParams); | ||
break; | ||
@@ -312,6 +323,6 @@ | ||
// throw Error because it's a problem with the swagger | ||
if (at !== 'query' || (type !== 'object' && type !== 'array')) throw Error('The spaceDelimited style only works with query parameters for the schema type array or object. Error at ' + at + ' parameter "' + name + '"'); | ||
queryValue = util.queryParams(name, req.query); | ||
if (!inQuery || (type !== 'object' && type !== 'array')) throw Error('The spaceDelimited style only works with query parameters for the schema type array or object. Error at ' + at + ' parameter "' + name + '"'); | ||
queryValue = util.queryParamsByName(name, req.query); | ||
if (!queryValue) return; | ||
parsed = params.spaceDelimited(type, queryValue.pop()); | ||
parsed = params.spaceDelimited(type, queryValue.pop(), inQuery && definedQueryParams); | ||
break; | ||
@@ -325,16 +336,15 @@ | ||
if (parsed.match) { | ||
const typed = this.enforcer.deserialize(schema, parsed.value); | ||
if (typed.errors) { | ||
errors.push('Invalid type for ' + at + ' parameter "' + name + '":\n\t' + typed.errors.join('\n\t')); | ||
} else { | ||
const validationErrors = this.enforcer.errors(schema, typed.value); | ||
if (validationErrors) { | ||
errors.push('Invalid value for ' + at + ' parameter "' + name + '":\n\t' + validationErrors.join('\n\t')); | ||
} | ||
const paramException = exception.nest('Invalid type for ' + at + ' parameter "' + name + '"'); | ||
const value = serial.deserialize(paramException, schema, parsed.value); | ||
if (!Exception.hasException(paramException)) { | ||
const errors = this.enforcer.errors(schema, value); | ||
if (errors) errors.forEach(error => paramException.push(error)); | ||
} | ||
// store typed value | ||
result[paramType][name] = typed.value; | ||
// store deserialized value | ||
result[paramType][name] = value; | ||
} else { | ||
errors.push('Expected ' + at + ' parameter "' + name + '" to be formatted in ' + | ||
exception.push('Expected ' + at + ' parameter "' + name + '" to be formatted in ' + | ||
(explode ? 'exploded ' : '') + style + ' style'); | ||
@@ -345,3 +355,3 @@ } | ||
} else if (definition.required) { | ||
errors.push('Missing required ' + at + ' parameter "' + name + '"'); | ||
exception.push('Missing required ' + at + ' parameter "' + name + '"'); | ||
} | ||
@@ -351,6 +361,14 @@ }); | ||
// look for any query parameters that are not allowed | ||
Object.keys(definedQueryParams) | ||
.forEach(name => { | ||
if (!definedQueryParams[name]) { | ||
exception.push('Unexpected query parameter "' + name + '" not permitted'); | ||
} | ||
}); | ||
// check for errors | ||
const hasErrors = errors.length; | ||
const hasErrors = Exception.hasException(exception); | ||
return { | ||
errors: hasErrors ? errors : null, | ||
exception: hasErrors ? exception : null, | ||
value: hasErrors ? null : result | ||
@@ -393,2 +411,15 @@ } | ||
populate: { | ||
oneOf: true | ||
}, | ||
traverse: { | ||
allOf: true, | ||
anyOf: true, | ||
array: true, | ||
oneOf: true, | ||
not: true, | ||
object: true | ||
}, | ||
validate: { | ||
@@ -395,0 +426,0 @@ depth: Number.MAX_VALUE, // validate to full depth |
@@ -116,3 +116,3 @@ /** | ||
exports.deepObject = function(name, value) { | ||
const rx = RegExp('(?:^|&)' + name + '\\[([^\\]]+)\\]=([^&]*)', 'g'); | ||
const rx = RegExp('(?:^|&)' + name + '\\[([^\\]]+)\\](?:=([^&]*))?', 'g'); | ||
const result = {}; | ||
@@ -119,0 +119,0 @@ let match; |
@@ -18,12 +18,20 @@ /** | ||
'use strict'; | ||
const parse = require('../parse'); | ||
const serial = require('../v2/serialize'); | ||
const util = require('../util'); | ||
exports.deserialize = deserialize; | ||
exports.serialize = function(schema, value) { | ||
exports.serialize = serialize; | ||
}; | ||
// TODO: add oneOf and anyOf support | ||
function deserialize(exception, schema, value) { | ||
if (schema.oneOf) { | ||
function deserialize(errors, prefix, schema, value) { | ||
} else if (schema.anyOf) { | ||
} else { | ||
return serial.deserialize(exception, schema, value); | ||
} | ||
} | ||
// TODO: add oneOf and anyOf support | ||
function serialize(exception, schema, value) { | ||
if (schema.oneOf) { | ||
@@ -34,4 +42,4 @@ | ||
} else { | ||
return serial.deserialize(errors, prefix, schema, value); | ||
return serial.serialize(exception, schema, value); | ||
} | ||
} |
@@ -122,3 +122,10 @@ /** | ||
maxMin(v, prefix, schema, 'binary length', 'maxLength', 'minLength', true, value.length * 8, schema.maxLength, schema.minLength); | ||
if (v.options.enum && schema.enum) _enum(v, prefix, schema, format.binary('', value)); | ||
if (v.options.enum && schema.enum) { | ||
const d = format.binary(value); | ||
if (d.error) { | ||
v.error(prefix, d.error) | ||
} else { | ||
_enum(v, prefix, schema, d.value); | ||
} | ||
} | ||
} | ||
@@ -142,3 +149,10 @@ }; | ||
maxMin(v, prefix, schema, 'byte length', 'maxLength', 'minLength', true, value.length, schema.maxLength, schema.minLength); | ||
if (v.options.enum && schema.enum) _enum(v, prefix, schema, format.byte('', value)); | ||
if (v.options.enum && schema.enum) { | ||
const d = format.byte(value); | ||
if (d.error) { | ||
v.error(prefix, d.error); | ||
} else { | ||
_enum(v, prefix, schema, d.value); | ||
} | ||
} | ||
} | ||
@@ -153,3 +167,10 @@ }; | ||
maxMin(v, prefix, schema, 'date', 'maximum', 'minimum', false, value, new Date(schema.maximum), new Date(schema.minimum)); | ||
if (v.options.enum && schema.enum) _enum(v, prefix, schema, format.date('', value)); | ||
if (v.options.enum && schema.enum) { | ||
const d = format.date(value); | ||
if (d.error) { | ||
v.error(prefix, d.error); | ||
} else { | ||
_enum(v, prefix, schema, d.value); | ||
} | ||
} | ||
} | ||
@@ -164,3 +185,10 @@ }; | ||
maxMin(v, prefix, schema, 'date-time', 'maximum', 'minimum', false, value, new Date(schema.maximum), new Date(schema.minimum)); | ||
if (v.options.enum && schema.enum) _enum(v, prefix, schema, format.dateTime('', value)); | ||
if (v.options.enum && schema.enum) { | ||
const d = format.dateTime(value); | ||
if (d.error) { | ||
v.error(prefix, d.error); | ||
} else { | ||
_enum(v, prefix, schema, d.value); | ||
} | ||
} | ||
} | ||
@@ -297,3 +325,3 @@ }; | ||
} | ||
if (!found) v.error(prefix, 'Value (' + smart(value) + ') did not meet enum requirements'); | ||
if (!found) v.error(prefix, 'Value ' + smart(value) + ' did not meet enum requirements'); | ||
} | ||
@@ -300,0 +328,0 @@ } |
@@ -0,26 +1,74 @@ | ||
export as namespace OpenApiEnforcer; | ||
interface OpenApiEnforcer { | ||
(definition: object, defaultOptions?: OpenApiEnforcerOptions): OpenApiEnforcerInstance | ||
} | ||
export = OpenApiEnforcer; | ||
interface OpenApiEnforcerInstance { | ||
deserialize(schema: object, value: any): any; | ||
errors(schema: object, value: any): string[]|void; | ||
serialize(schema: object, value: any): any; | ||
path(path: string): { path: string, params: object }; | ||
populate(config: { options?: object, params?: object, schema: object, value?: any }): any; | ||
request(req: string|RequestObject): object; | ||
schema(path: string, schema?: object); | ||
validate(schema: string, value: any):void; | ||
} | ||
declare function OpenApiEnforcer(definition: object, options?: OpenApiEnforcer.EnforcerOptions): OpenApiEnforcer.Instance; | ||
declare function OpenApiEnforcer(version: string, options?: OpenApiEnforcer.EnforcerOptions): OpenApiEnforcer.Instance; | ||
interface OpenApiEnforcerOptions { | ||
declare namespace OpenApiEnforcer { | ||
} | ||
export interface DeserializeOptions { | ||
throw?: boolean; | ||
} | ||
interface RequestObject { | ||
cookies?: object; | ||
headers?: object; | ||
method?: string; | ||
path?: string | ||
export interface EnforcerOptions { | ||
deserialize?: DeserializeOptions; | ||
errors: ErrorsOptions; | ||
populate?: PopulateOptions; | ||
request?: RequestOptions; | ||
serialize?: SerializeOptions; | ||
} | ||
export interface ErrorsOptions { | ||
prefix?: string; | ||
} | ||
export interface PopulateOptions { | ||
copy?: boolean; | ||
defaults?: boolean; | ||
ignoreMissingRequired?: boolean; | ||
replacement?: string; | ||
templateDefaults?: boolean; | ||
templates?: boolean; | ||
throw?: boolean; | ||
variables?: boolean; | ||
} | ||
export interface RequestOptions { | ||
throw?: boolean; | ||
} | ||
export interface RequestObject { | ||
body?: string|object; | ||
cookies?: object; | ||
headers?: object; | ||
method?: string; | ||
path?: string; | ||
} | ||
export interface ResponseObject { | ||
code: string; | ||
contentType: string; | ||
path: string; | ||
method?: string; | ||
} | ||
export interface SerializeOptions { | ||
throw?: boolean; | ||
} | ||
export interface Instance { | ||
deserialize(schema: object, value: any): any; | ||
errors(schema: object, value: any): string[]|void; | ||
path(path: string): { path: string, params: object }; | ||
random(schema: object, options?: object):any; | ||
populate(schema: object, params?: object, params?: object, options?: PopulateOptions): any; | ||
request(req: string|RequestObject): object; | ||
response(req: string|ResponseObject) | ||
schema(path: string, schema?: object); | ||
serialize(schema: object, value: any): any; | ||
validate(schema: string, value: any):void; | ||
version():string; | ||
} | ||
} |
{ | ||
"name": "openapi-enforcer", | ||
"version": "0.9.5", | ||
"version": "0.10.0", | ||
"description": "Library for validating, parsing, and formatting data against open api schemas.", | ||
@@ -9,6 +9,8 @@ "main": "index.js", | ||
}, | ||
"types": "index.d.ts", | ||
"scripts": { | ||
"test": "mocha test/*.js", | ||
"coverage": "nyc --reporter=html mocha test/*.js", | ||
"coverage:report": "nyc mocha test/*.js && nyc report --reporter=text-lcov | coveralls" | ||
"test": "mocha --recursive test", | ||
"coverage": "nyc --reporter=html npm test", | ||
"coverage:report": "nyc npm test && nyc report --reporter=text-lcov | coveralls", | ||
"coverage:watch": "chokidar 'test/**/*' 'bin/**/*' -c 'npm run coverage'" | ||
}, | ||
@@ -38,2 +40,3 @@ "repository": { | ||
"chai": "^3.5.0", | ||
"chokidar-cli": "^1.2.0", | ||
"connect-busboy": "0.0.2", | ||
@@ -40,0 +43,0 @@ "connect-multiparty": "^2.1.0", |
475
README.md
# OpenAPI-Enforcer | ||
**Supports OpenAPI 2.0 (formerly Swagger) and OpenAPI 3.0.0** | ||
**Supports OpenAPI 2.0 (formerly Swagger) and OpenAPI 3.x** | ||
Features | ||
- Connect middleware* | ||
- Request parsing and validating* | ||
- Response building, formatting, and validating* | ||
- Schema validation | ||
- Validate a value against a schema | ||
- Determine the schema for a provided path (allows path parameters) | ||
- Serialization and deserialization for interprocess or network communication | ||
- Request parsing and validating | ||
- Response building, serializing, and validating | ||
- Generate random valid values from a schema | ||
\* *Some features coming soon* | ||
**THIS IS A WORK IN PROGRESS - SUBJECT TO CHANGE** | ||
# Table of Contents | ||
- [Constructor](#constructor) | ||
- Prototype Methods | ||
- [Enforcer.prototype.deserialize](#enforcerprototypedeserialize) | ||
@@ -31,6 +28,6 @@ - [Enforcer.prototype.errors](#enforcerprototypeerrors) | ||
- [Enforcer.prototype.validate](#enforcerprototypevalidate) | ||
- Static Methods | ||
- [Enforcer.format](#enforcerformat) | ||
- [Enforcer.is](#enforceris) | ||
- [Enforcer.parse](#enforcerparse) | ||
- [Appendix](#appendix) | ||
- [About default, x-template, and x-variable](#about-default-x-template-and-x-variable) | ||
- [Error Throwing vs Reporting](#error-throwing-vs-reporting) | ||
- [Parameter Replacement](#parameter-replacement) | ||
@@ -40,27 +37,27 @@ # Example | ||
```js | ||
const RefParser = require('json-schema-ref-parser'); | ||
const Enforcer = require('openapi-enforcer'); | ||
const Enforcer = require('openapi-enforcer') | ||
// load, parse, dereference openapi document | ||
RefParser.dereference('/path/to/schema/file.json') // path can also be yaml | ||
.then(function(schema) { | ||
// define enforcer instance | ||
const enforcer = new Enforcer('3.0.0') | ||
// create an enforcer instance | ||
const enforcer = Enforcer(schema); | ||
// define the user schema | ||
const userSchema = { | ||
type: 'object', | ||
properties: { | ||
name: { | ||
type: 'string', | ||
minLength: 1 | ||
}, | ||
birthday: { | ||
type: 'string', | ||
format: 'date', | ||
} | ||
} | ||
} | ||
// get the schema that defines a user | ||
const userSchema = schema.components.schemas.user; | ||
// create a user object by using the user schema and variable mapping | ||
const user = enforcer.populate(userSchema, { | ||
name: 'Bob Smith', | ||
birthday: new Date('2000-01-01') | ||
}); | ||
// check the user object for any schema errors | ||
// (FYI - it wont have errors because it was just populated from the schema) | ||
const errors = enforcer.errors(userSchema, user); | ||
// continue processing | ||
}); | ||
// check a value for any schema errors | ||
const errors = enforcer.errors(userSchema, { | ||
name: 'Bob', | ||
birthday: new Date('2000-01-01') | ||
}); | ||
``` | ||
@@ -76,4 +73,41 @@ | ||
| --------- | ----------- | ---- | ------- | | ||
| definition | An openapi document or a string representing the version to use. | `string` or `object` | | | ||
| definition | An openapi document or a string representing the version to use. | `string` or `object` | | ||
| options | The options to use for all functions within the instance. This options can be overwritten per function called. | `object` | | ||
| options.deserialize | The default options to apply to deserialize functions | | ||
| options.populate | The default [options to apply to populate](#populate-options) functions. | | | ||
| options.request | The default options to apply to request functions | | | ||
| options.serialize | The default options to apply to serialize functions | | | ||
| Deserialize Option | Description | Default | | ||
| ------------------ | ----------- | ------- | | ||
| throw | Whether errors should be [thrown or reported](#error-throwing-vs-reporting). | `true` | | ||
| options.deserialize.throw | Set to `true` to throw errors | ||
deserialize: { | ||
throw: true | ||
}, | ||
errors: { | ||
prefix: '' | ||
}, | ||
populate: { | ||
copy: false, | ||
defaults: true, | ||
ignoreMissingRequired: true, | ||
oneOf: true, | ||
replacement: 'handlebar', | ||
templateDefaults: true, | ||
templates: true, | ||
throw: true, | ||
variables: true | ||
}, | ||
request: { | ||
throw: true | ||
}, | ||
serialize: { | ||
throw: true | ||
} | ||
**Returns** an instance of the OpenAPI Enforcer | ||
@@ -123,3 +157,3 @@ | ||
`Enforcer.prototype.deserialize ( schema, value )` | ||
`Enforcer.prototype.deserialize ( schema, value [, options ] )` | ||
@@ -130,4 +164,5 @@ | Parameter | Description | Type | | ||
| value | The value to deserialize. | Any | | ||
| options | The deserialize options | `object` | | ||
Returns: An object with two properties: `errors` and `value`. If deserialization failed due to an error then the `errors` property will contain an array of strings for each error that was found. If deserialization succeeds then the `errors` property will be `null` and the `value` property will have the deserialized value. | ||
Returns the deserialized [value or the report](#error-throwing-vs-reporting). | ||
@@ -165,10 +200,29 @@ ```js | ||
// { | ||
// errors: null, | ||
// value: { | ||
// integers: [1, 2, 3, 4], | ||
// date: <Date Object> | ||
// } | ||
// integers: [1, 2, 3, 4], | ||
// date: <Date Object> | ||
// } | ||
``` | ||
### Deserialize Options | ||
The [Enforcer.prototype.deserialize](#enforcerprototypedeserialize) `options` parameter can define these properties: | ||
| Option | Description | Default | | ||
| ------ | ----------- | ------- | | ||
| throw | Whether errors should be [thrown or reported](#error-throwing-vs-reporting). | `true` | | ||
### Default Deserialize Options | ||
You can change the global defaults for how the [Enforcer.prototype.deserialize](#enforcerprototypedeserialize) works: | ||
```js | ||
const Enforcer = require('openapi-enforcer'); | ||
Enforcer.defaults.deserialize = { | ||
throw: true | ||
} | ||
``` | ||
## Enforcer.prototype.errors | ||
@@ -296,6 +350,21 @@ | ||
### Enforcer Populate Options | ||
### Populate Options | ||
By using the options you can define how the `Enforcer.prototype.populate` builds the value. It is easy to overwrite the default options for all calls the the `Enforcer.prototype.populate` function: | ||
The [`Enforcer.prototype.populate`](#enforcerprototypepopulate) `options` parameter can define these properties: | ||
| Option | Description | Default | | ||
| ------ | ----------- | ------- | | ||
| copy | When executing [`Enforcer.prototype.populate`](#enforcerprototypepopulate) and providing the `value` property, you have the option to either mutate (modify) that value or to create a copy of the value and mutate that. Mutation is faster, but if you do not want to change the passed in `value` then you should set this value to `true`. | `false` | | ||
| defaults | Allow populated values to be built from a schema's `default` value. | `true` | | ||
| ignoreMissingRequired | When executing [`Enforcer.prototype.populate`](#enforcerprototypepopulate) there will be times where an object with required properties is missing values for those required properties. If this value is set to `false` then [`Enforcer.prototype.populate`](#enforcerprototypepopulate) will not add the object to the populated value. If set to `true` then partially completed objects will still be added to the populated value. | `true` | | ||
| replacement | The template [parameter replacement](#parameter-replacement) format to use. This can be one of `"handlebar"`, `"doubleHandlebar"`, or `"colon"`. | `"handlebar"` | | ||
| templateDefaults | If this is set to `true` and a default is being use to populate a value and the default value is a string then the value will act as an `x-template` value. This can be useful because `default` values generally appear in generated documentation but you may still want to perform an `x-template` transformation on the value. | `true` | | ||
| templates | Allow populated values to be built from a schema's `x-template` value. [More about default, x-template, and x-variable](#about-default-x-template-and-x-variable). | `true` | | ||
| throw | Whether errors should be [thrown or reported](#error-throwing-vs-reporting). | `true` | | ||
| variables | Allow populated values to be built from a schema's `x-variable` value. [More about default, x-template, and x-variable](#about-default-x-template-and-x-variable). | `true` | | ||
### Default Populate Options | ||
You can change the global defaults for how the [`Enforcer.prototype.populate`](#enforcerprototypepopulate) works like this: | ||
```js | ||
@@ -309,5 +378,5 @@ const Enforcer = require('openapi-enforcer'); | ||
replacement: 'handlebar', | ||
serialize: false, | ||
templateDefaults: true, | ||
templates: true, | ||
throw: true, | ||
variables: true | ||
@@ -317,118 +386,4 @@ } | ||
Below is a detailed description of each `Enforcer.prototype.populate` option, what it does, and what its default is. | ||
Alternatively you can specify the same options in the [constructor](#constructor) or when calling the [`Enforcer.prototype.populate`](#enforcerprototypepopulate) function. | ||
##### copy | ||
When executing [`Enforcer.prototype.populate`](#enforcerprototypepopulate) and providing the `value` property, you have the option to either mutate (modify) that value or to create a copy of the value and mutate that. Mutation is faster, but if you do not want to change the passed in `value` then you should set this value to `true`. | ||
Default: `false` | ||
##### defaults | ||
Allow populated values to be built from a schema's `default` value. | ||
[More about default, x-template, and x-variable](#about-default-x-template-and-x-variable). | ||
Default: `true` | ||
##### ignoreMissingRequired | ||
When executing [`Enforcer.prototype.populate`](#enforcerprototypepopulate) there will be times where an object with required properties is missing values for those required properties. If this value is set to `false` then [`Enforcer.prototype.populate`](#enforcerprototypepopulate) will not add the object to the populated value. If set to `true` then partially completed objects will still be added to the populated value. | ||
Default: `true` | ||
##### replacement | ||
The template [parameter replacement](#parameter-replacement) format to use. This can be one of `'handlebar'`, `'doubleHandlebar'`, or `'colon'`. | ||
| Format | Example | | ||
| ------ | ------- | | ||
| handlebar | `{param}` | | ||
| doubleHandlebar | `{{param}}` | | ||
| colon | `:param` | | ||
Default: `'handlebar'` | ||
##### serialize | ||
If set to `true` then values will automatically be [serialized](#enforcerprototypeserialize) while populating. Serialized values are less versatile but are ready to be sent as an HTTP response. | ||
Default: `false` | ||
##### templateDefaults | ||
If this is set to `true` and a default is being use to populate a value and the default value is a string then the value will act as an `x-template` value. This can be useful because `default` values generally appear in generated documentation but you may still want to perform an `x-template` transformation on the value. | ||
##### templates | ||
Allow populated values to be built from a schema's `x-template` value. | ||
[More about default, x-template, and x-variable](#about-default-x-template-and-x-variable). | ||
Default: `true` | ||
##### variables | ||
Allow populated values to be built from a schema's `x-variable` value. | ||
[More about default, x-template, and x-variable](#about-default-x-template-and-x-variable). | ||
Default: `true` | ||
### About default, x-template, and x-variable | ||
The `default` attribute is part of the OpenAPI specification. The type of it's value must be the same as the schema type. For example, if the schema is of type string, default cannot be a number. When `default` is a string [it can behave](#options-populate-templatedefaults) like `x-template` and [substitute parameters](#parameter-replacement) into the string. The advantage of using `default` over `x-template` in this scenario is that the `default` value will often appear in OpenAPI documentation generators. | ||
The `x-template` value must be a string that will have [parameter replacement](#parameter-replacement) occur on it. Parameters in the string may use handlebars, double handlebars, or colons depending on how the Enforcer instance has been [configured](#optionspopulatereplacement). | ||
The `x-variable` will perform value substitution only. | ||
If a conflict arises between the provided value, `default`, `x-template`, or `x-variable` then the following priority is observed: | ||
1. The provided value | ||
2. `x-variable` | ||
3. `x-template` | ||
4. `default` | ||
```js | ||
const Enforcer = require('openapi-enforcer'); | ||
const enforcer = new Enforcer('3.0.0'); | ||
const schema = { | ||
type: 'object', | ||
properties: { | ||
firstName: { | ||
type: 'string', | ||
'x-variable': 'firstName' | ||
}, | ||
lastName: { | ||
type: 'string', | ||
'x-variable': 'lastName' | ||
}, | ||
fullName: { | ||
type: 'string', | ||
'x-template': '{firstName} {lastName}' | ||
}, | ||
profileUrl: { | ||
type: 'string', | ||
default: 'https://your-domain.com/users/{id}' | ||
} | ||
} | ||
}; | ||
const params = { | ||
id: 12345, | ||
firstName: 'Jan', | ||
lastName: 'Smith' | ||
} | ||
const value = enforcer.populate(schema, params); | ||
// value ==> { | ||
// firstName: 'Jan', | ||
// lastName: 'Smith', | ||
// fullName: 'Jan Smith', | ||
// profileUrl: 'https://your-domain.com/users/12345' | ||
// } | ||
``` | ||
## Enforcer.prototype.random | ||
@@ -593,3 +548,3 @@ | ||
Signature: `serialize ( { body, headers } )` | ||
Signature: `serialize ( { body, headers, options, skipValidation } )` | ||
@@ -602,2 +557,3 @@ Takes a configuration object as it's parameter with the following properties: | ||
| headers | An initial header object with header names and values as key value pairs. If the headers object does not define the `'content-type'` header then it will be set to the same value as the contentType option. | | ||
| options | Options to pass to the [`enforcer.prototype.serialize`](#enforcerprototypeserialize) function. | | ||
| skipValidation | Skip validation prior to seraialization. This can save processing cycles if you have already used `Enforcer.prototype.response().errors()` to check for errors and have found none. Skipping validation when errors exist may still cause errors to occur. Defaults to `false`. | | ||
@@ -680,60 +636,2 @@ | ||
### Parameter Replacement | ||
Parameter replacement is when part of a string is populated with parameters. This applies to a schema's `x-template` value and potentially `default` value. There are three types of replacement: | ||
1. handlebar (default) | ||
```js | ||
const Enforcer = require('openapi-enforcer'); | ||
const options = { | ||
populate: { replacement: 'handlebar' } | ||
}; | ||
const enforcer = new Enforcer('3.0.0', options); | ||
const schema = { | ||
type: 'string', | ||
'x-template': '{name} is {age} years old' | ||
}; | ||
const value = enforcer.populate(schema, { name: 'Bob', age: 25 }); | ||
// value ===> 'Bob is 25 years old | ||
``` | ||
2. doubleHandlebar | ||
```js | ||
const Enforcer = require('openapi-enforcer'); | ||
const options = { | ||
populate: { replacement: 'doubleHandlebar' } | ||
}; | ||
const enforcer = new Enforcer('3.0.0', options); | ||
const schema = { | ||
type: 'string', | ||
'x-template': '{{name}} is {{age}} years old' | ||
}; | ||
const value = enforcer.populate(schema, { name: 'Bob', age: 25 }); | ||
// value ===> 'Bob is 25 years old | ||
``` | ||
3. colon | ||
```js | ||
const Enforcer = require('openapi-enforcer'); | ||
const options = { | ||
populate: { replacement: 'colon' } | ||
}; | ||
const enforcer = new Enforcer('3.0.0', options); | ||
const schema = { | ||
type: 'string', | ||
'x-template': ':name is :age years old' | ||
}; | ||
const value = enforcer.populate(schema, { name: 'Bob', age: 25 }); | ||
// value ===> 'Bob is 25 years old | ||
``` | ||
## Enforcer.prototype.request | ||
@@ -913,2 +811,135 @@ | ||
// at ... | ||
``` | ||
``` | ||
## Appendix | ||
### About default, x-template, and x-variable | ||
The `default` attribute is part of the OpenAPI specification. The type of it's value must be the same as the schema type. For example, if the schema is of type string, default cannot be a number. When `default` is a string [it can behave](#options-populate-templatedefaults) like `x-template` and [substitute parameters](#parameter-replacement) into the string. The advantage of using `default` over `x-template` in this scenario is that the `default` value will often appear in OpenAPI documentation generators. | ||
The `x-template` value must be a string that will have [parameter replacement](#parameter-replacement) occur on it. Parameters in the string may use handlebars, double handlebars, or colons depending on how the Enforcer instance has been [configured](#optionspopulatereplacement). | ||
The `x-variable` will perform value substitution only. | ||
If a conflict arises between the provided value, `default`, `x-template`, or `x-variable` then the following priority is observed: | ||
1. The provided value | ||
2. `x-variable` | ||
3. `x-template` | ||
4. `default` | ||
```js | ||
const Enforcer = require('openapi-enforcer'); | ||
const enforcer = new Enforcer('3.0.0'); | ||
const schema = { | ||
type: 'object', | ||
properties: { | ||
firstName: { | ||
type: 'string', | ||
'x-variable': 'firstName' | ||
}, | ||
lastName: { | ||
type: 'string', | ||
'x-variable': 'lastName' | ||
}, | ||
fullName: { | ||
type: 'string', | ||
'x-template': '{firstName} {lastName}' | ||
}, | ||
profileUrl: { | ||
type: 'string', | ||
default: 'https://your-domain.com/users/{id}' | ||
} | ||
} | ||
}; | ||
const params = { | ||
id: 12345, | ||
firstName: 'Jan', | ||
lastName: 'Smith' | ||
} | ||
const value = enforcer.populate(schema, params); | ||
// value ==> { | ||
// firstName: 'Jan', | ||
// lastName: 'Smith', | ||
// fullName: 'Jan Smith', | ||
// profileUrl: 'https://your-domain.com/users/12345' | ||
// } | ||
``` | ||
### Error Throwing vs Reporting | ||
In some cases it is useful to receive an error in the return value instead of having it thrown. To this end, some of the functions in this library have the ability to either throw an error or return it. The functions that support this functionality will have a `throw` option that defaults to `true`. | ||
If `throw` is set to `true` then an encountered error will be thrown. If no error is encountered then the value will returned normally. | ||
If `throw` is set to `false` then it will return an object with two properties: `error` and `value`. If an error occured then `value` will be `null`, otherwise `error` will be `null`. | ||
```js | ||
const data = enforcer.deserialize(schema, value, { throw: false }) | ||
if (data.error) { | ||
console.error('Error: ' + data.error) | ||
} else { | ||
console.log('Value:' + value) | ||
} | ||
``` | ||
### Parameter Replacement | ||
Parameter replacement is when part of a string is populated with parameters. This applies to a schema's `x-template` value and potentially `default` value. There are three types of replacement: | ||
1. handlebar (default) | ||
```js | ||
const Enforcer = require('openapi-enforcer'); | ||
const options = { | ||
populate: { replacement: 'handlebar' } | ||
}; | ||
const enforcer = new Enforcer('3.0.0', options); | ||
const schema = { | ||
type: 'string', | ||
'x-template': '{name} is {age} years old' | ||
}; | ||
const value = enforcer.populate(schema, { name: 'Bob', age: 25 }); | ||
// value ===> 'Bob is 25 years old | ||
``` | ||
2. doubleHandlebar | ||
```js | ||
const Enforcer = require('openapi-enforcer'); | ||
const options = { | ||
populate: { replacement: 'doubleHandlebar' } | ||
}; | ||
const enforcer = new Enforcer('3.0.0', options); | ||
const schema = { | ||
type: 'string', | ||
'x-template': '{{name}} is {{age}} years old' | ||
}; | ||
const value = enforcer.populate(schema, { name: 'Bob', age: 25 }); | ||
// value ===> 'Bob is 25 years old | ||
``` | ||
3. colon | ||
```js | ||
const Enforcer = require('openapi-enforcer'); | ||
const options = { | ||
populate: { replacement: 'colon' } | ||
}; | ||
const enforcer = new Enforcer('3.0.0', options); | ||
const schema = { | ||
type: 'string', | ||
'x-template': ':name is :age years old' | ||
}; | ||
const value = enforcer.populate(schema, { name: 'Bob', age: 25 }); | ||
// value ===> 'Bob is 25 years old | ||
``` |
@@ -20,2 +20,3 @@ /** | ||
const Enforcer = require('../index'); | ||
const Exception = require('../bin/exception'); | ||
@@ -31,3 +32,3 @@ describe('deserialize', () => { | ||
const v = enforcer.deserialize({ type: 'string' }, 'hello'); | ||
expect(v.value).to.equal('hello'); | ||
expect(v).to.equal('hello'); | ||
}); | ||
@@ -37,3 +38,3 @@ | ||
const v = enforcer.deserialize({ type: 'number' }, '2.4'); | ||
expect(v.value).to.equal(2.4); | ||
expect(v).to.equal(2.4); | ||
}); | ||
@@ -43,3 +44,3 @@ | ||
const v = enforcer.deserialize({ type: 'integer' }, '2'); | ||
expect(v.value).to.equal(2); | ||
expect(v).to.equal(2); | ||
}); | ||
@@ -49,4 +50,4 @@ | ||
const v = enforcer.deserialize({ type: 'string', format: 'byte' }, 'aGVsbG8='); | ||
expect(v.value).to.be.instanceOf(Buffer); | ||
expect(v.value.toString()).to.equal('hello'); | ||
expect(v).to.be.instanceOf(Buffer); | ||
expect(v.toString()).to.equal('hello'); | ||
}); | ||
@@ -56,11 +57,11 @@ | ||
const v = enforcer.deserialize({ type: 'string', format: 'binary' }, '01100001'); | ||
expect(v.value).to.be.instanceOf(Buffer); | ||
expect(v.value[0]).to.equal(97); | ||
expect(v.value.toString()).to.equal('a'); | ||
expect(v).to.be.instanceOf(Buffer); | ||
expect(v[0]).to.equal(97); | ||
expect(v.toString()).to.equal('a'); | ||
}); | ||
it('boolean', () => { | ||
expect(enforcer.deserialize({ type: 'boolean' }, '').value).to.be.false; | ||
expect(enforcer.deserialize({ type: 'boolean' }, 'false').value).to.be.false; | ||
expect(enforcer.deserialize({ type: 'boolean' }, 'true').value).to.be.true; | ||
expect(enforcer.deserialize({ type: 'boolean' }, '')).to.be.false; | ||
expect(enforcer.deserialize({ type: 'boolean' }, 'false')).to.be.false; | ||
expect(enforcer.deserialize({ type: 'boolean' }, 'true')).to.be.true; | ||
}); | ||
@@ -70,4 +71,4 @@ | ||
const v = enforcer.deserialize({ type: 'string', format: 'date' }, '2000-01-01'); | ||
expect(v.value).to.be.instanceOf(Date); | ||
expect(v.value.toISOString()).to.equal('2000-01-01T00:00:00.000Z'); | ||
expect(v).to.be.instanceOf(Date); | ||
expect(v.toISOString()).to.equal('2000-01-01T00:00:00.000Z'); | ||
}); | ||
@@ -77,4 +78,4 @@ | ||
const v = enforcer.deserialize({ type: 'string', format: 'date-time' }, '2000-01-01T01:02:03.456Z'); | ||
expect(v.value).to.be.instanceOf(Date); | ||
expect(v.value.toISOString()).to.equal('2000-01-01T01:02:03.456Z'); | ||
expect(v).to.be.instanceOf(Date); | ||
expect(v.toISOString()).to.equal('2000-01-01T01:02:03.456Z'); | ||
}); | ||
@@ -84,3 +85,3 @@ | ||
const v = enforcer.deserialize({ type: 'string', format: 'password' }, 'plain text'); | ||
expect(v.value).to.equal('plain text'); | ||
expect(v).to.equal('plain text'); | ||
}); | ||
@@ -94,3 +95,3 @@ | ||
const v = enforcer.deserialize(schema, ['2000-01-01']); | ||
expect(v.value).to.deep.equal([new Date('2000-01-01')]); | ||
expect(v).to.deep.equal([new Date('2000-01-01')]); | ||
}); | ||
@@ -107,3 +108,3 @@ | ||
const v = enforcer.deserialize(schema, { a: 1, b: '2000-01-01' }); | ||
expect(v.value).to.deep.equal({ a: 1, b: new Date('2000-01-01') }); | ||
expect(v).to.deep.equal({ a: 1, b: new Date('2000-01-01') }); | ||
}); | ||
@@ -130,3 +131,3 @@ | ||
const v = enforcer.deserialize(schema, { a: 1, b: '2000-01-01', c: '01100001' }); | ||
expect(v.value).to.deep.equal({ | ||
expect(v).to.deep.equal({ | ||
a: 1, | ||
@@ -138,8 +139,18 @@ b: new Date('2000-01-01'), | ||
it('error', () => { | ||
const v = enforcer.deserialize({ type: 'integer' }, '2.4'); | ||
expect(v.errors.length).to.equal(1); | ||
it('reported data', () => { | ||
const v = enforcer.deserialize({ type: 'integer' }, '2', { throw: false }); | ||
expect(v.error).to.equal(null); | ||
expect(v.value).to.equal(2); | ||
}); | ||
it('thrown error', () => { | ||
expect(() => enforcer.deserialize({ type: 'integer' }, '2.4')).to.throw(/errors occurred during deserialization/); | ||
}); | ||
it('reported error', () => { | ||
const v = enforcer.deserialize({ type: 'integer' }, '2.4', { throw: false }); | ||
expect(v.error.isOpenAPIException).to.be.true; | ||
expect(v.value).to.be.null; | ||
}) | ||
}); | ||
}); |
@@ -26,43 +26,43 @@ /** | ||
it('false is 00000000', () => { | ||
expect(format.binary('', false)).to.equal('00000000'); | ||
expect(format.binary(false).value).to.equal('00000000'); | ||
}); | ||
it('true is 00000001', () => { | ||
expect(format.binary('', true)).to.equal('00000001'); | ||
expect(format.binary(true).value).to.equal('00000001'); | ||
}); | ||
it('0 is 00000000', () => { | ||
expect(format.binary('', 0)).to.equal('00000000'); | ||
expect(format.binary(0).value).to.equal('00000000'); | ||
}); | ||
it('1 is 00000001', () => { | ||
expect(format.binary('', 1)).to.equal('00000001'); | ||
expect(format.binary(1).value).to.equal('00000001'); | ||
}); | ||
it('2 is 00000010', () => { | ||
expect(format.binary('', 2)).to.equal('00000010'); | ||
expect(format.binary(2).value).to.equal('00000010'); | ||
}); | ||
it('35 is 00100011', () => { | ||
expect(format.binary('', 35)).to.equal('00100011'); | ||
expect(format.binary(35).value).to.equal('00100011'); | ||
}); | ||
it('1315 is 0000010100100011', () => { | ||
expect(format.binary('', 1315)).to.equal('0000010100100011'); | ||
expect(format.binary(1315).value).to.equal('0000010100100011'); | ||
}); | ||
it('A is 01000001', () => { | ||
expect(format.binary('', 'A')).to.equal('01000001'); | ||
expect(format.binary('A').value).to.equal('01000001'); | ||
}); | ||
it('AA is 0100000101000001', () => { | ||
expect(format.binary('', 'AA')).to.equal('0100000101000001'); | ||
expect(format.binary('AA').value).to.equal('0100000101000001'); | ||
}); | ||
it('Buffer("Hello") is 0100100001100101011011000110110001101111', () => { | ||
expect(format.binary('', Buffer.from("Hello"))).to.equal('0100100001100101011011000110110001101111'); | ||
expect(format.binary(Buffer.from("Hello")).value).to.equal('0100100001100101011011000110110001101111'); | ||
}); | ||
it('Object throws error', () => { | ||
expect(() => format.binary('', {})).to.throw(Error); | ||
expect(format.binary({}).error).to.match(/cannot convert to binary/i); | ||
}); | ||
@@ -75,7 +75,7 @@ | ||
it('truthy is true', () => { | ||
expect(format.boolean('', 'hello')).to.be.true; | ||
expect(format.boolean('hello').value).to.be.true; | ||
}); | ||
it('falsy is false', () => { | ||
expect(format.boolean('', null)).to.be.false; | ||
expect(format.boolean(null).value).to.be.false; | ||
}); | ||
@@ -88,43 +88,43 @@ | ||
it('false is AA==', () => { | ||
expect(format.byte('', false)).to.equal('AA=='); | ||
expect(format.byte(false).value).to.equal('AA=='); | ||
}); | ||
it('true is AQ==', () => { | ||
expect(format.byte('', true)).to.equal('AQ=='); | ||
expect(format.byte(true).value).to.equal('AQ=='); | ||
}); | ||
it('0 is AA==', () => { | ||
expect(format.byte('', 0)).to.equal('AA=='); | ||
expect(format.byte(0).value).to.equal('AA=='); | ||
}); | ||
it('1 is AQ==', () => { | ||
expect(format.byte('', 1)).to.equal('AQ=='); | ||
expect(format.byte(1).value).to.equal('AQ=='); | ||
}); | ||
it('2 is Ag==', () => { | ||
expect(format.byte('', 2)).to.equal('Ag=='); | ||
expect(format.byte(2).value).to.equal('Ag=='); | ||
}); | ||
it('35 is Iw==', () => { | ||
expect(format.byte('', 35)).to.equal('Iw=='); | ||
expect(format.byte(35).value).to.equal('Iw=='); | ||
}); | ||
it('1315 is BSM=', () => { | ||
expect(format.byte('', 1315)).to.equal('BSM='); | ||
expect(format.byte(1315).value).to.equal('BSM='); | ||
}); | ||
it('A is QQ==', () => { | ||
expect(format.byte('', 'A')).to.equal('QQ=='); | ||
expect(format.byte('A').value).to.equal('QQ=='); | ||
}); | ||
it('AA is QUE=', () => { | ||
expect(format.byte('', 'AA')).to.equal('QUE='); | ||
expect(format.byte('AA').value).to.equal('QUE='); | ||
}); | ||
it('Buffer("Hello") is SGVsbG8=', () => { | ||
expect(format.byte('', Buffer.from("Hello"))).to.equal('SGVsbG8='); | ||
expect(format.byte(Buffer.from("Hello")).value).to.equal('SGVsbG8='); | ||
}); | ||
it('Object throws error', () => { | ||
expect(() => format.byte('', {})).to.throw(Error); | ||
expect(format.byte({}).error).to.match(/cannot convert to byte/i); | ||
}); | ||
@@ -138,19 +138,19 @@ | ||
const d = new Date(0); | ||
expect(format.date('', d)).to.equal('1970-01-01'); | ||
expect(format.date(d).value).to.equal('1970-01-01'); | ||
}); | ||
it('date string in ISO format', () => { | ||
expect(format.date('', '2000-01-01')).to.equal('2000-01-01'); | ||
expect(format.date('2000-01-01').value).to.equal('2000-01-01'); | ||
}); | ||
it('date-time string in ISO format', () => { | ||
expect(format.date('', '2000-01-01T11:11:11.111Z')).to.equal('2000-01-01'); | ||
expect(format.date('2000-01-01T11:11:11.111Z').value).to.equal('2000-01-01'); | ||
}); | ||
it('number', () => { | ||
expect(format.date('', 0)).to.equal('1970-01-01'); | ||
expect(format.date(0).value).to.equal('1970-01-01'); | ||
}); | ||
it('Object throws error', () => { | ||
expect(() => format.date('', {})).to.throw(Error); | ||
expect(format.date({}).error).to.match(/cannot convert to date/i) | ||
}); | ||
@@ -164,19 +164,19 @@ | ||
const d = new Date(0); | ||
expect(format.date('', d)).to.equal('1970-01-01'); | ||
expect(format.dateTime(d).value).to.equal('1970-01-01T00:00:00.000Z'); | ||
}); | ||
it('date string in ISO format', () => { | ||
expect(format.date('', '2000-01-01')).to.equal('2000-01-01'); | ||
expect(format.dateTime('2000-01-01').value).to.equal('2000-01-01T00:00:00.000Z'); | ||
}); | ||
it('date-time string in ISO format', () => { | ||
expect(format.date('', '2000-01-01T11:11:11.111Z')).to.equal('2000-01-01'); | ||
expect(format.dateTime('2000-01-01T11:11:11.111Z').value).to.equal('2000-01-01T11:11:11.111Z'); | ||
}); | ||
it('number', () => { | ||
expect(format.date('', 0)).to.equal('1970-01-01'); | ||
expect(format.dateTime(0).value).to.equal('1970-01-01T00:00:00.000Z'); | ||
}); | ||
it('Object throws error', () => { | ||
expect(() => format.date('', {})).to.throw(Error); | ||
expect(format.dateTime({}).error).to.match(/cannot convert to date-time/i) | ||
}); | ||
@@ -189,23 +189,23 @@ | ||
it('integer string', () => { | ||
expect(format.integer('', '123')).to.equal(123); | ||
expect(format.integer('123').value).to.equal(123); | ||
}); | ||
it('decimal string', () => { | ||
expect(format.integer('', '5.67')).to.equal(6); | ||
expect(format.integer('5.67').value).to.equal(6); | ||
}); | ||
it('integer number', () => { | ||
expect(format.integer('', 123)).to.equal(123); | ||
expect(format.integer(123).value).to.equal(123); | ||
}); | ||
it('decimal number', () => { | ||
expect(format.integer('', 5.67)).to.equal(6); | ||
expect(format.integer(5.67).value).to.equal(6); | ||
}); | ||
it('date object', () => { | ||
expect(format.integer('', new Date(1000))).to.equal(1000); | ||
expect(format.integer(new Date(1000)).value).to.equal(1000); | ||
}); | ||
it('invalid string throws error', () => { | ||
expect(() => format.integer('', 'hello')).to.throw(Error); | ||
expect(format.integer('hello').error).to.match(/cannot convert to integer/i); | ||
}); | ||
@@ -218,23 +218,23 @@ | ||
it('integer string', () => { | ||
expect(format.number('', '123')).to.equal(123); | ||
expect(format.number('123').value).to.equal(123); | ||
}); | ||
it('decimal string', () => { | ||
expect(format.number('', '5.67')).to.equal(5.67); | ||
expect(format.number('5.67').value).to.equal(5.67); | ||
}); | ||
it('integer number', () => { | ||
expect(format.number('', 123)).to.equal(123); | ||
expect(format.number(123).value).to.equal(123); | ||
}); | ||
it('decimal number', () => { | ||
expect(format.number('', 5.67)).to.equal(5.67); | ||
expect(format.number(5.67).value).to.equal(5.67); | ||
}); | ||
it('date object', () => { | ||
expect(format.number('', new Date(1000))).to.equal(1000); | ||
expect(format.number(new Date(1000)).value).to.equal(1000); | ||
}); | ||
it('invalid string throws error', () => { | ||
expect(() => format.number('', 'hello')).to.throw(Error); | ||
expect(format.number('hello').error).to.match(/cannot convert to number/i) | ||
}); | ||
@@ -247,15 +247,15 @@ | ||
it('string', () => { | ||
expect(format.string('', 'hello')).to.equal('hello'); | ||
expect(format.string('hello').value).to.equal('hello'); | ||
}); | ||
it('number', () => { | ||
expect(format.string('', 5.67)).to.equal('5.67'); | ||
expect(format.string(5.67).value).to.equal('5.67'); | ||
}); | ||
it('true', () => { | ||
expect(format.string('', true)).to.equal('true'); | ||
expect(format.string(true).value).to.equal('true'); | ||
}); | ||
it('false', () => { | ||
expect(format.string('', false)).to.equal('false'); | ||
expect(format.string(false).value).to.equal('false'); | ||
}); | ||
@@ -265,12 +265,12 @@ | ||
const o = { a: 1, b: 2 }; | ||
expect(format.string('', o)).to.equal(JSON.stringify(o)); | ||
expect(format.string(o).value).to.equal(JSON.stringify(o)); | ||
}); | ||
it('Date', () => { | ||
expect(format.string('', new Date(0))).to.equal('1970-01-01T00:00:00.000Z'); | ||
expect(format.string(new Date(0)).value).to.equal('1970-01-01T00:00:00.000Z'); | ||
}); | ||
it('Symbol throws error', () => { | ||
expect(() => format.string('', Symbol('s'))).to.throw(Error); | ||
}) | ||
expect(format.string(Symbol('s')).error).to.match(/cannot convert to string/i); | ||
}); | ||
@@ -277,0 +277,0 @@ }); |
@@ -77,2 +77,7 @@ /** | ||
it('unmatched path length', () => { | ||
const path = enforcer.path('/a/b/c/d/e/f/g'); | ||
expect(path).to.be.undefined; | ||
}); | ||
describe('path without parameters', () => { | ||
@@ -79,0 +84,0 @@ let path; |
@@ -31,7 +31,8 @@ /** | ||
}; | ||
let enforcer; | ||
before(() => { | ||
enforcer = new Enforcer(definition); | ||
enforcer = new Enforcer(definition, { | ||
populate: { throw: true } | ||
}); | ||
}); | ||
@@ -42,3 +43,3 @@ | ||
it('default', () => { | ||
const value = enforcer.populate({ schema: { type: 'number', default: 5 }}); | ||
const value = enforcer.populate({ type: 'number', default: 5 }); | ||
expect(value).to.equal(5); | ||
@@ -48,6 +49,3 @@ }); | ||
it('x-template', () => { | ||
const value = enforcer.populate({ | ||
schema: {type: 'number', 'x-template': ':myNumber'}, | ||
params: {myNumber: 5} | ||
}); | ||
const value = enforcer.populate({type: 'number', 'x-template': ':myNumber'}, {myNumber: 5}); | ||
expect(value).to.be.undefined; // x-template only works for strings | ||
@@ -57,6 +55,3 @@ }); | ||
it('x-variable', () => { | ||
const value = enforcer.populate({ | ||
schema: {type: 'number', 'x-variable': 'myNumber'}, | ||
params: {myNumber: 5} | ||
}); | ||
const value = enforcer.populate({type: 'number', 'x-variable': 'myNumber'}, {myNumber: 5}); | ||
expect(value).to.equal(5); | ||
@@ -66,6 +61,3 @@ }); | ||
it('default and x-variable 1', () => { | ||
const value = enforcer.populate({ | ||
schema: { type: 'number', default: 5, 'x-variable': 'myNumber' }, | ||
params: { myNumber: 6 } | ||
}); | ||
const value = enforcer.populate({ type: 'number', default: 5, 'x-variable': 'myNumber' }, { myNumber: 6 }); | ||
expect(value).to.equal(6); | ||
@@ -75,28 +67,30 @@ }); | ||
it('default and x-variable 2', () => { | ||
const value = enforcer.populate({ schema: { type: 'number', default: 5, 'x-variable': 'myNumber' }}); | ||
const value = enforcer.populate({ type: 'number', default: 5, 'x-variable': 'myNumber' }); | ||
expect(value).to.equal(5); | ||
}); | ||
it('auto format enabled', () => { | ||
it('does not format', () => { | ||
const enforcer = new Enforcer(definition); | ||
const date = new Date(); | ||
const value = enforcer.populate({ | ||
schema: { type: 'number', 'x-variable': 'myNumber' }, | ||
params: { myNumber: date }, | ||
options: { serialize: true } | ||
}); | ||
expect(value).to.equal(+date); | ||
const value = enforcer.populate({type: 'number', 'x-variable': 'myNumber'}, {myNumber: date}, undefined); | ||
expect(value).to.equal(date); | ||
}); | ||
it('auto format disabled', () => { | ||
it('report data', () => { | ||
const enforcer = new Enforcer(definition); | ||
const date = new Date(); | ||
const value = enforcer.populate({ | ||
schema: {type: 'number', 'x-variable': 'myNumber'}, | ||
params: {myNumber: date}, | ||
options: { serialize: false } | ||
}); | ||
expect(value).to.equal(date); | ||
const data = enforcer.populate({type: 'number', 'x-variable': 'myNumber'}, {myNumber: 1}, undefined, { throw: false }); | ||
expect(data.value).to.equal(1); | ||
}); | ||
it('report error', () => { | ||
const enforcer = new Enforcer(definition); | ||
const data = enforcer.populate({type: 'array'}, {}, {}, { throw: false }); | ||
expect(data.errors).not.to.be.null; | ||
}); | ||
it('throw error', () => { | ||
const enforcer = new Enforcer(definition); | ||
expect(() => enforcer.populate({type: 'array'}, {}, {}, { throw: true })).to.throw(/errors occurred during population/); | ||
}); | ||
}); | ||
@@ -107,3 +101,3 @@ | ||
it('default', () => { | ||
const value = enforcer.populate({ schema: { type: 'string', default: 'hello' } }); | ||
const value = enforcer.populate({ type: 'string', default: 'hello' }); | ||
expect(value).to.equal('hello'); | ||
@@ -113,6 +107,3 @@ }); | ||
it('x-template', () => { | ||
const value = enforcer.populate({ | ||
schema: { type: 'string', 'x-template': '{varName}' }, | ||
params: { varName: 'hello' } | ||
}); | ||
const value = enforcer.populate({ type: 'string', 'x-template': '{varName}' }, { varName: 'hello' }); | ||
expect(value).to.equal('hello'); | ||
@@ -122,6 +113,3 @@ }); | ||
it('x-template multiple', () => { | ||
const value = enforcer.populate({ | ||
schema: {type: 'string', 'x-template': '{greeting}, {name}!'}, | ||
params: {greeting: 'Hello', name: 'Bob'} | ||
}); | ||
const value = enforcer.populate({type: 'string', 'x-template': '{greeting}, {name}!'}, {greeting: 'Hello', name: 'Bob'}); | ||
expect(value).to.equal('Hello, Bob!'); | ||
@@ -131,6 +119,3 @@ }); | ||
it('x-variable', () => { | ||
const value = enforcer.populate({ | ||
schema: {type: 'string', 'x-variable': 'varName'}, | ||
params: {varName: 'hello'} | ||
}); | ||
const value = enforcer.populate({type: 'string', 'x-variable': 'varName'},{varName: 'hello'}); | ||
expect(value).to.equal('hello'); | ||
@@ -141,2 +126,36 @@ }); | ||
describe('date', () => { | ||
it('x-variable keeps type', () => { | ||
const schema = { | ||
type: 'string', | ||
format: 'date', | ||
'x-variable': 'myDate' | ||
}; | ||
const value = enforcer.populate(schema, { myDate: 'hello' }); | ||
expect(value).to.equal('hello'); | ||
}); | ||
it('default corrects type', () => { | ||
const schema = { | ||
type: 'string', | ||
format: 'date', | ||
default: '2000-01-01' | ||
}; | ||
const value = enforcer.populate(schema, { myDate: 'hello' }); | ||
expect(value).to.deep.equal(new Date('2000-01-01')); | ||
}); | ||
it('x-template corrects type', () => { | ||
const schema = { | ||
type: 'string', | ||
format: 'date', | ||
'x-template': '{year}-{month}-01' | ||
}; | ||
const value = enforcer.populate(schema, { year: '2000', month: '01' }); | ||
expect(value).to.deep.equal(new Date('2000-01-01')); | ||
}); | ||
}); | ||
describe('array', () => { | ||
@@ -147,8 +166,3 @@ | ||
const initValue = [1, 2, undefined, 3, 4, undefined]; | ||
const value = enforcer.populate({ | ||
options: { copy: true }, | ||
schema: schema, | ||
params: {}, | ||
value: initValue | ||
}); | ||
const value = enforcer.populate(schema, {}, initValue, { copy: true }); | ||
expect(value).not.to.equal(initValue); | ||
@@ -175,3 +189,3 @@ expect(value).to.deep.equal([1, 2, 5, 3, 4, 5]); | ||
}; | ||
const value = enforcer.populate({ schema: schema }); | ||
const value = enforcer.populate(schema); | ||
expect(value).to.deep.equal([{ x: 'hello', y: 'hi' }, { x: 'bye', y: 'later' }]); | ||
@@ -192,3 +206,3 @@ }); | ||
}; | ||
const value = enforcer.populate({ schema: schema }); | ||
const value = enforcer.populate(schema); | ||
expect(value).to.deep.equal({ age: 5 }) | ||
@@ -206,7 +220,3 @@ }); | ||
}; | ||
const value = enforcer.populate({ | ||
schema: schema, | ||
params: {}, | ||
options: { ignoreMissingRequired: false } | ||
}); | ||
const value = enforcer.populate(schema, {}, undefined, { ignoreMissingRequired: false }); | ||
expect(value).to.be.undefined; | ||
@@ -224,3 +234,3 @@ }); | ||
}; | ||
const value = enforcer.populate({ schema: schema, params: {}, value: { a: {} }}); | ||
const value = enforcer.populate(schema, {}, { a: {} }); | ||
expect(value).to.deep.equal({ a: { x: 5 } }); | ||
@@ -243,3 +253,3 @@ }); | ||
}; | ||
const value = enforcer.populate({ schema: schema }); | ||
const value = enforcer.populate(schema); | ||
expect(value).to.deep.equal({ a: 'A', b: 'B' }); | ||
@@ -257,3 +267,3 @@ }); | ||
it('does nothing', () => { | ||
const value = enforcer.populate({ schema: schema, params: {} }); | ||
const value = enforcer.populate(schema); | ||
expect(value).to.be.undefined; | ||
@@ -288,3 +298,3 @@ }); | ||
it('one', () => { | ||
const value = enforcer.populate({ schema: schema, params: {}, value: { mode: 'one' }}); | ||
const value = enforcer.populate(schema, {}, { mode: 'one' }); | ||
expect(value).to.deep.equal({ mode: 'one', value: 5 }); | ||
@@ -294,3 +304,3 @@ }); | ||
it('two', () => { | ||
const value = enforcer.populate({ schema: schema, params: {}, value: { mode: 'two' }}); | ||
const value = enforcer.populate(schema, {}, { mode: 'two' }); | ||
expect(value).to.deep.equal({ mode: 'two', value: 'hello' }); | ||
@@ -297,0 +307,0 @@ }); |
'use strict'; | ||
const Buffer = require('buffer').Buffer; | ||
const Enforcer = require('../index'); | ||
const Exception = require('../bin/exception'); | ||
const expect = require('chai').expect; | ||
@@ -8,9 +9,77 @@ const random = require('../bin/random'); | ||
describe('random', () => { | ||
const exception = Exception(); | ||
it('via enforcer', () => { | ||
const enforcer = new Enforcer('2.0'); | ||
const schema = { type: 'integer', minimum: 0, maximum: 5 }; | ||
const value = enforcer.random(schema); | ||
expect(value).to.be.at.most(5); | ||
expect(value).to.be.at.least(0); | ||
describe('via enforcer', () => { | ||
describe('v3', () => { | ||
let enforcer; | ||
before(() => { | ||
enforcer = new Enforcer('3.0.0'); | ||
}); | ||
it('integer', () => { | ||
const schema = { type: 'integer', minimum: 0, maximum: 5 }; | ||
const value = enforcer.random(schema); | ||
expect(value).to.be.at.most(5); | ||
expect(value).to.be.at.least(0); | ||
}); | ||
it('anyOf', () => { | ||
const schema = { | ||
anyOf: [ | ||
{ type: 'integer', minimum: 0, maximum: 5 }, | ||
{ type: 'string', maxLength: 5 } | ||
] | ||
}; | ||
const value = enforcer.random(schema); | ||
if (typeof value === 'string') { | ||
expect(value.length).to.be.at.most(5); | ||
} else if (typeof value === 'number') { | ||
expect(value).to.be.at.most(5); | ||
} else { | ||
expect.fail(value); | ||
} | ||
}); | ||
it('oneOf', () => { | ||
const schema = { | ||
oneOf: [ | ||
{ type: 'integer', minimum: 0, maximum: 5 }, | ||
{ type: 'string', maxLength: 5 } | ||
] | ||
}; | ||
const value = enforcer.random(schema); | ||
if (typeof value === 'string') { | ||
expect(value.length).to.be.at.most(5); | ||
} else if (typeof value === 'number') { | ||
expect(value).to.be.at.most(5); | ||
} else { | ||
expect.fail(value); | ||
} | ||
}); | ||
it.skip('allOf', () => { | ||
const schema = { | ||
type: 'object', | ||
properties: { | ||
x: { | ||
allOf: [ | ||
{ type: 'integer', minimum: 0, maximum: 5 }, | ||
{ type: 'integer', minimum: 5 } | ||
] | ||
} | ||
} | ||
}; | ||
throw Error('here'); | ||
// TODO: check error message | ||
expect(() => enforcer.random(schema)).to.throw(Error); | ||
}); | ||
}); | ||
}); | ||
@@ -21,3 +90,3 @@ | ||
it('is array', () => { | ||
expect(random.array({})).to.be.an.instanceOf(Array); | ||
expect(random.array(exception, {})).to.be.an.instanceOf(Array); | ||
}); | ||
@@ -27,3 +96,3 @@ | ||
const schema = { minItems: 5 }; | ||
expect(random.array(schema).length).to.be.at.least(5); | ||
expect(random.array(exception, schema).length).to.be.at.least(5); | ||
}); | ||
@@ -33,3 +102,3 @@ | ||
const schema = { maxItems: 1 }; | ||
expect(random.array(schema).length).to.be.at.most(1); | ||
expect(random.array(exception, schema).length).to.be.at.most(1); | ||
}); | ||
@@ -39,3 +108,3 @@ | ||
const schema = { minItems: 1, maxItems: 1 }; | ||
expect(random.array(schema).length).to.equal(1); | ||
expect(random.array(exception, schema).length).to.equal(1); | ||
}); | ||
@@ -48,3 +117,3 @@ | ||
it('is buffer', () => { | ||
expect(random.binary({})).to.be.an.instanceOf(Buffer); | ||
expect(random.binary(exception, {})).to.be.an.instanceOf(Buffer); | ||
}); | ||
@@ -54,3 +123,3 @@ | ||
const schema = { minLength: 5 }; | ||
expect(random.binary(schema).length).to.be.at.least(5); | ||
expect(random.binary(exception, schema).length).to.be.at.least(5); | ||
}); | ||
@@ -60,3 +129,3 @@ | ||
const schema = { maxLength: 1 }; | ||
expect(random.binary(schema).length).to.be.at.most(1); | ||
expect(random.binary(exception, schema).length).to.be.at.most(1); | ||
}); | ||
@@ -66,3 +135,3 @@ | ||
const schema = { minLength: 1, maxLength: 1 }; | ||
expect(random.binary(schema).length).to.equal(1); | ||
expect(random.binary(exception, schema).length).to.equal(1); | ||
}); | ||
@@ -76,7 +145,7 @@ | ||
const schema = { enum: [true] }; | ||
expect(random.boolean(schema)).to.be.true; | ||
expect(random.boolean(exception, schema)).to.be.true; | ||
}); | ||
it('true or false', () => { | ||
expect(random.boolean({})).to.be.oneOf([true, false]); | ||
expect(random.boolean(exception, {})).to.be.oneOf([true, false]); | ||
}); | ||
@@ -89,7 +158,7 @@ | ||
it('is a date', () => { | ||
expect(random.date({})).to.be.an.instanceOf(Date); | ||
expect(random.date(exception, {})).to.be.an.instanceOf(Date); | ||
}); | ||
it('is at start of day', () => { | ||
const value = random.date({}).toISOString().substr(10); | ||
const value = random.date(exception, {}).toISOString().substr(10); | ||
expect(value).to.equal('T00:00:00.000Z'); | ||
@@ -103,3 +172,3 @@ }); | ||
it('is a date', () => { | ||
expect(random.dateTime({})).to.be.an.instanceOf(Date); | ||
expect(random.dateTime(exception, {})).to.be.an.instanceOf(Date); | ||
}); | ||
@@ -111,3 +180,3 @@ | ||
const schema = { minimum: str }; | ||
expect(+random.dateTime(schema)).to.be.at.least(+d); | ||
expect(+random.dateTime(exception, schema)).to.be.at.least(+d); | ||
}); | ||
@@ -119,3 +188,3 @@ | ||
const schema = { maximum: str }; | ||
expect(+random.dateTime(schema)).to.be.at.most(+d); | ||
expect(+random.dateTime(exception, schema)).to.be.at.most(+d); | ||
}); | ||
@@ -129,3 +198,3 @@ | ||
const schema = { minimum: str1, maximum: str2 }; | ||
const v = +random.dateTime(schema); | ||
const v = +random.dateTime(exception, schema); | ||
expect(v).to.be.at.least(+d1); | ||
@@ -140,3 +209,3 @@ expect(v).to.be.at.most(+d2); | ||
it('is integer', () => { | ||
const value = random.integer({}); | ||
const value = random.integer(exception, {}); | ||
expect(value).to.be.a('number'); | ||
@@ -148,7 +217,7 @@ expect(Math.round(value)).to.equal(value); | ||
const schema = { enum: [1, 2, 3, -5] }; | ||
expect(random.integer(schema)).to.be.oneOf(schema.enum); | ||
expect(random.integer(exception, schema)).to.be.oneOf(schema.enum); | ||
}); | ||
it('random', () => { | ||
const value = random.integer({}); | ||
const value = random.integer(exception, {}); | ||
expect(value).to.be.at.most(500); | ||
@@ -160,3 +229,3 @@ expect(value).to.be.at.least(-500); | ||
const schema = { minimum: 0 }; | ||
expect(random.integer(schema)).to.be.at.least(0); | ||
expect(random.integer(exception, schema)).to.be.at.least(0); | ||
}); | ||
@@ -166,3 +235,3 @@ | ||
const schema = { maximum: 0 }; | ||
expect(random.integer(schema)).to.be.at.most(0); | ||
expect(random.integer(exception, schema)).to.be.at.most(0); | ||
}); | ||
@@ -172,3 +241,3 @@ | ||
const schema = { minimum: 0, maximum: 2 }; | ||
expect(random.integer(schema)).to.be.oneOf([0, 1, 2]) | ||
expect(random.integer(exception, schema)).to.be.oneOf([0, 1, 2]) | ||
}); | ||
@@ -178,3 +247,3 @@ | ||
const schema = { minimum: 0, maximum: 2, exclusiveMinimum: true, exclusiveMaximum: true }; | ||
expect(random.integer(schema)).to.equal(1); | ||
expect(random.integer(exception, schema)).to.equal(1); | ||
}); | ||
@@ -187,3 +256,3 @@ | ||
it('is number', () => { | ||
const value = random.number({}); | ||
const value = random.number(exception, {}); | ||
expect(value).to.be.a('number'); | ||
@@ -194,7 +263,7 @@ }); | ||
const schema = { enum: [1, 2.2, 3.3, -5.5] }; | ||
expect(random.number(schema)).to.be.oneOf(schema.enum); | ||
expect(random.number(exception, schema)).to.be.oneOf(schema.enum); | ||
}); | ||
it('random', () => { | ||
const value = random.number({}); | ||
const value = random.number(exception, {}); | ||
expect(value).to.be.at.most(500); | ||
@@ -206,3 +275,3 @@ expect(value).to.be.at.least(-500); | ||
const schema = { minimum: 0 }; | ||
expect(random.number(schema)).to.be.at.least(0); | ||
expect(random.number(exception, schema)).to.be.at.least(0); | ||
}); | ||
@@ -212,3 +281,3 @@ | ||
const schema = { maximum: 0 }; | ||
expect(random.number(schema)).to.be.at.most(0); | ||
expect(random.number(exception, schema)).to.be.at.most(0); | ||
}); | ||
@@ -218,3 +287,3 @@ | ||
const schema = { minimum: 0, maximum: 2 }; | ||
const value = random.number(schema); | ||
const value = random.number(exception, schema); | ||
expect(value).to.be.at.least(0); | ||
@@ -226,3 +295,3 @@ expect(value).to.be.at.most(2); | ||
const schema = { minimum: 0, maximum: 2, exclusiveMinimum: true, exclusiveMaximum: true }; | ||
const value = random.number(schema); | ||
const value = random.number(exception, schema); | ||
expect(value).to.be.greaterThan(0); | ||
@@ -236,3 +305,3 @@ expect(value).to.be.lessThan(2); | ||
it('is object', () => { | ||
expect(random.object({})).to.be.an.instanceOf(Object); | ||
expect(random.object(exception, {})).to.be.an.instanceOf(Object); | ||
}); | ||
@@ -255,3 +324,3 @@ | ||
}; | ||
const value = random.object(schema); | ||
const value = random.object(exception, schema); | ||
expect(value.a).to.be.a('string'); | ||
@@ -276,3 +345,3 @@ expect(value.b).to.be.an.instanceOf(Date); | ||
}; | ||
const value = random.object(schema); | ||
const value = random.object(exception, schema); | ||
expect(value.a).to.be.a('string'); | ||
@@ -300,3 +369,3 @@ expect(value.b).to.be.an.instanceOf(Date); | ||
}; | ||
const value = random.object(schema); | ||
const value = random.object(exception, schema); | ||
expect(Object.keys(value).length).to.equal(2); | ||
@@ -321,3 +390,3 @@ expect(value.a).to.be.a('string'); | ||
}; | ||
const value = random.object(schema); | ||
const value = random.object(exception, schema); | ||
expect(Object.keys(value).length).to.equal(2); | ||
@@ -333,3 +402,3 @@ expect(value.a).to.be.a('string'); | ||
it('is string', () => { | ||
const value = random.string({}); | ||
const value = random.string(exception, {}); | ||
expect(value).to.be.a('string'); | ||
@@ -340,3 +409,3 @@ }); | ||
const schema = { minLength: 500 }; | ||
expect(random.string(schema).length).to.be.at.least(500); | ||
expect(random.string(exception, schema).length).to.be.at.least(500); | ||
}); | ||
@@ -346,3 +415,3 @@ | ||
const schema = { maxLength: 10 }; | ||
expect(random.string(schema).length).to.be.at.most(10); | ||
expect(random.string(exception, schema).length).to.be.at.most(10); | ||
}); | ||
@@ -352,3 +421,3 @@ | ||
const schema = { minLength: 0, maxLength: 2 }; | ||
const value = random.string(schema); | ||
const value = random.string(exception, schema); | ||
expect(value.length).to.be.at.least(0); | ||
@@ -355,0 +424,0 @@ expect(value.length).to.be.at.most(2); |
@@ -22,7 +22,186 @@ /** | ||
describe('request', () => { | ||
let enforcer; | ||
it.skip('no paths defined', () => { | ||
const enforcer = Enforcer('2.0') | ||
}) | ||
before(() => { | ||
enforcer = new Enforcer({ | ||
swagger: '2.0', | ||
paths: { | ||
'/': { | ||
get: {}, | ||
post: { | ||
parameters: [ | ||
{ name: 'body', in: 'body', schema: { type: 'number' }} | ||
], | ||
responses: { | ||
'200': { | ||
schema: { | ||
type: 'number' | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}); | ||
}); | ||
describe('input validation', () => { | ||
describe('parameter', () => { | ||
it('string ok', () => { | ||
expect(() => enforcer.request('/')).not.to.throw(); | ||
}); | ||
it('object ok', () => { | ||
expect(() => enforcer.request({})).not.to.throw(); | ||
}); | ||
it('null throws error', () => { | ||
expect(() => enforcer.request(null)).to.throw(); | ||
}); | ||
it('number throws error', () => { | ||
expect(() => enforcer.request(5)).to.throw(/must be a string or an object/i); | ||
}); | ||
}); | ||
describe('body', () => { | ||
it('string ok', () => { | ||
expect(() => enforcer.request({ body: '' })).not.to.throw(); | ||
}); | ||
it('object ok', () => { | ||
expect(() => enforcer.request({ body: {} })).not.to.throw(); | ||
}); | ||
it('null ok', () => { | ||
expect(() => enforcer.request({ body: null })).not.to.throw(); | ||
}); | ||
it('number ok', () => { | ||
expect(() => enforcer.request({ body: 5, method: 'post' })).not.to.throw(); | ||
}); | ||
}); | ||
describe('cookies', () => { | ||
it('undefined ok', () => { | ||
expect(() => enforcer.request({})).not.to.throw(); | ||
}); | ||
it('object ok', () => { | ||
expect(() => enforcer.request({ cookies: {} })).not.to.throw(); | ||
}); | ||
it('null throws error', () => { | ||
expect(() => enforcer.request({ cookies: null })).to.throw(); | ||
}); | ||
it('string throws error', () => { | ||
expect(() => enforcer.request({ cookies: '' })).to.throw(); | ||
}); | ||
}); | ||
describe('headers', () => { | ||
it('undefined ok', () => { | ||
expect(() => enforcer.request({})).not.to.throw(); | ||
}); | ||
it('object ok', () => { | ||
expect(() => enforcer.request({ headers: {} })).not.to.throw(); | ||
}); | ||
it('null throws error', () => { | ||
expect(() => enforcer.request({ headers: null })).to.throw(); | ||
}); | ||
it('string throws error', () => { | ||
expect(() => enforcer.request({ headers: '' })).to.throw(); | ||
}); | ||
}); | ||
describe('path', () => { | ||
it('undefined ok', () => { | ||
expect(() => enforcer.request({})).not.to.throw(); | ||
}); | ||
it('string ok', () => { | ||
expect(() => enforcer.request({ path: '' })).not.to.throw(); | ||
}); | ||
it('path not defined', () => { | ||
expect(() => enforcer.request({ path: '/hello' })).to.throw(/path not found/i); | ||
}); | ||
it('null throws error', () => { | ||
expect(() => enforcer.request({ path: null })).to.throw(); | ||
}); | ||
it('object throws error', () => { | ||
expect(() => enforcer.request({ path: {} })).to.throw(); | ||
}); | ||
}); | ||
describe('method', () => { | ||
it('undefined ok', () => { | ||
expect(() => enforcer.request({})).not.to.throw(); | ||
}); | ||
it('string ok', () => { | ||
expect(() => enforcer.request({ method: 'get' })).not.to.throw(); | ||
}); | ||
it('method not defined', () => { | ||
expect(() => enforcer.request({ method: 'delete' })).to.throw(/method not allowed/i); | ||
}); | ||
it('null throws error', () => { | ||
expect(() => enforcer.request({ method: null })).to.throw(); | ||
}); | ||
it('object throws error', () => { | ||
expect(() => enforcer.request({ method: {} })).to.throw(); | ||
}); | ||
}); | ||
}); | ||
describe('response', () => { | ||
const properties = ['data', 'errors', 'example', 'populate', 'serialize']; | ||
const config = { code: 200, contentType: 'application/json' }; | ||
it('via request', () => { | ||
const req = enforcer.request('/'); | ||
const res = req.response(); | ||
expect(Object.keys(res)).to.deep.equal(properties); | ||
}); | ||
// it('invalid path', () => { | ||
// const options = Object.assign({}, config, { path: '/hello' }); | ||
// expect(() => enforcer.response(options).to.throw(/invalid path/i); | ||
// }); | ||
// | ||
// it('invalid method', () => { | ||
// const options = Object.assign({}, config, { path: '/', method: 'delete' }); | ||
// expect(() => enforcer.response(options)).to.throw(/invalid method/i); | ||
// }); | ||
// | ||
// it('has response properties', () => { | ||
// const res = enforcer.response('/'); | ||
// expect(Object.keys(res)).to.deep.equal(properties); | ||
// }) | ||
}); | ||
}); |
@@ -25,3 +25,3 @@ /** | ||
before(() => { | ||
enforcer = new OpenApiEnforcer('2.0'); | ||
enforcer = new OpenApiEnforcer('2.0', { serialize: { throw: true }}); | ||
}); | ||
@@ -71,3 +71,3 @@ | ||
it('object', () => { | ||
expect(() => enforcer.serialize(schema, {})).to.throw(Error); | ||
expect(() => enforcer.serialize(schema, {})).to.throw(/errors occurred during serialization/); | ||
}); | ||
@@ -143,3 +143,3 @@ | ||
it('invalid type', () => { | ||
expect(() => enforcer.serialize(schema, null)).to.throw(Error); | ||
expect(() => enforcer.serialize(schema, null)).to.throw(/errors occurred during serialization/); | ||
}); | ||
@@ -176,3 +176,3 @@ | ||
it('boolean', () => { | ||
expect(() => enforcer.serialize(schema, true)).to.throw(Error); | ||
expect(() => enforcer.serialize(schema, true)).to.throw(/errors occurred during serialization/); | ||
}); | ||
@@ -209,3 +209,3 @@ | ||
it('boolean', () => { | ||
expect(() => enforcer.serialize(schema, true)).to.throw(Error); | ||
expect(() => enforcer.serialize(schema, true)).to.throw(/errors occurred during serialization/); | ||
}); | ||
@@ -212,0 +212,0 @@ |
@@ -112,13 +112,13 @@ /** | ||
it('multiple accept', () => { | ||
const accept = 'text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8'; | ||
const store = ['text/plain', 'text/html', 'application/xml', 'application/xhtml']; | ||
const accept = 'text/html, application/abc+json, application/xml;q=0.9, */*;q=0.8'; | ||
const store = ['text/plain', 'text/html', 'application/json', 'application/xml']; | ||
const matches = util.findMediaMatch(accept, store); | ||
expect(matches).to.deep.equal(['text/html', 'application/xhtml', 'application/xml', 'text/plain']); | ||
expect(matches).to.deep.equal(['text/html', 'application/json', 'application/xml', 'text/plain']); | ||
}); | ||
it('specified extension match', () => { | ||
const accept = 'application/json+abc'; | ||
const store = ['application/json+xyz', 'application/json', 'application/json+abc']; | ||
const accept = 'application/abc+json'; | ||
const store = ['application/xyz+json', 'application/json', 'application/abc+json']; | ||
const matches = util.findMediaMatch(accept, store); | ||
expect(matches).to.deep.equal(['application/json+abc', 'application/json']); | ||
expect(matches).to.deep.equal(['application/abc+json', 'application/json']); | ||
}); | ||
@@ -128,5 +128,5 @@ | ||
const accept = 'application/json'; | ||
const store = ['application/json+xyz', 'application/json', 'application/json+abc']; | ||
const store = ['application/xyz+json', 'application/json', 'application/abc+json']; | ||
const matches = util.findMediaMatch(accept, store); | ||
expect(matches).to.deep.equal(['application/json', 'application/json+xyz', 'application/json+abc']); | ||
expect(matches).to.deep.equal(['application/json', 'application/xyz+json', 'application/abc+json']); | ||
}); | ||
@@ -133,0 +133,0 @@ |
@@ -76,3 +76,3 @@ /** | ||
parameters: [ | ||
{ name: 'name', in: 'path', required: true, schema: { type: 'string' }} | ||
{ name: 'name', in: 'path', required: true, type: 'string'} | ||
] | ||
@@ -97,4 +97,3 @@ } | ||
const params = instance.request(req); | ||
expect(params.errors).to.be.null; | ||
expect(params.request.body).to.deep.equal({ R: 100, G: 200, B: 150 }); | ||
expect(params.body).to.deep.equal({ R: 100, G: 200, B: 150 }); | ||
}); | ||
@@ -106,5 +105,3 @@ | ||
}); | ||
const params = instance.request(req); | ||
expect(params.errors.length).not.to.equal(0); | ||
expect(params.errors[0]).to.match(/The value must be numeric/i); | ||
expect(() => instance.request(req)).to.throw(/the value must be numeric/i); | ||
}); | ||
@@ -115,5 +112,3 @@ | ||
const instance = new enforcer(schema2, {}); | ||
const params = instance.request({ path: '/', method: 'put' }); | ||
expect(params.errors.length).to.equal(1); | ||
expect(params.errors[0]).to.match(/missing required body/i); | ||
expect(() => instance.request({ path: '/', method: 'put' })).to.throw(/missing required body/i); | ||
}); | ||
@@ -137,4 +132,3 @@ | ||
const params = instance.request(req); | ||
expect(params.errors).to.be.null; | ||
expect(params.request.headers['x-number']).to.equal(12345); | ||
expect(params.headers['x-number']).to.equal(12345); | ||
}); | ||
@@ -144,5 +138,3 @@ | ||
const req = request({ headers: { 'x-number': 'abc' } }); | ||
const params = instance.request(req); | ||
expect(params.errors).to.be.match(/value must be numeric/i); | ||
expect(params.request).to.be.null; | ||
expect(() => instance.request(req)).to.throw(/value must be numeric/i); | ||
}); | ||
@@ -155,4 +147,3 @@ | ||
const params = instance.request(req); | ||
expect(params.errors).to.be.null; | ||
expect(params.request.headers['x-number']).to.equal(10); | ||
expect(params.headers['x-number']).to.equal(10); | ||
}); | ||
@@ -164,5 +155,3 @@ | ||
const req = request({}); | ||
const params = instance.request(req); | ||
expect(params.errors[0]).to.match(/missing required header/i); | ||
expect(params.request).to.be.null; | ||
expect(() => instance.request(req)).to.throw(/missing required header/i); | ||
}); | ||
@@ -176,4 +165,3 @@ | ||
const params = instance.request({ path: '/12345'}); | ||
expect(params.errors).to.be.null; | ||
expect(params.request.path.name).to.equal('12345'); | ||
expect(params.params.name).to.equal('12345'); | ||
}); | ||
@@ -187,4 +175,3 @@ | ||
const params = instance.request({ path: '/?number=1&number=2'}); | ||
expect(params.errors).to.be.null; | ||
expect(params.request.query.number).to.equal(2); | ||
expect(params.query.number).to.equal(2); | ||
}); | ||
@@ -194,4 +181,3 @@ | ||
const params = instance.request({ path: '/?color=red,green,blue'}); | ||
expect(params.errors).to.be.null; | ||
expect(params.request.query.color).to.deep.equal(['red', 'green', 'blue']); | ||
expect(params.query.color).to.deep.equal(['red', 'green', 'blue']); | ||
}); | ||
@@ -203,4 +189,3 @@ | ||
const params = instance.request({ path: '/?color=red green%20blue'}); | ||
expect(params.errors).to.be.null; | ||
expect(params.request.query.color).to.deep.equal(['red', 'green', 'blue']); | ||
expect(params.query.color).to.deep.equal(['red', 'green', 'blue']); | ||
}); | ||
@@ -212,4 +197,3 @@ | ||
const params = instance.request({ path: '/?color=red\tgreen%09blue'}); | ||
expect(params.errors).to.be.null; | ||
expect(params.request.query.color).to.deep.equal(['red', 'green', 'blue']); | ||
expect(params.query.color).to.deep.equal(['red', 'green', 'blue']); | ||
}); | ||
@@ -221,4 +205,3 @@ | ||
const params = instance.request({ path: '/?color=red|green%7Cblue'}); | ||
expect(params.errors).to.be.null; | ||
expect(params.request.query.color).to.deep.equal(['red', 'green', 'blue']); | ||
expect(params.query.color).to.deep.equal(['red', 'green', 'blue']); | ||
}); | ||
@@ -230,6 +213,9 @@ | ||
const params = instance.request({ path: '/?color=red&color=green&color=blue'}); | ||
expect(params.errors).to.be.null; | ||
expect(params.request.query.color).to.deep.equal(['red', 'green', 'blue']); | ||
expect(params.query.color).to.deep.equal(['red', 'green', 'blue']); | ||
}); | ||
it('undefined parameter produces 400', () => { | ||
expect(() => instance.request({ path: '/?nope' })).to.throw(/unexpected query parameter/i); | ||
}); | ||
}); | ||
@@ -236,0 +222,0 @@ |
@@ -104,3 +104,5 @@ /** | ||
schema: { type: 'number' } | ||
} | ||
}, | ||
{ name: 'x', in: 'query', schema: { type: 'number' } }, | ||
{ name: 'y', in: 'query', schema: { type: 'number' } } | ||
] | ||
@@ -132,4 +134,3 @@ }, | ||
const params = instance.request(req); | ||
expect(params.errors).to.be.null; | ||
expect(params.request.body).to.equal('this is some text'); | ||
expect(params.body).to.equal('this is some text'); | ||
}); | ||
@@ -143,4 +144,3 @@ | ||
const params = instance.request(req); | ||
expect(params.errors).to.be.null; | ||
expect(params.request.body).to.deep.equal({ R: 100, G: 200, B: 150 }); | ||
expect(params.body).to.deep.equal({ R: 100, G: 200, B: 150 }); | ||
}); | ||
@@ -160,5 +160,4 @@ | ||
const params = instance.request(req); | ||
expect(params.errors).to.be.null; | ||
expect(+params.request.body.end).to.equal(+end); | ||
expect(+params.request.body.start).to.equal(+new Date('2000-01-01')); | ||
expect(+params.body.end).to.equal(+end); | ||
expect(+params.body.start).to.equal(+new Date('2000-01-01')); | ||
}); | ||
@@ -171,5 +170,3 @@ | ||
}); | ||
const params = instance.request(req); | ||
expect(params.errors.length).not.to.equal(0); | ||
expect(params.errors[0]).to.match(/expected a string/i); | ||
expect(() => instance.request(req)).to.throw(/expected a string/i); | ||
}); | ||
@@ -180,5 +177,3 @@ | ||
const instance = new enforcer(schema2, {}); | ||
const params = instance.request({ path: '/', method: 'put' }); | ||
expect(params.errors.length).to.equal(1); | ||
expect(params.errors[0]).to.match(/missing required request body/i); | ||
expect(() => instance.request({ path: '/', method: 'put' })).to.throw(/missing required request body/i); | ||
}); | ||
@@ -195,4 +190,3 @@ | ||
const params = instance.request(req); | ||
expect(params.errors).to.be.null; | ||
expect(params.request.cookies.user).to.deep.equal({ id: 12345, sessionStart: new Date(ds) }); | ||
expect(params.cookies.user).to.deep.equal({ id: 12345, sessionStart: new Date(ds) }); | ||
}); | ||
@@ -251,4 +245,3 @@ | ||
const params = instance.request(req); | ||
expect(params.errors).to.be.null; | ||
expect(params.request.headers['x-number']).to.equal(12345); | ||
expect(params.headers['x-number']).to.equal(12345); | ||
}); | ||
@@ -306,4 +299,3 @@ | ||
const params = instance.request(req); | ||
expect(params.errors).to.be.null; | ||
expect(params.request.path.name).to.equal('Bob'); | ||
expect(params.params.name).to.equal('Bob'); | ||
}); | ||
@@ -316,3 +308,3 @@ | ||
const params = instance.request(req); | ||
expect(params.request.path.name).to.equal('Bob'); | ||
expect(params.params.name).to.equal('Bob'); | ||
}); | ||
@@ -325,3 +317,3 @@ | ||
const params = instance.request(req); | ||
expect(params.request.path.name).to.equal('Bob'); | ||
expect(params.params.name).to.equal('Bob'); | ||
}); | ||
@@ -362,2 +354,6 @@ | ||
it('undefined parameter produces 400', () => { | ||
expect(() => instance.request({ path: '/?nope' })).to.throw(/unexpected query parameter/i); | ||
}); | ||
describe('default style (form)', () => { | ||
@@ -370,4 +366,3 @@ | ||
const params = instance.request(req); | ||
expect(params.errors).to.be.null; | ||
expect(params.request.query.color).to.equal(''); | ||
expect(params.query.color).to.equal(''); | ||
}); | ||
@@ -380,4 +375,3 @@ | ||
const params = instance.request(req); | ||
expect(params.errors).to.be.null; | ||
expect(params.request.query.color).to.equal('red'); | ||
expect(params.query.color).to.equal('red'); | ||
}); | ||
@@ -390,4 +384,3 @@ | ||
const params = instance.request(req); | ||
expect(params.errors).to.be.null; | ||
expect(params.request.query.color).to.deep.equal(['blue', 'black', 'brown']); | ||
expect(params.query.color).to.deep.equal(['blue', 'black', 'brown']); | ||
}); | ||
@@ -400,4 +393,3 @@ | ||
const params = instance.request(req); | ||
expect(params.errors).to.be.null; | ||
expect(params.request.query.color).to.deep.equal(['blue', 'black', 'brown']); | ||
expect(params.query.color).to.deep.equal(['blue', 'black', 'brown']); | ||
}); | ||
@@ -410,4 +402,3 @@ | ||
const params = instance.request(req); | ||
expect(params.errors).to.be.null; | ||
expect(params.request.query.color).to.deep.equal({ R: 100, G: 200, B: 150 }); | ||
expect(params.query.color).to.deep.equal({ R: 100, G: 200, B: 150 }); | ||
}); | ||
@@ -420,4 +411,3 @@ | ||
const params = instance.request(req); | ||
expect(params.errors).to.be.null; | ||
expect(params.request.query.color).to.deep.equal({ R: 100, G: 200, B: 150 }); | ||
expect(params.query.color).to.deep.equal({ R: 100, G: 200, B: 150 }); | ||
}); | ||
@@ -434,4 +424,3 @@ | ||
const params = instance.request(req); | ||
expect(params.errors).to.be.null; | ||
expect(params.request.query.color).to.deep.equal(['blue', 'black', 'brown']); | ||
expect(params.query.color).to.deep.equal(['blue', 'black', 'brown']); | ||
}); | ||
@@ -444,4 +433,3 @@ | ||
const params = instance.request(req); | ||
expect(params.errors).to.be.null; | ||
expect(params.request.query.color).to.deep.equal({ R: 100, G: 200, B: 150 }); | ||
expect(params.query.color).to.deep.equal({ R: 100, G: 200, B: 150 }); | ||
}); | ||
@@ -458,4 +446,3 @@ | ||
const params = instance.request(req); | ||
expect(params.errors).to.be.null; | ||
expect(params.request.query.color).to.deep.equal(['blue', 'black', 'brown']); | ||
expect(params.query.color).to.deep.equal(['blue', 'black', 'brown']); | ||
}); | ||
@@ -468,4 +455,3 @@ | ||
const params = instance.request(req); | ||
expect(params.errors).to.be.null; | ||
expect(params.request.query.color).to.deep.equal({ R: 100, G: 200, B: 150 }); | ||
expect(params.query.color).to.deep.equal({ R: 100, G: 200, B: 150 }); | ||
}); | ||
@@ -501,4 +487,3 @@ | ||
const params = instance.request(req); | ||
expect(params.errors).to.be.null; | ||
expect(params.request.query.color).to.deep.equal({ R: 100, G: 200, B: 150 }); | ||
expect(params.query.color).to.deep.equal({ R: 100, G: 200, B: 150 }); | ||
}); | ||
@@ -505,0 +490,0 @@ |
458786
48
7981
932
17