@kravc/dos 2.0.0-alpha.2 → 2.0.0-alpha.20
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/.claude/settings.local.json +7 -0
- package/bin/spec.js +3 -0
- package/dist/Context/MockedContext.d.ts +8 -0
- package/dist/Context/MockedContext.d.ts.map +1 -0
- package/dist/Context/MockedContext.js +22 -0
- package/dist/Context/MockedContext.js.map +1 -0
- package/dist/Context/index.d.ts +2 -1
- package/dist/Context/index.d.ts.map +1 -1
- package/dist/Context/index.js +3 -1
- package/dist/Context/index.js.map +1 -1
- package/dist/Document/Document.d.ts +12 -7
- package/dist/Document/Document.d.ts.map +1 -1
- package/dist/Document/Document.js +23 -13
- package/dist/Document/Document.js.map +1 -1
- package/dist/Document/MemoryDocument.d.ts +5 -4
- package/dist/Document/MemoryDocument.d.ts.map +1 -1
- package/dist/Document/MemoryDocument.js +13 -7
- package/dist/Document/MemoryDocument.js.map +1 -1
- package/dist/Operation/Operation.d.ts +1 -1
- package/dist/Operation/Operation.d.ts.map +1 -1
- package/dist/Operation/Operation.js +2 -2
- package/dist/Operation/Operation.js.map +1 -1
- package/dist/Operation/errors/{UnprocessibleConditionError.d.ts → UnprocessableConditionError.d.ts} +6 -6
- package/dist/Operation/errors/{UnprocessibleConditionError.d.ts.map → UnprocessableConditionError.d.ts.map} +1 -1
- package/dist/Operation/errors/{UnprocessibleConditionError.js → UnprocessableConditionError.js} +8 -8
- package/dist/Operation/errors/{UnprocessibleConditionError.js.map → UnprocessableConditionError.js.map} +1 -1
- package/dist/Operation/errors/index.d.ts +2 -2
- package/dist/Operation/errors/index.js +3 -3
- package/dist/Operation/operations/Index.js +7 -7
- package/dist/Operation/operations/Index.js.map +1 -1
- package/dist/Operation/operations/List.d.ts.map +1 -1
- package/dist/Operation/operations/List.js +13 -2
- package/dist/Operation/operations/List.js.map +1 -1
- package/dist/Operation/security/userAuthorization.d.ts +2 -0
- package/dist/Operation/security/userAuthorization.d.ts.map +1 -1
- package/dist/Operation/security/userAuthorization.js.map +1 -1
- package/dist/Service/Service.d.ts +0 -1
- package/dist/Service/Service.d.ts.map +1 -1
- package/dist/Service/Service.js +13 -4
- package/dist/Service/Service.js.map +1 -1
- package/dist/Service/errors/OperationError.d.ts +2 -2
- package/dist/Service/errors/OperationError.d.ts.map +1 -1
- package/dist/Service/errors/OperationError.js.map +1 -1
- package/dist/Service/test/execute.d.ts +2 -2
- package/dist/Service/test/execute.d.ts.map +1 -1
- package/package.json +13 -9
- package/scripts/spec.ts +23 -0
- package/src/Context/MockedContext.ts +21 -0
- package/src/Context/__tests__/MockedContext.test.ts +14 -0
- package/src/Context/index.ts +2 -0
- package/src/Document/Document.ts +33 -19
- package/src/Document/MemoryDocument.ts +16 -8
- package/src/Operation/Operation.ts +7 -10
- package/src/Operation/errors/{UnprocessibleConditionError.ts → UnprocessableConditionError.ts} +7 -7
- package/src/Operation/errors/__tests__/UnprocessableConditionError.test.ts +20 -0
- package/src/Operation/errors/index.ts +2 -2
- package/src/Operation/operations/Index.ts +8 -8
- package/src/Operation/operations/List.ts +14 -2
- package/src/Operation/operations/__tests__/Create.test.ts +1 -1
- package/src/Operation/operations/__tests__/Delete.test.ts +1 -1
- package/src/Operation/operations/__tests__/Index.test.ts +4 -4
- package/src/Operation/operations/__tests__/List.test.ts +1 -1
- package/src/Operation/operations/__tests__/Read.test.ts +1 -1
- package/src/Operation/operations/__tests__/Update.test.ts +1 -1
- package/src/Operation/security/userAuthorization.ts +2 -0
- package/src/Service/Service.ts +18 -5
- package/src/Service/__tests__/Service.test.ts +1 -6
- package/src/Service/errors/OperationError.ts +2 -2
- package/src/Service/errors/OperationErrorAttributes.d.ts +1 -1
- package/src/Service/test/execute.ts +1 -1
- package/src/Operation/errors/__tests__/UnprocessibleConditionError.test.ts +0 -20
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { got } from '@kravc/schema';
|
|
2
|
-
import
|
|
2
|
+
import Context, { type QueryMap, type MutationMap } from '../Context';
|
|
3
3
|
import { get, set, last, unset, cloneDeep, sortBy } from 'lodash';
|
|
4
4
|
import { DocumentExistsError, DocumentNotFoundError } from '../Operation';
|
|
5
5
|
import Document, { type IndexOptions, type IndexAllOptions } from './Document';
|
|
@@ -11,6 +11,9 @@ type Item = {
|
|
|
11
11
|
|
|
12
12
|
const _MEMORY_STORE = {} as Record<string, Record<string, Item>>;
|
|
13
13
|
|
|
14
|
+
const QUERY_ERROR_TEMPLATE = 'Query parameter "$PATH" is required';
|
|
15
|
+
const ATTRIBUTE_ERROR_TEMPLATE = 'Attribute "$PATH" is required';
|
|
16
|
+
|
|
14
17
|
/** Example implementation of a document class stored in memory. */
|
|
15
18
|
class MemoryDocument<T> extends Document<T> {
|
|
16
19
|
/** Returns collection where documents are stored. */
|
|
@@ -35,6 +38,7 @@ class MemoryDocument<T> extends Document<T> {
|
|
|
35
38
|
|
|
36
39
|
/** Implements interface to get documents in batches. */
|
|
37
40
|
static async _index<T>(query: QueryMap, options: IndexOptions): Promise<{
|
|
41
|
+
limit: number;
|
|
38
42
|
count: number;
|
|
39
43
|
items: T[];
|
|
40
44
|
lastEvaluatedKey?: string;
|
|
@@ -81,6 +85,7 @@ class MemoryDocument<T> extends Document<T> {
|
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
return {
|
|
88
|
+
limit: limit || count,
|
|
84
89
|
items,
|
|
85
90
|
count,
|
|
86
91
|
lastEvaluatedKey,
|
|
@@ -102,7 +107,7 @@ class MemoryDocument<T> extends Document<T> {
|
|
|
102
107
|
|
|
103
108
|
/** Implements interface to get a document. */
|
|
104
109
|
static async _read<T>(query: QueryMap): Promise<T> {
|
|
105
|
-
const idValue = got(query, this.idKey,
|
|
110
|
+
const idValue = got(query, this.idKey, QUERY_ERROR_TEMPLATE) as string;
|
|
106
111
|
|
|
107
112
|
const item = get(this.collection, idValue) as T;
|
|
108
113
|
|
|
@@ -114,8 +119,9 @@ class MemoryDocument<T> extends Document<T> {
|
|
|
114
119
|
}
|
|
115
120
|
|
|
116
121
|
/** Implements interface to save a document. */
|
|
117
|
-
|
|
118
|
-
|
|
122
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
123
|
+
static async _create<T>(attributes: T, _context: Context): Promise<void> {
|
|
124
|
+
const idValue = got(attributes, this.idKey, ATTRIBUTE_ERROR_TEMPLATE) as string;
|
|
119
125
|
|
|
120
126
|
const item = get(this.collection, idValue) as T;
|
|
121
127
|
|
|
@@ -127,8 +133,9 @@ class MemoryDocument<T> extends Document<T> {
|
|
|
127
133
|
}
|
|
128
134
|
|
|
129
135
|
/** Implements interface to update a document. */
|
|
130
|
-
|
|
131
|
-
|
|
136
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
137
|
+
static async _update<T>(query: QueryMap, mutation: MutationMap, _context: Context, _previousAttributes: T): Promise<T> {
|
|
138
|
+
const idValue = got(query, this.idKey, QUERY_ERROR_TEMPLATE) as string;
|
|
132
139
|
|
|
133
140
|
const componentTitle = this.getTitle();
|
|
134
141
|
const item = got(this.collection, idValue, `${componentTitle} with ID "$PATH" is not found`) as T;
|
|
@@ -139,8 +146,9 @@ class MemoryDocument<T> extends Document<T> {
|
|
|
139
146
|
}
|
|
140
147
|
|
|
141
148
|
/** Implements interface to delete a document. */
|
|
142
|
-
|
|
143
|
-
|
|
149
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
150
|
+
static async _delete<T>(query: QueryMap, _context: Context, _previousAttributes: T): Promise<void> {
|
|
151
|
+
const idValue = got(query, this.idKey, QUERY_ERROR_TEMPLATE) as string;
|
|
144
152
|
|
|
145
153
|
const componentTitle = this.getTitle();
|
|
146
154
|
got(this.collection, idValue, `${componentTitle} with ID "$PATH" is not found`) as T;
|
|
@@ -184,11 +184,11 @@ class Operation {
|
|
|
184
184
|
errors.InvalidParametersError = {
|
|
185
185
|
statusCode: 400,
|
|
186
186
|
description: 'Invalid operation parameters, input syntax is correct,' +
|
|
187
|
-
' but input values are not
|
|
187
|
+
' but input values are not processable'
|
|
188
188
|
};
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
errors.
|
|
191
|
+
errors.UnprocessableConditionError = {
|
|
192
192
|
statusCode: 422,
|
|
193
193
|
description: 'Operation failed to process the request cause of expected' +
|
|
194
194
|
' exit condition'
|
|
@@ -331,7 +331,7 @@ class Operation {
|
|
|
331
331
|
}
|
|
332
332
|
|
|
333
333
|
/** Executes component action. */
|
|
334
|
-
async action(parameters: Record<string, unknown>) {
|
|
334
|
+
async action(parameters: Record<string, unknown>): Promise<{ data?: Result }> {
|
|
335
335
|
const { Component } = this.constructor as typeof Operation;
|
|
336
336
|
|
|
337
337
|
if (!Component) {
|
|
@@ -343,14 +343,11 @@ class Operation {
|
|
|
343
343
|
|
|
344
344
|
const data = await componentActionMethod(this.context, query, mutation as MutationMap);
|
|
345
345
|
|
|
346
|
-
return { data }
|
|
346
|
+
return { data };
|
|
347
347
|
}
|
|
348
348
|
|
|
349
349
|
/** Post-processes operation result after action. */
|
|
350
|
-
async after(
|
|
351
|
-
_parameters: Record<string, unknown>,
|
|
352
|
-
result?: Record<string, unknown>
|
|
353
|
-
): Promise<Result | void> {
|
|
350
|
+
async after(_parameters: Record<string, unknown>, result?: unknown): Promise<unknown | void> {
|
|
354
351
|
return result;
|
|
355
352
|
}
|
|
356
353
|
|
|
@@ -367,7 +364,7 @@ class Operation {
|
|
|
367
364
|
/** Executes operation for the request input. */
|
|
368
365
|
async exec(input: Record<string, unknown>): Promise<OperationResponse> {
|
|
369
366
|
let parameters = cloneDeep(input);
|
|
370
|
-
let result;
|
|
367
|
+
let result: Result;
|
|
371
368
|
|
|
372
369
|
const beforeResult = await this.before(parameters);
|
|
373
370
|
|
|
@@ -383,7 +380,7 @@ class Operation {
|
|
|
383
380
|
? (
|
|
384
381
|
result.data
|
|
385
382
|
? { ...result, data: afterResult }
|
|
386
|
-
: afterResult
|
|
383
|
+
: afterResult as Result
|
|
387
384
|
)
|
|
388
385
|
: result;
|
|
389
386
|
|
package/src/Operation/errors/{UnprocessibleConditionError.ts → UnprocessableConditionError.ts}
RENAMED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import CommonError from './CommonError';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Unprocessable Condition Error
|
|
5
5
|
*
|
|
6
|
-
* `
|
|
6
|
+
* `UnprocessableConditionError` represents a **422 Unprocessable Entity** error that occurs
|
|
7
7
|
* when an operation encounters an expected exit condition that prevents it from processing
|
|
8
8
|
* the request. This error indicates that the request is well-formed and valid, but the
|
|
9
9
|
* operation cannot proceed due to business logic constraints or state conditions.
|
|
@@ -22,11 +22,11 @@ import CommonError from './CommonError';
|
|
|
22
22
|
* The 422 status code indicates that the server understands the request but cannot process
|
|
23
23
|
* it due to semantic errors or business logic constraints.
|
|
24
24
|
*/
|
|
25
|
-
class
|
|
26
|
-
/** Creates an instance of
|
|
27
|
-
constructor(message: string = '
|
|
28
|
-
super('
|
|
25
|
+
class UnprocessableConditionError extends CommonError {
|
|
26
|
+
/** Creates an instance of unprocessable condition error. */
|
|
27
|
+
constructor(message: string = 'Unprocessable condition') {
|
|
28
|
+
super('UnprocessableConditionError', message);
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
export default
|
|
32
|
+
export default UnprocessableConditionError;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import UnprocessableConditionError from '../UnprocessableConditionError';
|
|
2
|
+
|
|
3
|
+
describe('UnprocessableConditionError', () => {
|
|
4
|
+
describe('UnprocessableConditionError.constructor(message)', () => {
|
|
5
|
+
it('creates an instance with default message when no message is provided', () => {
|
|
6
|
+
const err = new UnprocessableConditionError();
|
|
7
|
+
|
|
8
|
+
expect(err.code).toBe('UnprocessableConditionError');
|
|
9
|
+
expect(err.message).toBe('Unprocessable condition');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('creates an instance with custom message when provided', () => {
|
|
13
|
+
const customMessage = 'Order is already canceled';
|
|
14
|
+
const err = new UnprocessableConditionError(customMessage);
|
|
15
|
+
|
|
16
|
+
expect(err.code).toBe('UnprocessableConditionError');
|
|
17
|
+
expect(err.message).toBe(customMessage);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -4,7 +4,7 @@ import AccessDeniedError from './AccessDeniedError';
|
|
|
4
4
|
import DocumentExistsError from './DocumentExistsError';
|
|
5
5
|
import DocumentNotFoundError from './DocumentNotFoundError';
|
|
6
6
|
import InvalidParametersError from './InvalidParametersError';
|
|
7
|
-
import
|
|
7
|
+
import UnprocessableConditionError from './UnprocessableConditionError';
|
|
8
8
|
|
|
9
9
|
export {
|
|
10
10
|
CommonError,
|
|
@@ -13,5 +13,5 @@ export {
|
|
|
13
13
|
DocumentExistsError,
|
|
14
14
|
DocumentNotFoundError,
|
|
15
15
|
InvalidParametersError,
|
|
16
|
-
|
|
16
|
+
UnprocessableConditionError,
|
|
17
17
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Component from '../../Component';
|
|
2
|
-
import { capitalize } from 'lodash';
|
|
2
|
+
import { get, capitalize } from 'lodash';
|
|
3
3
|
import Operation, { type Result } from '../Operation';
|
|
4
4
|
import { type PropertiesSchemaSource } from '@kravc/schema';
|
|
5
5
|
|
|
@@ -9,7 +9,7 @@ const SORT_ORDER = {
|
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
const DEFAULT_LIMIT = 20;
|
|
12
|
-
const DEFAULT_LIMIT_MAX =
|
|
12
|
+
const DEFAULT_LIMIT_MAX = 100;
|
|
13
13
|
const DEFAULT_SORT_ORDER = SORT_ORDER.DESC;
|
|
14
14
|
|
|
15
15
|
export type PageInfo = {
|
|
@@ -53,12 +53,12 @@ const Index = (
|
|
|
53
53
|
|
|
54
54
|
/** Returns default value for a limit parameter. */
|
|
55
55
|
static get defaultLimit() {
|
|
56
|
-
return DEFAULT_LIMIT;
|
|
56
|
+
return get(ComponentClass, 'indexDefaultLimit', DEFAULT_LIMIT);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
/** Returns maximum number for a limit parameter. */
|
|
60
60
|
static get limitMax() {
|
|
61
|
-
return DEFAULT_LIMIT_MAX;
|
|
61
|
+
return get(ComponentClass, 'indexLimitMax', DEFAULT_LIMIT_MAX);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
/** Returns default value for a sort parameter. */
|
|
@@ -106,12 +106,12 @@ const Index = (
|
|
|
106
106
|
},
|
|
107
107
|
count: {
|
|
108
108
|
type: 'integer',
|
|
109
|
-
example:
|
|
109
|
+
example: 5,
|
|
110
110
|
description: `Number of ${documentTitle}`,
|
|
111
111
|
},
|
|
112
112
|
limit: {
|
|
113
113
|
type: 'integer',
|
|
114
|
-
example:
|
|
114
|
+
example: get(ComponentClass, 'indexDefaultLimit', DEFAULT_LIMIT),
|
|
115
115
|
description: `Limit number of ${documentTitle} to be returned`,
|
|
116
116
|
},
|
|
117
117
|
lastEvaluatedKey: {
|
|
@@ -127,7 +127,7 @@ const Index = (
|
|
|
127
127
|
const {
|
|
128
128
|
sort,
|
|
129
129
|
limit,
|
|
130
|
-
|
|
130
|
+
indexName,
|
|
131
131
|
exclusiveStartKey,
|
|
132
132
|
...query
|
|
133
133
|
} = parameters;
|
|
@@ -135,7 +135,7 @@ const Index = (
|
|
|
135
135
|
const options = {
|
|
136
136
|
sort,
|
|
137
137
|
limit,
|
|
138
|
-
|
|
138
|
+
indexName,
|
|
139
139
|
exclusiveStartKey,
|
|
140
140
|
};
|
|
141
141
|
|
|
@@ -35,17 +35,29 @@ const List = (
|
|
|
35
35
|
return componentAction;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/** Returns schema source for the operation output with pagination. */
|
|
39
|
+
static get output() {
|
|
40
|
+
return {
|
|
41
|
+
data: {
|
|
42
|
+
items: {
|
|
43
|
+
$ref: ComponentClass.schema!.id
|
|
44
|
+
},
|
|
45
|
+
required: true,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
38
50
|
/** Executes components list action. */
|
|
39
51
|
async action(parameters: Record<string, unknown>) {
|
|
40
52
|
const {
|
|
41
53
|
sort,
|
|
42
|
-
|
|
54
|
+
indexName,
|
|
43
55
|
...query
|
|
44
56
|
} = parameters;
|
|
45
57
|
|
|
46
58
|
const options = {
|
|
47
59
|
sort,
|
|
48
|
-
|
|
60
|
+
indexName,
|
|
49
61
|
};
|
|
50
62
|
|
|
51
63
|
const { componentActionMethod } = this.constructor as typeof Operation;
|
|
@@ -100,7 +100,7 @@ describe('Create(Document, actionMethod)', () => {
|
|
|
100
100
|
const errorCodes = Object.keys(CreateProfile.errors);
|
|
101
101
|
expect(errorCodes).toContain('InvalidInputError');
|
|
102
102
|
expect(errorCodes).toContain('InvalidOutputError');
|
|
103
|
-
expect(errorCodes).toContain('
|
|
103
|
+
expect(errorCodes).toContain('UnprocessableConditionError');
|
|
104
104
|
expect(errorCodes).toContain('DocumentExistsError');
|
|
105
105
|
});
|
|
106
106
|
});
|
|
@@ -99,7 +99,7 @@ describe('Delete(Document, actionMethod)', () => {
|
|
|
99
99
|
it('includes related errors', () => {
|
|
100
100
|
const errorCodes = Object.keys(DeleteProfile.errors);
|
|
101
101
|
expect(errorCodes).toContain('InvalidInputError');
|
|
102
|
-
expect(errorCodes).toContain('
|
|
102
|
+
expect(errorCodes).toContain('UnprocessableConditionError');
|
|
103
103
|
expect(errorCodes).toContain('DocumentNotFoundError');
|
|
104
104
|
});
|
|
105
105
|
});
|
|
@@ -95,7 +95,7 @@ describe('Index(Document, actionMethod)', () => {
|
|
|
95
95
|
const errorCodes = Object.keys(IndexProfiles.errors);
|
|
96
96
|
expect(errorCodes).toContain('InvalidInputError');
|
|
97
97
|
expect(errorCodes).toContain('InvalidOutputError');
|
|
98
|
-
expect(errorCodes).toContain('
|
|
98
|
+
expect(errorCodes).toContain('UnprocessableConditionError');
|
|
99
99
|
});
|
|
100
100
|
});
|
|
101
101
|
|
|
@@ -107,7 +107,7 @@ describe('Index(Document, actionMethod)', () => {
|
|
|
107
107
|
example: 20,
|
|
108
108
|
default: 20,
|
|
109
109
|
minimum: 1,
|
|
110
|
-
maximum:
|
|
110
|
+
maximum: 100,
|
|
111
111
|
description: 'Limit number of profiles to be returned',
|
|
112
112
|
},
|
|
113
113
|
sort: {
|
|
@@ -146,12 +146,12 @@ describe('Index(Document, actionMethod)', () => {
|
|
|
146
146
|
},
|
|
147
147
|
count: {
|
|
148
148
|
type: 'integer',
|
|
149
|
-
example:
|
|
149
|
+
example: 5,
|
|
150
150
|
description: 'Number of profiles',
|
|
151
151
|
},
|
|
152
152
|
limit: {
|
|
153
153
|
type: 'integer',
|
|
154
|
-
example:
|
|
154
|
+
example: 20,
|
|
155
155
|
description: 'Limit number of profiles to be returned',
|
|
156
156
|
},
|
|
157
157
|
lastEvaluatedKey: {
|
|
@@ -93,7 +93,7 @@ describe('List(Document, actionMethod)', () => {
|
|
|
93
93
|
it('includes related errors', () => {
|
|
94
94
|
const errorCodes = Object.keys(ListProfiles.errors);
|
|
95
95
|
expect(errorCodes).toContain('InvalidOutputError');
|
|
96
|
-
expect(errorCodes).toContain('
|
|
96
|
+
expect(errorCodes).toContain('UnprocessableConditionError');
|
|
97
97
|
});
|
|
98
98
|
});
|
|
99
99
|
|
|
@@ -94,7 +94,7 @@ describe('Read(Document, actionMethod)', () => {
|
|
|
94
94
|
const errorCodes = Object.keys(ReadProfile.errors);
|
|
95
95
|
expect(errorCodes).toContain('InvalidInputError');
|
|
96
96
|
expect(errorCodes).toContain('InvalidOutputError');
|
|
97
|
-
expect(errorCodes).toContain('
|
|
97
|
+
expect(errorCodes).toContain('UnprocessableConditionError');
|
|
98
98
|
expect(errorCodes).toContain('DocumentNotFoundError');
|
|
99
99
|
});
|
|
100
100
|
});
|
|
@@ -100,7 +100,7 @@ describe('Update(Document, actionMethod)', () => {
|
|
|
100
100
|
const errorCodes = Object.keys(UpdateProfile.errors);
|
|
101
101
|
expect(errorCodes).toContain('InvalidInputError');
|
|
102
102
|
expect(errorCodes).toContain('InvalidOutputError');
|
|
103
|
-
expect(errorCodes).toContain('
|
|
103
|
+
expect(errorCodes).toContain('UnprocessableConditionError');
|
|
104
104
|
expect(errorCodes).toContain('DocumentNotFoundError');
|
|
105
105
|
});
|
|
106
106
|
});
|
package/src/Service/Service.ts
CHANGED
|
@@ -34,7 +34,6 @@ const createDefaultContext = (
|
|
|
34
34
|
|
|
35
35
|
type Module = {
|
|
36
36
|
id: string;
|
|
37
|
-
get isComponent(): boolean;
|
|
38
37
|
};
|
|
39
38
|
|
|
40
39
|
type Options = {
|
|
@@ -45,6 +44,17 @@ type Options = {
|
|
|
45
44
|
skipOperations?: string[];
|
|
46
45
|
};
|
|
47
46
|
|
|
47
|
+
/** Returns true if TargetClass is a constructor whose prototype extends BaseClass. */
|
|
48
|
+
function isExtendedFrom(
|
|
49
|
+
TargetClass: unknown,
|
|
50
|
+
BaseClass: typeof Operation | typeof Component
|
|
51
|
+
): boolean {
|
|
52
|
+
return (
|
|
53
|
+
typeof TargetClass === 'function' &&
|
|
54
|
+
(TargetClass as abstract new (...args: unknown[]) => unknown).prototype instanceof BaseClass
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
48
58
|
/** Service */
|
|
49
59
|
class Service {
|
|
50
60
|
private _url: string;
|
|
@@ -71,11 +81,14 @@ class Service {
|
|
|
71
81
|
skipOperations = DEFAULT_SKIP_OPERATIONS,
|
|
72
82
|
} = options;
|
|
73
83
|
|
|
74
|
-
|
|
75
|
-
.filter(
|
|
84
|
+
const nonSchemas = modules
|
|
85
|
+
.filter(m => !(m instanceof Schema));
|
|
86
|
+
|
|
87
|
+
let components = nonSchemas
|
|
88
|
+
.filter(m => isExtendedFrom(m, Component)) as (typeof Component)[];
|
|
76
89
|
|
|
77
|
-
const operations =
|
|
78
|
-
.filter(
|
|
90
|
+
const operations = nonSchemas
|
|
91
|
+
.filter(m => isExtendedFrom(m, Operation))
|
|
79
92
|
.filter(({ id: operationId }) => !skipOperations.includes(operationId)) as (typeof Operation)[];
|
|
80
93
|
|
|
81
94
|
const referencedComponents = compact(operations.map(({ Component }) => Component));
|
|
@@ -178,12 +178,7 @@ describe('Service', () => {
|
|
|
178
178
|
|
|
179
179
|
const body = JSON.parse(json!);
|
|
180
180
|
|
|
181
|
-
expect(body).toEqual(
|
|
182
|
-
info: {
|
|
183
|
-
title: '@kravc/dos',
|
|
184
|
-
version: '2.0.0-alpha.2',
|
|
185
|
-
},
|
|
186
|
-
});
|
|
181
|
+
expect(body.info.title).toEqual('@kravc/dos');
|
|
187
182
|
});
|
|
188
183
|
|
|
189
184
|
it('returns composer file for /Enums.yaml path', async () => {
|
|
@@ -2,7 +2,7 @@ import path from 'path';
|
|
|
2
2
|
import Component from '../../Component';
|
|
3
3
|
import { Context } from '../../Context';
|
|
4
4
|
import { loadSync } from '@kravc/schema';
|
|
5
|
-
import {
|
|
5
|
+
import { OperationErrorAttributes } from './OperationErrorAttributes';
|
|
6
6
|
import logOperationError, { type OriginalError, type ErrorAttributes } from './logOperationError';
|
|
7
7
|
|
|
8
8
|
const SCHEMA_PATH = path.resolve(__dirname) + '/OperationError.yaml';
|
|
@@ -28,7 +28,7 @@ const INTERNAL_ERROR_CODE = 500;
|
|
|
28
28
|
* This class acts as the final error handler in the operation processing pipeline,
|
|
29
29
|
* transforming any thrown error into a standardized, schema-validated response format.
|
|
30
30
|
*/
|
|
31
|
-
class OperationError extends Component<
|
|
31
|
+
class OperationError extends Component<OperationErrorAttributes> {
|
|
32
32
|
/** Returns schema of the operation error. */
|
|
33
33
|
static get schema() {
|
|
34
34
|
return operationErrorSchema;
|
|
@@ -10,7 +10,7 @@ type Parameters = {
|
|
|
10
10
|
[index: string]: unknown;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
type SuccessResult = { data: Record<string, unknown>; };
|
|
13
|
+
type SuccessResult = { data: Record<string, unknown>[] | Record<string, unknown>; };
|
|
14
14
|
type ErrorResult = { error: ErrorAttributes; };
|
|
15
15
|
type Result = SuccessResult | ErrorResult;
|
|
16
16
|
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import UnprocessibleConditionError from '../UnprocessibleConditionError';
|
|
2
|
-
|
|
3
|
-
describe('UnprocessibleConditionError', () => {
|
|
4
|
-
describe('UnprocessibleConditionError.constructor(message)', () => {
|
|
5
|
-
it('creates an instance with default message when no message is provided', () => {
|
|
6
|
-
const err = new UnprocessibleConditionError();
|
|
7
|
-
|
|
8
|
-
expect(err.code).toBe('UnprocessibleConditionError');
|
|
9
|
-
expect(err.message).toBe('Unprocessible condition');
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
it('creates an instance with custom message when provided', () => {
|
|
13
|
-
const customMessage = 'Order is already cancelled';
|
|
14
|
-
const err = new UnprocessibleConditionError(customMessage);
|
|
15
|
-
|
|
16
|
-
expect(err.code).toBe('UnprocessibleConditionError');
|
|
17
|
-
expect(err.message).toBe(customMessage);
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
});
|