UNPKG

sg-openapi-validator

Version:

openapi3.0 schema��� 이용해서 express로 들어오는 req의 값들을 validation 한다.

250 lines (223 loc) 6.83 kB
const validator = require('validator') const _ = require('lodash') var Validator = require('jsonschema').Validator var v = new Validator() const sc = require('sg-schema-casting') const utils = { codes: { invalidInteger: 'invalidInteger', invalidNumber: 'invalidNumber', invalidString: 'invalidString', invalidObject: 'invalidObject', invalidArray: 'invalidArray', invalidBoolean: 'invalidBoolean', invalidNull: 'invalidNull', invalidEnum: 'invalidEnum', invalidRequired: 'invalidRequired', invalidMinLength: 'invalidMinLength', invalidMaxLength: 'invalidMaxLength', invalidMinimum: 'invalidMinimum', invalidMaximum: 'invalidMaximum', invalidPattern: 'invalidPattern' }, /** * 실제 요청받은 url을 open api 용 route url로 변환해준다. * @param {string} url - 실제 요청받은 url * @param {Object} paths - open api paths array spec. * @returns {string} open api route url. */ replaceRealKeywordToOpenAPIKeyword: (url, paths) => { for (let key in paths) { let temp = key let isSearched = false if (paths.hasOwnProperty(key)) { let keySplit = key.split('/') let urlSplit = url.split('/') if (urlSplit.length === keySplit.length) { for (let i = 0; i < urlSplit.length; ++i) { if (urlSplit[i] === keySplit[i] || keySplit[i].indexOf('{') >= 0) { isSearched = true } else { isSearched = false break } } } if (isSearched === true) { url = temp break } } } return url }, /** * 익스프레스 용 router url을 open api 용으로 바꿔준다. * @param {string} url - express route url * @returns {string} open api route url */ replaceExpressKeywordToOpenAPIKeyword: (url) => { while (url.indexOf(':') !== -1) { let start = url.indexOf(':') let end = start + url.substr(start + 1, url.length + 1 - start).indexOf('/') + 1 if (start === end) { end = url.length } if (start !== -1 && end !== -1 && start < end) { let keyword = url.substr(start, end - start) keyword = keyword.replace(':', '{') keyword = keyword + '}' url = url.substr(0, start) + keyword + url.substr(end, url.length - end) } } return url }, /** * nullable: true면 type을 배열로 "null" 추가해 주는 함수. * @param {Object} schema - jsonschema object * @returns {Object} schema - 변환된 jsonschema object */ replaceNullable(schema) { if (schema.type instanceof Array) { schema.type = schema.type[0] } if (schema.type === 'object') { if (schema.nullable === true) { schema.type = [schema.type, 'null'] } let properties = schema.properties for (let key in properties) { let prop = properties[key] utils.replaceNullable(prop) } } else if (schema.type === 'array') { if (schema.nullable === true) { schema.type = [schema.type, 'null'] } utils.replaceNullable(schema.items) } else if (schema.oneOf) { for (let i = 0; i < schema.oneOf.length; ++i) { schema.oneOf[i] = utils.replaceNullable(schema.oneOf[i]) } } else if (schema.anyOf) { for (let i = 0; i < schema.anyOf.length; ++i) { schema.anyOf[i] = utils.replaceNullable(schema.anyOf[i]) } } else { if (schema.nullable === true) { schema.type = [schema.type, 'null'] } } return schema }, /** * allOf 키워드를 쓴 schema를 하나로 합쳐준다. 반드시 allOf에는 같은 schmea 배열이 있어야한다. * @param {Object} schema - jsonschema object * @returns {Object} schema - 하나로 합쳐진 schema */ combineAllOf(schema) { try { const recursive = (parent, key, schema) => { if (schema instanceof Object || schema instanceof Array) { for (let schemaKey in schema) { if (schemaKey === 'allOf') { let schemaArr = schema[schemaKey] let fullSchema = schemaArr[0] for (let i = 1; i < schemaArr.length; ++i) { let schemaItem = schemaArr[i] fullSchema = _.merge(fullSchema, schemaItem) } parent[key] = fullSchema } else { recursive(schema, schemaKey, schema[schemaKey]) } } } } let parent = { 'root': schema } // 3depth 까지 변환. for (let i = 0; i < 3; ++i) { recursive(parent, 'root', parent['root']) } return parent.root } catch (e) { throw e } }, /** * jsonschema error를 정규화해준다. * @param {Object} instance - 비교할 실제 값 * @param {Object} schema - jsonschema * @param {string} inputName - query, headers, params, cookie, body * @param {string} name - parameter or body key * @returns {Array} */ getErrors: (instance, schema, inputName, name) => { schema = _.cloneDeep(schema) schema = utils.combineAllOf(schema) schema = utils.replaceNullable(schema) let errors = [] let result = v.validate(instance, schema) let resultErrors = result.errors if (resultErrors && resultErrors.length > 0) { for (let j = 0; j < resultErrors.length; ++j) { let err = resultErrors[j] let typeName = err.name if (err.property) { if (err.property.indexOf('instance.') !== -1) { name = err.property.replace('instance.', '') } } if (!typeName || typeName === 'type') { typeName = err.schema.type } else { if ((err.argument instanceof String) || (err.argument instanceof Number) || (err.argument instanceof Boolean)) { name = err.argument } } if (typeName && typeName instanceof Array && typeName.length > 1) { typeName = typeName[0] } let firstName = typeName.substr(0, 1) let otherName = typeName.substr(1, typeName.length - 1) firstName = firstName.toUpperCase() let fullName = firstName + otherName const errObj = { code: 'invalid' + fullName, msg: 'invalid' + fullName, domain: inputName, param: name, value: instance } if (err.schema && err.schema.code) { errObj.code = err.schema.code errObj.msg = err.schema.code } errors.push(errObj) } } return errors }, /** * swagger schema의 parameters 객체 스펙과 express의 req 객체를 받아서 spec에 정의된 데이터타입으로 query, params의 값들을 변경한다. (params, query에는 모두 string만 들어있다.) * @param {Object[]} parameters - swagger spec parameter array * @param {Object} req - express request object */ convertParameterType: (parameters, req) => { parameters.map((parameter) => { let inName = parameter.in if (inName === 'path') inName = 'params' if (inName === 'header') inName = 'headers' if (req[inName]) { let value = req[inName][parameter.name] if (value) { req[inName][parameter.name] = sc(parameter.schema, value) } } }) return req } } module.exports = utils