@bedrockio/yada 1.0.0 → 1.0.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.
@@ -0,0 +1,265 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = void 0;
5
+ exports.isSchema = isSchema;
6
+ var _errors = require("./errors");
7
+ const INITIAL_TYPES = ['default', 'required', 'type', 'transform'];
8
+ const REQUIRED_TYPES = ['default', 'required'];
9
+ class Schema {
10
+ constructor(meta = {}) {
11
+ this.assertions = [];
12
+ this.meta = meta;
13
+ }
14
+
15
+ // Public
16
+
17
+ required() {
18
+ return this.clone({
19
+ required: true
20
+ }).assert('required', val => {
21
+ if (val === undefined) {
22
+ throw new _errors.LocalizedError('Value is required.');
23
+ }
24
+ });
25
+ }
26
+ default(value) {
27
+ return this.clone({
28
+ default: value
29
+ }).assert('default', val => {
30
+ if (val === undefined) {
31
+ return value;
32
+ }
33
+ });
34
+ }
35
+ custom(...args) {
36
+ const type = args.length > 1 ? args[0] : 'custom';
37
+ const fn = args.length > 1 ? args[1] : args[0];
38
+ if (!type) {
39
+ throw new Error('Assertion type required.');
40
+ } else if (!fn) {
41
+ throw new Error('Assertion function required.');
42
+ }
43
+ return this.clone().assert(type, async (val, options) => {
44
+ return await fn(val, options);
45
+ });
46
+ }
47
+ allow(...set) {
48
+ return this.assertEnum(set, true);
49
+ }
50
+ reject(...set) {
51
+ return this.assertEnum(set, false);
52
+ }
53
+ message(message) {
54
+ return this.clone({
55
+ message
56
+ });
57
+ }
58
+ tag(tags) {
59
+ return this.clone({
60
+ tags: {
61
+ ...this.meta.tags,
62
+ ...tags
63
+ }
64
+ });
65
+ }
66
+ description(description) {
67
+ return this.tag({
68
+ description
69
+ });
70
+ }
71
+ options(options) {
72
+ return this.clone({
73
+ ...options
74
+ });
75
+ }
76
+ async validate(value, options = {}) {
77
+ let details = [];
78
+ options = {
79
+ root: value,
80
+ ...options,
81
+ ...this.meta,
82
+ original: value
83
+ };
84
+ for (let assertion of this.assertions) {
85
+ if (!assertion.required && value === undefined) {
86
+ break;
87
+ }
88
+ try {
89
+ const result = await this.runAssertion(assertion, value, options);
90
+ if (result !== undefined) {
91
+ value = result;
92
+ }
93
+ } catch (error) {
94
+ if (error instanceof _errors.ArrayError) {
95
+ details = [...details, ...error.details];
96
+ } else {
97
+ details.push(error);
98
+ }
99
+ if (assertion.halt) {
100
+ break;
101
+ }
102
+ }
103
+ }
104
+ if (details.length) {
105
+ const {
106
+ message = 'Input failed validation.'
107
+ } = this.meta;
108
+ throw new _errors.ValidationError(message, details);
109
+ }
110
+ return value;
111
+ }
112
+ clone(meta) {
113
+ const clone = Object.create(this.constructor.prototype);
114
+ clone.assertions = [...this.assertions];
115
+ clone.meta = {
116
+ ...this.meta,
117
+ ...meta
118
+ };
119
+ return clone;
120
+ }
121
+
122
+ // Private
123
+
124
+ assertEnum(set, allow) {
125
+ if (set.length === 1 && Array.isArray(set[0])) {
126
+ set = set[0];
127
+ }
128
+ const types = set.map(el => {
129
+ if (!isSchema(el)) {
130
+ el = JSON.stringify(el);
131
+ }
132
+ return el;
133
+ });
134
+ const msg = `${allow ? 'Must' : 'Must not'} be one of [{types}].`;
135
+ return this.clone({
136
+ enum: set
137
+ }).assert('enum', async (val, options) => {
138
+ if (val !== undefined) {
139
+ for (let el of set) {
140
+ if (isSchema(el)) {
141
+ try {
142
+ await el.validate(val, options);
143
+ return;
144
+ } catch (error) {
145
+ continue;
146
+ }
147
+ } else if (el === val === allow) {
148
+ return;
149
+ }
150
+ }
151
+ throw new _errors.LocalizedError(msg, {
152
+ types: types.join(', ')
153
+ });
154
+ }
155
+ });
156
+ }
157
+ assert(type, fn) {
158
+ this.pushAssertion({
159
+ halt: INITIAL_TYPES.includes(type),
160
+ required: REQUIRED_TYPES.includes(type),
161
+ type,
162
+ fn
163
+ });
164
+ return this;
165
+ }
166
+ pushAssertion(assertion) {
167
+ this.assertions.push(assertion);
168
+ this.assertions.sort((a, b) => {
169
+ return this.getSortIndex(a.type) - this.getSortIndex(b.type);
170
+ });
171
+ }
172
+ transform(fn) {
173
+ this.assert('transform', (val, options) => {
174
+ if (val !== undefined) {
175
+ return fn(val, options);
176
+ }
177
+ });
178
+ return this;
179
+ }
180
+ getSortIndex(type) {
181
+ const index = INITIAL_TYPES.indexOf(type);
182
+ return index === -1 ? INITIAL_TYPES.length : index;
183
+ }
184
+ async runAssertion(assertion, value, options = {}) {
185
+ const {
186
+ type,
187
+ fn
188
+ } = assertion;
189
+ try {
190
+ return await fn(value, options);
191
+ } catch (error) {
192
+ if ((0, _errors.isSchemaError)(error)) {
193
+ throw error;
194
+ }
195
+ throw new _errors.AssertionError(error.message, type, error);
196
+ }
197
+ }
198
+ toOpenApi(extra) {
199
+ const {
200
+ required,
201
+ format,
202
+ tags,
203
+ default: defaultValue
204
+ } = this.meta;
205
+ return {
206
+ ...(required && {
207
+ required: true
208
+ }),
209
+ ...(defaultValue && {
210
+ default: defaultValue
211
+ }),
212
+ ...(format && {
213
+ format
214
+ }),
215
+ ...this.enumToOpenApi(),
216
+ ...tags,
217
+ ...extra
218
+ };
219
+ }
220
+ enumToOpenApi() {
221
+ const {
222
+ enum: allowed
223
+ } = this.meta;
224
+ if (allowed?.length) {
225
+ const type = typeof allowed[0];
226
+ const allowEnum = allowed.every(entry => {
227
+ const entryType = typeof entry;
228
+ return entryType !== 'object' && entryType === type;
229
+ });
230
+ if (allowEnum) {
231
+ return {
232
+ type,
233
+ enum: allowed
234
+ };
235
+ } else {
236
+ const oneOf = [];
237
+ for (let entry of allowed) {
238
+ if (isSchema(entry)) {
239
+ oneOf.push(entry.toOpenApi());
240
+ } else {
241
+ const type = typeof entry;
242
+ let forType = oneOf.find(el => {
243
+ return el.type === type;
244
+ });
245
+ if (!forType) {
246
+ forType = {
247
+ type,
248
+ enum: []
249
+ };
250
+ oneOf.push(forType);
251
+ }
252
+ forType.enum.push(entry);
253
+ }
254
+ }
255
+ return {
256
+ oneOf
257
+ };
258
+ }
259
+ }
260
+ }
261
+ }
262
+ exports.default = Schema;
263
+ function isSchema(arg) {
264
+ return arg instanceof Schema;
265
+ }
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = void 0;
5
+ var _Schema = _interopRequireDefault(require("./Schema"));
6
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
7
+ class TypeSchema extends _Schema.default {
8
+ constructor(Class, meta) {
9
+ const type = Class.name.toLowerCase();
10
+ super({
11
+ type,
12
+ ...meta
13
+ });
14
+ }
15
+ format(name, fn) {
16
+ return this.clone({
17
+ format: name
18
+ }).assert('format', fn);
19
+ }
20
+ toString() {
21
+ return this.meta.type;
22
+ }
23
+ toOpenApi(extra) {
24
+ return {
25
+ type: this.meta.type,
26
+ ...super.toOpenApi(extra)
27
+ };
28
+ }
29
+ }
30
+ exports.default = TypeSchema;
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = void 0;
5
+ var _Schema = _interopRequireDefault(require("./Schema"));
6
+ var _errors = require("./errors");
7
+ var _utils = require("./utils");
8
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
9
+ class ArraySchema extends _Schema.default {
10
+ constructor(...args) {
11
+ let schemas, meta;
12
+ if (Array.isArray(args[0])) {
13
+ schemas = args[0];
14
+ meta = args[1];
15
+ } else {
16
+ schemas = args;
17
+ }
18
+ super({
19
+ message: 'Array failed validation.',
20
+ ...meta,
21
+ schemas
22
+ });
23
+ this.setup();
24
+ }
25
+ setup() {
26
+ const {
27
+ schemas
28
+ } = this.meta;
29
+ const schema = schemas.length > 1 ? new _Schema.default().allow(schemas) : schemas[0];
30
+ this.assert('type', (val, options) => {
31
+ if (typeof val === 'string' && options.cast) {
32
+ val = val.split(',');
33
+ }
34
+ if (!Array.isArray(val)) {
35
+ throw new _errors.LocalizedError('Must be an array.');
36
+ }
37
+ return val;
38
+ });
39
+ if (schema) {
40
+ this.assert('elements', async (arr, options) => {
41
+ const errors = [];
42
+ const result = [];
43
+ for (let i = 0; i < arr.length; i++) {
44
+ const el = arr[i];
45
+ try {
46
+ result.push(await schema.validate(el, options));
47
+ } catch (error) {
48
+ if (error.details?.length === 1) {
49
+ errors.push(new _errors.ElementError(error.details[0].message, i));
50
+ } else {
51
+ errors.push(new _errors.ElementError('Element failed validation.', i, error.details));
52
+ }
53
+ }
54
+ }
55
+ if (errors.length) {
56
+ throw new _errors.ArrayError(this.meta.message, errors);
57
+ } else {
58
+ return result;
59
+ }
60
+ });
61
+ }
62
+ }
63
+ min(length) {
64
+ return this.clone().assert('length', arr => {
65
+ if (arr.length < length) {
66
+ const s = length === 1 ? '' : 's';
67
+ throw new _errors.LocalizedError('Must contain at least {length} element{s}.', {
68
+ length,
69
+ s
70
+ });
71
+ }
72
+ });
73
+ }
74
+ max(length) {
75
+ return this.clone().assert('length', arr => {
76
+ if (arr.length > length) {
77
+ const s = length === 1 ? '' : 's';
78
+ throw new _errors.LocalizedError('Cannot contain more than {length} element{s}.', {
79
+ length,
80
+ s
81
+ });
82
+ }
83
+ });
84
+ }
85
+ latlng() {
86
+ return this.clone({
87
+ format: 'latlng'
88
+ }).assert('format', arr => {
89
+ if (arr.length !== 2) {
90
+ throw new _errors.LocalizedError('Must be an array of length 2.');
91
+ } else {
92
+ const [lat, lng] = arr;
93
+ if (typeof lat !== 'number' || lat < -90 || lat > 90) {
94
+ throw new _errors.LocalizedError('Invalid latitude.');
95
+ } else if (typeof lng !== 'number' || lng < -180 || lng > 180) {
96
+ throw new _errors.LocalizedError('Invalid longitude.');
97
+ }
98
+ }
99
+ });
100
+ }
101
+ toString() {
102
+ return 'array';
103
+ }
104
+ toOpenApi(extra) {
105
+ let other;
106
+ const {
107
+ schemas
108
+ } = this.meta;
109
+ if (schemas.length > 1) {
110
+ other = {
111
+ oneOf: schemas.map(schema => {
112
+ return schema.toOpenApi();
113
+ })
114
+ };
115
+ } else if (schemas.length === 1) {
116
+ other = {
117
+ items: schemas[0].toOpenApi()
118
+ };
119
+ }
120
+ return {
121
+ type: 'array',
122
+ ...super.toOpenApi(extra),
123
+ ...other
124
+ };
125
+ }
126
+ }
127
+ var _default = (0, _utils.wrapSchema)(ArraySchema);
128
+ exports.default = _default;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = void 0;
5
+ var _TypeSchema = _interopRequireDefault(require("./TypeSchema"));
6
+ var _errors = require("./errors");
7
+ var _utils = require("./utils");
8
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
9
+ class BooleanSchema extends _TypeSchema.default {
10
+ constructor() {
11
+ super(Boolean);
12
+ this.assert('type', (val, options) => {
13
+ if (typeof val === 'string' && options.cast) {
14
+ const str = val.toLowerCase();
15
+ if (str === 'true' || str === '1') {
16
+ val = true;
17
+ } else if (str === 'false' || str === '0') {
18
+ val = false;
19
+ }
20
+ }
21
+ if (typeof val !== 'boolean') {
22
+ throw new _errors.LocalizedError('Must be a boolean.');
23
+ }
24
+ return val;
25
+ });
26
+ }
27
+ }
28
+ var _default = (0, _utils.wrapSchema)(BooleanSchema);
29
+ exports.default = _default;
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = void 0;
5
+ var _validator = _interopRequireDefault(require("validator"));
6
+ var _utils = require("./utils");
7
+ var _errors = require("./errors");
8
+ var _Schema = _interopRequireDefault(require("./Schema"));
9
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
10
+ class DateSchema extends _Schema.default {
11
+ constructor() {
12
+ super();
13
+ this.assert('type', val => {
14
+ const date = new Date(val);
15
+ if (!val && val !== 0 || isNaN(date.getTime())) {
16
+ throw new _errors.LocalizedError('Must be a valid date input.');
17
+ } else {
18
+ return date;
19
+ }
20
+ });
21
+ }
22
+ min(min) {
23
+ min = new Date(min);
24
+ return this.clone().assert('min', date => {
25
+ if (date < min) {
26
+ throw new _errors.LocalizedError('Must be after {date}.', {
27
+ date: min.toISOString()
28
+ });
29
+ }
30
+ });
31
+ }
32
+ max(max) {
33
+ max = new Date(max);
34
+ return this.clone().assert('max', date => {
35
+ if (date > max) {
36
+ throw new _errors.LocalizedError('Must be before {date}.', {
37
+ date: max.toISOString()
38
+ });
39
+ }
40
+ });
41
+ }
42
+ before(max) {
43
+ max = new Date(max);
44
+ return this.clone().assert('before', date => {
45
+ if (date >= max) {
46
+ throw new _errors.LocalizedError('Must be before {date}.', {
47
+ date: max.toISOString()
48
+ });
49
+ }
50
+ });
51
+ }
52
+ after(min) {
53
+ min = new Date(min);
54
+ return this.clone().assert('after', date => {
55
+ if (date <= min) {
56
+ throw new _errors.LocalizedError('Must be after {date}.', {
57
+ date: min.toISOString()
58
+ });
59
+ }
60
+ });
61
+ }
62
+ past() {
63
+ return this.clone().assert('past', date => {
64
+ const now = new Date();
65
+ if (date > now) {
66
+ throw new _errors.LocalizedError('Must be in the past.');
67
+ }
68
+ });
69
+ }
70
+ future() {
71
+ return this.clone().assert('future', date => {
72
+ const now = new Date();
73
+ if (date < now) {
74
+ throw new _errors.LocalizedError('Must be in the future.');
75
+ }
76
+ });
77
+ }
78
+ iso(format = 'date-time') {
79
+ return this.clone({
80
+ format
81
+ }).assert('format', (val, options) => {
82
+ const {
83
+ original
84
+ } = options;
85
+ if (typeof original !== 'string') {
86
+ throw new _errors.LocalizedError('Must be a string.');
87
+ } else if (!_validator.default.isISO8601(original)) {
88
+ throw new _errors.LocalizedError('Must be in ISO 8601 format.');
89
+ }
90
+ });
91
+ }
92
+ timestamp() {
93
+ return this.clone({
94
+ format: 'timestamp'
95
+ }).assert('format', (date, {
96
+ original
97
+ }) => {
98
+ if (typeof original !== 'number') {
99
+ throw new _errors.LocalizedError('Must be a timestamp in milliseconds.');
100
+ }
101
+ });
102
+ }
103
+ unix() {
104
+ return this.clone({
105
+ format: 'unix timestamp'
106
+ }).assert('format', (date, {
107
+ original
108
+ }) => {
109
+ if (typeof original !== 'number') {
110
+ throw new _errors.LocalizedError('Must be a timestamp in seconds.');
111
+ } else {
112
+ return new Date(original * 1000);
113
+ }
114
+ });
115
+ }
116
+ toString() {
117
+ return 'date';
118
+ }
119
+ toOpenApi(extra) {
120
+ const {
121
+ format
122
+ } = this.meta;
123
+ return {
124
+ type: format.includes('timestamp') ? 'number' : 'string',
125
+ ...super.toOpenApi(extra)
126
+ };
127
+ }
128
+ }
129
+ var _default = (0, _utils.wrapSchema)(DateSchema);
130
+ exports.default = _default;
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.ValidationError = exports.LocalizedError = exports.FieldError = exports.ElementError = exports.AssertionError = exports.ArrayError = void 0;
5
+ exports.isSchemaError = isSchemaError;
6
+ var _messages = require("./messages");
7
+ var _localization = require("./localization");
8
+ class LocalizedError extends Error {
9
+ constructor(message, values) {
10
+ super((0, _localization.localize)(message, values));
11
+ }
12
+ }
13
+ exports.LocalizedError = LocalizedError;
14
+ class ValidationError extends Error {
15
+ constructor(message, details = [], type = 'validation') {
16
+ super((0, _localization.localize)(message));
17
+ this.details = details;
18
+ this.type = type;
19
+ }
20
+ toJSON() {
21
+ return {
22
+ type: this.type,
23
+ message: this.message,
24
+ details: this.details
25
+ };
26
+ }
27
+ getFullMessage(options) {
28
+ return (0, _messages.getFullMessage)(this, {
29
+ delimiter: ' ',
30
+ ...options
31
+ });
32
+ }
33
+ }
34
+ exports.ValidationError = ValidationError;
35
+ class FieldError extends ValidationError {
36
+ constructor(message, field, original, details) {
37
+ super(message, details, 'field');
38
+ this.field = field;
39
+ this.original = original;
40
+ this.details = details;
41
+ }
42
+ toJSON() {
43
+ return {
44
+ field: this.field,
45
+ ...super.toJSON()
46
+ };
47
+ }
48
+ }
49
+ exports.FieldError = FieldError;
50
+ class ElementError extends ValidationError {
51
+ constructor(message, index, details) {
52
+ super(message, details, 'element');
53
+ this.index = index;
54
+ this.details = details;
55
+ }
56
+ toJSON() {
57
+ return {
58
+ index: this.index,
59
+ ...super.toJSON()
60
+ };
61
+ }
62
+ }
63
+ exports.ElementError = ElementError;
64
+ class AssertionError extends Error {
65
+ constructor(message, type, original) {
66
+ super(message);
67
+ this.type = type;
68
+ this.original = original;
69
+ }
70
+ toJSON() {
71
+ return {
72
+ type: this.type,
73
+ message: this.message
74
+ };
75
+ }
76
+ }
77
+ exports.AssertionError = AssertionError;
78
+ class ArrayError extends Error {
79
+ constructor(message, details) {
80
+ super(message);
81
+ this.details = details;
82
+ }
83
+ }
84
+ exports.ArrayError = ArrayError;
85
+ function isSchemaError(arg) {
86
+ return arg instanceof ValidationError || arg instanceof AssertionError || arg instanceof ArrayError;
87
+ }