Spaces:
Runtime error
Runtime error
File size: 6,049 Bytes
23ac194 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 | 'use strict'
const fastClone = require('rfdc')({ circles: false, proto: true })
const { kSchemaVisited, kSchemaResponse } = require('./symbols')
const kFluentSchema = Symbol.for('fluent-schema-object')
const {
FST_ERR_SCH_MISSING_ID,
FST_ERR_SCH_ALREADY_PRESENT,
FST_ERR_SCH_DUPLICATE,
FST_ERR_SCH_CONTENT_MISSING_SCHEMA
} = require('./errors')
const SCHEMAS_SOURCE = ['params', 'body', 'querystring', 'query', 'headers']
function Schemas (initStore) {
this.store = initStore || {}
}
Schemas.prototype.add = function (inputSchema) {
const schema = fastClone((inputSchema.isFluentSchema || inputSchema.isFluentJSONSchema || inputSchema[kFluentSchema])
? inputSchema.valueOf()
: inputSchema
)
// developers can add schemas without $id, but with $def instead
const id = schema.$id
if (!id) {
throw new FST_ERR_SCH_MISSING_ID()
}
if (this.store[id]) {
throw new FST_ERR_SCH_ALREADY_PRESENT(id)
}
this.store[id] = schema
}
Schemas.prototype.getSchemas = function () {
return Object.assign({}, this.store)
}
Schemas.prototype.getSchema = function (schemaId) {
return this.store[schemaId]
}
/**
* Checks whether a schema is a non-plain object.
*
* @param {*} schema the schema to check
* @returns {boolean} true if schema has a custom prototype
*/
function isCustomSchemaPrototype (schema) {
return typeof schema === 'object' && Object.getPrototypeOf(schema) !== Object.prototype
}
function normalizeSchema (routeSchemas, serverOptions) {
if (routeSchemas[kSchemaVisited]) {
return routeSchemas
}
// alias query to querystring schema
if (routeSchemas.query) {
// check if our schema has both querystring and query
if (routeSchemas.querystring) {
throw new FST_ERR_SCH_DUPLICATE('querystring')
}
routeSchemas.querystring = routeSchemas.query
}
generateFluentSchema(routeSchemas)
for (const key of SCHEMAS_SOURCE) {
const schema = routeSchemas[key]
if (schema && !isCustomSchemaPrototype(schema)) {
if (key === 'body' && schema.content) {
const contentProperty = schema.content
const keys = Object.keys(contentProperty)
for (let i = 0; i < keys.length; i++) {
const contentType = keys[i]
const contentSchema = contentProperty[contentType].schema
if (!contentSchema) {
throw new FST_ERR_SCH_CONTENT_MISSING_SCHEMA(contentType)
}
}
continue
}
}
}
if (routeSchemas.response) {
const httpCodes = Object.keys(routeSchemas.response)
for (const code of httpCodes) {
if (isCustomSchemaPrototype(routeSchemas.response[code])) {
continue
}
const contentProperty = routeSchemas.response[code].content
if (contentProperty) {
const keys = Object.keys(contentProperty)
for (let i = 0; i < keys.length; i++) {
const mediaName = keys[i]
if (!contentProperty[mediaName].schema) {
throw new FST_ERR_SCH_CONTENT_MISSING_SCHEMA(mediaName)
}
}
}
}
}
routeSchemas[kSchemaVisited] = true
return routeSchemas
}
function generateFluentSchema (schema) {
for (const key of SCHEMAS_SOURCE) {
if (schema[key] && (schema[key].isFluentSchema || schema[key][kFluentSchema])) {
schema[key] = schema[key].valueOf()
}
}
if (schema.response) {
const httpCodes = Object.keys(schema.response)
for (const code of httpCodes) {
if (schema.response[code].isFluentSchema || schema.response[code][kFluentSchema]) {
schema.response[code] = schema.response[code].valueOf()
}
}
}
}
/**
* Search for the right JSON schema compiled function in the request context
* setup by the route configuration `schema.response`.
* It will look for the exact match (eg 200) or generic (eg 2xx)
*
* @param {object} context the request context
* @param {number} statusCode the http status code
* @param {string} [contentType] the reply content type
* @returns {function|false} the right JSON Schema function to serialize
* the reply or false if it is not set
*/
function getSchemaSerializer (context, statusCode, contentType) {
const responseSchemaDef = context[kSchemaResponse]
if (!responseSchemaDef) {
return false
}
if (responseSchemaDef[statusCode]) {
if (responseSchemaDef[statusCode].constructor === Object && contentType) {
const mediaName = contentType.split(';', 1)[0]
if (responseSchemaDef[statusCode][mediaName]) {
return responseSchemaDef[statusCode][mediaName]
}
// fallback to match all media-type
if (responseSchemaDef[statusCode]['*/*']) {
return responseSchemaDef[statusCode]['*/*']
}
return false
}
return responseSchemaDef[statusCode]
}
const fallbackStatusCode = (statusCode + '')[0] + 'xx'
if (responseSchemaDef[fallbackStatusCode]) {
if (responseSchemaDef[fallbackStatusCode].constructor === Object && contentType) {
const mediaName = contentType.split(';', 1)[0]
if (responseSchemaDef[fallbackStatusCode][mediaName]) {
return responseSchemaDef[fallbackStatusCode][mediaName]
}
// fallback to match all media-type
if (responseSchemaDef[fallbackStatusCode]['*/*']) {
return responseSchemaDef[fallbackStatusCode]['*/*']
}
return false
}
return responseSchemaDef[fallbackStatusCode]
}
if (responseSchemaDef.default) {
if (responseSchemaDef.default.constructor === Object && contentType) {
const mediaName = contentType.split(';', 1)[0]
if (responseSchemaDef.default[mediaName]) {
return responseSchemaDef.default[mediaName]
}
// fallback to match all media-type
if (responseSchemaDef.default['*/*']) {
return responseSchemaDef.default['*/*']
}
return false
}
return responseSchemaDef.default
}
return false
}
module.exports = {
buildSchemas (initStore) { return new Schemas(initStore) },
getSchemaSerializer,
normalizeSchema
}
|