@e22m4u/js-openapi 0.0.5 → 0.0.7

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.
Files changed (70) hide show
  1. package/README.md +226 -146
  2. package/dist/cjs/index.cjs +2025 -2092
  3. package/package.json +10 -8
  4. package/schema/openapi-3-1/dialect/base.js +21 -0
  5. package/schema/openapi-3-1/meta/base.js +76 -0
  6. package/schema/openapi-3-1/schema-base.js +32 -0
  7. package/schema/openapi-3-1/schema.js +1403 -0
  8. package/src/ajv.js +32 -0
  9. package/src/errors/index.d.ts +1 -1
  10. package/src/errors/index.js +1 -1
  11. package/src/errors/oa-document-object-validation-error.d.ts +6 -0
  12. package/src/errors/oa-document-object-validation-error.js +6 -0
  13. package/src/errors/oa-document-object-validation-error.spec.js +10 -0
  14. package/src/index.d.ts +1 -3
  15. package/src/index.js +1 -3
  16. package/src/json-pointer/resolve-json-pointer.js +1 -1
  17. package/src/json-pointer/unescape-json-pointer.d.ts +1 -2
  18. package/src/oa-document-builder.d.ts +302 -111
  19. package/src/oa-document-builder.js +208 -142
  20. package/src/oa-document-builder.spec.js +1411 -0
  21. package/src/oa-document-object/index.d.ts +1 -1
  22. package/src/oa-document-object/index.js +1 -1
  23. package/src/oa-document-object/validate-oa-document-object.d.ts +18 -0
  24. package/src/oa-document-object/validate-oa-document-object.js +53 -0
  25. package/src/oa-document-object/validate-oa-document-object.spec.js +122 -0
  26. package/src/{oa-document-scope.d.ts → oa-operation-group.d.ts} +7 -7
  27. package/src/{oa-document-scope.js → oa-operation-group.js} +101 -46
  28. package/src/oa-operation-group.spec.js +544 -0
  29. package/src/oa-reference-object/is-oa-reference-object.js +1 -1
  30. package/src/oa-reference-object/is-oa-reference-object.spec.js +1 -1
  31. package/src/oa-reference-object/oa-ref.js +7 -0
  32. package/src/oa-reference-object/resolve-oa-reference-object.js +1 -11
  33. package/src/oa-reference-object/resolve-oa-reference-object.spec.js +0 -10
  34. package/src/oa-specification.d.ts +55 -66
  35. package/src/oa-specification.js +11 -22
  36. package/src/data-type/index.d.ts +0 -1
  37. package/src/data-type/index.js +0 -1
  38. package/src/data-type/infer-openapi-data-type.d.ts +0 -30
  39. package/src/data-type/infer-openapi-data-type.js +0 -38
  40. package/src/data-validation/data-format-validator-map.d.ts +0 -13
  41. package/src/data-validation/data-format-validator-map.js +0 -36
  42. package/src/data-validation/data-format-validator-map.spec.js +0 -39
  43. package/src/data-validation/data-format-validators.d.ts +0 -84
  44. package/src/data-validation/data-format-validators.js +0 -217
  45. package/src/data-validation/index.d.ts +0 -3
  46. package/src/data-validation/index.js +0 -3
  47. package/src/data-validation/validate-data-with-openapi-schema.d.ts +0 -46
  48. package/src/data-validation/validate-data-with-openapi-schema.js +0 -1913
  49. package/src/data-validation/validate-data-with-openapi-schema.spec.js +0 -6953
  50. package/src/errors/oa-data-validation-error.d.ts +0 -6
  51. package/src/errors/oa-data-validation-error.js +0 -6
  52. package/src/errors/oa-data-validation-error.spec.js +0 -17
  53. package/src/oa-document-object/validate-shallow-oa-document.d.ts +0 -10
  54. package/src/oa-document-object/validate-shallow-oa-document.js +0 -209
  55. package/src/oa-document-object/validate-shallow-oa-document.spec.js +0 -362
  56. package/src/utils/count-unicode.d.ts +0 -11
  57. package/src/utils/count-unicode.js +0 -15
  58. package/src/utils/index.d.ts +0 -5
  59. package/src/utils/index.js +0 -5
  60. package/src/utils/join-path.d.ts +0 -6
  61. package/src/utils/join-path.js +0 -36
  62. package/src/utils/join-path.spec.js +0 -104
  63. package/src/utils/normalize-path.d.ts +0 -12
  64. package/src/utils/normalize-path.js +0 -22
  65. package/src/utils/normalize-path.spec.js +0 -56
  66. package/src/utils/to-pascal-case.d.ts +0 -6
  67. package/src/utils/to-pascal-case.js +0 -26
  68. package/src/utils/to-pascal-case.spec.js +0 -15
  69. package/src/utils/to-spaced-json.d.ts +0 -17
  70. package/src/utils/to-spaced-json.js +0 -27
@@ -1 +1 @@
1
- export * from './validate-shallow-oa-document.js';
1
+ export * from './validate-oa-document-object.js';
@@ -1 +1 @@
1
- export * from './validate-shallow-oa-document.js';
1
+ export * from './validate-oa-document-object.js';
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Document object validation options.
3
+ */
4
+ export type OADocumentObjectValidationOptions = {
5
+ schemaUri?: string;
6
+ dataSourceUri?: string;
7
+ };
8
+
9
+ /**
10
+ * Validate OADocumentObject.
11
+ *
12
+ * @param data
13
+ * @param options
14
+ */
15
+ export function validateOADocumentObject(
16
+ data: any,
17
+ options?: OADocumentObjectValidationOptions,
18
+ ): void;
@@ -0,0 +1,53 @@
1
+ import {InvalidArgumentError} from '@e22m4u/js-format';
2
+ import {ajv, OA_DOCUMENT_OBJECT_JSON_SCHEMA_URI} from '../ajv.js';
3
+ import {OADocumentObjectValidationError} from '../errors/index.js';
4
+
5
+ /**
6
+ * Validate OADocumentObject.
7
+ *
8
+ * @param {*} data
9
+ * @param {*} options
10
+ */
11
+ export function validateOADocumentObject(data, options = {}) {
12
+ if (!options || typeof options !== 'object' || Array.isArray(options)) {
13
+ throw new InvalidArgumentError(
14
+ 'Parameter "options" must be an Object, but %v was given.',
15
+ options,
16
+ );
17
+ }
18
+ if (options.schemaUri !== undefined) {
19
+ if (typeof options.schemaUri !== 'string') {
20
+ throw new InvalidArgumentError(
21
+ 'Option "schemaUri" must be a String, but %v was given.',
22
+ options.schemaUri,
23
+ );
24
+ }
25
+ }
26
+ if (options.dataSourceUri !== undefined) {
27
+ if (typeof options.dataSourceUri !== 'string') {
28
+ throw new InvalidArgumentError(
29
+ 'Option "dataSourceUri" must be a String, but %v was given.',
30
+ options.dataSourceUri,
31
+ );
32
+ }
33
+ }
34
+ const validate = ajv.getSchema(
35
+ `${OA_DOCUMENT_OBJECT_JSON_SCHEMA_URI}${options.schemaUri || ''}`,
36
+ );
37
+ const isValid = validate(data);
38
+ if (!isValid) {
39
+ const error = validate.errors[0];
40
+ let instancePath = error.instancePath || '';
41
+ let dataSourceUri = options.dataSourceUri || '';
42
+ if (!options.schemaUri && !options.dataSourceUri) {
43
+ dataSourceUri = '#';
44
+ }
45
+ throw new OADocumentObjectValidationError(
46
+ 'Value at %v %s.',
47
+ dataSourceUri && error.instancePath
48
+ ? [dataSourceUri, error.instancePath].join('/').replace(/\/+/g, '/')
49
+ : dataSourceUri + instancePath,
50
+ error.message.replace(/'/g, '"'),
51
+ );
52
+ }
53
+ }
@@ -0,0 +1,122 @@
1
+ import {expect} from 'chai';
2
+ import {validateOADocumentObject} from './validate-oa-document-object.js';
3
+ import {format} from '@e22m4u/js-format';
4
+
5
+ const VALID_DOC_SAMPLE = {
6
+ openapi: '3.1.2',
7
+ info: {
8
+ title: 'API Documentation',
9
+ version: '0.0.1',
10
+ },
11
+ components: {},
12
+ };
13
+
14
+ describe('validateOADocumentObject', function () {
15
+ it('should require the parameter "option" to be an Object', function () {
16
+ const throwable = v => () => validateOADocumentObject(VALID_DOC_SAMPLE, v);
17
+ const error = s =>
18
+ format('Parameter "options" must be an Object, but %s was given.', s);
19
+ expect(throwable('str')).to.throw(error('"str"'));
20
+ expect(throwable('')).to.throw(error('""'));
21
+ expect(throwable(10)).to.throw(error('10'));
22
+ expect(throwable(0)).to.throw(error('0'));
23
+ expect(throwable(true)).to.throw(error('true'));
24
+ expect(throwable(false)).to.throw(error('false'));
25
+ expect(throwable([])).to.throw(error('Array'));
26
+ expect(throwable(null)).to.throw(error('null'));
27
+ throwable({})();
28
+ throwable(undefined)();
29
+ });
30
+
31
+ it('should require the option "schemaUri" to be a String', function () {
32
+ const throwable = v => () =>
33
+ validateOADocumentObject(VALID_DOC_SAMPLE, {schemaUri: v});
34
+ const error = s =>
35
+ format('Option "schemaUri" must be a String, but %s was given.', s);
36
+ expect(throwable(10)).to.throw(error('10'));
37
+ expect(throwable(0)).to.throw(error('0'));
38
+ expect(throwable(true)).to.throw(error('true'));
39
+ expect(throwable(false)).to.throw(error('false'));
40
+ expect(throwable([])).to.throw(error('Array'));
41
+ expect(throwable({})).to.throw(error('Object'));
42
+ expect(throwable(null)).to.throw(error('null'));
43
+ throwable('#')();
44
+ throwable(undefined)();
45
+ });
46
+
47
+ it('should require the option "dataSourceUri" to be a String', function () {
48
+ const throwable = v => () =>
49
+ validateOADocumentObject(VALID_DOC_SAMPLE, {dataSourceUri: v});
50
+ const error = s =>
51
+ format('Option "dataSourceUri" must be a String, but %s was given.', s);
52
+ expect(throwable(10)).to.throw(error('10'));
53
+ expect(throwable(0)).to.throw(error('0'));
54
+ expect(throwable(true)).to.throw(error('true'));
55
+ expect(throwable(false)).to.throw(error('false'));
56
+ expect(throwable([])).to.throw(error('Array'));
57
+ expect(throwable({})).to.throw(error('Object'));
58
+ expect(throwable(null)).to.throw(error('null'));
59
+ throwable('/str')();
60
+ throwable('')();
61
+ throwable(undefined)();
62
+ });
63
+
64
+ it('should pass validation for a valid document', function () {
65
+ validateOADocumentObject({
66
+ openapi: '3.1.2',
67
+ info: {
68
+ title: 'API Documentation',
69
+ version: '0.0.1',
70
+ },
71
+ components: {},
72
+ });
73
+ });
74
+
75
+ it('should require the "openapi" keyword', function () {
76
+ const throwable = () =>
77
+ validateOADocumentObject({
78
+ info: {
79
+ title: 'API Documentation',
80
+ version: '0.0.1',
81
+ },
82
+ components: {},
83
+ });
84
+ expect(throwable).to.throw(
85
+ 'Value at "#" must have required property "openapi".',
86
+ );
87
+ });
88
+
89
+ it('should require the "info" keyword', function () {
90
+ const throwable = () =>
91
+ validateOADocumentObject({
92
+ openapi: '3.1.2',
93
+ components: {},
94
+ });
95
+ expect(throwable).to.throw(
96
+ 'Value at "#" must have required property "info".',
97
+ );
98
+ });
99
+
100
+ it('should allow to select the schema by the "schemaUri" option', function () {
101
+ const throwable = () =>
102
+ validateOADocumentObject({type: 123}, {schemaUri: '#/$defs/schema'});
103
+ expect(throwable).to.throw(
104
+ 'Value at "/type" must be equal to one of the allowed values.',
105
+ );
106
+ });
107
+
108
+ it('should add the "dataSourceUri" option as prefix to the instance path', function () {
109
+ const throwable = () =>
110
+ validateOADocumentObject(
111
+ {type: 123},
112
+ {
113
+ schemaUri: '#/$defs/schema',
114
+ dataSourceUri: '#/components/schemas/mySchema',
115
+ },
116
+ );
117
+ expect(throwable).to.throw(
118
+ 'Value at "#/components/schemas/mySchema/type" ' +
119
+ 'must be equal to one of the allowed values.',
120
+ );
121
+ });
122
+ });
@@ -4,22 +4,22 @@ import {
4
4
  } from './oa-document-builder.js';
5
5
 
6
6
  /**
7
- * Document scope options.
7
+ * Operation group options.
8
8
  */
9
- export type OADocumentScopeOptions = {
9
+ export type OAOperationGroupOptions = {
10
10
  pathPrefix?: string;
11
11
  tags?: string[];
12
12
  };
13
13
 
14
14
  /**
15
- * Document scope.
15
+ * Operation group.
16
16
  */
17
- export declare class OADocumentScope {
17
+ export declare class OAOperationGroup {
18
18
  /**
19
19
  * @param builder
20
20
  * @param options
21
21
  */
22
- constructor(builder: OADocumentBuilder, options?: OADocumentScopeOptions);
22
+ constructor(builder: OADocumentBuilder, options?: OAOperationGroupOptions);
23
23
 
24
24
  /**
25
25
  * Get builder.
@@ -44,9 +44,9 @@ export declare class OADocumentScope {
44
44
  defineOperation(operationDef: OAOperationDefinition): this;
45
45
 
46
46
  /**
47
- * Create scope.
47
+ * Create operation group.
48
48
  *
49
49
  * @param options
50
50
  */
51
- createScope(options?: OADocumentScopeOptions): OADocumentScope;
51
+ createOperationGroup(options?: OAOperationGroupOptions): OAOperationGroup;
52
52
  }
@@ -1,12 +1,39 @@
1
1
  import {InvalidArgumentError} from '@e22m4u/js-format';
2
2
  import {OAOperationMethod} from './oa-specification.js';
3
- import {joinPath, normalizePath} from './utils/index.js';
4
3
  import {OADocumentBuilder} from './oa-document-builder.js';
5
4
 
6
5
  /**
7
- * Document scope.
6
+ * Operation group.
7
+ *
8
+ * Предоставляет группу операций API с общими параметрами.
9
+ *
10
+ * - Общий префикс пути для всех операций в области.
11
+ * - Общие теги, применяемые ко всем операциям.
12
+ * - Возможность создания вложенных групп для иерархической организации.
13
+ *
14
+ * Пример:
15
+ * ```js
16
+ * // создание группы
17
+ * const usersOps = builder.createOperationGroup({
18
+ * pathPrefix: '/users',
19
+ * tags: ['Users']
20
+ * });
21
+ *
22
+ * // GET "/users/{id}"
23
+ * usersOps.defineOperation({
24
+ * path: '/{id}',
25
+ * method: OAOperationMethod.GET,
26
+ * operation: { ... }
27
+ * });
28
+ *
29
+ * // вложенная группа "/users/admin"
30
+ * const adminOps = usersOps.createOperationGroup({
31
+ * pathPrefix: '/admin',
32
+ * tags: ['Admin']
33
+ * });
34
+ * ```
8
35
  */
9
- export class OADocumentScope {
36
+ export class OAOperationGroup {
10
37
  /**
11
38
  * Builder.
12
39
  *
@@ -50,10 +77,15 @@ export class OADocumentScope {
50
77
  }
51
78
  // options.pathPrefix
52
79
  if (options.pathPrefix !== undefined) {
53
- if (!options.pathPrefix || typeof options.pathPrefix !== 'string') {
80
+ if (typeof options.pathPrefix !== 'string') {
54
81
  throw new InvalidArgumentError(
55
- 'Property "pathPrefix" must be a non-empty String, ' +
56
- 'but %v was given.',
82
+ 'Option "pathPrefix" must be a String, but %v was given.',
83
+ options.pathPrefix,
84
+ );
85
+ }
86
+ if (!options.pathPrefix.startsWith('/')) {
87
+ throw new InvalidArgumentError(
88
+ 'Option "pathPrefix" must start with "/", but %v was given.',
57
89
  options.pathPrefix,
58
90
  );
59
91
  }
@@ -62,14 +94,15 @@ export class OADocumentScope {
62
94
  if (options.tags !== undefined) {
63
95
  if (!Array.isArray(options.tags)) {
64
96
  throw new InvalidArgumentError(
65
- 'Property "tags" must be an Array, but %v was given.',
97
+ 'Option "tags" must be an Array, but %v was given.',
66
98
  options.tags,
67
99
  );
68
100
  }
69
101
  options.tags.forEach((tag, index) => {
70
102
  if (!tag || typeof tag !== 'string') {
71
103
  throw new InvalidArgumentError(
72
- 'Element "tags[%d]" must be a non-empty String, but %v was given.',
104
+ 'Element %v in the option "tags" must be a non-empty String, ' +
105
+ 'but %v was given.',
73
106
  index,
74
107
  tag,
75
108
  );
@@ -77,8 +110,8 @@ export class OADocumentScope {
77
110
  });
78
111
  }
79
112
  this._builder = builder;
80
- this._pathPrefix = normalizePath(options.pathPrefix || '/');
81
- this._tags = options.tags || [];
113
+ this._pathPrefix = options.pathPrefix || '/';
114
+ this._tags = [...new Set(options.tags || [])];
82
115
  }
83
116
 
84
117
  /**
@@ -111,7 +144,7 @@ export class OADocumentScope {
111
144
  /**
112
145
  * Define operation.
113
146
  *
114
- * @param {object} operationDef
147
+ * @param {import('./oa-document-builder.js').OAOperationDefinition} operationDef
115
148
  * @returns {this}
116
149
  */
117
150
  defineOperation(operationDef) {
@@ -126,44 +159,53 @@ export class OADocumentScope {
126
159
  );
127
160
  }
128
161
  // path
129
- if (!operationDef.path || typeof operationDef.path !== 'string') {
162
+ if (typeof operationDef.path !== 'string') {
130
163
  throw new InvalidArgumentError(
131
- 'Property "path" must be a non-empty String, but %v was given.',
164
+ 'Keyword "path" must be a String, but %v was given.',
132
165
  operationDef.path,
133
166
  );
134
167
  }
135
- // method
136
- if (!operationDef.method || typeof operationDef.method !== 'string') {
168
+ if (!operationDef.path.startsWith('/')) {
137
169
  throw new InvalidArgumentError(
138
- 'Property "method" must be a non-empty String, but %v was given.',
139
- operationDef.method,
170
+ 'Keyword "path" must start with "/", but %v was given.',
171
+ operationDef.path,
140
172
  );
141
173
  }
174
+ // method
142
175
  if (!Object.values(OAOperationMethod).includes(operationDef.method)) {
143
176
  throw new InvalidArgumentError(
144
- 'Property "method" must be one of values: %l, but %v was given.',
145
- Object.values(OAOperationMethod),
177
+ 'Keyword "method" must be one of the allowed values, but %v was given.',
178
+ operationDef.method,
146
179
  );
147
180
  }
148
181
  // operation
149
- if (
150
- !operationDef.operation ||
151
- typeof operationDef.operation !== 'object' ||
152
- Array.isArray(operationDef.operation)
153
- ) {
154
- throw new InvalidArgumentError(
155
- 'Property "operation" must be an Object, but %v was given.',
156
- operationDef.operation,
157
- );
182
+ if (operationDef.operation !== undefined) {
183
+ if (
184
+ !operationDef.operation ||
185
+ typeof operationDef.operation !== 'object' ||
186
+ Array.isArray(operationDef.operation)
187
+ ) {
188
+ throw new InvalidArgumentError(
189
+ 'Keyword "operation" must be an Object, but %v was given.',
190
+ operationDef.operation,
191
+ );
192
+ }
193
+ }
194
+ // объединение префикса и пути операции выполняется
195
+ // с учетом наличия косой черты в начале пути
196
+ let fullPath = this._pathPrefix;
197
+ if (operationDef.path !== '/') {
198
+ if (fullPath === '/') {
199
+ fullPath = operationDef.path;
200
+ } else {
201
+ fullPath += operationDef.path;
202
+ }
158
203
  }
159
- // склеивание пути
160
- const fullPath = joinPath(this._pathPrefix, operationDef.path);
161
- // создание копии схемы операции
162
- // чтобы избежать мутацию аргумента
163
- const operation = structuredClone(operationDef.operation);
164
- // объединение тегов текущей области
165
- // с тегами текущей операции и удаление
166
- // дубликатов
204
+ // создание поверхностной копии определения
205
+ // операции для избежания мутации аргумента
206
+ const operation = {...operationDef.operation};
207
+ // объединение тегов текущей области с тегами
208
+ // текущей операции и удаление дубликатов
167
209
  if (this._tags.length > 0) {
168
210
  operation.tags = [...this._tags, ...(operation.tags || [])];
169
211
  operation.tags = [...new Set(operation.tags)];
@@ -179,12 +221,12 @@ export class OADocumentScope {
179
221
  }
180
222
 
181
223
  /**
182
- * Create scope.
224
+ * Create operation group.
183
225
  *
184
226
  * @param {object} [options]
185
- * @returns {OADocumentScope}
227
+ * @returns {OAOperationGroup}
186
228
  */
187
- createScope(options = {}) {
229
+ createOperationGroup(options = {}) {
188
230
  // options
189
231
  if (!options || typeof options !== 'object' || Array.isArray(options)) {
190
232
  throw new InvalidArgumentError(
@@ -194,10 +236,15 @@ export class OADocumentScope {
194
236
  }
195
237
  // options.pathPrefix
196
238
  if (options.pathPrefix !== undefined) {
197
- if (!options.pathPrefix || typeof options.pathPrefix !== 'string') {
239
+ if (typeof options.pathPrefix !== 'string') {
198
240
  throw new InvalidArgumentError(
199
- 'Property "pathPrefix" must be a non-empty String, ' +
200
- 'but %v was given.',
241
+ 'Option "pathPrefix" must be a String, but %v was given.',
242
+ options.pathPrefix,
243
+ );
244
+ }
245
+ if (!options.pathPrefix.startsWith('/')) {
246
+ throw new InvalidArgumentError(
247
+ 'Option "pathPrefix" must start with "/", but %v was given.',
201
248
  options.pathPrefix,
202
249
  );
203
250
  }
@@ -206,22 +253,30 @@ export class OADocumentScope {
206
253
  if (options.tags !== undefined) {
207
254
  if (!Array.isArray(options.tags)) {
208
255
  throw new InvalidArgumentError(
209
- 'Property "tags" must be an Array, but %v was given.',
256
+ 'Option "tags" must be an Array, but %v was given.',
210
257
  options.tags,
211
258
  );
212
259
  }
213
260
  options.tags.forEach((tag, index) => {
214
261
  if (!tag || typeof tag !== 'string') {
215
262
  throw new InvalidArgumentError(
216
- 'Element "tags[%d]" must be a non-empty String, but %v was given.',
263
+ 'Element %v in the option "tags" must be a non-empty String, but %v was given.',
217
264
  index,
218
265
  tag,
219
266
  );
220
267
  }
221
268
  });
222
269
  }
223
- return new OADocumentScope(this._builder, {
224
- pathPrefix: joinPath(this._pathPrefix, options.pathPrefix),
270
+ let fullPathPrefix = this._pathPrefix;
271
+ if (options.pathPrefix && options.pathPrefix !== '/') {
272
+ if (fullPathPrefix === '/') {
273
+ fullPathPrefix = options.pathPrefix;
274
+ } else {
275
+ fullPathPrefix += options.pathPrefix;
276
+ }
277
+ }
278
+ return new OAOperationGroup(this._builder, {
279
+ pathPrefix: fullPathPrefix,
225
280
  tags: [...this._tags, ...(options.tags || [])],
226
281
  });
227
282
  }