mares-swagger-spec-maker
Version:
mares framework ���조에 맞춰서openapi spec을 combine 해주는 모듈입니다.
382 lines (308 loc) • 9.96 kB
JavaScript
module.exports = {
/**
* tcomb를 wrapping한 도메인 class의 schema를 읽어서 swagger의 schema로 변환해준다. 단 required반영은 아직 하지 않았음.
* @param {Object} Domain - tcomb형태의 schema struct 객체
* @return {Object} swagger에서 사용하는 schema객체 - {key: {type: 'string'}}
*/
convertDomainToSchema: (Domain) => {
// Domain = require('../../message-context/domains/test').Root;
let parent = {}
let isRoot = true
let recursive = (wrapper, prop, maybe = false, required = null) => {
let isSearched = true
// 최초 기본 프리미티브 데이터 타입을 찾는다.
switch (prop.displayName) {
case 'String':
break
case 'Number':
break
case 'Integer':
break
case 'Boolean':
break
case 'Object':
break
case 'Date':
break
default:
// 프리미티브 데이터 타입을 찾지 못한 경우 list, enums, maybe, struct 인지를 찾아야 한다.
isSearched = false
}
// 프리미티브일 경우
if (isSearched === true) {
if (prop.displayName === 'Date') {
// Date인 경우 type: string, format: date로 해야한다.
wrapper.type = 'string'
wrapper.format = 'date'
}
else if (prop.displayName === 'Object') {
wrapper.type = 'object'
wrapper.additionalProperties = true
}
else {
// type을 소문자로 바꾸고, 필수값 여부를 체크한다.
wrapper.type = prop.displayName.toLowerCase()
}
if (maybe === true) wrapper.type = '?' + wrapper.type
}
// 프리미티브가 아닐경우, struct, enums, maybe, list인경우, 그외 오류
else {
let meta = prop.meta
switch (prop.meta.kind) {
case 'enums':
// enums는 예외 프리미티브로 취급
wrapper.type = 'string'
wrapper.enum = Object.values(meta.map)
if (maybe === true) wrapper.type = '?' + wrapper.type
break
case 'maybe':
recursive(wrapper, meta.type, true)
break
case 'list':
wrapper.type = 'array'
wrapper.items = {}
if (maybe === true) wrapper.type = '?' + wrapper.type
isRoot = false
recursive(wrapper.items, meta.type)
break
case 'struct':
// 이름을 지정하지 않았거나 root인 경우 다시 재귀호출
if (isRoot === true || prop.displayName.indexOf('Struct{') > -1 && prop.displayName.substr(prop.displayName.length - 1, 1) === '}') {
wrapper.type = 'object'
wrapper.properties = {}
wrapper.required = []
if (maybe === true) wrapper.type = '?' + wrapper.type
for (let k in meta.props) {
wrapper.properties[k] = {}
isRoot = false
recursive(wrapper.properties[k], meta.props[k])
}
}
// 이름을 지정한 경우 components로 대체
else {
wrapper['$ref'] = '#/components/schemas/' + prop.displayName
}
break
}
}
}
recursive(parent, Domain)
return parent
},
/**
* convertDomainToSchema로 만들어진 객체에 ? 를 검사하여 required에 반영해준다.
* @param {Object} schema - swagger schema spec - {key: {type: 'string'}}
* @return {Object} required가 추가된 swagger schema.
*/
updateRequiredType: (schema) => {
let recursive = (schema) => {
if (schema.type === 'object') {
let properties = schema.properties
if (!schema.required) schema.required = []
for (let k in properties) {
let type = properties[k].type
// $ref가 아니라면
if (type) {
if (type.indexOf('?') === -1) {
schema.required.push(k)
} else {
properties[k].type = type.replace('?', '')
}
recursive(properties[k])
}
}
}
else if (schema.type === 'array') {
recursive(schema.items)
}
}
recursive(schema)
return schema
},
/**
* components.schemas에 해당 스키마를 추가한다. (같은 키값이면 덮어쓴다.)
* @param {Object} components - swagger root.components spec
* @param {Object} schemas - swagger schema spec - {key: {type: 'string'}}
* @return {Object} schema가 추가된 swagger components spec
*/
addSchemasToComponents: (components, schemas) => {
let compSchemas = components.schemas
for (let schemaKey in schemas) {
if (schemas.hasOwnProperty(schemaKey)) {
compSchemas[schemaKey] = schemas[schemaKey]
}
}
components.schemas = compSchemas
return components
},
/**
* path안에 각 method 부분에 security를 추가한다.
* @param {Object} path - 버전을 추가할 bounded-context의 api path spec.
* @param {string} securitySchemes - swagger securitySchemes spec
* @return {Object} version이 추가된 swagger path spec.
*/
addSecurityToPath: (path, securitySchemes) => {
let ret = path
for (let key in path) {
for (let key2 in ret[key]) {
let methodObj = ret[key][key2]
if (!methodObj.security) methodObj.security = []
for (let security in securitySchemes) {
let securityObj = {}
securityObj[security] = []
methodObj.security.push(securityObj)
}
}
}
return ret
},
/**
* path안에 각 method 부분에 security를 추가한다.
* @param {Object} path - 버전을 추가할 bounded-context의 api path spec.
* @param {Object} version - version spec.
* @return {Object} version이 추가된 swagger path spec.
*/
addVersionToPath: (path, version) => {
let ret = path
for (let key in path) {
for (let key2 in ret[key]) {
let methodObj = ret[key][key2]
if (!methodObj.parameters) methodObj.parameters = []
let isFoundVersion = false
for (let i = 0; i < methodObj.parameters.length; ++i) {
if (methodObj.parameters[i].name === 'version') {
isFoundVersion = true
}
}
if (!isFoundVersion) {
methodObj.parameters.push(version)
}
}
}
return ret
},
/**
* root spec과 bc spec을 merge한다. bc spec에는 components와 tags가 있다. 키값이 같으면 나중에 호출한 쪽에서 덮어쓴다.
* @param {Object} root - swagger root spec
* @param {Object} bc - swagger bounded-context spec
* @return {Object} schema가 추가된 swagger root spec
*/
mergeRootAndBoundedContext: (root, bc) => {
let ret = root
if (!ret.components) ret.components = {}
if (!ret.tags) ret.tags = []
for (let key in ret) {
if (key === 'components' && bc && bc.components) {
for (let compKey in bc.components) {
let compSort = bc.components[compKey]
for (let sortKey in compSort) {
if (!ret.components[compKey]) ret.components[compKey] = {}
ret.components[compKey][sortKey] = compSort[sortKey]
}
}
}
if (key === 'tags' && bc && bc.tags) {
for (let i = 0; i < bc.tags.length; ++i) {
let bcTagName = bc.tags[i].name
let searchIdx = -1
for (let j = 0; j < ret.tags.length; ++j) {
if (ret.tags[j].name === bcTagName) {
searchIdx = j
break
}
}
if (searchIdx > -1) {
ret.tags[searchIdx] = bc.tags[i]
} else {
ret.tags.push(bc.tags[i])
}
}
}
}
return ret
},
/**
* root spec과 path spec을 merge한다.
* @param {Object} root - swagger root spec
* @param {Object} paths - swagger path spec - {url: {get: {}, post: {}}, url2: {get: {}, post: {}}}
* @return {Object} path가 추가된 swagger root spec
*/
mergeRootAndPath: (root, paths) => {
let ret = root
if (!ret.paths) ret.paths = {}
for (let res in paths) {
for (let method in paths[res]) {
if (!ret.paths[res]) ret.paths[res] = {}
ret.paths[res][method] = paths[res][method]
}
}
return ret
},
/**
* 모든 paths의 리소스명을 돌아서 앞에 prefix를 붙여준다.
* @param {Object} paths - swagger paths spec
* @param {string} prefix - 리소스명 앞의 접두어, '/' 문자 포함 해야함
* @return {Object} path에 prefix가 추가된 swagger root spec
*/
addResourcePrefixInPaths: (paths, prefix) => {
if (prefix.indexOf('/') !== 0) {
prefix = '/' + prefix
}
let ret = {}
for(let prop in paths){
ret[prefix + prop] = paths[prop]
}
return ret
},
/**
* swagger3.0 spec에서 components의 값을 실제 스펙안으로 대치시킨다.
* @param {Object} rootSpec - swagger 3.0 spec,
*/
replaceAllComponents: (rootSpec) => {
let spec = _.cloneDeep(rootSpec)
var cnt = 0
var responsesCnt = 0
/**
* 컴포넌트 스트링을 찾아서 실제 객체로 변환해준다. (예 - {$ref: '#/components/parameters/AccessToken'} -> {...real schema})
* @param {Object | Array} parent - 부모객체
* @param {*} obj - 변환할객체
* @param {Object} components - swagger components
*/
const doRecursive = (parent, obj, components, isResponses = false) => {
cnt++
if (obj instanceof Object || obj instanceof Array) {
for (let key in obj) {
let inner = obj[key]
if (inner instanceof Object) {
if (key === 'responses'){
doRecursive(obj, inner, components, true)
} else if (inner['$ref']) {
let componentString = inner['$ref']
let splitedString = componentString.split('/')
let componentName = splitedString[2]
let componentKey = splitedString[3]
if(isResponses){
if (components[componentName]) {
delete obj[key]['$ref']
obj[key] = {description: `check ${componentName} ${componentKey}`}
}
} else {
if (components[componentName]) {
let realSpecObject = components[componentName][componentKey]
delete obj[key]['$ref']
obj[key] = {...obj[key], ..._.cloneDeep(realSpecObject)}
}
}
} else {
doRecursive(obj, inner, components, isResponses)
}
} else {
doRecursive(obj, inner, components, isResponses)
}
}
}
}
doRecursive(null, spec.paths, spec.components, false)
return spec
}
}