// The parts of META_REQUEST / JSON_SCHEMA_REQUEST replies that describe the // platform's own message classes rather than a project's fields: the same for // every event, every dataserver and every request server. Copied from GSF // 8.15.29 recordings (test/conformance/recordings/showcase-real/meta-event, // json-schema-{event,dataserver,request-server}.json); the conformance suite // keeps them in step. import type { Row } from '../types.ts'; const MESSAGE_CORE = 'global.genesis.message.core'; const EVENT_REPLY = `${MESSAGE_CORE}.event.EventReply`; const EVENT_ACK = `${EVENT_REPLY}.EventAck`; const EVENT_APPROVABLE_ACK = `${EVENT_REPLY}.EventApprovableAck`; const EVENT_NACK = `${EVENT_REPLY}.EventNack`; const APPROVAL_ENTITY_DETAILS = `${MESSAGE_CORE}.event.ApprovalEntityDetails`; const APPROVAL_TYPE = `${MESSAGE_CORE}.event.ApprovalType`; const GENESIS_ERROR = `${MESSAGE_CORE}.error.GenesisError`; const STANDARD_ERROR = `${MESSAGE_CORE}.error.StandardError`; const HTTP_STATUS_CODE = `${MESSAGE_CORE}.HttpStatusCode`; const DATA_LOGON_REPLY = `${MESSAGE_CORE}.dataserver.DataLogonReply`; const LOGON_ACK = `${DATA_LOGON_REPLY}.LogonAck`; const LOGON_NACK = `${DATA_LOGON_REPLY}.LogonNack`; const INVALID_INDEX_NACK = `${LOGON_NACK}.InvalidIndexNack`; const INVALID_INDEX = `${INVALID_INDEX_NACK}.Index`; const QUERY_UPDATE = `${DATA_LOGON_REPLY}.QueryUpdate`; export const QUERY_ROW = `${MESSAGE_CORE}.dataserver.QueryRow`; const ROW_DETAILS = `${MESSAGE_CORE}.dataserver.RowDetails`; const ROW_OPERATION = `${MESSAGE_CORE}.dataserver.RowOperation`; const KOTLIN_STRING = 'kotlin.String'; const KOTLIN_LIST = 'kotlin.collections.List'; const APPROVAL_TYPES = ['NEW', 'UPDATE', 'REMOVE', 'UNKNOWN']; const DEFAULT_APPROVAL_MESSAGE = 'Your request was successful and has been submitted for approval'; // global.genesis.message.core.HttpStatusCode, in declaration order. const HTTP_STATUS_CODES = [ '100 Continue', '101 Switching Protocols', '102 Processing', '103 Early Hints', '200 OK', '201 Created', '202 Accepted', '203 Non-Authoritative Information', '204 No Content', '205 Reset Content', '206 Partial Content', '207 Multi-Status', '208 Already Reported', '226 IM Used', '300 Multiple Choice', '301 Moved Permanently', '302 Found', '303 See Other', '304 Not Modified', '305 Use Proxy', '307 Temporary Redirect', '308 Permanent Redirect', '400 Bad Request', '401 Unauthorized', '402 Payment Required', '403 Forbidden', '404 Not Found', '405 Method Not Allowed', '406 Not Acceptable', '407 Proxy Authentication Required', '408 Request Timeout', '409 Conflict', '410 Gone', '411 Length Required', '412 Precondition Failed', '413 Payload Too Large', '414 URI Too Long', '415 Unsupported Media Type', '416 Range Not Satisfiable', '417 Expectation Failed', '418 I Am A Teapot', '418 Enhance Your Calm', '421 Misdirected Request', '422 Unprocessable Entity', '423 Locked', '424 Failed Dependency', '425 Too Early', '426 Upgrade Required', '428 Precondition Required', '429 Too Many Requests', '431 Request Header Fields Too Large', '444 No Response', '449 RetryWith', '450 Blocked By Windows Parental Controls', '451 Unavailable For Legal Reasons', '499 Client Closed Request', '500 Internal Server Error', '501 Not Implemented', '502 Bad Gateway', '503 Service Unavailable', '504 Gateway Timeout', '505 HTTP Version Not Supported', '506 Variant Also Negotiates', '507 Insufficient Storage', '508 Loop Detected', '509 Bandwidth Limit Exceeded', '510 Not Extended', '511 Network Authentication Required', '598 Network Read Timeout Error', '599 Network Connection Timeout Error', ]; // --- event META: DETAILS.REPLY ----------------------------------------------- // The META shape of a class property (EventMeta's MetaField), for the reply // classes below. function metaMessageType(constant: string): Row { return { NAME: 'MESSAGE_TYPE', TITLE: 'Message Type', DESCRIPTION: 'Message Type', NULLABLE: false, TYPE: 'STRING', JSON_TYPE: 'string', OPTIONAL: false, READ_ONLY: false, CONSTANT: constant, GENESIS_TYPE: null, }; } const META_SOURCE_REF: Row = { NAME: 'SOURCE_REF', TITLE: 'Source Ref', DESCRIPTION: 'Source Ref', NULLABLE: false, TYPE: 'STRING', JSON_TYPE: 'string', OPTIONAL: false, READ_ONLY: false, GENESIS_TYPE: null, }; function metaString(name: string, optional: boolean): Row { return { NAME: name, DESCRIPTION: KOTLIN_STRING, NULLABLE: false, TYPE: 'STRING', JSON_TYPE: 'string', OPTIONAL: optional, READ_ONLY: false, GENESIS_TYPE: null, }; } function metaEnum(name: string, enumClass: string, values: string[], optional: boolean): Row { return { NAME: name, DESCRIPTION: enumClass, NULLABLE: false, VALID_VALUES: values, DEFAULT_VALUE: values[0], ENUM_CLASS: enumClass, TYPE: 'ENUM', JSON_TYPE: 'string', OPTIONAL: optional, READ_ONLY: false, ...(optional ? { DEFAULT: values[0] } : {}), GENESIS_TYPE: null, }; } function metaList(name: string, valueType: Row): Row { return { NAME: name, DESCRIPTION: KOTLIN_LIST, NULLABLE: false, VALUE_TYPE: valueType, UNIQUE: false, TYPE: 'ARRAY', JSON_TYPE: 'array', OPTIONAL: true, READ_ONLY: false, GENESIS_TYPE: null, }; } function metaReference(reference: string): Row { return { NULLABLE: false, REFERENCE: reference, TYPE: 'OBJECT', JSON_TYPE: 'object' }; } function metaClass(name: string, description: string, properties: Row[]): Row { return { NAME: name, DESCRIPTION: description, PROPERTIES: properties }; } // DETAILS.REPLY of every event's META: what EVENT_ACK, EVENT_APPROVABLE_ACK // and EVENT_NACK carry. Frozen copies would be shared across replies, so this // builds a fresh object each time. export function eventReplyMeta(): Row { return { NAME: 'EVENT_REPLY', DESCRIPTION: EVENT_REPLY, FIELD: [], DEFINITIONS: { [EVENT_ACK]: metaClass('EVENT_ACK', EVENT_ACK, [ metaMessageType('EVENT_ACK'), META_SOURCE_REF, metaList('GENERATED', { NULLABLE: false, KEY_TYPE: { NULLABLE: false, TYPE: 'STRING', JSON_TYPE: 'string' }, VALUE_TYPE: { TYPE: 'ANY', JSON_TYPE: '{}', NULLABLE: true, DEFAULT_VALUE: null }, TYPE: 'MAP', JSON_TYPE: 'object', }), ]), [EVENT_APPROVABLE_ACK]: metaClass('EVENT_APPROVABLE_ACK', EVENT_APPROVABLE_ACK, [ metaMessageType('EVENT_APPROVABLE_ACK'), META_SOURCE_REF, metaString('ADDITIONAL_DETAILS', true), metaString('APPROVAL_MESSAGE', true), metaEnum('APPROVAL_TYPE', APPROVAL_TYPE, APPROVAL_TYPES, true), metaList('GENERATED', metaReference(APPROVAL_ENTITY_DETAILS)), { ...metaString('DEFAULT_APPROVAL_MESSAGE', true), READ_ONLY: true, DEFAULT: DEFAULT_APPROVAL_MESSAGE, GENESIS_TYPE: null, }, ]), [APPROVAL_ENTITY_DETAILS]: metaClass('APPROVAL_ENTITY_DETAILS', APPROVAL_ENTITY_DETAILS, [ metaEnum('APPROVAL_TYPE', APPROVAL_TYPE, APPROVAL_TYPES, false), metaString('ENTITY_ID', false), metaString('ENTITY_TABLE', false), ]), [EVENT_NACK]: metaClass('EVENT_NACK', EVENT_NACK, [ metaMessageType('EVENT_NACK'), META_SOURCE_REF, metaList('ERROR', metaReference(GENESIS_ERROR)), metaList('WARNING', metaReference(GENESIS_ERROR)), ]), [GENESIS_ERROR]: metaClass('GENESIS_ERROR', GENESIS_ERROR, [ metaString('CODE', false), metaEnum('STATUS_CODE', HTTP_STATUS_CODE, HTTP_STATUS_CODES, true), metaString('TEXT', false), ]), }, }; } // --- JSON schemas of the reply classes --------------------------------------- const SCHEMA_SOURCE_REF: Row = { type: 'string', title: 'Source Ref', description: 'Source Ref' }; const SCHEMA_STRING: Row = { type: 'string', description: KOTLIN_STRING }; const SCHEMA_INT: Row = { type: 'integer', description: 'kotlin.Int' }; const SCHEMA_BOOLEAN: Row = { type: 'boolean', description: 'kotlin.Boolean' }; const SCHEMA_NULLABLE_STRING: Row = { oneOf: [{ type: 'null' }, { type: 'string' }], description: KOTLIN_STRING, }; const ref = (definition: string) => `#/$defs/${definition}`; function schemaList(definition: string): Row { return { type: 'array', items: { type: 'object', $ref: ref(definition) }, description: KOTLIN_LIST, }; } function schemaClass( description: string, properties: Row, required: string[], additionalProperties = false, ): Row { return { type: 'object', description, properties, additionalProperties, required }; } function errorClass(description: string): Row { return schemaClass( description, { CODE: SCHEMA_STRING, STATUS_CODE: { type: 'string', enum: HTTP_STATUS_CODES, description: HTTP_STATUS_CODE }, TEXT: SCHEMA_STRING, }, ['CODE', 'TEXT'], true, ); } const ENVELOPE_REQUIRED = ['MESSAGE_TYPE', 'SOURCE_REF']; // OUTBOUND of every event's JSON schema: the EventReply union. export function eventReplySchema(): Row { const approvalType = { type: 'string', enum: APPROVAL_TYPES, description: APPROVAL_TYPE }; return { description: EVENT_REPLY, type: 'object', oneOf: [ { $ref: ref(EVENT_ACK) }, { $ref: ref(EVENT_APPROVABLE_ACK) }, { $ref: ref(EVENT_NACK) }, ], $defs: { [EVENT_ACK]: schemaClass( EVENT_ACK, { MESSAGE_TYPE: { const: 'EVENT_ACK' }, SOURCE_REF: SCHEMA_SOURCE_REF, GENERATED: { type: 'array', items: { type: 'object' }, description: KOTLIN_LIST }, }, ENVELOPE_REQUIRED, ), [EVENT_APPROVABLE_ACK]: schemaClass( EVENT_APPROVABLE_ACK, { MESSAGE_TYPE: { const: 'EVENT_APPROVABLE_ACK' }, SOURCE_REF: SCHEMA_SOURCE_REF, ADDITIONAL_DETAILS: SCHEMA_STRING, APPROVAL_MESSAGE: SCHEMA_STRING, APPROVAL_TYPE: approvalType, GENERATED: schemaList(APPROVAL_ENTITY_DETAILS), DEFAULT_APPROVAL_MESSAGE: { type: 'string', readOnly: true, default: DEFAULT_APPROVAL_MESSAGE, description: KOTLIN_STRING, }, }, ENVELOPE_REQUIRED, ), [APPROVAL_ENTITY_DETAILS]: schemaClass( APPROVAL_ENTITY_DETAILS, { APPROVAL_TYPE: approvalType, ENTITY_ID: SCHEMA_STRING, ENTITY_TABLE: SCHEMA_STRING }, ['APPROVAL_TYPE', 'ENTITY_ID', 'ENTITY_TABLE'], ), [EVENT_NACK]: schemaClass( EVENT_NACK, { MESSAGE_TYPE: { const: 'EVENT_NACK' }, SOURCE_REF: SCHEMA_SOURCE_REF, ERROR: schemaList(GENESIS_ERROR), WARNING: schemaList(GENESIS_ERROR), }, ENVELOPE_REQUIRED, ), [GENESIS_ERROR]: errorClass(GENESIS_ERROR), }, }; } // The envelope properties of an event's INBOUND schema (EventMeta's // getMessageJsonSchema), around the event's own DETAILS. export function eventInboundProperties(eventName: string, details: Row): Row { const nullableString = { oneOf: [{ type: 'string' }, { type: 'null' }] }; return { MESSAGE_TYPE: { const: eventName, description: 'Message type', type: 'string' }, SOURCE_REF: { description: 'Source reference', type: 'string' }, USER_NAME: { description: 'User name', type: 'string' }, VALIDATE: { type: 'boolean' }, IGNORE_WARNINGS: { type: 'boolean' }, REQUIRES_APPROVAL: { readOnly: true, type: 'boolean' }, APPROVAL_KEY: nullableString, APPROVAL_MESSAGE: nullableString, REASON: nullableString, SCHEDULABLE: { readOnly: true, const: false, type: 'boolean' }, DETAILS: details, }; } export const GENESIS_META_SCHEMA = 'https://genesis.global/schema/v1/meta/genesis'; // INBOUND of a dataserver's JSON schema: the DATA_LOGON message. export function dataLogonSchema(queryName: string): Row { return { $schema: 'https://json-schema.org/draft/2019-09/schema', description: `GPAL Dataserver definition for ${queryName}`, type: 'object', properties: { MESSAGE_TYPE: { const: 'DATA_LOGON' }, SOURCE_REF: { type: 'string' }, USER_NAME: { type: 'string' }, DETAILS: { type: 'object', properties: { CRITERIA_MATCH: SCHEMA_NULLABLE_STRING, DATASOURCE_NAME: { const: queryName }, FIELDS: SCHEMA_NULLABLE_STRING, MAX_ROWS: SCHEMA_INT, MAX_VIEW: SCHEMA_INT, MOVING_VIEW: SCHEMA_BOOLEAN, ORDER_BY: SCHEMA_NULLABLE_STRING, REVERSE: SCHEMA_BOOLEAN, VIEW_NUMBER: SCHEMA_INT, }, additionalProperties: false, required: ['DATASOURCE_NAME'], }, }, additionalProperties: false, required: ['MESSAGE_TYPE', 'SOURCE_REF', 'USER_NAME', 'DETAILS'], }; } // OUTBOUND of a dataserver's JSON schema: the DataLogonReply union, with the // query's own fields in $defs[...QueryRow]. foundation-forms' filters read // that definition (filters/filters.ts). export function dataLogonReplySchema(rowProperties: Row): Row { return { description: DATA_LOGON_REPLY, type: 'object', oneOf: [ { $ref: ref(LOGON_ACK) }, { $ref: ref(LOGON_NACK) }, { $ref: ref(INVALID_INDEX_NACK) }, { $ref: ref(QUERY_UPDATE) }, ], $defs: { [LOGON_ACK]: schemaClass( LOGON_ACK, { MESSAGE_TYPE: { const: 'LOGON_ACK' }, SOURCE_REF: SCHEMA_SOURCE_REF, SEQUENCE_ID: SCHEMA_INT, }, ENVELOPE_REQUIRED, ), [LOGON_NACK]: schemaClass( LOGON_NACK, { MESSAGE_TYPE: { const: 'LOGON_NACK' }, SOURCE_REF: SCHEMA_SOURCE_REF, ERROR: schemaList(STANDARD_ERROR), WARNING: schemaList(STANDARD_ERROR), }, ENVELOPE_REQUIRED, ), [STANDARD_ERROR]: errorClass(STANDARD_ERROR), [INVALID_INDEX_NACK]: schemaClass( INVALID_INDEX_NACK, { MESSAGE_TYPE: { const: 'INVALID_INDEX_NACK' }, SOURCE_REF: SCHEMA_SOURCE_REF, AVAILABLE_INDEXES: schemaList(INVALID_INDEX), ERROR: schemaList(STANDARD_ERROR), WARNING: schemaList(STANDARD_ERROR), }, ['MESSAGE_TYPE', 'SOURCE_REF', 'AVAILABLE_INDEXES', 'ERROR'], ), [INVALID_INDEX]: schemaClass( INVALID_INDEX, { FIELDS: { type: 'array', items: { type: 'string' }, description: KOTLIN_LIST }, NAME: SCHEMA_STRING, }, ['FIELDS', 'NAME'], ), [QUERY_UPDATE]: schemaClass( QUERY_UPDATE, { MESSAGE_TYPE: { const: 'QUERY_UPDATE' }, SOURCE_REF: SCHEMA_SOURCE_REF, MORE_ROWS: SCHEMA_BOOLEAN, ROW: schemaList(QUERY_ROW), ROWS_COUNT: { oneOf: [{ type: 'null' }, { type: 'integer' }], description: 'kotlin.Int' }, SEQUENCE_ID: SCHEMA_INT, }, ['MESSAGE_TYPE', 'SOURCE_REF', 'ROW', 'SEQUENCE_ID'], ), [QUERY_ROW]: schemaClass( QUERY_ROW, { DETAILS: { type: 'object', $ref: ref(ROW_DETAILS), description: ROW_DETAILS }, ...rowProperties, }, ['DETAILS'], ), [ROW_DETAILS]: schemaClass( ROW_DETAILS, { OPERATION: { type: 'string', enum: ['INSERT', 'MODIFY', 'DELETE'], description: ROW_OPERATION, }, // Verbatim from GSF 8.15.29, which serializes this one property as a // string instead of a schema. Kept so the mock fails wherever the // real server does; no client reads it. ROW_REF: 'type = string\ndescription = kotlin.String\n', }, ['OPERATION', 'ROW_REF'], ), }, }; } // The DETAILS a request server accepts besides REQUEST. export const REQUEST_DETAILS_SCHEMA: Row = { properties: { VIEW_NUMBER: { description: 'The page / view to request', type: 'integer' }, MAX_ROWS: { minimum: 1, description: 'Maximum number of rows to return', type: 'integer' }, CRITERIA_MATCH: { description: 'Additional criteria for request server', type: 'string' }, }, type: 'object', };