1 | 'use strict'
|
2 |
|
3 | const fs = require('fs')
|
4 | const util = require('./util')
|
5 |
|
6 | const HTTP_METHODS = [
|
7 | 'get',
|
8 | 'put',
|
9 | 'post',
|
10 | 'delete',
|
11 | 'options',
|
12 | 'head',
|
13 | 'patch'
|
14 | ]
|
15 |
|
16 | const PARAM_GROUP = {
|
17 | HEADER: 'header',
|
18 | PATH: 'path',
|
19 | QUERY: 'query',
|
20 | BODY: 'body',
|
21 | FORM_DATA: 'formData'
|
22 | }
|
23 |
|
24 | const PARAM_GROUPS =
|
25 | Object.keys(PARAM_GROUP)
|
26 | .map(k => PARAM_GROUP[k])
|
27 |
|
28 | exports.PARAM_GROUP = PARAM_GROUP
|
29 | exports.PARAM_GROUPS = PARAM_GROUPS
|
30 | exports.getSpec = getSpec
|
31 | exports.getSpecSync = getSpecSync
|
32 | exports.getAllOperations = getAllOperations
|
33 | exports.createPathOperation = createPathOperation
|
34 |
|
35 | function 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 |
|
43 | function getSpecSync(spec) {
|
44 | if (typeof spec === 'string') {
|
45 | spec = loadSpecSync(spec)
|
46 | }
|
47 | return applyDefaults(spec)
|
48 | }
|
49 |
|
50 | function loadSpec(specPath) {
|
51 | return util.readFile(specPath)
|
52 | .then(contents => util.parseFileContents(contents, specPath))
|
53 | }
|
54 |
|
55 | function loadSpecSync(specPath) {
|
56 | const contents = fs.readFileSync(specPath)
|
57 | return util.parseFileContents(contents, specPath)
|
58 | }
|
59 |
|
60 | function applyDefaults(spec) {
|
61 | if (!spec.basePath) spec.basePath = '/'
|
62 | return spec
|
63 | }
|
64 |
|
65 | function getAllOperations(spec) {
|
66 |
|
67 | spec = JSON.parse(JSON.stringify(spec))
|
68 | return getPaths(spec)
|
69 | .reduce((ops, pathInfo) =>
|
70 | ops.concat(getPathOperations(pathInfo, spec)), [])
|
71 | }
|
72 |
|
73 | function getPaths(spec) {
|
74 | return Object.keys(spec.paths || {})
|
75 | .map(path => Object.assign({ path }, spec.paths[path]))
|
76 | }
|
77 |
|
78 | function 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 |
|
85 | function 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 |
|
94 | function 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 |
|
113 | function getOperationProperty(prop, pathInfo, spec) {
|
114 | return (pathInfo && pathInfo[prop]) ? pathInfo[prop] : spec[prop]
|
115 | }
|
116 |
|
117 | function 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 |
|
130 | function createParamsSchema(params, loc) {
|
131 | if (loc === PARAM_GROUP.BODY) {
|
132 | const param = params.shift()
|
133 | if (param) {
|
134 | param.name = 'body'
|
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 |
|
152 | function 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 |
|
165 | function 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 |
|
175 | function 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 | }
|