sg-openapi-validator
Version:
openapi3.0 schema��� 이용해서 express로 들어오는 req의 값들을 validation 한다.
250 lines (223 loc) • 6.83 kB
JavaScript
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