UNPKG

4.87 kBJavaScriptView Raw
1'use strict'
2
3const fs = require('fs')
4const util = require('./util')
5
6const HTTP_METHODS = [
7 'get',
8 'put',
9 'post',
10 'delete',
11 'options',
12 'head',
13 'patch'
14]
15
16const PARAM_GROUP = {
17 HEADER: 'header',
18 PATH: 'path',
19 QUERY: 'query',
20 BODY: 'body',
21 FORM_DATA: 'formData'
22}
23
24const PARAM_GROUPS =
25 Object.keys(PARAM_GROUP)
26 .map(k => PARAM_GROUP[k])
27
28exports.PARAM_GROUP = PARAM_GROUP
29exports.PARAM_GROUPS = PARAM_GROUPS
30exports.getSpec = getSpec
31exports.getSpecSync = getSpecSync
32exports.getAllOperations = getAllOperations
33exports.createPathOperation = createPathOperation
34
35function getSpec(spec) {
36 if (typeof spec === 'string') {
37 return loadSpec(spec).then(spec => applyDefaults(spec))
38 } else {
39 return Promise.resolve(applyDefaults(spec))
40 }
41}
42
43function getSpecSync(spec) {
44 if (typeof spec === 'string') {
45 spec = loadSpecSync(spec)
46 }
47 return applyDefaults(spec)
48}
49
50function loadSpec(specPath) {
51 return util.readFile(specPath)
52 .then(contents => util.parseFileContents(contents, specPath))
53}
54
55function loadSpecSync(specPath) {
56 const contents = fs.readFileSync(specPath)
57 return util.parseFileContents(contents, specPath)
58}
59
60function applyDefaults(spec) {
61 if (!spec.basePath) spec.basePath = '/'
62 return spec
63}
64
65function getAllOperations(spec) {
66 // we need to resolve refs so a deep copy is needed to avoid modifying the original
67 spec = JSON.parse(JSON.stringify(spec))
68 return getPaths(spec)
69 .reduce((ops, pathInfo) =>
70 ops.concat(getPathOperations(pathInfo, spec)), [])
71}
72
73function getPaths(spec) {
74 return Object.keys(spec.paths || {})
75 .map(path => Object.assign({ path }, spec.paths[path]))
76}
77
78function getPathOperations(pathInfo, spec) {
79 const xProps = getXProps(pathInfo)
80 return Object.keys(pathInfo)
81 .filter(key => HTTP_METHODS.indexOf(key) !== -1)
82 .map(method => createPathOperation(method, pathInfo, xProps, spec))
83}
84
85function getXProps(data) {
86 return Object.keys(data)
87 .filter(prop => prop.startsWith('x-'))
88 .reduce((xProps, prop) => {
89 xProps[prop] = data[prop]
90 return xProps
91 }, {})
92}
93
94function createPathOperation(method, pathInfo, pathsXProps, spec) {
95 const operationInfo = util.resolveSchemaRefs(pathInfo[method], spec)
96 if (!operationInfo.parameters) operationInfo.parameters = []
97 if (!operationInfo.responses) operationInfo.responses = {}
98 const operation = Object.assign({
99 id: operationInfo.operationId,
100 pkg: getPackageName(operationInfo),
101 path: pathInfo.path,
102 fullPath: `/${spec.basePath}/${pathInfo.path}`.replace(/\/{2,}/g,'/'),
103 consumes: getOperationProperty('consumes', operationInfo, spec),
104 produces: getOperationProperty('produces', operationInfo, spec),
105 paramGroupSchemas: createParamGroupSchemas(operationInfo.parameters, spec),
106 responseSchemas: createResponseSchemas(operationInfo.responses, spec),
107 method
108 }, pathsXProps, operationInfo)
109 delete operation.operationId
110 return operation
111}
112
113function getOperationProperty(prop, pathInfo, spec) {
114 return (pathInfo && pathInfo[prop]) ? pathInfo[prop] : spec[prop]
115}
116
117function createParamGroupSchemas(parameters) {
118 return PARAM_GROUPS
119 .map(loc => {
120 const params = parameters.filter(param => param.in === loc)
121 return { 'in': loc, schema: createParamsSchema(params, loc) }
122 })
123 .filter(param => Object.keys(param.schema.properties || {}).length)
124 .reduce((map, param) => {
125 map[param.in] = param.schema
126 return map
127 }, {})
128}
129
130function createParamsSchema(params, loc) {
131 if (loc === PARAM_GROUP.BODY) {
132 const param = params.shift() // there can only be a single body param
133 if (param) {
134 param.name = 'body' // using a consistent name helps with error reporting
135 if (param.schema) return param.schema
136 }
137 }
138 return {
139 type: 'object',
140 properties: params.reduce((props, param) => {
141 const p = Object.assign({}, param)
142 delete p.required
143 props[param.name] = p
144 return props
145 }, {}),
146 required: params
147 .filter(param => param.required)
148 .map(param => param.name)
149 }
150}
151
152function createResponseSchemas(responses, spec) {
153 return Object.keys(responses)
154 .map(id => ({
155 id,
156 bodySchema: responses[id].schema,
157 headersSchema: createResponseHeadersSchema(responses[id].headers, spec)
158 }))
159 .reduce((result, response) => {
160 result[response.id] = response
161 return result
162 }, {})
163}
164
165function createResponseHeadersSchema(headers) {
166 if (!headers) return undefined
167 return {
168 type: 'object',
169 properties: headers,
170 required: Object.keys(headers)
171 .filter(name => headers[name].required)
172 }
173}
174
175function getPackageName(op) {
176 if (!op.tags || !op.tags[0]) return 'default'
177
178 let pkg = op.tags[0].replace(/[^\w$]/g, '')
179 if (/^[^a-zA-Z_$]/.test(pkg)) {
180 pkg = `$${pkg}`
181 }
182 return pkg
183}