-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
/
schemas.js
153 lines (128 loc) · 4.22 KB
/
schemas.js
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
'use strict'
const fastClone = require('rfdc')({ circles: false, proto: true })
const {
codes: {
FST_ERR_SCH_MISSING_ID,
FST_ERR_SCH_ALREADY_PRESENT,
FST_ERR_SCH_NOT_PRESENT,
FST_ERR_SCH_DUPLICATE
}
} = require('./errors')
const URI_NAME_FRAGMENT = /^#[A-Za-z]{1}[\w-:.]{0,}$/
function Schemas () {
this.store = {}
}
Schemas.prototype.add = function (inputSchema) {
const schema = fastClone(inputSchema)
const id = schema['$id']
if (id === undefined) {
throw new FST_ERR_SCH_MISSING_ID()
}
if (this.store[id] !== undefined) {
throw new FST_ERR_SCH_ALREADY_PRESENT(id)
}
this.store[id] = this.resolveRefs(schema, true)
}
Schemas.prototype.resolve = function (id) {
if (this.store[id] === undefined) {
throw new FST_ERR_SCH_NOT_PRESENT(id)
}
return this.store[id]
}
Schemas.prototype.resolveRefs = function (routeSchemas, dontClearId) {
// 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
}
// let's check if our schemas have a custom prototype
for (const key of ['headers', 'querystring', 'params', 'body']) {
if (typeof routeSchemas[key] === 'object' && Object.getPrototypeOf(routeSchemas[key]) !== Object.prototype) {
return routeSchemas
}
}
try {
// this will work only for standard json schemas
// other compilers such as Joi will fail
this.traverse(routeSchemas)
// when a plugin uses the 'skip-override' and call addSchema
// the same JSON will be pass throug all the avvio tree. In this case
// it is not possible clean the id. The id will be cleared
// in the startup phase by the call of validation.js. Details PR #1496
if (dontClearId !== true) {
this.cleanId(routeSchemas)
}
} catch (err) {
// if we have failed because `resolve has thrown
// let's rethrow the error and let avvio handle it
if (/FST_ERR_SCH_*/.test(err.code)) throw err
}
if (routeSchemas.headers) {
routeSchemas.headers = this.getSchemaAnyway(routeSchemas.headers)
}
if (routeSchemas.querystring) {
routeSchemas.querystring = this.getSchemaAnyway(routeSchemas.querystring)
}
if (routeSchemas.params) {
routeSchemas.params = this.getSchemaAnyway(routeSchemas.params)
}
return routeSchemas
}
Schemas.prototype.traverse = function (schema) {
for (var key in schema) {
// resolve the `sharedSchemaId#' only if is not a standard $ref JSON Pointer
if (typeof schema[key] === 'string' && key !== '$schema' && key !== '$ref' && schema[key].slice(-1) === '#') {
schema[key] = this.resolve(schema[key].slice(0, -1))
}
if (schema[key] !== null && typeof schema[key] === 'object') {
this.traverse(schema[key])
}
}
}
Schemas.prototype.cleanId = function (schema) {
for (var key in schema) {
if (key === '$id' && !URI_NAME_FRAGMENT.test(schema[key])) {
delete schema[key]
}
if (schema[key] !== null && typeof schema[key] === 'object') {
this.cleanId(schema[key])
}
}
}
Schemas.prototype.getSchemaAnyway = function (schema) {
if (schema.oneOf || schema.allOf || schema.anyOf) return schema
if (!schema.type || !schema.properties) {
return {
type: 'object',
properties: schema
}
}
return schema
}
Schemas.prototype.getSchemas = function () {
return Object.assign({}, this.store)
}
Schemas.prototype.getJsonSchemas = function (options) {
const store = this.getSchemas()
const schemasArray = Object.keys(store).map(schemaKey => {
// if the shared-schema "replace-way" has been used, the $id field has been removed
if (store[schemaKey]['$id'] === undefined) {
store[schemaKey]['$id'] = schemaKey
}
return store[schemaKey]
})
if (options && options.onlyAbsoluteUri === true) {
// the caller wants only the absolute URI (without the shared schema - "replace-way" usage)
return schemasArray.filter(_ => !/^\w*$/g.test(_.$id))
}
return schemasArray
}
function buildSchemas (s) {
const schema = new Schemas()
s.getJsonSchemas().forEach(_ => schema.add(_))
return schema
}
module.exports = { Schemas, buildSchemas }