1 | 'use strict'
|
2 |
|
3 | const fastClone = require('rfdc')({ circles: false, proto: true })
|
4 | const { kSchemaVisited, kSchemaResponse } = require('./symbols')
|
5 | const kFluentSchema = Symbol.for('fluent-schema-object')
|
6 |
|
7 | const {
|
8 | FST_ERR_SCH_MISSING_ID,
|
9 | FST_ERR_SCH_ALREADY_PRESENT,
|
10 | FST_ERR_SCH_DUPLICATE,
|
11 | FST_ERR_SCH_CONTENT_MISSING_SCHEMA
|
12 | } = require('./errors')
|
13 |
|
14 | const SCHEMAS_SOURCE = ['params', 'body', 'querystring', 'query', 'headers']
|
15 |
|
16 | function Schemas (initStore) {
|
17 | this.store = initStore || {}
|
18 | }
|
19 |
|
20 | Schemas.prototype.add = function (inputSchema) {
|
21 | const schema = fastClone((inputSchema.isFluentSchema || inputSchema.isFluentJSONSchema || inputSchema[kFluentSchema])
|
22 | ? inputSchema.valueOf()
|
23 | : inputSchema
|
24 | )
|
25 |
|
26 |
|
27 | const id = schema.$id
|
28 | if (!id) {
|
29 | throw new FST_ERR_SCH_MISSING_ID()
|
30 | }
|
31 |
|
32 | if (this.store[id]) {
|
33 | throw new FST_ERR_SCH_ALREADY_PRESENT(id)
|
34 | }
|
35 |
|
36 | this.store[id] = schema
|
37 | }
|
38 |
|
39 | Schemas.prototype.getSchemas = function () {
|
40 | return Object.assign({}, this.store)
|
41 | }
|
42 |
|
43 | Schemas.prototype.getSchema = function (schemaId) {
|
44 | return this.store[schemaId]
|
45 | }
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 | function isCustomSchemaPrototype (schema) {
|
54 | return typeof schema === 'object' && Object.getPrototypeOf(schema) !== Object.prototype
|
55 | }
|
56 |
|
57 | function normalizeSchema (routeSchemas, serverOptions) {
|
58 | if (routeSchemas[kSchemaVisited]) {
|
59 | return routeSchemas
|
60 | }
|
61 |
|
62 |
|
63 | if (routeSchemas.query) {
|
64 |
|
65 | if (routeSchemas.querystring) {
|
66 | throw new FST_ERR_SCH_DUPLICATE('querystring')
|
67 | }
|
68 | routeSchemas.querystring = routeSchemas.query
|
69 | }
|
70 |
|
71 | generateFluentSchema(routeSchemas)
|
72 |
|
73 | for (const key of SCHEMAS_SOURCE) {
|
74 | const schema = routeSchemas[key]
|
75 | if (schema && !isCustomSchemaPrototype(schema)) {
|
76 | routeSchemas[key] = getSchemaAnyway(schema, serverOptions.jsonShorthand)
|
77 | }
|
78 | }
|
79 |
|
80 | if (routeSchemas.response) {
|
81 | const httpCodes = Object.keys(routeSchemas.response)
|
82 | for (const code of httpCodes) {
|
83 | if (isCustomSchemaPrototype(routeSchemas.response[code])) {
|
84 | continue
|
85 | }
|
86 |
|
87 | const contentProperty = routeSchemas.response[code].content
|
88 |
|
89 | let hasContentMultipleContentTypes = false
|
90 | if (contentProperty) {
|
91 | const keys = Object.keys(contentProperty)
|
92 | for (let i = 0; i < keys.length; i++) {
|
93 | const mediaName = keys[i]
|
94 | if (!contentProperty[mediaName].schema) {
|
95 | if (keys.length === 1) { break }
|
96 | throw new FST_ERR_SCH_CONTENT_MISSING_SCHEMA(mediaName)
|
97 | }
|
98 | routeSchemas.response[code].content[mediaName].schema = getSchemaAnyway(contentProperty[mediaName].schema, serverOptions.jsonShorthand)
|
99 | if (i === keys.length - 1) {
|
100 | hasContentMultipleContentTypes = true
|
101 | }
|
102 | }
|
103 | }
|
104 |
|
105 | if (!hasContentMultipleContentTypes) {
|
106 | routeSchemas.response[code] = getSchemaAnyway(routeSchemas.response[code], serverOptions.jsonShorthand)
|
107 | }
|
108 | }
|
109 | }
|
110 |
|
111 | routeSchemas[kSchemaVisited] = true
|
112 | return routeSchemas
|
113 | }
|
114 |
|
115 | function generateFluentSchema (schema) {
|
116 | for (const key of SCHEMAS_SOURCE) {
|
117 | if (schema[key] && (schema[key].isFluentSchema || schema[key][kFluentSchema])) {
|
118 | schema[key] = schema[key].valueOf()
|
119 | }
|
120 | }
|
121 |
|
122 | if (schema.response) {
|
123 | const httpCodes = Object.keys(schema.response)
|
124 | for (const code of httpCodes) {
|
125 | if (schema.response[code].isFluentSchema || schema.response[code][kFluentSchema]) {
|
126 | schema.response[code] = schema.response[code].valueOf()
|
127 | }
|
128 | }
|
129 | }
|
130 | }
|
131 |
|
132 | function getSchemaAnyway (schema, jsonShorthand) {
|
133 | if (!jsonShorthand || schema.$ref || schema.oneOf || schema.allOf || schema.anyOf || schema.$merge || schema.$patch) return schema
|
134 | if (!schema.type && !schema.properties) {
|
135 | return {
|
136 | type: 'object',
|
137 | properties: schema
|
138 | }
|
139 | }
|
140 | return schema
|
141 | }
|
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 | function getSchemaSerializer (context, statusCode, contentType) {
|
155 | const responseSchemaDef = context[kSchemaResponse]
|
156 | if (!responseSchemaDef) {
|
157 | return false
|
158 | }
|
159 | if (responseSchemaDef[statusCode]) {
|
160 | if (responseSchemaDef[statusCode].constructor === Object && contentType) {
|
161 | const mediaName = contentType.split(';', 1)[0]
|
162 | if (responseSchemaDef[statusCode][mediaName]) {
|
163 | return responseSchemaDef[statusCode][mediaName]
|
164 | }
|
165 |
|
166 | return false
|
167 | }
|
168 | return responseSchemaDef[statusCode]
|
169 | }
|
170 | const fallbackStatusCode = (statusCode + '')[0] + 'xx'
|
171 | if (responseSchemaDef[fallbackStatusCode]) {
|
172 | if (responseSchemaDef[fallbackStatusCode].constructor === Object && contentType) {
|
173 | const mediaName = contentType.split(';', 1)[0]
|
174 | if (responseSchemaDef[fallbackStatusCode][mediaName]) {
|
175 | return responseSchemaDef[fallbackStatusCode][mediaName]
|
176 | }
|
177 |
|
178 | return false
|
179 | }
|
180 |
|
181 | return responseSchemaDef[fallbackStatusCode]
|
182 | }
|
183 | if (responseSchemaDef.default) {
|
184 | if (responseSchemaDef.default.constructor === Object && contentType) {
|
185 | const mediaName = contentType.split(';', 1)[0]
|
186 | if (responseSchemaDef.default[mediaName]) {
|
187 | return responseSchemaDef.default[mediaName]
|
188 | }
|
189 |
|
190 | return false
|
191 | }
|
192 |
|
193 | return responseSchemaDef.default
|
194 | }
|
195 | return false
|
196 | }
|
197 |
|
198 | module.exports = {
|
199 | buildSchemas (initStore) { return new Schemas(initStore) },
|
200 | getSchemaSerializer,
|
201 | normalizeSchema
|
202 | }
|