import type { Row } from '../types.ts'; import type { NackErrorEntry } from './errors.ts'; import { MESSAGE_TYPE } from './messageTypes.ts'; // A service's own MSG_NACK (genesis-messages MsgNack): how a request server // answers a request it fails, GSF 8.15.x. Unlike the router's frame below, its // items are GenesisErrors (@type, a string CODE, TEXT, STATUS_CODE), there is // a WARNING list and no DETAILS. Recorded: a criteria-only REQ_ without // REQUEST answers {WARNING: [], ERROR: [{'@type': 'StandardError', CODE: // 'INTERNAL_ERROR', TEXT: 'Unexpected server error', STATUS_CODE: '500 // Internal Server Error'}], MESSAGE_TYPE: 'MSG_NACK'}. export function serviceMsgNack(errors: NackErrorEntry[], warnings: NackErrorEntry[] = []): Row { return { WARNING: warnings, ERROR: errors, MESSAGE_TYPE: MESSAGE_TYPE.MSG_NACK }; } const HTTP_BAD_REQUEST = 400; const HTTP_NOT_FOUND = 404; const HTTP_CONFLICT = 409; // The real router's rejection frame (genesis-router MsgType.Outbound.Error, // GSF 8.15.x): an integer HTTP status CODE, and the status line as // STATUS_CODE, repeated under DETAILS. It is a different family from the // handler-level EVENT_NACK/REP_ errors (string CODEs such as // VALIDATION_ERROR) — the router sends it before a request ever reaches a // service. foundation-comms' socket REJECTS the pending promise on MSG_NACK // (connect/socket.ts), where a typed reply carrying ERROR resolves it. function routerNack(code: number, reason: string, text: string): Row { const STATUS_CODE = `${code} ${reason}`; return { MESSAGE_TYPE: MESSAGE_TYPE.MSG_NACK, ERROR: [{ CODE: code, TEXT: text, STATUS_CODE }], DETAILS: { ERROR: text, STATUS_CODE }, }; } // The router reads SOURCE_REF and MESSAGE_TYPE before anything else and // refuses a message missing either, or naming a blank one // (RouterMessageDelegator.processMessage @v8.15.29). With no SOURCE_REF to // echo, the first is sent on an empty one. export function missingSourceRefNack(): Row { return { ...routerNack(HTTP_BAD_REQUEST, 'Bad Request', 'No source ref provided'), SOURCE_REF: '', }; } export function missingMessageTypeNack(): Row { return routerNack(HTTP_BAD_REQUEST, 'Bad Request', 'No message type provided'); } // The router resolves every message to a resource name first and answers this // when no service serves it: DETAILS.DATASOURCE_NAME for DATA_LOGON, the name // after REQ_ for request servers, DETAILS.FEATURE for META_REQUEST / // JSON_SCHEMA_REQUEST, and the MESSAGE_TYPE itself for commit events. A // missing name is the empty string, as on the real router. export function resourceNotMappedNack(resourceName: string | undefined): Row { return routerNack( HTTP_NOT_FOUND, 'Not Found', `Resource ${resourceName ?? ''} is not mapped to a service`, ); } // MORE_ROWS or DATA_LOGOFF naming a SOURCE_REF this connection has no live // subscription on (never logged on, already logged off, or another socket's). export function nonExistentSubscriptionNack(): Row { return routerNack(HTTP_NOT_FOUND, 'Not Found', 'Dataserver subscription does not exist'); } // DATA_LOGON on a SOURCE_REF that already has a live subscription on this // connection. The existing subscription is left untouched. export function subscriptionAlreadyExistsNack(): Row { return routerNack( HTTP_CONFLICT, 'Conflict', 'Subscription exists for source ref. If using HTTP, try HTTP GET instead of HTTP POST', ); } const HTTP_UNAUTHORIZED = 401; const HTTP_FORBIDDEN = 403; // A cookie-authenticated router refuses EVENT_LOGIN_AUTH and EVENT_LOGOUT on // a socket, which the cookie already authenticates (genesis-router // WebsocketHandler.kt:54-58 @v8.15.29). Recorded in ws-login.json and // ws-logout.json. export function websocketForbiddenNack(messageType: string): Row { return routerNack(HTTP_FORBIDDEN, 'Forbidden', `Unable to call ${messageType} via websocket`); } // An HTTP request that needs a session but names none (AuthModel.kt:134-137 // @v8.15.29), or names one that is not live (genesis-commons ErrorMessage // INVALID_AUTH_TOKEN_MSG). Sent with HTTP status 401; recorded in // http-login-failed.json and http-login.json. export function notLoggedInNack(tokenSupplied: boolean): Row { return routerNack( HTTP_UNAUTHORIZED, 'Unauthorized', tokenSupplied ? 'User is not logged in and supplied auth token is invalid.' : 'This request needs the user to be logged in, but the current authentication status is logged out', ); }