@genesislcap/mock-server 15.51.1 → 15.53.0
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/README.md +408 -39
- package/dist/db/criteria.d.ts +13 -0
- package/dist/db/criteria.js +60 -21
- package/dist/db/identity.d.ts +11 -0
- package/dist/db/identity.js +46 -0
- package/dist/db/ordering.d.ts +8 -0
- package/dist/db/ordering.js +49 -0
- package/dist/db/schema.d.ts +62 -0
- package/dist/db/schema.js +490 -0
- package/dist/db/store.d.ts +20 -3
- package/dist/db/store.js +300 -50
- package/dist/handlers/commitEvent.js +47 -9
- package/dist/handlers/crudEvents.d.ts +2 -0
- package/dist/handlers/crudEvents.js +138 -0
- package/dist/handlers/dataLogon.js +105 -6
- package/dist/handlers/eventValidation.d.ts +5 -0
- package/dist/handlers/eventValidation.js +29 -0
- package/dist/handlers/jsonSchema.d.ts +4 -2
- package/dist/handlers/jsonSchema.js +222 -61
- package/dist/handlers/meta.d.ts +6 -1
- package/dist/handlers/meta.js +168 -21
- package/dist/handlers/requestReply.js +519 -30
- package/dist/handlers/resourceAuth.d.ts +1 -0
- package/dist/handlers/resourceAuth.js +14 -0
- package/dist/handlers/resources.js +3 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/protocol/connection.d.ts +2 -0
- package/dist/protocol/errors.d.ts +100 -2
- package/dist/protocol/errors.js +300 -12
- package/dist/protocol/fieldTypes.d.ts +10 -0
- package/dist/protocol/fieldTypes.js +107 -0
- package/dist/protocol/gsfSchemas.d.ts +9 -0
- package/dist/protocol/gsfSchemas.js +423 -0
- package/dist/protocol/messageTypes.d.ts +1 -0
- package/dist/protocol/messageTypes.js +1 -0
- package/dist/protocol/msgNack.d.ts +2 -0
- package/dist/protocol/msgNack.js +10 -0
- package/dist/protocol/rowUpdate.d.ts +5 -3
- package/dist/protocol/rowUpdate.js +33 -15
- package/dist/protocol/schemaValidation.d.ts +11 -0
- package/dist/protocol/schemaValidation.js +186 -0
- package/dist/server.js +95 -29
- package/dist/types.d.ts +45 -0
- package/package.json +1 -1
- package/src/db/criteria.ts +72 -21
- package/src/db/identity.ts +53 -0
- package/src/db/ordering.ts +56 -0
- package/src/db/schema.ts +672 -0
- package/src/db/store.ts +343 -50
- package/src/handlers/commitEvent.ts +54 -10
- package/src/handlers/crudEvents.ts +181 -0
- package/src/handlers/dataLogon.ts +127 -7
- package/src/handlers/eventValidation.ts +43 -0
- package/src/handlers/jsonSchema.ts +302 -69
- package/src/handlers/meta.ts +227 -23
- package/src/handlers/requestReply.ts +626 -33
- package/src/handlers/resourceAuth.ts +18 -0
- package/src/handlers/resources.ts +3 -0
- package/src/index.ts +17 -1
- package/src/protocol/connection.ts +10 -2
- package/src/protocol/errors.ts +361 -13
- package/src/protocol/fieldTypes.ts +115 -0
- package/src/protocol/gsfSchemas.ts +505 -0
- package/src/protocol/messageTypes.ts +1 -0
- package/src/protocol/msgNack.ts +12 -0
- package/src/protocol/rowUpdate.ts +40 -20
- package/src/protocol/schemaValidation.ts +208 -0
- package/src/server.ts +106 -28
- package/src/types.ts +187 -5
|
@@ -1,17 +1,115 @@
|
|
|
1
|
+
declare const ERROR_CODES: {
|
|
2
|
+
readonly GENERIC_ERROR: readonly ["Something is wrong", "500 Internal Server Error"];
|
|
3
|
+
readonly MISSING_FIELD: readonly ["Field is missing", "400 Bad Request"];
|
|
4
|
+
readonly VALIDATION_ERROR: readonly ["Message failed validation", "400 Bad Request"];
|
|
5
|
+
readonly INVALID_MESSAGE: readonly ["Invalid message or invalid message format", "400 Bad Request"];
|
|
6
|
+
readonly NOT_SUPPORTED_ENUM_VALUE: readonly ["Enum value not supported", "400 Bad Request"];
|
|
7
|
+
readonly NOT_AUTHORISED: readonly ["Not authorised", "403 Forbidden"];
|
|
8
|
+
readonly DUPLICATE_KEY: readonly ["Record conflicts with unique index already stored in the database", "500 Internal Server Error"];
|
|
9
|
+
readonly INVALID_MESSAGE_TYPE: readonly ["Invalid message type", "400 Bad Request"];
|
|
10
|
+
readonly INVALID_DATASOURCE: readonly ["Invalid DATASOURCE_NAME", "400 Bad Request"];
|
|
11
|
+
readonly INVALID_PARAMETER: readonly ["MOVING_VIEW can't be true if maxView is set to 0", "400 Bad Request"];
|
|
12
|
+
readonly LOGIN_ERROR: readonly ["There was a problem logging in to the SOAP WebService", "401 Unauthorized"];
|
|
13
|
+
readonly MULTIPLE_TABLES: readonly ["Only one table can be referred", "500 Internal Server Error"];
|
|
14
|
+
readonly MISSING_KEY: readonly ["Key name must be provided", "400 Bad Request"];
|
|
15
|
+
readonly UNKNOWN_TABLE: readonly ["No such table", "400 Bad Request"];
|
|
16
|
+
readonly UNKNOWN_FIELD: readonly ["No such field on table", "400 Bad Request"];
|
|
17
|
+
readonly UNKNOWN: readonly ["Unknown error", "500 Internal Server Error"];
|
|
18
|
+
readonly NO_MESSAGE_TYPE: readonly ["No message type provided", "400 Bad Request"];
|
|
19
|
+
readonly NO_SOURCE_REF: readonly ["No source ref provided", "400 Bad Request"];
|
|
20
|
+
readonly NO_USER_NAME: readonly ["No user name provided", "400 Bad Request"];
|
|
21
|
+
readonly UNAVAILABLE: readonly ["Process is currently unavailable", "503 Service Unavailable"];
|
|
22
|
+
readonly UNKNOWN_MESSAGE_TYPE: readonly ["Message type not supported", "400 Bad Request"];
|
|
23
|
+
readonly FEATURE_NOT_PROVIDED: readonly ["Feature not provided", "400 Bad Request"];
|
|
24
|
+
readonly FEATURE_NOT_FOUND: readonly ["Feature not found", "404 Not Found"];
|
|
25
|
+
readonly JSON_SCHEMA_NOT_FOUND: readonly ["JSON schema not found", "404 Not Found"];
|
|
26
|
+
readonly REJECT_RULE_DOES_NOT_EXIST: readonly ["Failed to find Dynamic Rule", "404 Not Found"];
|
|
27
|
+
readonly ERROR_CHECKING_EXISTING_RULE: readonly ["Error while checking for rule", "400 Bad Request"];
|
|
28
|
+
readonly NO_DS_NAME: readonly ["No data source provided", "400 Bad Request"];
|
|
29
|
+
readonly INVALID_DS_NAME: readonly ["Data source does not exist", "404 Not Found"];
|
|
30
|
+
readonly INVALID_INDEX: readonly ["Invalid index", "400 Bad Request"];
|
|
31
|
+
readonly REJECT_RULE_MISSING: readonly ["Rule does not exist", "404 Not Found"];
|
|
32
|
+
readonly ERROR_MODIFYING_RULE: readonly ["Error encountered trying to modify rule", "500 Internal Server Error"];
|
|
33
|
+
readonly INVALID_CRITERIA: readonly ["Criteria has failed validation", "400 Bad Request"];
|
|
34
|
+
readonly MAX_LOGON_LIMIT: readonly ["Tried to connect too many times", "429 Too Many Requests"];
|
|
35
|
+
readonly RECORD_NOT_FOUND: readonly ["No record found for given key data", "404 Not Found"];
|
|
36
|
+
readonly SERVICE_NOT_FOUND: readonly ["No service found", "404 Not Found"];
|
|
37
|
+
readonly DATABASE_FAILURE: readonly ["There was an internal database error", "500 Internal Server Error"];
|
|
38
|
+
readonly DATABASE_ERROR: readonly ["Database error", "500 Internal Server Error"];
|
|
39
|
+
readonly OPERATION_TIMEOUT: readonly ["Server timed out", "408 Request Timeout"];
|
|
40
|
+
readonly DEPENDENT_RECORD_FOUND: readonly ["Dependency found on other records for changed value(s)", "500 Internal Server Error"];
|
|
41
|
+
readonly REQUIRES_APPROVAL: readonly ["Transaction requires approval", "403 Forbidden"];
|
|
42
|
+
readonly APPROVAL_MESSAGE_MISSING: readonly ["Transaction requires approval, but the approval message is missing", "400 Bad Request"];
|
|
43
|
+
readonly INTERNAL_ERROR: readonly ["Unexpected server error", "500 Internal Server Error"];
|
|
44
|
+
readonly GATEWAY_ERROR: readonly ["Notify gateway error", "400 Bad Request"];
|
|
45
|
+
readonly REQUEST_FAILED: readonly ["Problem handling request", "400 Bad Request"];
|
|
46
|
+
readonly UNABLE_TO_UPDATE_APPROVAL: readonly ["Pending approval error, unable to update approval", "500 Internal Server Error"];
|
|
47
|
+
readonly APPROVAL_SAME_USER_CANNOT_ACCEPT: readonly ["Pending approval error, same user cannot accept", "400 Bad Request"];
|
|
48
|
+
readonly APPROVAL_RECORD_NOT_FOUND: readonly ["Pending approval record not found", "404 Not Found"];
|
|
49
|
+
readonly REJECTED_BY_SERVICE: readonly ["Pending approval rejected by service", "500 Internal Server Error"];
|
|
50
|
+
readonly APPROVAL_DIFF_USER_CANNOT_CANCEL: readonly ["Pending approval different user cannot cancel", "400 Bad Request"];
|
|
51
|
+
readonly APPROVAL_WRONG_STATUS_CANNOT_CANCEL: readonly ["Pending approval error, wrong status cannot cancel", "400 Bad Request"];
|
|
52
|
+
readonly APPROVAL_WRONG_STATUS_CANNOT_ACCEPT: readonly ["Pending approval error, wrong status cannot accept", "400 Bad Request"];
|
|
53
|
+
readonly APPROVAL_SAME_USER_CANNOT_REJECT: readonly ["Pending approval error, same user cannot reject", "400 Bad Request"];
|
|
54
|
+
readonly APPROVAL_WRONG_STATUS_CANNOT_REJECT: readonly ["Pending approval error, wrong status cannot reject", "400 Bad Request"];
|
|
55
|
+
readonly MISSING_HOSTNAME: readonly ["Missing hostname", "400 Bad Request"];
|
|
56
|
+
readonly NUMBER_OF_RECORDS_DOES_NOT_MATCH: readonly ["Event nack, Number of keys and records does not match", "400 Bad Request"];
|
|
57
|
+
readonly OPTIMISTIC_CONCURRENCY_ERROR: readonly ["Optimistic concurrency error", "400 Bad Request"];
|
|
58
|
+
};
|
|
59
|
+
export type ErrorCodeName = keyof typeof ERROR_CODES;
|
|
60
|
+
export declare const ErrorCode: { readonly [Name in ErrorCodeName]: Name; };
|
|
61
|
+
export declare function statusCodeFor(code: string): string;
|
|
62
|
+
export declare function defaultTextFor(code: string): string | undefined;
|
|
63
|
+
export type GenesisErrorType = 'StandardError' | 'FieldError' | 'TableStandardError' | 'LoginError';
|
|
1
64
|
export interface NackErrorInput {
|
|
65
|
+
'@type'?: GenesisErrorType;
|
|
2
66
|
CODE?: string;
|
|
3
67
|
code?: string;
|
|
4
68
|
TEXT?: string;
|
|
5
69
|
text?: string;
|
|
70
|
+
STATUS_CODE?: string;
|
|
71
|
+
statusCode?: string;
|
|
6
72
|
FIELD?: string;
|
|
7
73
|
field?: string;
|
|
74
|
+
PATH?: string | null;
|
|
75
|
+
path?: string | null;
|
|
76
|
+
TABLE?: string;
|
|
77
|
+
table?: string;
|
|
8
78
|
}
|
|
9
79
|
export interface NackErrorEntry {
|
|
80
|
+
'@type'?: GenesisErrorType;
|
|
10
81
|
CODE: string;
|
|
11
82
|
TEXT: string;
|
|
12
|
-
|
|
83
|
+
STATUS_CODE?: string;
|
|
84
|
+
FIELD?: string | null;
|
|
85
|
+
PATH?: string | null;
|
|
86
|
+
TABLE?: string | null;
|
|
87
|
+
DETAILS?: null;
|
|
88
|
+
}
|
|
89
|
+
export declare function toNackEntry(input: NackErrorInput): NackErrorEntry;
|
|
90
|
+
export declare function toLegacyNackEntry(input: NackErrorInput): NackErrorEntry;
|
|
91
|
+
export interface NackErrorOptions {
|
|
92
|
+
warnings?: NackErrorInput | NackErrorInput[];
|
|
13
93
|
}
|
|
14
94
|
export declare class NackError extends Error {
|
|
15
95
|
readonly errors: NackErrorEntry[];
|
|
16
|
-
|
|
96
|
+
readonly warnings: NackErrorEntry[];
|
|
97
|
+
readonly inputs: readonly NackErrorInput[];
|
|
98
|
+
readonly warningInputs: readonly NackErrorInput[];
|
|
99
|
+
constructor(errors: string | NackErrorInput | NackErrorInput[], options?: NackErrorOptions);
|
|
100
|
+
static field(field: string, text: string, path?: string | null, code?: string): NackError;
|
|
101
|
+
static standard(code: string, text?: string): NackError;
|
|
102
|
+
static duplicateKey(table: string, index: string, value: unknown, text?: string): NackError;
|
|
103
|
+
static recordNotFound(table: string, index?: string, text?: string): NackError;
|
|
104
|
+
static nack(text: string): NackError;
|
|
105
|
+
static warning(warnings: string | NackErrorInput | NackErrorInput[]): NackError;
|
|
17
106
|
}
|
|
107
|
+
export declare function nackPayload(error: unknown, legacy: boolean): {
|
|
108
|
+
ERROR: NackErrorEntry[];
|
|
109
|
+
WARNING?: NackErrorEntry[];
|
|
110
|
+
};
|
|
111
|
+
export declare function warningPayload(warnings: NackErrorInput[], legacy: boolean): {
|
|
112
|
+
ERROR: NackErrorEntry[];
|
|
113
|
+
WARNING?: NackErrorEntry[];
|
|
114
|
+
};
|
|
115
|
+
export {};
|
package/dist/protocol/errors.js
CHANGED
|
@@ -1,16 +1,304 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// The error vocabulary of service NACKs (EVENT_NACK, REP_* errors), as GSF
|
|
2
|
+
// 8.15.x serializes it: genesis-messages' GenesisError family
|
|
3
|
+
// (global.genesis.message.core.error.GenesisError.kt) and the ErrorCode enum
|
|
4
|
+
// (ErrorCode.kt). Every item names its class in `@type`, carries a string
|
|
5
|
+
// CODE, TEXT and a "<code> <reason>" STATUS_CODE, plus the class's own
|
|
6
|
+
// fields; the envelope carries WARNING next to ERROR. Checked against frames
|
|
7
|
+
// recorded from a GSF 8.15.29 app (test/conformance/recordings).
|
|
8
|
+
//
|
|
9
|
+
// The router's MSG_NACK is a different family (no @type, an integer CODE,
|
|
10
|
+
// DETAILS {ERROR, STATUS_CODE}) — see msgNack.ts.
|
|
11
|
+
// HttpStatusCode.kt's status lines ("$code $description") for the statuses
|
|
12
|
+
// ErrorCode uses.
|
|
13
|
+
const BAD_REQUEST = '400 Bad Request';
|
|
14
|
+
const UNAUTHORIZED = '401 Unauthorized';
|
|
15
|
+
const FORBIDDEN = '403 Forbidden';
|
|
16
|
+
const NOT_FOUND = '404 Not Found';
|
|
17
|
+
const REQUEST_TIMEOUT = '408 Request Timeout';
|
|
18
|
+
const TOO_MANY_REQUESTS = '429 Too Many Requests';
|
|
19
|
+
const INTERNAL_SERVER_ERROR = '500 Internal Server Error';
|
|
20
|
+
const SERVICE_UNAVAILABLE = '503 Service Unavailable';
|
|
21
|
+
// [readable text, HTTP status line] per ErrorCode.kt entry, in the enum's order.
|
|
22
|
+
// The readable text is what StandardError(ErrorCode.X) sends as TEXT when the
|
|
23
|
+
// caller gives none.
|
|
24
|
+
const ERROR_CODES = {
|
|
25
|
+
GENERIC_ERROR: ['Something is wrong', INTERNAL_SERVER_ERROR],
|
|
26
|
+
MISSING_FIELD: ['Field is missing', BAD_REQUEST],
|
|
27
|
+
VALIDATION_ERROR: ['Message failed validation', BAD_REQUEST],
|
|
28
|
+
INVALID_MESSAGE: ['Invalid message or invalid message format', BAD_REQUEST],
|
|
29
|
+
NOT_SUPPORTED_ENUM_VALUE: ['Enum value not supported', BAD_REQUEST],
|
|
30
|
+
NOT_AUTHORISED: ['Not authorised', FORBIDDEN],
|
|
31
|
+
DUPLICATE_KEY: [
|
|
32
|
+
'Record conflicts with unique index already stored in the database',
|
|
33
|
+
INTERNAL_SERVER_ERROR,
|
|
34
|
+
],
|
|
35
|
+
INVALID_MESSAGE_TYPE: ['Invalid message type', BAD_REQUEST],
|
|
36
|
+
INVALID_DATASOURCE: ['Invalid DATASOURCE_NAME', BAD_REQUEST],
|
|
37
|
+
INVALID_PARAMETER: ["MOVING_VIEW can't be true if maxView is set to 0", BAD_REQUEST],
|
|
38
|
+
LOGIN_ERROR: ['There was a problem logging in to the SOAP WebService', UNAUTHORIZED],
|
|
39
|
+
MULTIPLE_TABLES: ['Only one table can be referred', INTERNAL_SERVER_ERROR],
|
|
40
|
+
MISSING_KEY: ['Key name must be provided', BAD_REQUEST],
|
|
41
|
+
UNKNOWN_TABLE: ['No such table', BAD_REQUEST],
|
|
42
|
+
UNKNOWN_FIELD: ['No such field on table', BAD_REQUEST],
|
|
43
|
+
UNKNOWN: ['Unknown error', INTERNAL_SERVER_ERROR],
|
|
44
|
+
NO_MESSAGE_TYPE: ['No message type provided', BAD_REQUEST],
|
|
45
|
+
NO_SOURCE_REF: ['No source ref provided', BAD_REQUEST],
|
|
46
|
+
NO_USER_NAME: ['No user name provided', BAD_REQUEST],
|
|
47
|
+
UNAVAILABLE: ['Process is currently unavailable', SERVICE_UNAVAILABLE],
|
|
48
|
+
UNKNOWN_MESSAGE_TYPE: ['Message type not supported', BAD_REQUEST],
|
|
49
|
+
FEATURE_NOT_PROVIDED: ['Feature not provided', BAD_REQUEST],
|
|
50
|
+
FEATURE_NOT_FOUND: ['Feature not found', NOT_FOUND],
|
|
51
|
+
JSON_SCHEMA_NOT_FOUND: ['JSON schema not found', NOT_FOUND],
|
|
52
|
+
REJECT_RULE_DOES_NOT_EXIST: ['Failed to find Dynamic Rule', NOT_FOUND],
|
|
53
|
+
ERROR_CHECKING_EXISTING_RULE: ['Error while checking for rule', BAD_REQUEST],
|
|
54
|
+
NO_DS_NAME: ['No data source provided', BAD_REQUEST],
|
|
55
|
+
INVALID_DS_NAME: ['Data source does not exist', NOT_FOUND],
|
|
56
|
+
INVALID_INDEX: ['Invalid index', BAD_REQUEST],
|
|
57
|
+
REJECT_RULE_MISSING: ['Rule does not exist', NOT_FOUND],
|
|
58
|
+
ERROR_MODIFYING_RULE: ['Error encountered trying to modify rule', INTERNAL_SERVER_ERROR],
|
|
59
|
+
INVALID_CRITERIA: ['Criteria has failed validation', BAD_REQUEST],
|
|
60
|
+
MAX_LOGON_LIMIT: ['Tried to connect too many times', TOO_MANY_REQUESTS],
|
|
61
|
+
RECORD_NOT_FOUND: ['No record found for given key data', NOT_FOUND],
|
|
62
|
+
SERVICE_NOT_FOUND: ['No service found', NOT_FOUND],
|
|
63
|
+
DATABASE_FAILURE: ['There was an internal database error', INTERNAL_SERVER_ERROR],
|
|
64
|
+
DATABASE_ERROR: ['Database error', INTERNAL_SERVER_ERROR],
|
|
65
|
+
OPERATION_TIMEOUT: ['Server timed out', REQUEST_TIMEOUT],
|
|
66
|
+
DEPENDENT_RECORD_FOUND: [
|
|
67
|
+
'Dependency found on other records for changed value(s)',
|
|
68
|
+
INTERNAL_SERVER_ERROR,
|
|
69
|
+
],
|
|
70
|
+
REQUIRES_APPROVAL: ['Transaction requires approval', FORBIDDEN],
|
|
71
|
+
APPROVAL_MESSAGE_MISSING: [
|
|
72
|
+
'Transaction requires approval, but the approval message is missing',
|
|
73
|
+
BAD_REQUEST,
|
|
74
|
+
],
|
|
75
|
+
INTERNAL_ERROR: ['Unexpected server error', INTERNAL_SERVER_ERROR],
|
|
76
|
+
GATEWAY_ERROR: ['Notify gateway error', BAD_REQUEST],
|
|
77
|
+
REQUEST_FAILED: ['Problem handling request', BAD_REQUEST],
|
|
78
|
+
UNABLE_TO_UPDATE_APPROVAL: [
|
|
79
|
+
'Pending approval error, unable to update approval',
|
|
80
|
+
INTERNAL_SERVER_ERROR,
|
|
81
|
+
],
|
|
82
|
+
APPROVAL_SAME_USER_CANNOT_ACCEPT: [
|
|
83
|
+
'Pending approval error, same user cannot accept',
|
|
84
|
+
BAD_REQUEST,
|
|
85
|
+
],
|
|
86
|
+
APPROVAL_RECORD_NOT_FOUND: ['Pending approval record not found', NOT_FOUND],
|
|
87
|
+
REJECTED_BY_SERVICE: ['Pending approval rejected by service', INTERNAL_SERVER_ERROR],
|
|
88
|
+
APPROVAL_DIFF_USER_CANNOT_CANCEL: ['Pending approval different user cannot cancel', BAD_REQUEST],
|
|
89
|
+
APPROVAL_WRONG_STATUS_CANNOT_CANCEL: [
|
|
90
|
+
'Pending approval error, wrong status cannot cancel',
|
|
91
|
+
BAD_REQUEST,
|
|
92
|
+
],
|
|
93
|
+
APPROVAL_WRONG_STATUS_CANNOT_ACCEPT: [
|
|
94
|
+
'Pending approval error, wrong status cannot accept',
|
|
95
|
+
BAD_REQUEST,
|
|
96
|
+
],
|
|
97
|
+
APPROVAL_SAME_USER_CANNOT_REJECT: [
|
|
98
|
+
'Pending approval error, same user cannot reject',
|
|
99
|
+
BAD_REQUEST,
|
|
100
|
+
],
|
|
101
|
+
APPROVAL_WRONG_STATUS_CANNOT_REJECT: [
|
|
102
|
+
'Pending approval error, wrong status cannot reject',
|
|
103
|
+
BAD_REQUEST,
|
|
104
|
+
],
|
|
105
|
+
MISSING_HOSTNAME: ['Missing hostname', BAD_REQUEST],
|
|
106
|
+
NUMBER_OF_RECORDS_DOES_NOT_MATCH: [
|
|
107
|
+
'Event nack, Number of keys and records does not match',
|
|
108
|
+
BAD_REQUEST,
|
|
109
|
+
],
|
|
110
|
+
OPTIMISTIC_CONCURRENCY_ERROR: ['Optimistic concurrency error', BAD_REQUEST],
|
|
111
|
+
};
|
|
112
|
+
// GSF's ErrorCode names, for CODE values: ErrorCode.DUPLICATE_KEY ===
|
|
113
|
+
// 'DUPLICATE_KEY'. CODE is a plain string on the wire, so a project's own
|
|
114
|
+
// codes work too (their STATUS_CODE defaults to 500, as StandardError's does).
|
|
115
|
+
export const ErrorCode = Object.fromEntries(Object.keys(ERROR_CODES).map((name) => [name, name]));
|
|
116
|
+
// Codes that aren't in ErrorCode.kt but do appear in platform NACKs.
|
|
117
|
+
const EXTRA_STATUSES = {
|
|
118
|
+
INCORRECT_CREDENTIALS: UNAUTHORIZED, // platform-auth's LoginError
|
|
119
|
+
};
|
|
120
|
+
function knownCode(code) {
|
|
121
|
+
return Object.hasOwn(ERROR_CODES, code) ? ERROR_CODES[code] : undefined;
|
|
122
|
+
}
|
|
123
|
+
// The "<code> <reason>" STATUS_CODE for a CODE: the ErrorCode's own status,
|
|
124
|
+
// or 500 for codes GSF doesn't know (GenesisError's default statusCode).
|
|
125
|
+
export function statusCodeFor(code) {
|
|
126
|
+
return knownCode(code)?.[1] ?? EXTRA_STATUSES[code] ?? INTERNAL_SERVER_ERROR;
|
|
127
|
+
}
|
|
128
|
+
// ErrorCode's readable text, used as TEXT when an error gives none.
|
|
129
|
+
export function defaultTextFor(code) {
|
|
130
|
+
return knownCode(code)?.[0];
|
|
131
|
+
}
|
|
132
|
+
const LEGACY_DEFAULT_CODE = 'VALIDATION_ERROR';
|
|
133
|
+
const UNKNOWN_TEXT = 'Unknown error';
|
|
134
|
+
function inferType(input) {
|
|
135
|
+
if (input['@type'])
|
|
136
|
+
return input['@type'];
|
|
137
|
+
if ((input.TABLE ?? input.table) !== undefined)
|
|
138
|
+
return 'TableStandardError';
|
|
139
|
+
const hasField = (input.FIELD ?? input.field) !== undefined;
|
|
140
|
+
const hasPath = (input.PATH ?? input.path) !== undefined;
|
|
141
|
+
return hasField || hasPath ? 'FieldError' : 'StandardError';
|
|
142
|
+
}
|
|
143
|
+
// Renders one input as GSF serializes the matching GenesisError subclass,
|
|
144
|
+
// keys in the same order. Nullable class fields are sent as null, as
|
|
145
|
+
// FieldError's PATH is on the wire.
|
|
146
|
+
export function toNackEntry(input) {
|
|
147
|
+
const type = inferType(input);
|
|
148
|
+
const CODE = input.CODE ??
|
|
149
|
+
input.code ??
|
|
150
|
+
(type === 'FieldError' ? ErrorCode.VALIDATION_ERROR : ErrorCode.REQUEST_FAILED);
|
|
151
|
+
const TEXT = input.TEXT ?? input.text ?? defaultTextFor(CODE) ?? UNKNOWN_TEXT;
|
|
152
|
+
const entry = {
|
|
153
|
+
'@type': type,
|
|
154
|
+
CODE,
|
|
155
|
+
TEXT,
|
|
156
|
+
STATUS_CODE: input.STATUS_CODE ?? input.statusCode ?? statusCodeFor(CODE),
|
|
157
|
+
};
|
|
158
|
+
const field = input.FIELD ?? input.field;
|
|
159
|
+
switch (type) {
|
|
160
|
+
case 'FieldError':
|
|
161
|
+
entry.FIELD = field ?? '';
|
|
162
|
+
entry.PATH = input.PATH ?? input.path ?? null;
|
|
163
|
+
break;
|
|
164
|
+
case 'TableStandardError':
|
|
165
|
+
entry.TABLE = input.TABLE ?? input.table ?? null;
|
|
166
|
+
entry.FIELD = field ?? null;
|
|
167
|
+
break;
|
|
168
|
+
case 'LoginError':
|
|
169
|
+
entry.DETAILS = null;
|
|
170
|
+
break;
|
|
171
|
+
default:
|
|
172
|
+
if (field !== undefined)
|
|
173
|
+
entry.FIELD = field;
|
|
174
|
+
}
|
|
175
|
+
return entry;
|
|
176
|
+
}
|
|
177
|
+
// The {CODE, TEXT, FIELD?} item this engine sent up to 15.47, for
|
|
178
|
+
// fidelity: 'legacy' (a helper's code still lends its readable text).
|
|
179
|
+
export function toLegacyNackEntry(input) {
|
|
180
|
+
const field = input.FIELD ?? input.field;
|
|
181
|
+
const code = input.CODE ?? input.code;
|
|
182
|
+
return {
|
|
183
|
+
CODE: code ?? LEGACY_DEFAULT_CODE,
|
|
184
|
+
TEXT: input.TEXT ?? input.text ?? (code && defaultTextFor(code)) ?? UNKNOWN_TEXT,
|
|
185
|
+
...(field ? { FIELD: field } : {}),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function asList(inputs) {
|
|
189
|
+
if (inputs === undefined)
|
|
190
|
+
return [];
|
|
191
|
+
if (typeof inputs === 'string')
|
|
192
|
+
return [{ TEXT: inputs }];
|
|
193
|
+
return Array.isArray(inputs) ? inputs : [inputs];
|
|
194
|
+
}
|
|
195
|
+
// Throw this from an event handler or request-reply resolver for a
|
|
196
|
+
// protocol-correct NACK: ERROR is always an array of items, never
|
|
197
|
+
// {error, details} (lowercase, non-array), which crashes real clients calling
|
|
198
|
+
// .forEach on it. Pass a string for a failed GPAL require() (StandardError
|
|
199
|
+
// REQUEST_FAILED, 400), one input, or several to report every failing field
|
|
200
|
+
// at once, as a handler's EventNack(error = listOf(...)) does. The static
|
|
201
|
+
// helpers build the platform's common errors, nack() included.
|
|
5
202
|
export class NackError extends Error {
|
|
6
203
|
errors;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
204
|
+
warnings;
|
|
205
|
+
inputs;
|
|
206
|
+
warningInputs;
|
|
207
|
+
constructor(errors, options = {}) {
|
|
208
|
+
const inputs = asList(errors);
|
|
209
|
+
const warningInputs = asList(options.warnings);
|
|
210
|
+
const entries = inputs.map(toNackEntry);
|
|
211
|
+
const warnings = warningInputs.map(toNackEntry);
|
|
212
|
+
super([...entries, ...warnings].map((entry) => entry.TEXT).join(', ') || UNKNOWN_TEXT);
|
|
213
|
+
this.name = 'NackError';
|
|
214
|
+
this.inputs = inputs;
|
|
215
|
+
this.warningInputs = warningInputs;
|
|
216
|
+
this.errors = entries;
|
|
217
|
+
this.warnings = warnings;
|
|
218
|
+
}
|
|
219
|
+
// A FieldError: VALIDATION_ERROR (400) naming the field. PATH is the JSON
|
|
220
|
+
// path the error was found at — null (the default) for a handler's own
|
|
221
|
+
// check, '$.DETAILS...' for a schema failure. Forms highlight FIELD/PATH,
|
|
222
|
+
// and the AI assistant reads a null PATH as "the app said no".
|
|
223
|
+
static field(field, text, path = null, code = ErrorCode.VALIDATION_ERROR) {
|
|
224
|
+
return new NackError({
|
|
225
|
+
'@type': 'FieldError',
|
|
226
|
+
CODE: code,
|
|
227
|
+
TEXT: text,
|
|
228
|
+
FIELD: field,
|
|
229
|
+
PATH: path,
|
|
230
|
+
});
|
|
15
231
|
}
|
|
232
|
+
// A StandardError with a GSF ErrorCode (or a project's own code), TEXT
|
|
233
|
+
// defaulting to the code's readable text: NackError.standard('NOT_AUTHORISED').
|
|
234
|
+
static standard(code, text) {
|
|
235
|
+
return new NackError({ '@type': 'StandardError', CODE: code, TEXT: text });
|
|
236
|
+
}
|
|
237
|
+
// The insert that hits an existing key: TableStandardError DUPLICATE_KEY
|
|
238
|
+
// (500), FIELD '<INDEX_NAME> -> <value>', e.g.
|
|
239
|
+
// NackError.duplicateKey('COUNTERPARTY', 'COUNTERPARTY_BY_ID', 'CP001').
|
|
240
|
+
static duplicateKey(table, index, value, text) {
|
|
241
|
+
return new NackError({
|
|
242
|
+
'@type': 'TableStandardError',
|
|
243
|
+
CODE: ErrorCode.DUPLICATE_KEY,
|
|
244
|
+
TEXT: text,
|
|
245
|
+
TABLE: table,
|
|
246
|
+
FIELD: `${index} -> ${String(value)}`,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
// A modify or delete of a record that isn't there: TableStandardError
|
|
250
|
+
// RECORD_NOT_FOUND (404). GSF names the unique index in FIELD for a modify
|
|
251
|
+
// and sends '' for a delete, so `index` defaults to ''.
|
|
252
|
+
static recordNotFound(table, index = '', text) {
|
|
253
|
+
return new NackError({
|
|
254
|
+
'@type': 'TableStandardError',
|
|
255
|
+
CODE: ErrorCode.RECORD_NOT_FOUND,
|
|
256
|
+
TEXT: text,
|
|
257
|
+
TABLE: table,
|
|
258
|
+
FIELD: index,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
// GPAL's nack("text"): GSF wraps the text in an Exception, which
|
|
262
|
+
// EventHandlerUtils.handleError answers with StandardError INTERNAL_ERROR
|
|
263
|
+
// (500) carrying the text. (A failed require() is `new NackError(text)`.)
|
|
264
|
+
static nack(text) {
|
|
265
|
+
return new NackError({ '@type': 'StandardError', CODE: ErrorCode.INTERNAL_ERROR, TEXT: text });
|
|
266
|
+
}
|
|
267
|
+
// A NACK that only warns: ERROR stays empty, WARNING carries these.
|
|
268
|
+
static warning(warnings) {
|
|
269
|
+
return new NackError([], { warnings: asList(warnings) });
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// The ERROR/WARNING part of a service NACK for anything a handler threw. A
|
|
273
|
+
// NackError gives its own items; anything else is StandardError
|
|
274
|
+
// INTERNAL_ERROR with the error's message, as EventHandlerUtils.handleError
|
|
275
|
+
// answers an unexpected exception. Legacy mode sends the pre-15.48 items
|
|
276
|
+
// (no @type/STATUS_CODE, VALIDATION_ERROR by default) and no WARNING: it had
|
|
277
|
+
// no warnings, so they go in ERROR, after the errors.
|
|
278
|
+
export function nackPayload(error, legacy) {
|
|
279
|
+
if (error instanceof NackError) {
|
|
280
|
+
return legacy
|
|
281
|
+
? { ERROR: [...error.inputs, ...error.warningInputs].map(toLegacyNackEntry) }
|
|
282
|
+
: { ERROR: error.errors, WARNING: error.warnings };
|
|
283
|
+
}
|
|
284
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
285
|
+
if (legacy)
|
|
286
|
+
return { ERROR: [{ CODE: LEGACY_DEFAULT_CODE, TEXT: message }] };
|
|
287
|
+
return {
|
|
288
|
+
ERROR: [
|
|
289
|
+
toNackEntry({
|
|
290
|
+
'@type': 'StandardError',
|
|
291
|
+
CODE: ErrorCode.INTERNAL_ERROR,
|
|
292
|
+
TEXT: message || undefined,
|
|
293
|
+
}),
|
|
294
|
+
],
|
|
295
|
+
WARNING: [],
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
// The payload for warnings a handler returned instead of throwing. Legacy
|
|
299
|
+
// mode had no WARNING list, so they are sent as its {CODE, TEXT} errors.
|
|
300
|
+
export function warningPayload(warnings, legacy) {
|
|
301
|
+
if (legacy)
|
|
302
|
+
return { ERROR: warnings.map(toLegacyNackEntry) };
|
|
303
|
+
return { ERROR: [], WARNING: warnings.map(toNackEntry) };
|
|
16
304
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const BIGDECIMAL_PATTERN = "[0-9]+(\\.[0-9]+)?";
|
|
2
|
+
export declare function jsonTypeOf(type: string): string;
|
|
3
|
+
export declare function rangeOf(type: string): [number, number] | undefined;
|
|
4
|
+
export declare function eventMetaTypeOf(type: string): string;
|
|
5
|
+
export declare function titleCase(name: string): string;
|
|
6
|
+
export declare function pascalCase(name: string): string;
|
|
7
|
+
export declare function daoClassOf(table: string): string;
|
|
8
|
+
export declare function viewEntityClassOf(view: string): string;
|
|
9
|
+
export declare function enumClassOf(table: string | undefined, field: string, appName?: string): string;
|
|
10
|
+
export declare function jvmClassOf(type: string, enumClass: () => string): string;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// How the real server describes a Genesis field type on the wire: the JSON
|
|
2
|
+
// type a value travels as, the JVM class its generated DAO property has, and
|
|
3
|
+
// the names it derives from table and field names. Checked against GSF
|
|
4
|
+
// 8.15.29 recordings (test/conformance/recordings/showcase-real/meta-*.json,
|
|
5
|
+
// json-schema-*.json) and genesis-process's PALMetadataUtils.kt.
|
|
6
|
+
// The JSON type each Genesis type travels as. Dates are epoch millis and
|
|
7
|
+
// BigDecimals are strings, so a client must not read the Genesis type as the
|
|
8
|
+
// JSON one.
|
|
9
|
+
const JSON_TYPES = {
|
|
10
|
+
STRING: 'string',
|
|
11
|
+
ENUM: 'string',
|
|
12
|
+
INT: 'integer',
|
|
13
|
+
SHORT: 'integer',
|
|
14
|
+
LONG: 'integer',
|
|
15
|
+
DOUBLE: 'number',
|
|
16
|
+
BIGDECIMAL: 'string',
|
|
17
|
+
BOOLEAN: 'boolean',
|
|
18
|
+
DATE: 'integer',
|
|
19
|
+
DATETIME: 'integer',
|
|
20
|
+
NANO_TIMESTAMP: 'integer',
|
|
21
|
+
RAW: 'string',
|
|
22
|
+
};
|
|
23
|
+
// The JVM class of the generated DAO property, which the real server puts in
|
|
24
|
+
// DESCRIPTION / description. foundation-forms recognises a date field by
|
|
25
|
+
// 'org.joda.time.DateTime' (jsonforms/testers/isDate.ts).
|
|
26
|
+
const JVM_CLASSES = {
|
|
27
|
+
STRING: 'kotlin.String',
|
|
28
|
+
INT: 'kotlin.Int',
|
|
29
|
+
SHORT: 'kotlin.Short',
|
|
30
|
+
LONG: 'kotlin.Long',
|
|
31
|
+
DOUBLE: 'kotlin.Double',
|
|
32
|
+
BIGDECIMAL: 'java.math.BigDecimal',
|
|
33
|
+
BOOLEAN: 'kotlin.Boolean',
|
|
34
|
+
DATE: 'org.joda.time.DateTime',
|
|
35
|
+
DATETIME: 'org.joda.time.DateTime',
|
|
36
|
+
NANO_TIMESTAMP: 'kotlin.Long',
|
|
37
|
+
RAW: 'kotlin.ByteArray',
|
|
38
|
+
};
|
|
39
|
+
// Value ranges the DAO's generated annotations carry, reported as MIN/MAX in
|
|
40
|
+
// event META and minimum/maximum in event JSON schemas. LONG is bounded by
|
|
41
|
+
// JavaScript's safe integers, not by Long.MAX_VALUE.
|
|
42
|
+
const INT_MIN = -2147483648;
|
|
43
|
+
const INT_MAX = 2147483647;
|
|
44
|
+
const SHORT_MIN = -32768;
|
|
45
|
+
const SHORT_MAX = 32767;
|
|
46
|
+
const RANGES = {
|
|
47
|
+
INT: [INT_MIN, INT_MAX],
|
|
48
|
+
SHORT: [SHORT_MIN, SHORT_MAX],
|
|
49
|
+
LONG: [-Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER],
|
|
50
|
+
NANO_TIMESTAMP: [-Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER],
|
|
51
|
+
DOUBLE: [-Number.MAX_VALUE, Number.MAX_VALUE],
|
|
52
|
+
};
|
|
53
|
+
// What the real event schema puts on a BigDecimal property instead of a
|
|
54
|
+
// genesisType.
|
|
55
|
+
export const BIGDECIMAL_PATTERN = '[0-9]+(\\.[0-9]+)?';
|
|
56
|
+
export function jsonTypeOf(type) {
|
|
57
|
+
return JSON_TYPES[type] ?? 'string';
|
|
58
|
+
}
|
|
59
|
+
export function rangeOf(type) {
|
|
60
|
+
return RANGES[type];
|
|
61
|
+
}
|
|
62
|
+
// TYPE in event META follows the JVM class, not the dictionary: a DATE column
|
|
63
|
+
// is a DateTime property, so it is TYPE 'DATETIME' (GENESIS_TYPE says 'DATE').
|
|
64
|
+
export function eventMetaTypeOf(type) {
|
|
65
|
+
if (type === 'DATE')
|
|
66
|
+
return 'DATETIME';
|
|
67
|
+
if (type === 'NANO_TIMESTAMP')
|
|
68
|
+
return 'LONG';
|
|
69
|
+
return type;
|
|
70
|
+
}
|
|
71
|
+
// 'COUNTERPARTY_ID' -> 'Counterparty Id', the TITLE the DAO generator gives.
|
|
72
|
+
export function titleCase(name) {
|
|
73
|
+
return name
|
|
74
|
+
.split('_')
|
|
75
|
+
.filter(Boolean)
|
|
76
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
77
|
+
.join(' ');
|
|
78
|
+
}
|
|
79
|
+
// 'ALL_TYPES' -> 'AllTypes', the generated class name.
|
|
80
|
+
export function pascalCase(name) {
|
|
81
|
+
return titleCase(name).replace(/ /g, '');
|
|
82
|
+
}
|
|
83
|
+
// The generated DAO class an event's DETAILS deserialize into.
|
|
84
|
+
export function daoClassOf(table) {
|
|
85
|
+
return `global.genesis.gen.dao.${pascalCase(table)}`;
|
|
86
|
+
}
|
|
87
|
+
// The generated entity class of a view (ViewEntityGenerator.kt PACKAGE_NAME,
|
|
88
|
+
// GSF v8.15.29): what a request server over the view names in
|
|
89
|
+
// PARAMETRIC_TYPE.
|
|
90
|
+
export function viewEntityClassOf(view) {
|
|
91
|
+
return `global.genesis.gen.view.entity.${pascalCase(view)}`;
|
|
92
|
+
}
|
|
93
|
+
// The generated enum class of an ENUM column.
|
|
94
|
+
export function enumClassOf(table, field, appName) {
|
|
95
|
+
const segments = ['global.genesis.gen.dao.enums'];
|
|
96
|
+
if (appName)
|
|
97
|
+
segments.push(appName);
|
|
98
|
+
if (table)
|
|
99
|
+
segments.push(table.toLowerCase());
|
|
100
|
+
segments.push(pascalCase(field));
|
|
101
|
+
return segments.join('.');
|
|
102
|
+
}
|
|
103
|
+
export function jvmClassOf(type, enumClass) {
|
|
104
|
+
if (type === 'ENUM')
|
|
105
|
+
return enumClass();
|
|
106
|
+
return JVM_CLASSES[type] ?? 'kotlin.String';
|
|
107
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Row } from '../types.ts';
|
|
2
|
+
export declare const QUERY_ROW = "global.genesis.message.core.dataserver.QueryRow";
|
|
3
|
+
export declare function eventReplyMeta(): Row;
|
|
4
|
+
export declare function eventReplySchema(): Row;
|
|
5
|
+
export declare function eventInboundProperties(eventName: string, details: Row): Row;
|
|
6
|
+
export declare const GENESIS_META_SCHEMA = "https://genesis.global/schema/v1/meta/genesis";
|
|
7
|
+
export declare function dataLogonSchema(queryName: string): Row;
|
|
8
|
+
export declare function dataLogonReplySchema(rowProperties: Row): Row;
|
|
9
|
+
export declare const REQUEST_DETAILS_SCHEMA: Row;
|