rpc-express-toolkit 4.2.1 → 4.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +0 -0
- package/README.md +88 -9
- package/examples/example.js +0 -0
- package/examples/introspection/client.js +68 -68
- package/examples/introspection/server.js +89 -89
- package/examples/safe-preset-example.js +0 -0
- package/examples/server-client-example.js +0 -0
- package/examples/ssl-development-example.js +0 -0
- package/package.json +124 -126
- package/src/batch.js +90 -55
- package/src/index.d.ts +33 -63
- package/src/index.js +1153 -1035
- package/src/logger.js +17 -5
- package/src/middleware.js +0 -0
- package/src/protocol.js +97 -0
- package/src/safe.d.ts +0 -0
- package/src/safe.js +0 -0
- package/src/validation.js +222 -222
- package/src/clients/rpc-client.js +0 -403
- package/src/clients/rpc-client.min.js +0 -1
- package/src/clients/rpc-client.min.mjs +0 -1
- package/src/clients/rpc-client.mjs +0 -393
package/src/logger.js
CHANGED
|
@@ -54,9 +54,14 @@ class Logger {
|
|
|
54
54
|
*/
|
|
55
55
|
safeStringify(obj) {
|
|
56
56
|
try {
|
|
57
|
+
const seen = new WeakSet();
|
|
57
58
|
return JSON.stringify(obj, (key, value) => {
|
|
58
59
|
if (typeof value === 'bigint') return `${value.toString()}n`;
|
|
59
60
|
if (value instanceof Date) return value.toISOString();
|
|
61
|
+
if (value && typeof value === 'object') {
|
|
62
|
+
if (seen.has(value)) return '[Circular]';
|
|
63
|
+
seen.add(value);
|
|
64
|
+
}
|
|
60
65
|
return value;
|
|
61
66
|
});
|
|
62
67
|
} catch (e) {
|
|
@@ -156,7 +161,8 @@ class Logger {
|
|
|
156
161
|
duration: `${duration}ms`,
|
|
157
162
|
error: error.message,
|
|
158
163
|
code: error.code,
|
|
159
|
-
stack:
|
|
164
|
+
stack:
|
|
165
|
+
this.levels[this.level] >= this.levels.debug ? error.stack : undefined,
|
|
160
166
|
});
|
|
161
167
|
}
|
|
162
168
|
|
|
@@ -175,14 +181,19 @@ class Logger {
|
|
|
175
181
|
'secret',
|
|
176
182
|
'auth',
|
|
177
183
|
'authorization',
|
|
184
|
+
'credential',
|
|
185
|
+
'cookie',
|
|
186
|
+
'session',
|
|
178
187
|
];
|
|
179
|
-
const
|
|
188
|
+
const seen = new WeakSet();
|
|
180
189
|
|
|
181
190
|
const sanitizeObj = (input) => {
|
|
182
191
|
if (typeof input !== 'object' || input === null) return input;
|
|
192
|
+
if (seen.has(input)) return '[Circular]';
|
|
183
193
|
|
|
194
|
+
seen.add(input);
|
|
184
195
|
const output = Array.isArray(input) ? [] : {};
|
|
185
|
-
|
|
196
|
+
Object.entries(input).forEach(([key, value]) => {
|
|
186
197
|
const isSensitive = sensitiveKeys.some((sensitive) =>
|
|
187
198
|
key.toLowerCase().includes(sensitive)
|
|
188
199
|
);
|
|
@@ -193,11 +204,12 @@ class Logger {
|
|
|
193
204
|
} else {
|
|
194
205
|
output[key] = value;
|
|
195
206
|
}
|
|
196
|
-
}
|
|
207
|
+
});
|
|
208
|
+
seen.delete(input);
|
|
197
209
|
return output;
|
|
198
210
|
};
|
|
199
211
|
|
|
200
|
-
return sanitizeObj(
|
|
212
|
+
return sanitizeObj(params);
|
|
201
213
|
}
|
|
202
214
|
}
|
|
203
215
|
|
package/src/middleware.js
CHANGED
|
File without changes
|
package/src/protocol.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
|
|
2
|
+
|
|
3
|
+
function isObject(value) {
|
|
4
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function isValidId(id) {
|
|
8
|
+
return id === null || typeof id === 'string' || typeof id === 'number';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function isValidParams(params) {
|
|
12
|
+
return Array.isArray(params) || isObject(params);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function validationError(message, id = null) {
|
|
16
|
+
return {
|
|
17
|
+
valid: false,
|
|
18
|
+
responseId: id,
|
|
19
|
+
error: {
|
|
20
|
+
code: -32600,
|
|
21
|
+
message,
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function validateEnvelope(request) {
|
|
27
|
+
if (!isObject(request)) {
|
|
28
|
+
return validationError('Invalid Request: request must be an object');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const hasId = hasOwn(request, 'id');
|
|
32
|
+
const responseId = hasId && isValidId(request.id) ? request.id : null;
|
|
33
|
+
|
|
34
|
+
if (request.jsonrpc !== '2.0') {
|
|
35
|
+
return validationError(
|
|
36
|
+
"Invalid Request: 'jsonrpc' must be '2.0'.",
|
|
37
|
+
responseId
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (typeof request.method !== 'string') {
|
|
42
|
+
return validationError(
|
|
43
|
+
"Invalid Request: 'method' must be a string.",
|
|
44
|
+
responseId
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (request.method.length === 0) {
|
|
49
|
+
return validationError(
|
|
50
|
+
"Invalid Request: 'method' must not be empty.",
|
|
51
|
+
responseId
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (hasOwn(request, 'params') && !isValidParams(request.params)) {
|
|
56
|
+
return validationError(
|
|
57
|
+
"Invalid Request: 'params' must be an object or array.",
|
|
58
|
+
responseId
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (hasId && !isValidId(request.id)) {
|
|
63
|
+
return validationError(
|
|
64
|
+
"Invalid Request: 'id' must be a string, number, or null.",
|
|
65
|
+
null
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
valid: true,
|
|
71
|
+
hasId,
|
|
72
|
+
isNotification: !hasId,
|
|
73
|
+
responseId,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function addBatchIndex(error, batchIndex) {
|
|
78
|
+
const data =
|
|
79
|
+
error.data !== undefined && isObject(error.data) ? error.data : {};
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
...error,
|
|
83
|
+
data: {
|
|
84
|
+
...data,
|
|
85
|
+
...(error.data !== undefined && !isObject(error.data)
|
|
86
|
+
? { value: error.data }
|
|
87
|
+
: {}),
|
|
88
|
+
batchIndex,
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = {
|
|
94
|
+
addBatchIndex,
|
|
95
|
+
hasOwn,
|
|
96
|
+
validateEnvelope,
|
|
97
|
+
};
|
package/src/safe.d.ts
CHANGED
|
File without changes
|
package/src/safe.js
CHANGED
|
File without changes
|
package/src/validation.js
CHANGED
|
@@ -1,222 +1,222 @@
|
|
|
1
|
-
/* eslint max-classes-per-file: off */
|
|
2
|
-
const Ajv = require('ajv');
|
|
3
|
-
const addFormats = require('ajv-formats');
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* JSON-RPC 2.0 Schema Validation Error
|
|
7
|
-
*/
|
|
8
|
-
class SchemaValidator {
|
|
9
|
-
constructor(options = {}) {
|
|
10
|
-
this.ajv = new Ajv({
|
|
11
|
-
allErrors: true,
|
|
12
|
-
removeAdditional: options.removeAdditional || false,
|
|
13
|
-
useDefaults: options.useDefaults !== false,
|
|
14
|
-
coerceTypes: options.coerceTypes || false,
|
|
15
|
-
strict: false, // Allow unknown keywords like exposeSchema in method options
|
|
16
|
-
...options.ajvOptions,
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
addFormats(this.ajv);
|
|
20
|
-
|
|
21
|
-
// Add custom formats
|
|
22
|
-
this.addCustomFormats();
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Add custom format validators
|
|
27
|
-
*/
|
|
28
|
-
addCustomFormats() {
|
|
29
|
-
// BigInt format
|
|
30
|
-
this.ajv.addFormat('bigint', {
|
|
31
|
-
type: 'string',
|
|
32
|
-
validate: (data) => /^-?\d+n?$/.test(data),
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// ObjectId format (MongoDB style)
|
|
36
|
-
this.ajv.addFormat('objectid', {
|
|
37
|
-
type: 'string',
|
|
38
|
-
validate: (data) => /^[0-9a-fA-F]{24}$/.test(data),
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Validate parameters against a schema
|
|
44
|
-
* @param {any} params
|
|
45
|
-
* @param {Object} schema
|
|
46
|
-
* @returns {Object} validation result
|
|
47
|
-
*/
|
|
48
|
-
validate(params, schema) {
|
|
49
|
-
const validator = this.ajv.compile(schema);
|
|
50
|
-
const valid = validator(params);
|
|
51
|
-
|
|
52
|
-
return {
|
|
53
|
-
valid,
|
|
54
|
-
errors: valid ? null : validator.errors,
|
|
55
|
-
data: params,
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Create a validation middleware
|
|
61
|
-
* @param {Object} schema
|
|
62
|
-
* @returns {Function}
|
|
63
|
-
*/
|
|
64
|
-
middleware(schema) {
|
|
65
|
-
return async (context) => {
|
|
66
|
-
const { params } = context;
|
|
67
|
-
const result = this.validate(params, schema);
|
|
68
|
-
|
|
69
|
-
if (!result.valid) {
|
|
70
|
-
const error = new Error('Validation failed');
|
|
71
|
-
error.code = -32602; // Invalid params
|
|
72
|
-
error.data = {
|
|
73
|
-
validationErrors: result.errors.map((err) => ({
|
|
74
|
-
field: err.instancePath || err.schemaPath,
|
|
75
|
-
message: err.message,
|
|
76
|
-
value: err.data,
|
|
77
|
-
})),
|
|
78
|
-
};
|
|
79
|
-
throw error;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Update params with validated/coerced data
|
|
83
|
-
context.params = result.data;
|
|
84
|
-
return context;
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Add a custom keyword
|
|
90
|
-
* @param {string} name
|
|
91
|
-
* @param {Object} definition
|
|
92
|
-
*/
|
|
93
|
-
addKeyword(name, definition) {
|
|
94
|
-
this.ajv.addKeyword(name, definition);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Common schema definitions
|
|
100
|
-
*/
|
|
101
|
-
const commonSchemas = {
|
|
102
|
-
// Pagination parameters
|
|
103
|
-
pagination: {
|
|
104
|
-
type: 'object',
|
|
105
|
-
properties: {
|
|
106
|
-
page: { type: 'integer', minimum: 1, default: 1 },
|
|
107
|
-
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
|
108
|
-
sort: { type: 'string' },
|
|
109
|
-
order: { type: 'string', enum: ['asc', 'desc'], default: 'asc' },
|
|
110
|
-
},
|
|
111
|
-
},
|
|
112
|
-
|
|
113
|
-
// User identification
|
|
114
|
-
userId: {
|
|
115
|
-
oneOf: [
|
|
116
|
-
{ type: 'string', format: 'objectid' },
|
|
117
|
-
{ type: 'string', format: 'uuid' },
|
|
118
|
-
{ type: 'integer', minimum: 1 },
|
|
119
|
-
],
|
|
120
|
-
},
|
|
121
|
-
|
|
122
|
-
// Email validation
|
|
123
|
-
email: {
|
|
124
|
-
type: 'string',
|
|
125
|
-
format: 'email',
|
|
126
|
-
maxLength: 255,
|
|
127
|
-
},
|
|
128
|
-
|
|
129
|
-
// Password requirements
|
|
130
|
-
password: {
|
|
131
|
-
type: 'string',
|
|
132
|
-
minLength: 8,
|
|
133
|
-
maxLength: 128,
|
|
134
|
-
pattern: '^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)',
|
|
135
|
-
},
|
|
136
|
-
|
|
137
|
-
// BigInt validation
|
|
138
|
-
bigintString: {
|
|
139
|
-
type: 'string',
|
|
140
|
-
format: 'bigint',
|
|
141
|
-
},
|
|
142
|
-
|
|
143
|
-
// Date validation
|
|
144
|
-
dateString: {
|
|
145
|
-
type: 'string',
|
|
146
|
-
format: 'date-time',
|
|
147
|
-
},
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Schema builder helper
|
|
152
|
-
*/
|
|
153
|
-
class SchemaBuilder {
|
|
154
|
-
constructor() {
|
|
155
|
-
this.schema = {
|
|
156
|
-
type: 'object',
|
|
157
|
-
properties: {},
|
|
158
|
-
required: [],
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Add a property to the schema
|
|
164
|
-
* @param {string} name
|
|
165
|
-
* @param {Object} definition
|
|
166
|
-
* @param {boolean} required
|
|
167
|
-
* @returns {SchemaBuilder}
|
|
168
|
-
*/
|
|
169
|
-
property(name, definition, required = false) {
|
|
170
|
-
this.schema.properties[name] = definition;
|
|
171
|
-
if (required) {
|
|
172
|
-
this.schema.required.push(name);
|
|
173
|
-
}
|
|
174
|
-
return this;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Add multiple properties
|
|
179
|
-
* @param {Object} properties
|
|
180
|
-
* @returns {SchemaBuilder}
|
|
181
|
-
*/
|
|
182
|
-
properties(properties) {
|
|
183
|
-
Object.entries(properties).forEach(([name, definition]) => {
|
|
184
|
-
this.property(name, definition);
|
|
185
|
-
});
|
|
186
|
-
return this;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Set required fields
|
|
191
|
-
* @param {string[]} fields
|
|
192
|
-
* @returns {SchemaBuilder}
|
|
193
|
-
*/
|
|
194
|
-
required(fields) {
|
|
195
|
-
this.schema.required = [...new Set([...this.schema.required, ...fields])];
|
|
196
|
-
return this;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Add additional properties setting
|
|
201
|
-
* @param {boolean} allowed
|
|
202
|
-
* @returns {SchemaBuilder}
|
|
203
|
-
*/
|
|
204
|
-
additionalProperties(allowed) {
|
|
205
|
-
this.schema.additionalProperties = allowed;
|
|
206
|
-
return this;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Build and return the schema
|
|
211
|
-
* @returns {Object}
|
|
212
|
-
*/
|
|
213
|
-
build() {
|
|
214
|
-
return this.schema;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
module.exports = {
|
|
219
|
-
SchemaValidator,
|
|
220
|
-
commonSchemas,
|
|
221
|
-
SchemaBuilder,
|
|
222
|
-
};
|
|
1
|
+
/* eslint max-classes-per-file: off */
|
|
2
|
+
const Ajv = require('ajv');
|
|
3
|
+
const addFormats = require('ajv-formats');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* JSON-RPC 2.0 Schema Validation Error
|
|
7
|
+
*/
|
|
8
|
+
class SchemaValidator {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.ajv = new Ajv({
|
|
11
|
+
allErrors: true,
|
|
12
|
+
removeAdditional: options.removeAdditional || false,
|
|
13
|
+
useDefaults: options.useDefaults !== false,
|
|
14
|
+
coerceTypes: options.coerceTypes || false,
|
|
15
|
+
strict: false, // Allow unknown keywords like exposeSchema in method options
|
|
16
|
+
...options.ajvOptions,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
addFormats(this.ajv);
|
|
20
|
+
|
|
21
|
+
// Add custom formats
|
|
22
|
+
this.addCustomFormats();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Add custom format validators
|
|
27
|
+
*/
|
|
28
|
+
addCustomFormats() {
|
|
29
|
+
// BigInt format
|
|
30
|
+
this.ajv.addFormat('bigint', {
|
|
31
|
+
type: 'string',
|
|
32
|
+
validate: (data) => /^-?\d+n?$/.test(data),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// ObjectId format (MongoDB style)
|
|
36
|
+
this.ajv.addFormat('objectid', {
|
|
37
|
+
type: 'string',
|
|
38
|
+
validate: (data) => /^[0-9a-fA-F]{24}$/.test(data),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Validate parameters against a schema
|
|
44
|
+
* @param {any} params
|
|
45
|
+
* @param {Object} schema
|
|
46
|
+
* @returns {Object} validation result
|
|
47
|
+
*/
|
|
48
|
+
validate(params, schema) {
|
|
49
|
+
const validator = this.ajv.compile(schema);
|
|
50
|
+
const valid = validator(params);
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
valid,
|
|
54
|
+
errors: valid ? null : validator.errors,
|
|
55
|
+
data: params,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Create a validation middleware
|
|
61
|
+
* @param {Object} schema
|
|
62
|
+
* @returns {Function}
|
|
63
|
+
*/
|
|
64
|
+
middleware(schema) {
|
|
65
|
+
return async (context) => {
|
|
66
|
+
const { params } = context;
|
|
67
|
+
const result = this.validate(params, schema);
|
|
68
|
+
|
|
69
|
+
if (!result.valid) {
|
|
70
|
+
const error = new Error('Validation failed');
|
|
71
|
+
error.code = -32602; // Invalid params
|
|
72
|
+
error.data = {
|
|
73
|
+
validationErrors: result.errors.map((err) => ({
|
|
74
|
+
field: err.instancePath || err.schemaPath,
|
|
75
|
+
message: err.message,
|
|
76
|
+
value: err.data,
|
|
77
|
+
})),
|
|
78
|
+
};
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Update params with validated/coerced data
|
|
83
|
+
context.params = result.data;
|
|
84
|
+
return context;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Add a custom keyword
|
|
90
|
+
* @param {string} name
|
|
91
|
+
* @param {Object} definition
|
|
92
|
+
*/
|
|
93
|
+
addKeyword(name, definition) {
|
|
94
|
+
this.ajv.addKeyword(name, definition);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Common schema definitions
|
|
100
|
+
*/
|
|
101
|
+
const commonSchemas = {
|
|
102
|
+
// Pagination parameters
|
|
103
|
+
pagination: {
|
|
104
|
+
type: 'object',
|
|
105
|
+
properties: {
|
|
106
|
+
page: { type: 'integer', minimum: 1, default: 1 },
|
|
107
|
+
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
|
108
|
+
sort: { type: 'string' },
|
|
109
|
+
order: { type: 'string', enum: ['asc', 'desc'], default: 'asc' },
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
// User identification
|
|
114
|
+
userId: {
|
|
115
|
+
oneOf: [
|
|
116
|
+
{ type: 'string', format: 'objectid' },
|
|
117
|
+
{ type: 'string', format: 'uuid' },
|
|
118
|
+
{ type: 'integer', minimum: 1 },
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
// Email validation
|
|
123
|
+
email: {
|
|
124
|
+
type: 'string',
|
|
125
|
+
format: 'email',
|
|
126
|
+
maxLength: 255,
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
// Password requirements
|
|
130
|
+
password: {
|
|
131
|
+
type: 'string',
|
|
132
|
+
minLength: 8,
|
|
133
|
+
maxLength: 128,
|
|
134
|
+
pattern: '^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)',
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
// BigInt validation
|
|
138
|
+
bigintString: {
|
|
139
|
+
type: 'string',
|
|
140
|
+
format: 'bigint',
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
// Date validation
|
|
144
|
+
dateString: {
|
|
145
|
+
type: 'string',
|
|
146
|
+
format: 'date-time',
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Schema builder helper
|
|
152
|
+
*/
|
|
153
|
+
class SchemaBuilder {
|
|
154
|
+
constructor() {
|
|
155
|
+
this.schema = {
|
|
156
|
+
type: 'object',
|
|
157
|
+
properties: {},
|
|
158
|
+
required: [],
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Add a property to the schema
|
|
164
|
+
* @param {string} name
|
|
165
|
+
* @param {Object} definition
|
|
166
|
+
* @param {boolean} required
|
|
167
|
+
* @returns {SchemaBuilder}
|
|
168
|
+
*/
|
|
169
|
+
property(name, definition, required = false) {
|
|
170
|
+
this.schema.properties[name] = definition;
|
|
171
|
+
if (required) {
|
|
172
|
+
this.schema.required.push(name);
|
|
173
|
+
}
|
|
174
|
+
return this;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Add multiple properties
|
|
179
|
+
* @param {Object} properties
|
|
180
|
+
* @returns {SchemaBuilder}
|
|
181
|
+
*/
|
|
182
|
+
properties(properties) {
|
|
183
|
+
Object.entries(properties).forEach(([name, definition]) => {
|
|
184
|
+
this.property(name, definition);
|
|
185
|
+
});
|
|
186
|
+
return this;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Set required fields
|
|
191
|
+
* @param {string[]} fields
|
|
192
|
+
* @returns {SchemaBuilder}
|
|
193
|
+
*/
|
|
194
|
+
required(fields) {
|
|
195
|
+
this.schema.required = [...new Set([...this.schema.required, ...fields])];
|
|
196
|
+
return this;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Add additional properties setting
|
|
201
|
+
* @param {boolean} allowed
|
|
202
|
+
* @returns {SchemaBuilder}
|
|
203
|
+
*/
|
|
204
|
+
additionalProperties(allowed) {
|
|
205
|
+
this.schema.additionalProperties = allowed;
|
|
206
|
+
return this;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Build and return the schema
|
|
211
|
+
* @returns {Object}
|
|
212
|
+
*/
|
|
213
|
+
build() {
|
|
214
|
+
return this.schema;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
module.exports = {
|
|
219
|
+
SchemaValidator,
|
|
220
|
+
commonSchemas,
|
|
221
|
+
SchemaBuilder,
|
|
222
|
+
};
|