@defra/forms-engine-plugin 3.0.4 → 3.0.6
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/.server/server/common/helpers/redis-client.js +2 -3
- package/.server/server/common/helpers/redis-client.js.map +1 -1
- package/.server/server/plugins/engine/helpers.js +1 -2
- package/.server/server/plugins/engine/helpers.js.map +1 -1
- package/.server/server/plugins/engine/models/FormModel.js +9 -7
- package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
- package/.server/server/plugins/engine/options.js +2 -1
- package/.server/server/plugins/engine/options.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js +2 -2
- package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -1
- package/.server/server/plugins/engine/routes/file-upload.js +2 -3
- package/.server/server/plugins/engine/routes/file-upload.js.map +1 -1
- package/.server/server/plugins/engine/services/notifyService.js +1 -2
- package/.server/server/plugins/engine/services/notifyService.js.map +1 -1
- package/.server/server/plugins/errorPages.js +3 -3
- package/.server/server/plugins/errorPages.js.map +1 -1
- package/package.json +2 -2
- package/src/server/common/helpers/redis-client.js +5 -3
- package/src/server/plugins/engine/helpers.ts +2 -3
- package/src/server/plugins/engine/models/FormModel.ts +8 -7
- package/src/server/plugins/engine/options.js +3 -1
- package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +2 -2
- package/src/server/plugins/engine/routes/file-upload.ts +3 -4
- package/src/server/plugins/engine/services/notifyService.test.ts +1 -1
- package/src/server/plugins/engine/services/notifyService.ts +2 -3
- package/src/server/plugins/errorPages.ts +3 -3
|
@@ -48,9 +48,8 @@ export function buildRedisClient() {
|
|
|
48
48
|
redisClient.on('close', () => {
|
|
49
49
|
logger.warn('[redisDisconnected] Redis connection closed attempting reconnect with default behavior');
|
|
50
50
|
});
|
|
51
|
-
redisClient.on('error',
|
|
52
|
-
|
|
53
|
-
logger.error(err, `[redisConnectionError] Redis connection error - ${err}`);
|
|
51
|
+
redisClient.on('error', err => {
|
|
52
|
+
logger.error(err, `[redisConnectionError] Redis connection error - ${getErrorMessage(err)}`);
|
|
54
53
|
});
|
|
55
54
|
return redisClient;
|
|
56
55
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redis-client.js","names":["getErrorMessage","Cluster","Redis","config","createLogger","buildRedisClient","logger","port","db","redisConfig","get","keyPrefix","host","redisClient","info","slotsRefreshTimeout","dnsLookup","address","callback","redisOptions","username","password","tls","on","warn","
|
|
1
|
+
{"version":3,"file":"redis-client.js","names":["getErrorMessage","Cluster","Redis","config","createLogger","buildRedisClient","logger","port","db","redisConfig","get","keyPrefix","host","redisClient","info","slotsRefreshTimeout","dnsLookup","address","callback","redisOptions","username","password","tls","on","warn","err","error"],"sources":["../../../../src/server/common/helpers/redis-client.js"],"sourcesContent":["import { getErrorMessage } from '@defra/forms-model'\nimport { Cluster, Redis } from 'ioredis'\n\nimport { config } from '~/src/config/index.js'\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\n\n/**\n * Setup Redis and provide a redis client\n *\n * Local development - 1 Redis instance\n * Out in the wild - Elasticache / Redis Cluster with username and password\n */\nexport function buildRedisClient() {\n const logger = createLogger()\n\n const port = 6379\n const db = 0\n const redisConfig = config.get('redis')\n const keyPrefix = redisConfig.keyPrefix\n const host = redisConfig.host\n let redisClient\n\n if (!config.get('isProduction')) {\n logger.info('Connecting to Redis using single instance')\n\n redisClient = new Redis({\n port,\n host,\n db,\n keyPrefix\n })\n } else {\n logger.info('Connecting to Redis using cluster')\n\n redisClient = new Cluster(\n [\n {\n host,\n port\n }\n ],\n {\n keyPrefix,\n slotsRefreshTimeout: 2000,\n dnsLookup: (address, callback) => callback(null, address),\n redisOptions: {\n username: redisConfig.username,\n password: redisConfig.password,\n db,\n tls: {}\n }\n }\n )\n }\n\n redisClient.on('connect', () => {\n logger.info('[redisConnected] Connected to Redis server')\n })\n\n redisClient.on('close', () => {\n logger.warn(\n '[redisDisconnected] Redis connection closed attempting reconnect with default behavior'\n )\n })\n\n redisClient.on('error', (err) => {\n logger.error(\n err,\n `[redisConnectionError] Redis connection error - ${getErrorMessage(err)}`\n )\n })\n\n return redisClient\n}\n"],"mappings":"AAAA,SAASA,eAAe,QAAQ,oBAAoB;AACpD,SAASC,OAAO,EAAEC,KAAK,QAAQ,SAAS;AAExC,SAASC,MAAM;AACf,SAASC,YAAY;;AAErB;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,gBAAgBA,CAAA,EAAG;EACjC,MAAMC,MAAM,GAAGF,YAAY,CAAC,CAAC;EAE7B,MAAMG,IAAI,GAAG,IAAI;EACjB,MAAMC,EAAE,GAAG,CAAC;EACZ,MAAMC,WAAW,GAAGN,MAAM,CAACO,GAAG,CAAC,OAAO,CAAC;EACvC,MAAMC,SAAS,GAAGF,WAAW,CAACE,SAAS;EACvC,MAAMC,IAAI,GAAGH,WAAW,CAACG,IAAI;EAC7B,IAAIC,WAAW;EAEf,IAAI,CAACV,MAAM,CAACO,GAAG,CAAC,cAAc,CAAC,EAAE;IAC/BJ,MAAM,CAACQ,IAAI,CAAC,2CAA2C,CAAC;IAExDD,WAAW,GAAG,IAAIX,KAAK,CAAC;MACtBK,IAAI;MACJK,IAAI;MACJJ,EAAE;MACFG;IACF,CAAC,CAAC;EACJ,CAAC,MAAM;IACLL,MAAM,CAACQ,IAAI,CAAC,mCAAmC,CAAC;IAEhDD,WAAW,GAAG,IAAIZ,OAAO,CACvB,CACE;MACEW,IAAI;MACJL;IACF,CAAC,CACF,EACD;MACEI,SAAS;MACTI,mBAAmB,EAAE,IAAI;MACzBC,SAAS,EAAEA,CAACC,OAAO,EAAEC,QAAQ,KAAKA,QAAQ,CAAC,IAAI,EAAED,OAAO,CAAC;MACzDE,YAAY,EAAE;QACZC,QAAQ,EAAEX,WAAW,CAACW,QAAQ;QAC9BC,QAAQ,EAAEZ,WAAW,CAACY,QAAQ;QAC9Bb,EAAE;QACFc,GAAG,EAAE,CAAC;MACR;IACF,CACF,CAAC;EACH;EAEAT,WAAW,CAACU,EAAE,CAAC,SAAS,EAAE,MAAM;IAC9BjB,MAAM,CAACQ,IAAI,CAAC,4CAA4C,CAAC;EAC3D,CAAC,CAAC;EAEFD,WAAW,CAACU,EAAE,CAAC,OAAO,EAAE,MAAM;IAC5BjB,MAAM,CAACkB,IAAI,CACT,wFACF,CAAC;EACH,CAAC,CAAC;EAEFX,WAAW,CAACU,EAAE,CAAC,OAAO,EAAGE,GAAG,IAAK;IAC/BnB,MAAM,CAACoB,KAAK,CACVD,GAAG,EACH,mDAAmDzB,eAAe,CAACyB,GAAG,CAAC,EACzE,CAAC;EACH,CAAC,CAAC;EAEF,OAAOZ,WAAW;AACpB","ignoreList":[]}
|
|
@@ -85,8 +85,7 @@ export function encodeUrl(link) {
|
|
|
85
85
|
try {
|
|
86
86
|
return new URL(link).toString(); // escape the search params without breaking the ? and & reserved characters in rfc2368
|
|
87
87
|
} catch (err) {
|
|
88
|
-
|
|
89
|
-
logger.error(errMsg, `[urlEncodingFailed] Failed to encode URL: ${link} - ${errMsg}`);
|
|
88
|
+
logger.error(err, `[urlEncodingFailed] Failed to encode URL: ${link} - ${getErrorMessage(err)}`);
|
|
90
89
|
throw err;
|
|
91
90
|
}
|
|
92
91
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.js","names":["ControllerPath","Engine","getErrorMessage","hasComponents","isFormType","Boom","format","parseISO","StatusCodes","Liquid","createLogger","getAnswer","FormAction","FormStatus","logger","engine","outputEscape","jsTruthy","ownPropertyOnly","registerFilter","template","globals","context","evaluated","evaluateTemplate","path","pageDef","pages","get","query","page","pageMap","undefined","getPageHref","name","componentDef","components","component","componentMap","isFormComponent","answer","relevantState","proceed","request","h","nextUrl","method","payload","returnUrl","isReturnAllowed","action","Continue","Validate","response","isPathRelative","redirect","redirectPath","code","SEE_OTHER","MOVED_TEMPORARILY","encodeUrl","link","URL","toString","err","errMsg","error","pathOrQuery","queryOnly","Error","getHref","isRelative","params","Object","entries","filter","url","value","searchParams","set","pathname","search","href","startsWith","normalisePath","trim","replace","getPage","model","findPage","notFound","findPath","find","getStartPath","V2","startPath","def","at","Start","startPage","checkFormStatus","isPreview","state","Live","Draft","checkEmailAddressForLiveFormSubmission","emailAddress","internal","getErrors","details","length","map","getError","detail","message","key","text","safeGenerateCrumb","server","plugins","crumb","generate","route","settings","getExponentialBackoffDelay","depth","BASE_DELAY_MS","CAP_DELAY_MS","delay","Math","min","pageDefMap","componentDefMap","parseAndRenderSync","getCacheService","getPluginOptions","cacheService","getSaveAndExitHelpers","saveAndExit","handleLegacyRedirect","targetUrl","permanent","takeover","setPageTitles","forEach","title","firstFormComponent","type","formNameMsg","info"],"sources":["../../../../src/server/plugins/engine/helpers.ts"],"sourcesContent":["import {\n ControllerPath,\n Engine,\n getErrorMessage,\n hasComponents,\n isFormType,\n type ComponentDef,\n type FormDefinition,\n type Page\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type ResponseToolkit, type Server } from '@hapi/hapi'\nimport { format, parseISO } from 'date-fns'\nimport { StatusCodes } from 'http-status-codes'\nimport { type Schema, type ValidationErrorItem } from 'joi'\nimport { Liquid } from 'liquidjs'\n\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport {\n getAnswer,\n type Field\n} from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport {\n type AnyFormRequest,\n type FormContext,\n type FormContextRequest,\n type FormSubmissionError\n} from '~/src/server/plugins/engine/types.js'\nimport {\n FormAction,\n FormStatus,\n type FormParams,\n type FormQuery,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\n\nconst logger = createLogger()\n\nexport const engine = new Liquid({\n outputEscape: 'escape',\n jsTruthy: true,\n ownPropertyOnly: false\n})\n\nexport interface GlobalScope {\n context: FormContext\n pages: Map<string, Page>\n components: Map<string, ComponentDef>\n}\n\nengine.registerFilter('evaluate', function (template?: string) {\n if (typeof template !== 'string') {\n return template\n }\n\n const globals = this.context.globals as GlobalScope\n const evaluated = evaluateTemplate(template, globals.context)\n\n return evaluated\n})\n\nengine.registerFilter('page', function (path?: string) {\n if (typeof path !== 'string') {\n return\n }\n\n const globals = this.context.globals as GlobalScope\n const pageDef = globals.pages.get(path)\n\n return pageDef\n})\n\nengine.registerFilter('href', function (path: string, query?: FormQuery) {\n if (typeof path !== 'string') {\n return\n }\n\n const globals = this.context.globals as GlobalScope\n const page = globals.context.pageMap.get(path)\n\n if (page === undefined) {\n return\n }\n\n return getPageHref(page, query)\n})\n\nengine.registerFilter('field', function (name: string) {\n if (typeof name !== 'string') {\n return\n }\n\n const globals = this.context.globals as GlobalScope\n const componentDef = globals.components.get(name)\n\n return componentDef\n})\n\nengine.registerFilter('answer', function (name: string) {\n if (typeof name !== 'string') {\n return\n }\n\n const globals = this.context.globals as GlobalScope\n const component = globals.context.componentMap.get(name)\n\n if (!component?.isFormComponent) {\n return\n }\n\n const answer = getAnswer(component as Field, globals.context.relevantState)\n\n return answer\n})\n\nexport function proceed(\n request: Pick<FormContextRequest, 'method' | 'payload' | 'query'>,\n h: FormResponseToolkit,\n nextUrl: string\n) {\n const { method, payload, query } = request\n const { returnUrl } = query\n\n const isReturnAllowed =\n payload && 'action' in payload\n ? payload.action === FormAction.Continue ||\n payload.action === FormAction.Validate\n : false\n\n // Redirect to return location (optional)\n const response =\n isReturnAllowed && isPathRelative(returnUrl)\n ? h.redirect(returnUrl)\n : h.redirect(redirectPath(nextUrl))\n\n // Redirect POST to GET to avoid resubmission\n return method === 'post'\n ? response.code(StatusCodes.SEE_OTHER)\n : response.code(StatusCodes.MOVED_TEMPORARILY)\n}\n\n/**\n * Encodes a URL, returning undefined if the process fails.\n */\nexport function encodeUrl(link?: string) {\n if (link) {\n try {\n return new URL(link).toString() // escape the search params without breaking the ? and & reserved characters in rfc2368\n } catch (err) {\n const errMsg = getErrorMessage(err)\n logger.error(\n errMsg,\n `[urlEncodingFailed] Failed to encode URL: ${link} - ${errMsg}`\n )\n throw err\n }\n }\n}\n\n/**\n * Get page href\n */\nexport function getPageHref(\n page: PageControllerClass,\n query?: FormQuery\n): string\n\n/**\n * Get page href by path\n */\nexport function getPageHref(\n page: PageControllerClass,\n path: string,\n query?: FormQuery\n): string\n\nexport function getPageHref(\n page: PageControllerClass,\n pathOrQuery?: string | FormQuery,\n queryOnly: FormQuery = {}\n) {\n const path = typeof pathOrQuery === 'string' ? pathOrQuery : page.path\n const query = typeof pathOrQuery === 'object' ? pathOrQuery : queryOnly\n\n if (!isPathRelative(path)) {\n throw Error(`Only relative URLs are allowed: ${path}`)\n }\n\n // Return path with page href as base\n return redirectPath(page.getHref(path), query)\n}\n\n/**\n * Get redirect path with optional query params\n */\nexport function redirectPath(nextUrl: string, query: FormQuery = {}) {\n const isRelative = isPathRelative(nextUrl)\n\n // Filter string query params only\n const params = Object.entries(query).filter(\n (query): query is [string, string] => typeof query[1] === 'string'\n )\n\n // Build URL with relative path support\n const url = isRelative\n ? new URL(nextUrl, 'http://example.com')\n : new URL(nextUrl)\n\n // Append query params\n for (const [name, value] of params) {\n url.searchParams.set(name, value)\n }\n\n if (isRelative) {\n return `${url.pathname}${url.search}`\n }\n\n return url.href\n}\n\nexport function isPathRelative(path?: string) {\n return (path ?? '').startsWith('/')\n}\n\nexport function normalisePath(path = '') {\n return path\n .trim() // Trim empty spaces\n .replace(/^\\//, '') // Remove leading slash\n .replace(/\\/$/, '') // Remove trailing slash\n}\n\nexport function getPage(\n model: FormModel | undefined,\n request: FormContextRequest\n) {\n const { params } = request\n\n const page = findPage(model, `/${params.path}`)\n\n if (!page) {\n throw Boom.notFound(`No page found for /${params.path}`)\n }\n\n return page\n}\n\nexport function findPage(model: FormModel | undefined, path?: string) {\n const findPath = `/${normalisePath(path)}`\n return model?.pages.find(({ path }) => path === findPath)\n}\n\nexport function getStartPath(model?: FormModel) {\n if (model?.engine === Engine.V2) {\n const startPath = normalisePath(model.def.pages.at(0)?.path)\n return startPath ? `/${startPath}` : ControllerPath.Start\n }\n\n const startPath = normalisePath(model?.def.startPage)\n return startPath ? `/${startPath}` : ControllerPath.Start\n}\n\nexport function checkFormStatus(params?: FormParams) {\n const isPreview = !!params?.state\n\n let state = FormStatus.Live\n\n if (isPreview && params.state === FormStatus.Draft) {\n state = FormStatus.Draft\n }\n\n return {\n isPreview,\n state\n }\n}\n\nexport function checkEmailAddressForLiveFormSubmission(\n emailAddress: string | undefined,\n isPreview: boolean\n) {\n if (!emailAddress && !isPreview) {\n throw Boom.internal(\n 'An email address is required to complete the form submission'\n )\n }\n}\n\n/**\n * Parses the errors from {@link Schema.validate} so they can be rendered by govuk-frontend templates\n * @param [details] - provided by {@link Schema.validate}\n */\nexport function getErrors(\n details?: ValidationErrorItem[]\n): FormSubmissionError[] | undefined {\n if (!details?.length) {\n return\n }\n\n return details.map(getError)\n}\n\nexport function getError(detail: ValidationErrorItem): FormSubmissionError {\n const { context, message, path } = detail\n\n const name = context?.key ?? ''\n const href = `#${name}`\n\n const text = message.replace(\n /\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+([+-][0-2]\\d:[0-5]\\d|Z)/,\n (text) => format(parseISO(text), 'd MMMM yyyy')\n )\n\n return {\n path,\n href,\n name,\n text,\n context\n }\n}\n\n/**\n * A small helper to safely generate a crumb token.\n * Checks that the crumb plugin is available, that crumb\n * is not disabled on the current route, and that cookies/state are present.\n */\nexport function safeGenerateCrumb(\n request: AnyFormRequest | null\n): string | undefined {\n // no request or no .state\n if (!request?.state) {\n return undefined\n }\n\n // crumb plugin or its generate method doesn't exist\n if (!request.server.plugins.crumb.generate) {\n return undefined\n }\n\n // crumb is explicitly disabled for this route\n if (request.route.settings.plugins?.crumb === false) {\n return undefined\n }\n\n return request.server.plugins.crumb.generate(request)\n}\n\n/**\n * Calculates an exponential backoff delay (in milliseconds) based on the current retry depth,\n * using a base delay of 2000ms (2 seconds) and doubling for each additional depth, while capping the delay at 25,000ms (25 seconds).\n * @param depth - The current retry depth (1, 2, 3, …)\n * @returns The calculated delay in milliseconds.\n */\nexport function getExponentialBackoffDelay(depth: number): number {\n const BASE_DELAY_MS = 2000 // 2 seconds initial delay\n const CAP_DELAY_MS = 25000 // cap each delay to 25 seconds\n const delay = BASE_DELAY_MS * 2 ** (depth - 1)\n return Math.min(delay, CAP_DELAY_MS)\n}\n\nexport function evaluateTemplate(\n template: string,\n context: FormContext\n): string {\n const globals: GlobalScope = {\n context,\n pages: context.pageDefMap,\n components: context.componentDefMap\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return engine.parseAndRenderSync(template, context.relevantState, {\n globals\n })\n}\n\nexport function getCacheService(server: Server) {\n return getPluginOptions(server).cacheService\n}\n\nexport function getSaveAndExitHelpers(server: Server) {\n return getPluginOptions(server).saveAndExit\n}\n\nexport function getPluginOptions(server: Server) {\n return server.plugins['forms-engine-plugin']\n}\n\n/**\n * Handles logging and issuing a permanent redirect for legacy routes.\n * @param h - The Hapi response toolkit.\n * @param targetUrl - The URL to redirect to.\n * @returns The Hapi response object configured for permanent redirect.\n */\nexport function handleLegacyRedirect(h: ResponseToolkit, targetUrl: string) {\n return h.redirect(targetUrl).permanent().takeover()\n}\n\n/**\n * If the page doesn't have a title, set it from the title of the first form component\n * @param def - the form definition\n */\nexport function setPageTitles(def: FormDefinition) {\n def.pages.forEach((page) => {\n if (!page.title) {\n if (hasComponents(page)) {\n // Set the page title from the first form component\n const firstFormComponent = page.components.find((component) =>\n isFormType(component.type)\n )\n\n page.title = firstFormComponent?.title ?? ''\n }\n\n if (!page.title) {\n const formNameMsg = def.name ? ` in form '${def.name}'` : ''\n\n logger.info(\n `[pageTitleMissing] Page '${page.path}' has no title${formNameMsg}`\n )\n }\n }\n })\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"helpers.js","names":["ControllerPath","Engine","getErrorMessage","hasComponents","isFormType","Boom","format","parseISO","StatusCodes","Liquid","createLogger","getAnswer","FormAction","FormStatus","logger","engine","outputEscape","jsTruthy","ownPropertyOnly","registerFilter","template","globals","context","evaluated","evaluateTemplate","path","pageDef","pages","get","query","page","pageMap","undefined","getPageHref","name","componentDef","components","component","componentMap","isFormComponent","answer","relevantState","proceed","request","h","nextUrl","method","payload","returnUrl","isReturnAllowed","action","Continue","Validate","response","isPathRelative","redirect","redirectPath","code","SEE_OTHER","MOVED_TEMPORARILY","encodeUrl","link","URL","toString","err","error","pathOrQuery","queryOnly","Error","getHref","isRelative","params","Object","entries","filter","url","value","searchParams","set","pathname","search","href","startsWith","normalisePath","trim","replace","getPage","model","findPage","notFound","findPath","find","getStartPath","V2","startPath","def","at","Start","startPage","checkFormStatus","isPreview","state","Live","Draft","checkEmailAddressForLiveFormSubmission","emailAddress","internal","getErrors","details","length","map","getError","detail","message","key","text","safeGenerateCrumb","server","plugins","crumb","generate","route","settings","getExponentialBackoffDelay","depth","BASE_DELAY_MS","CAP_DELAY_MS","delay","Math","min","pageDefMap","componentDefMap","parseAndRenderSync","getCacheService","getPluginOptions","cacheService","getSaveAndExitHelpers","saveAndExit","handleLegacyRedirect","targetUrl","permanent","takeover","setPageTitles","forEach","title","firstFormComponent","type","formNameMsg","info"],"sources":["../../../../src/server/plugins/engine/helpers.ts"],"sourcesContent":["import {\n ControllerPath,\n Engine,\n getErrorMessage,\n hasComponents,\n isFormType,\n type ComponentDef,\n type FormDefinition,\n type Page\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type ResponseToolkit, type Server } from '@hapi/hapi'\nimport { format, parseISO } from 'date-fns'\nimport { StatusCodes } from 'http-status-codes'\nimport { type Schema, type ValidationErrorItem } from 'joi'\nimport { Liquid } from 'liquidjs'\n\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport {\n getAnswer,\n type Field\n} from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport {\n type AnyFormRequest,\n type FormContext,\n type FormContextRequest,\n type FormSubmissionError\n} from '~/src/server/plugins/engine/types.js'\nimport {\n FormAction,\n FormStatus,\n type FormParams,\n type FormQuery,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\n\nconst logger = createLogger()\n\nexport const engine = new Liquid({\n outputEscape: 'escape',\n jsTruthy: true,\n ownPropertyOnly: false\n})\n\nexport interface GlobalScope {\n context: FormContext\n pages: Map<string, Page>\n components: Map<string, ComponentDef>\n}\n\nengine.registerFilter('evaluate', function (template?: string) {\n if (typeof template !== 'string') {\n return template\n }\n\n const globals = this.context.globals as GlobalScope\n const evaluated = evaluateTemplate(template, globals.context)\n\n return evaluated\n})\n\nengine.registerFilter('page', function (path?: string) {\n if (typeof path !== 'string') {\n return\n }\n\n const globals = this.context.globals as GlobalScope\n const pageDef = globals.pages.get(path)\n\n return pageDef\n})\n\nengine.registerFilter('href', function (path: string, query?: FormQuery) {\n if (typeof path !== 'string') {\n return\n }\n\n const globals = this.context.globals as GlobalScope\n const page = globals.context.pageMap.get(path)\n\n if (page === undefined) {\n return\n }\n\n return getPageHref(page, query)\n})\n\nengine.registerFilter('field', function (name: string) {\n if (typeof name !== 'string') {\n return\n }\n\n const globals = this.context.globals as GlobalScope\n const componentDef = globals.components.get(name)\n\n return componentDef\n})\n\nengine.registerFilter('answer', function (name: string) {\n if (typeof name !== 'string') {\n return\n }\n\n const globals = this.context.globals as GlobalScope\n const component = globals.context.componentMap.get(name)\n\n if (!component?.isFormComponent) {\n return\n }\n\n const answer = getAnswer(component as Field, globals.context.relevantState)\n\n return answer\n})\n\nexport function proceed(\n request: Pick<FormContextRequest, 'method' | 'payload' | 'query'>,\n h: FormResponseToolkit,\n nextUrl: string\n) {\n const { method, payload, query } = request\n const { returnUrl } = query\n\n const isReturnAllowed =\n payload && 'action' in payload\n ? payload.action === FormAction.Continue ||\n payload.action === FormAction.Validate\n : false\n\n // Redirect to return location (optional)\n const response =\n isReturnAllowed && isPathRelative(returnUrl)\n ? h.redirect(returnUrl)\n : h.redirect(redirectPath(nextUrl))\n\n // Redirect POST to GET to avoid resubmission\n return method === 'post'\n ? response.code(StatusCodes.SEE_OTHER)\n : response.code(StatusCodes.MOVED_TEMPORARILY)\n}\n\n/**\n * Encodes a URL, returning undefined if the process fails.\n */\nexport function encodeUrl(link?: string) {\n if (link) {\n try {\n return new URL(link).toString() // escape the search params without breaking the ? and & reserved characters in rfc2368\n } catch (err) {\n logger.error(\n err,\n `[urlEncodingFailed] Failed to encode URL: ${link} - ${getErrorMessage(err)}`\n )\n throw err\n }\n }\n}\n\n/**\n * Get page href\n */\nexport function getPageHref(\n page: PageControllerClass,\n query?: FormQuery\n): string\n\n/**\n * Get page href by path\n */\nexport function getPageHref(\n page: PageControllerClass,\n path: string,\n query?: FormQuery\n): string\n\nexport function getPageHref(\n page: PageControllerClass,\n pathOrQuery?: string | FormQuery,\n queryOnly: FormQuery = {}\n) {\n const path = typeof pathOrQuery === 'string' ? pathOrQuery : page.path\n const query = typeof pathOrQuery === 'object' ? pathOrQuery : queryOnly\n\n if (!isPathRelative(path)) {\n throw Error(`Only relative URLs are allowed: ${path}`)\n }\n\n // Return path with page href as base\n return redirectPath(page.getHref(path), query)\n}\n\n/**\n * Get redirect path with optional query params\n */\nexport function redirectPath(nextUrl: string, query: FormQuery = {}) {\n const isRelative = isPathRelative(nextUrl)\n\n // Filter string query params only\n const params = Object.entries(query).filter(\n (query): query is [string, string] => typeof query[1] === 'string'\n )\n\n // Build URL with relative path support\n const url = isRelative\n ? new URL(nextUrl, 'http://example.com')\n : new URL(nextUrl)\n\n // Append query params\n for (const [name, value] of params) {\n url.searchParams.set(name, value)\n }\n\n if (isRelative) {\n return `${url.pathname}${url.search}`\n }\n\n return url.href\n}\n\nexport function isPathRelative(path?: string) {\n return (path ?? '').startsWith('/')\n}\n\nexport function normalisePath(path = '') {\n return path\n .trim() // Trim empty spaces\n .replace(/^\\//, '') // Remove leading slash\n .replace(/\\/$/, '') // Remove trailing slash\n}\n\nexport function getPage(\n model: FormModel | undefined,\n request: FormContextRequest\n) {\n const { params } = request\n\n const page = findPage(model, `/${params.path}`)\n\n if (!page) {\n throw Boom.notFound(`No page found for /${params.path}`)\n }\n\n return page\n}\n\nexport function findPage(model: FormModel | undefined, path?: string) {\n const findPath = `/${normalisePath(path)}`\n return model?.pages.find(({ path }) => path === findPath)\n}\n\nexport function getStartPath(model?: FormModel) {\n if (model?.engine === Engine.V2) {\n const startPath = normalisePath(model.def.pages.at(0)?.path)\n return startPath ? `/${startPath}` : ControllerPath.Start\n }\n\n const startPath = normalisePath(model?.def.startPage)\n return startPath ? `/${startPath}` : ControllerPath.Start\n}\n\nexport function checkFormStatus(params?: FormParams) {\n const isPreview = !!params?.state\n\n let state = FormStatus.Live\n\n if (isPreview && params.state === FormStatus.Draft) {\n state = FormStatus.Draft\n }\n\n return {\n isPreview,\n state\n }\n}\n\nexport function checkEmailAddressForLiveFormSubmission(\n emailAddress: string | undefined,\n isPreview: boolean\n) {\n if (!emailAddress && !isPreview) {\n throw Boom.internal(\n 'An email address is required to complete the form submission'\n )\n }\n}\n\n/**\n * Parses the errors from {@link Schema.validate} so they can be rendered by govuk-frontend templates\n * @param [details] - provided by {@link Schema.validate}\n */\nexport function getErrors(\n details?: ValidationErrorItem[]\n): FormSubmissionError[] | undefined {\n if (!details?.length) {\n return\n }\n\n return details.map(getError)\n}\n\nexport function getError(detail: ValidationErrorItem): FormSubmissionError {\n const { context, message, path } = detail\n\n const name = context?.key ?? ''\n const href = `#${name}`\n\n const text = message.replace(\n /\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+([+-][0-2]\\d:[0-5]\\d|Z)/,\n (text) => format(parseISO(text), 'd MMMM yyyy')\n )\n\n return {\n path,\n href,\n name,\n text,\n context\n }\n}\n\n/**\n * A small helper to safely generate a crumb token.\n * Checks that the crumb plugin is available, that crumb\n * is not disabled on the current route, and that cookies/state are present.\n */\nexport function safeGenerateCrumb(\n request: AnyFormRequest | null\n): string | undefined {\n // no request or no .state\n if (!request?.state) {\n return undefined\n }\n\n // crumb plugin or its generate method doesn't exist\n if (!request.server.plugins.crumb.generate) {\n return undefined\n }\n\n // crumb is explicitly disabled for this route\n if (request.route.settings.plugins?.crumb === false) {\n return undefined\n }\n\n return request.server.plugins.crumb.generate(request)\n}\n\n/**\n * Calculates an exponential backoff delay (in milliseconds) based on the current retry depth,\n * using a base delay of 2000ms (2 seconds) and doubling for each additional depth, while capping the delay at 25,000ms (25 seconds).\n * @param depth - The current retry depth (1, 2, 3, …)\n * @returns The calculated delay in milliseconds.\n */\nexport function getExponentialBackoffDelay(depth: number): number {\n const BASE_DELAY_MS = 2000 // 2 seconds initial delay\n const CAP_DELAY_MS = 25000 // cap each delay to 25 seconds\n const delay = BASE_DELAY_MS * 2 ** (depth - 1)\n return Math.min(delay, CAP_DELAY_MS)\n}\n\nexport function evaluateTemplate(\n template: string,\n context: FormContext\n): string {\n const globals: GlobalScope = {\n context,\n pages: context.pageDefMap,\n components: context.componentDefMap\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return engine.parseAndRenderSync(template, context.relevantState, {\n globals\n })\n}\n\nexport function getCacheService(server: Server) {\n return getPluginOptions(server).cacheService\n}\n\nexport function getSaveAndExitHelpers(server: Server) {\n return getPluginOptions(server).saveAndExit\n}\n\nexport function getPluginOptions(server: Server) {\n return server.plugins['forms-engine-plugin']\n}\n\n/**\n * Handles logging and issuing a permanent redirect for legacy routes.\n * @param h - The Hapi response toolkit.\n * @param targetUrl - The URL to redirect to.\n * @returns The Hapi response object configured for permanent redirect.\n */\nexport function handleLegacyRedirect(h: ResponseToolkit, targetUrl: string) {\n return h.redirect(targetUrl).permanent().takeover()\n}\n\n/**\n * If the page doesn't have a title, set it from the title of the first form component\n * @param def - the form definition\n */\nexport function setPageTitles(def: FormDefinition) {\n def.pages.forEach((page) => {\n if (!page.title) {\n if (hasComponents(page)) {\n // Set the page title from the first form component\n const firstFormComponent = page.components.find((component) =>\n isFormType(component.type)\n )\n\n page.title = firstFormComponent?.title ?? ''\n }\n\n if (!page.title) {\n const formNameMsg = def.name ? ` in form '${def.name}'` : ''\n\n logger.info(\n `[pageTitleMissing] Page '${page.path}' has no title${formNameMsg}`\n )\n }\n }\n })\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -240,7 +240,11 @@ export class FormModel {
|
|
|
240
240
|
while (nextPage) {
|
|
241
241
|
// Add page to context
|
|
242
242
|
context.relevantPages.push(nextPage);
|
|
243
|
-
|
|
243
|
+
|
|
244
|
+
// Engine.V2 is excluded here as this will have already been done in initialiseContext()
|
|
245
|
+
if (this.engine !== Engine.V2) {
|
|
246
|
+
this.assignEvaluationState(context, nextPage);
|
|
247
|
+
}
|
|
244
248
|
this.assignRelevantState(context, nextPage);
|
|
245
249
|
|
|
246
250
|
// Stop at current page
|
|
@@ -260,14 +264,12 @@ export class FormModel {
|
|
|
260
264
|
return context;
|
|
261
265
|
}
|
|
262
266
|
initialiseContext(context) {
|
|
263
|
-
// For the V2 engine, we
|
|
264
|
-
//
|
|
265
|
-
//
|
|
267
|
+
// For the V2 engine, we initialise `evaluationState` for all keys.
|
|
268
|
+
// This is because the current condition evaluation library (eval-expr)
|
|
269
|
+
// will throw if an expression uses a key that is undefined.
|
|
266
270
|
if (this.engine === Engine.V2) {
|
|
267
271
|
for (const page of this.pages) {
|
|
268
|
-
|
|
269
|
-
context.evaluationState[key] = null;
|
|
270
|
-
}
|
|
272
|
+
this.assignEvaluationState(context, page);
|
|
271
273
|
}
|
|
272
274
|
}
|
|
273
275
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FormModel.js","names":["ComponentType","ConditionsModel","ControllerPath","ControllerType","Engine","SchemaVersion","convertConditionWrapperFromV2","formDefinitionSchema","formDefinitionV2Schema","generateConditionAlias","hasComponents","hasRepeater","isConditionWrapperV2","yesNoListId","yesNoListName","add","format","Parser","joi","createLogger","hasListFormField","todayAsDateOnly","findPage","getError","getPage","setPageTitles","createPage","validationOptions","opts","defaultServices","FormAction","merge","logger","FormModel","engine","schemaVersion","def","lists","sections","name","values","basePath","versionNumber","conditions","pages","services","controllers","pageDefMap","listDefMap","listDefIdMap","componentDefMap","componentDefIdMap","pageMap","componentMap","constructor","options","schema","V1","warn","result","validate","abortEarly","error","structuredClone","value","push","id","title","type","items","text","Map","map","page","path","list","filter","flatMap","components","component","forEach","conditionDef","condition","makeCondition","pageDef","some","controller","Status","collection","makeFilteredSchema","relevantPages","object","required","concat","stateSchema","parser","operators","logical","Object","assign","functions","dateForComparison","timePeriod","timeUnit","displayName","expr","toConditionExpression","fn","evaluationState","ctx","toConditionContext","evaluate","context","conditionId","propertyName","V2","defineProperty","get","from","parse","toExpression","getList","nameOrId","find","getFormContext","request","state","errors","query","currentPath","startPath","getStartPath","isForceAccess","relevantState","payload","getFormDataFromState","paths","data","referenceNumber","getReferenceNumber","submittedVersionNumber","validateFormPayload","nextPage","initialiseContext","assignEvaluationState","assignRelevantState","pageStateIsInvalid","getNextPath","validateFormState","assignPaths","key","keys","getContextValueFromState","listFields","fields","field","undefined","YesNoField","hasOptionalItems","item","length","fieldStateIsInvalid","validValues","fieldState","getFormValueFromState","isInvalid","isArray","Array","every","includes","href","getComponentById","componentId","getListById","listId","getConditionById","action","getFormParams","Validate","SaveAndExit","update","CheckboxesField","formState","getStateFromValidForm","previousPages","relevantPage","model","stripUnknown","errorsState","details","$$__referenceNumber","Error"],"sources":["../../../../../src/server/plugins/engine/models/FormModel.ts"],"sourcesContent":["import {\n ComponentType,\n ConditionsModel,\n ControllerPath,\n ControllerType,\n Engine,\n SchemaVersion,\n convertConditionWrapperFromV2,\n formDefinitionSchema,\n formDefinitionV2Schema,\n generateConditionAlias,\n hasComponents,\n hasRepeater,\n isConditionWrapperV2,\n yesNoListId,\n yesNoListName,\n type ComponentDef,\n type ConditionWrapper,\n type ConditionWrapperV2,\n type ConditionsModelData,\n type DateUnits,\n type FormDefinition,\n type List,\n type Page\n} from '@defra/forms-model'\nimport { add, format } from 'date-fns'\nimport { Parser, type Value } from 'expr-eval'\nimport joi from 'joi'\n\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport { type ListFormComponent } from '~/src/server/plugins/engine/components/ListFormComponent.js'\nimport {} from '~/src/server/plugins/engine/components/YesNoField.js'\nimport {\n hasListFormField,\n type Component\n} from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { todayAsDateOnly } from '~/src/server/plugins/engine/date-helper.js'\nimport {\n findPage,\n getError,\n getPage,\n setPageTitles\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n createPage,\n type PageControllerClass\n} from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport { validationOptions as opts } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormState,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport { FormAction } from '~/src/server/routes/types.js'\nimport { merge } from '~/src/server/services/cacheService.js'\nimport { type Services } from '~/src/server/types.js'\n\nconst logger = createLogger()\n\nexport class FormModel {\n /** The runtime engine that should be used */\n engine?: Engine\n\n schemaVersion: SchemaVersion\n\n /** the entire form JSON as an object */\n def: FormDefinition\n\n lists: FormDefinition['lists']\n sections: FormDefinition['sections'] = []\n name: string\n values: FormDefinition\n basePath: string\n versionNumber?: number\n conditions: Partial<Record<string, ExecutableCondition>>\n pages: PageControllerClass[]\n services: Services\n\n controllers?: Record<string, typeof PageController>\n pageDefMap: Map<string, Page>\n\n listDefMap: Map<string, List>\n listDefIdMap: Map<string, List>\n\n componentDefMap: Map<string, ComponentDef>\n componentDefIdMap: Map<string, ComponentDef>\n\n pageMap: Map<string, PageControllerClass>\n componentMap: Map<string, Component>\n\n constructor(\n def: typeof this.def,\n options: { basePath: string; versionNumber?: number },\n services: Services = defaultServices,\n controllers?: Record<string, typeof PageController>\n ) {\n let schema = formDefinitionV2Schema\n\n if (!def.schema || def.schema === SchemaVersion.V1) {\n logger.warn(\n `[DEPRECATION NOTICE] Form \"${def.name}\" constructed with legacy V1 schema. See https://defra.github.io/forms-engine-plugin/schemas/form-definition-schema.html.`\n )\n schema = formDefinitionSchema\n }\n\n const result = schema.validate(def, { abortEarly: false })\n\n if (result.error) {\n throw result.error\n }\n\n // Make a clone of the shallow copy returned\n // by joi so as not to change the source data.\n def = structuredClone(result.value)\n\n // Add default lists\n def.lists.push({\n id: def.schema === SchemaVersion.V1 ? yesNoListName : yesNoListId,\n name: '__yesNo',\n title: 'Yes/No',\n type: 'boolean',\n items: [\n {\n id: '02900d42-83d1-4c72-a719-c4e8228952fa',\n text: 'Yes',\n value: true\n },\n {\n id: 'f39000eb-c51b-4019-8f82-bbda0423f04d',\n text: 'No',\n value: false\n }\n ]\n })\n\n // Fix up page titles\n setPageTitles(def)\n\n this.engine = def.engine\n this.schemaVersion = def.schema ?? SchemaVersion.V1\n this.def = def\n this.lists = def.lists\n this.sections = def.sections\n this.name = def.name ?? ''\n this.values = result.value\n this.basePath = options.basePath\n this.versionNumber = options.versionNumber\n this.conditions = {}\n this.services = services\n this.controllers = controllers\n\n this.pageDefMap = new Map(def.pages.map((page) => [page.path, page]))\n this.listDefMap = new Map(def.lists.map((list) => [list.name, list]))\n this.listDefIdMap = new Map(\n def.lists\n .filter((list) => list.id) // Skip lists without an ID\n // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style\n .map((list) => [list.id as string, list])\n )\n this.componentDefMap = new Map(\n def.pages\n .filter(hasComponents)\n .flatMap((page) =>\n page.components.map((component) => [component.name, component])\n )\n )\n this.componentDefIdMap = new Map(\n def.pages.filter(hasComponents).flatMap((page) =>\n page.components\n .filter((component) => component.id) // Skip components without an ID\n .map((component) => {\n // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style\n return [component.id as string, component]\n })\n )\n )\n\n def.conditions.forEach((conditionDef) => {\n const condition = this.makeCondition(\n isConditionWrapperV2(conditionDef)\n ? convertConditionWrapperFromV2(conditionDef, this)\n : conditionDef\n )\n this.conditions[condition.name] = condition\n })\n\n this.pages = def.pages.map((pageDef) => createPage(this, pageDef))\n\n if (\n !def.pages.some(\n ({ controller }) =>\n // Check for user-provided status page (optional)\n controller === ControllerType.Status\n )\n ) {\n this.pages.push(\n createPage(this, {\n title: 'Form submitted',\n path: ControllerPath.Status,\n controller: ControllerType.Status\n })\n )\n }\n\n this.pageMap = new Map(this.pages.map((page) => [page.path, page]))\n this.componentMap = new Map(\n this.pages.flatMap((page) =>\n page.collection.components.map((component) => [\n component.name,\n component\n ])\n )\n )\n }\n\n /**\n * build the entire model schema from individual pages/sections and filter out answers\n * for pages which are no longer accessible due to an answer that has been changed\n */\n makeFilteredSchema(relevantPages: PageControllerClass[]) {\n // Build the entire model schema\n // from the individual pages/sections\n let schema = joi.object<FormSubmissionState>().required()\n\n relevantPages.forEach((page) => {\n schema = schema.concat(page.collection.stateSchema)\n })\n\n return schema\n }\n\n /**\n * Instantiates a Condition based on {@link ConditionWrapper}\n * @param condition\n */\n makeCondition(condition: ConditionWrapper): ExecutableCondition {\n const parser = new Parser({\n operators: {\n logical: true\n }\n })\n\n Object.assign(parser.functions, {\n dateForComparison(timePeriod: number, timeUnit: DateUnits) {\n // The time element must be stripped (hence using startOfDay() which has no time element),\n // then formatted as YYYY-MM-DD otherwise we can hit time element and BST issues giving the\n // wrong date to compare against.\n // Do not use .toISOString() to format the date as that introduces BST errors.\n return format(\n add(todayAsDateOnly(), { [timeUnit]: timePeriod }),\n 'yyyy-MM-dd'\n )\n }\n })\n\n const { name, displayName, value } = condition\n const expr = this.toConditionExpression(value, parser)\n\n const fn = (evaluationState: FormState) => {\n const ctx = this.toConditionContext(evaluationState, this.conditions)\n try {\n return expr.evaluate(ctx) as boolean\n } catch {\n return false\n }\n }\n\n return {\n name,\n displayName,\n value,\n expr,\n fn\n }\n }\n\n toConditionContext(\n evaluationState: FormState,\n conditions: Partial<Record<string, ExecutableCondition>>\n ) {\n const context = { ...evaluationState }\n\n for (const conditionId in conditions) {\n const propertyName =\n this.schemaVersion === SchemaVersion.V2\n ? generateConditionAlias(conditionId)\n : conditionId\n\n Object.defineProperty(context, propertyName, {\n get() {\n return conditions[conditionId]?.fn(evaluationState)\n }\n })\n }\n\n return context as Extract<Value, Record<string, Value>>\n }\n\n toConditionExpression(value: ConditionsModelData, parser: Parser) {\n const conditions = ConditionsModel.from(value)\n return parser.parse(conditions.toExpression())\n }\n\n getList(nameOrId: string): List | undefined {\n return this.schemaVersion === SchemaVersion.V1\n ? this.lists.find((list) => list.name === nameOrId)\n : this.lists.find((list) => list.id === nameOrId)\n }\n\n /**\n * Form context for the current page\n */\n getFormContext(\n request: FormContextRequest,\n state: FormState,\n errors?: FormSubmissionError[]\n ): FormContext {\n const { query } = request\n\n const page = getPage(this, request)\n\n // Determine form paths\n const currentPath = page.path\n const startPath = page.getStartPath()\n\n // Preview URL direct access is allowed\n const isForceAccess = 'force' in query\n\n let context: FormContext = {\n evaluationState: {},\n relevantState: {},\n relevantPages: [],\n payload: page.getFormDataFromState(request, state),\n state,\n paths: [],\n errors,\n isForceAccess,\n data: {},\n pageDefMap: this.pageDefMap,\n listDefMap: this.listDefMap,\n componentDefMap: this.componentDefMap,\n pageMap: this.pageMap,\n componentMap: this.componentMap,\n referenceNumber: getReferenceNumber(state),\n submittedVersionNumber: this.versionNumber\n }\n\n // Validate current page\n context = validateFormPayload(request, page, context)\n\n // Find start page\n let nextPage = findPage(this, startPath)\n\n this.initialiseContext(context)\n\n // Walk form pages from start\n while (nextPage) {\n // Add page to context\n context.relevantPages.push(nextPage)\n\n this.assignEvaluationState(context, nextPage)\n\n this.assignRelevantState(context, nextPage)\n\n // Stop at current page\n if (\n this.pageStateIsInvalid(context, nextPage) ||\n nextPage.path === currentPath\n ) {\n break\n }\n\n // Apply conditions to determine next page\n nextPage = findPage(this, nextPage.getNextPath(context))\n }\n\n // Validate form state\n context = validateFormState(request, page, context)\n\n // Add paths for navigation\n this.assignPaths(context)\n\n return context\n }\n\n private initialiseContext(context: FormContext) {\n // For the V2 engine, we need to initialise `evaluationState` to null\n // for all keys. This is because the current condition evaluation\n // library (eval-expr) will throw if an expression uses a key that is undefined.\n if (this.engine === Engine.V2) {\n for (const page of this.pages) {\n for (const key of page.keys) {\n context.evaluationState[key] = null\n }\n }\n }\n }\n\n private assignEvaluationState(\n context: FormContext,\n page: PageControllerClass\n ) {\n const { collection, pageDef } = page\n // Skip evaluation state for repeater pages\n\n if (!hasRepeater(pageDef)) {\n Object.assign(\n context.evaluationState,\n collection.getContextValueFromState(context.state)\n )\n }\n }\n\n private assignRelevantState(context: FormContext, page: PageControllerClass) {\n // Copy relevant state by expected keys\n for (const key of page.keys) {\n if (typeof context.state[key] !== 'undefined') {\n context.relevantState[key] = context.state[key]\n }\n }\n }\n\n private pageStateIsInvalid(context: FormContext, page: PageControllerClass) {\n // Get any list-bound fields on the page\n const listFields = page.collection.fields.filter(hasListFormField)\n\n // For each list field that is bound to a list that contains any conditional items,\n // we need to check any answers are still valid. Do this by evaluating the conditions\n // and ensuring any current answers are all included in the set of valid answers\n for (const field of listFields) {\n const list = field.list\n\n // Filter out YesNo as they can't be conditional\n if (list !== undefined && field.type !== ComponentType.YesNoField) {\n const hasOptionalItems =\n list.items.filter((item) => item.condition).length > 0\n\n if (hasOptionalItems) {\n return this.fieldStateIsInvalid(context, field, list)\n }\n }\n }\n }\n\n private fieldStateIsInvalid(\n context: FormContext,\n field: ListFormComponent,\n list: List\n ) {\n const { evaluationState, state } = context\n\n const validValues = list.items\n .filter((item) =>\n item.condition\n ? this.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n .map((item) => item.value)\n\n // Get the field state\n const fieldState = field.getFormValueFromState(state)\n\n if (fieldState !== undefined) {\n let isInvalid = false\n const isArray = Array.isArray(fieldState)\n\n // Check if any saved state value(s) are still valid\n // and return true if any are invalid\n if (isArray) {\n isInvalid = !fieldState.every((item) => validValues.includes(item))\n } else {\n isInvalid = !validValues.includes(fieldState)\n }\n\n if (isInvalid) {\n context.errors ??= []\n\n const text =\n 'Options are different because you changed a previous answer'\n\n context.errors.push({\n text,\n name: field.name,\n href: `#${field.name}`,\n path: [`#${field.name}`]\n })\n }\n\n return isInvalid\n }\n }\n\n private assignPaths(context: FormContext) {\n for (const { keys, path } of context.relevantPages) {\n context.paths.push(path)\n\n // Stop at page with errors\n if (\n context.errors?.some(({ name, path }) => {\n return keys.includes(name) || keys.some((key) => path.includes(key))\n })\n ) {\n break\n }\n }\n }\n\n getComponentById(componentId: string): ComponentDef | undefined {\n return this.componentDefIdMap.get(componentId)\n }\n\n getListById(listId: string): List | undefined {\n return this.listDefIdMap.get(listId)\n }\n\n /**\n * Returns a condition by its ID. O(n) lookup time.\n * @param conditionId\n * @returns\n */\n getConditionById(conditionId: string): ConditionWrapperV2 | undefined {\n return this.def.conditions\n .filter(isConditionWrapperV2)\n .find((condition) => condition.id === conditionId)\n }\n}\n\n/**\n * Validate current page only\n */\nfunction validateFormPayload(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { collection } = page\n const { payload, state } = context\n\n const { action } = page.getFormParams(request)\n\n // Skip validation GET requests or other actions\n if (\n !request.payload ||\n (action && ![FormAction.Validate, FormAction.SaveAndExit].includes(action))\n ) {\n return context\n }\n\n // For checkbox fields missing in the payload (i.e. unchecked),\n // explicitly set their value to undefined so that any previously\n // stored value is cleared and required field validation is enforced.\n const update = { ...request.payload }\n collection.fields.forEach((field) => {\n if (\n field.type === ComponentType.CheckboxesField &&\n !(field.name in update)\n ) {\n update[field.name] = undefined\n }\n })\n\n const { value, errors } = collection.validate({\n ...payload,\n ...update\n })\n\n // Add sanitised payload (ready to save)\n const formState = page.getStateFromValidForm(request, state, value)\n\n return {\n ...context,\n payload: merge(payload, value),\n state: merge(state, formState),\n errors\n }\n}\n\n/**\n * Validate entire form state\n */\nfunction validateFormState(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { errors = [], relevantPages, relevantState } = context\n\n // Exclude current page\n const previousPages = relevantPages.filter(\n (relevantPage) => relevantPage !== page\n )\n\n // Validate relevant state\n const { error } = page.model\n .makeFilteredSchema(previousPages)\n .validate(relevantState, { ...opts, stripUnknown: true })\n\n // Add relevant state errors\n if (error) {\n const errorsState = error.details.map(getError)\n return { ...context, errors: errors.concat(errorsState) }\n }\n\n return context\n}\n\nfunction getReferenceNumber(state: FormState): string {\n if (\n !state.$$__referenceNumber ||\n typeof state.$$__referenceNumber !== 'string'\n ) {\n throw Error('Reference number not found in form state')\n }\n\n return state.$$__referenceNumber\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"FormModel.js","names":["ComponentType","ConditionsModel","ControllerPath","ControllerType","Engine","SchemaVersion","convertConditionWrapperFromV2","formDefinitionSchema","formDefinitionV2Schema","generateConditionAlias","hasComponents","hasRepeater","isConditionWrapperV2","yesNoListId","yesNoListName","add","format","Parser","joi","createLogger","hasListFormField","todayAsDateOnly","findPage","getError","getPage","setPageTitles","createPage","validationOptions","opts","defaultServices","FormAction","merge","logger","FormModel","engine","schemaVersion","def","lists","sections","name","values","basePath","versionNumber","conditions","pages","services","controllers","pageDefMap","listDefMap","listDefIdMap","componentDefMap","componentDefIdMap","pageMap","componentMap","constructor","options","schema","V1","warn","result","validate","abortEarly","error","structuredClone","value","push","id","title","type","items","text","Map","map","page","path","list","filter","flatMap","components","component","forEach","conditionDef","condition","makeCondition","pageDef","some","controller","Status","collection","makeFilteredSchema","relevantPages","object","required","concat","stateSchema","parser","operators","logical","Object","assign","functions","dateForComparison","timePeriod","timeUnit","displayName","expr","toConditionExpression","fn","evaluationState","ctx","toConditionContext","evaluate","context","conditionId","propertyName","V2","defineProperty","get","from","parse","toExpression","getList","nameOrId","find","getFormContext","request","state","errors","query","currentPath","startPath","getStartPath","isForceAccess","relevantState","payload","getFormDataFromState","paths","data","referenceNumber","getReferenceNumber","submittedVersionNumber","validateFormPayload","nextPage","initialiseContext","assignEvaluationState","assignRelevantState","pageStateIsInvalid","getNextPath","validateFormState","assignPaths","getContextValueFromState","key","keys","listFields","fields","field","undefined","YesNoField","hasOptionalItems","item","length","fieldStateIsInvalid","validValues","fieldState","getFormValueFromState","isInvalid","isArray","Array","every","includes","href","getComponentById","componentId","getListById","listId","getConditionById","action","getFormParams","Validate","SaveAndExit","update","CheckboxesField","formState","getStateFromValidForm","previousPages","relevantPage","model","stripUnknown","errorsState","details","$$__referenceNumber","Error"],"sources":["../../../../../src/server/plugins/engine/models/FormModel.ts"],"sourcesContent":["import {\n ComponentType,\n ConditionsModel,\n ControllerPath,\n ControllerType,\n Engine,\n SchemaVersion,\n convertConditionWrapperFromV2,\n formDefinitionSchema,\n formDefinitionV2Schema,\n generateConditionAlias,\n hasComponents,\n hasRepeater,\n isConditionWrapperV2,\n yesNoListId,\n yesNoListName,\n type ComponentDef,\n type ConditionWrapper,\n type ConditionWrapperV2,\n type ConditionsModelData,\n type DateUnits,\n type FormDefinition,\n type List,\n type Page\n} from '@defra/forms-model'\nimport { add, format } from 'date-fns'\nimport { Parser, type Value } from 'expr-eval'\nimport joi from 'joi'\n\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport { type ListFormComponent } from '~/src/server/plugins/engine/components/ListFormComponent.js'\nimport {} from '~/src/server/plugins/engine/components/YesNoField.js'\nimport {\n hasListFormField,\n type Component\n} from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { todayAsDateOnly } from '~/src/server/plugins/engine/date-helper.js'\nimport {\n findPage,\n getError,\n getPage,\n setPageTitles\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n createPage,\n type PageControllerClass\n} from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport { validationOptions as opts } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormState,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport { FormAction } from '~/src/server/routes/types.js'\nimport { merge } from '~/src/server/services/cacheService.js'\nimport { type Services } from '~/src/server/types.js'\n\nconst logger = createLogger()\n\nexport class FormModel {\n /** The runtime engine that should be used */\n engine?: Engine\n\n schemaVersion: SchemaVersion\n\n /** the entire form JSON as an object */\n def: FormDefinition\n\n lists: FormDefinition['lists']\n sections: FormDefinition['sections'] = []\n name: string\n values: FormDefinition\n basePath: string\n versionNumber?: number\n conditions: Partial<Record<string, ExecutableCondition>>\n pages: PageControllerClass[]\n services: Services\n\n controllers?: Record<string, typeof PageController>\n pageDefMap: Map<string, Page>\n\n listDefMap: Map<string, List>\n listDefIdMap: Map<string, List>\n\n componentDefMap: Map<string, ComponentDef>\n componentDefIdMap: Map<string, ComponentDef>\n\n pageMap: Map<string, PageControllerClass>\n componentMap: Map<string, Component>\n\n constructor(\n def: typeof this.def,\n options: { basePath: string; versionNumber?: number },\n services: Services = defaultServices,\n controllers?: Record<string, typeof PageController>\n ) {\n let schema = formDefinitionV2Schema\n\n if (!def.schema || def.schema === SchemaVersion.V1) {\n logger.warn(\n `[DEPRECATION NOTICE] Form \"${def.name}\" constructed with legacy V1 schema. See https://defra.github.io/forms-engine-plugin/schemas/form-definition-schema.html.`\n )\n schema = formDefinitionSchema\n }\n\n const result = schema.validate(def, { abortEarly: false })\n\n if (result.error) {\n throw result.error\n }\n\n // Make a clone of the shallow copy returned\n // by joi so as not to change the source data.\n def = structuredClone(result.value)\n\n // Add default lists\n def.lists.push({\n id: def.schema === SchemaVersion.V1 ? yesNoListName : yesNoListId,\n name: '__yesNo',\n title: 'Yes/No',\n type: 'boolean',\n items: [\n {\n id: '02900d42-83d1-4c72-a719-c4e8228952fa',\n text: 'Yes',\n value: true\n },\n {\n id: 'f39000eb-c51b-4019-8f82-bbda0423f04d',\n text: 'No',\n value: false\n }\n ]\n })\n\n // Fix up page titles\n setPageTitles(def)\n\n this.engine = def.engine\n this.schemaVersion = def.schema ?? SchemaVersion.V1\n this.def = def\n this.lists = def.lists\n this.sections = def.sections\n this.name = def.name ?? ''\n this.values = result.value\n this.basePath = options.basePath\n this.versionNumber = options.versionNumber\n this.conditions = {}\n this.services = services\n this.controllers = controllers\n\n this.pageDefMap = new Map(def.pages.map((page) => [page.path, page]))\n this.listDefMap = new Map(def.lists.map((list) => [list.name, list]))\n this.listDefIdMap = new Map(\n def.lists\n .filter((list) => list.id) // Skip lists without an ID\n // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style\n .map((list) => [list.id as string, list])\n )\n this.componentDefMap = new Map(\n def.pages\n .filter(hasComponents)\n .flatMap((page) =>\n page.components.map((component) => [component.name, component])\n )\n )\n this.componentDefIdMap = new Map(\n def.pages.filter(hasComponents).flatMap((page) =>\n page.components\n .filter((component) => component.id) // Skip components without an ID\n .map((component) => {\n // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style\n return [component.id as string, component]\n })\n )\n )\n\n def.conditions.forEach((conditionDef) => {\n const condition = this.makeCondition(\n isConditionWrapperV2(conditionDef)\n ? convertConditionWrapperFromV2(conditionDef, this)\n : conditionDef\n )\n this.conditions[condition.name] = condition\n })\n\n this.pages = def.pages.map((pageDef) => createPage(this, pageDef))\n\n if (\n !def.pages.some(\n ({ controller }) =>\n // Check for user-provided status page (optional)\n controller === ControllerType.Status\n )\n ) {\n this.pages.push(\n createPage(this, {\n title: 'Form submitted',\n path: ControllerPath.Status,\n controller: ControllerType.Status\n })\n )\n }\n\n this.pageMap = new Map(this.pages.map((page) => [page.path, page]))\n this.componentMap = new Map(\n this.pages.flatMap((page) =>\n page.collection.components.map((component) => [\n component.name,\n component\n ])\n )\n )\n }\n\n /**\n * build the entire model schema from individual pages/sections and filter out answers\n * for pages which are no longer accessible due to an answer that has been changed\n */\n makeFilteredSchema(relevantPages: PageControllerClass[]) {\n // Build the entire model schema\n // from the individual pages/sections\n let schema = joi.object<FormSubmissionState>().required()\n\n relevantPages.forEach((page) => {\n schema = schema.concat(page.collection.stateSchema)\n })\n\n return schema\n }\n\n /**\n * Instantiates a Condition based on {@link ConditionWrapper}\n * @param condition\n */\n makeCondition(condition: ConditionWrapper): ExecutableCondition {\n const parser = new Parser({\n operators: {\n logical: true\n }\n })\n\n Object.assign(parser.functions, {\n dateForComparison(timePeriod: number, timeUnit: DateUnits) {\n // The time element must be stripped (hence using startOfDay() which has no time element),\n // then formatted as YYYY-MM-DD otherwise we can hit time element and BST issues giving the\n // wrong date to compare against.\n // Do not use .toISOString() to format the date as that introduces BST errors.\n return format(\n add(todayAsDateOnly(), { [timeUnit]: timePeriod }),\n 'yyyy-MM-dd'\n )\n }\n })\n\n const { name, displayName, value } = condition\n const expr = this.toConditionExpression(value, parser)\n\n const fn = (evaluationState: FormState) => {\n const ctx = this.toConditionContext(evaluationState, this.conditions)\n try {\n return expr.evaluate(ctx) as boolean\n } catch {\n return false\n }\n }\n\n return {\n name,\n displayName,\n value,\n expr,\n fn\n }\n }\n\n toConditionContext(\n evaluationState: FormState,\n conditions: Partial<Record<string, ExecutableCondition>>\n ) {\n const context = { ...evaluationState }\n\n for (const conditionId in conditions) {\n const propertyName =\n this.schemaVersion === SchemaVersion.V2\n ? generateConditionAlias(conditionId)\n : conditionId\n\n Object.defineProperty(context, propertyName, {\n get() {\n return conditions[conditionId]?.fn(evaluationState)\n }\n })\n }\n\n return context as Extract<Value, Record<string, Value>>\n }\n\n toConditionExpression(value: ConditionsModelData, parser: Parser) {\n const conditions = ConditionsModel.from(value)\n return parser.parse(conditions.toExpression())\n }\n\n getList(nameOrId: string): List | undefined {\n return this.schemaVersion === SchemaVersion.V1\n ? this.lists.find((list) => list.name === nameOrId)\n : this.lists.find((list) => list.id === nameOrId)\n }\n\n /**\n * Form context for the current page\n */\n getFormContext(\n request: FormContextRequest,\n state: FormState,\n errors?: FormSubmissionError[]\n ): FormContext {\n const { query } = request\n\n const page = getPage(this, request)\n\n // Determine form paths\n const currentPath = page.path\n const startPath = page.getStartPath()\n\n // Preview URL direct access is allowed\n const isForceAccess = 'force' in query\n\n let context: FormContext = {\n evaluationState: {},\n relevantState: {},\n relevantPages: [],\n payload: page.getFormDataFromState(request, state),\n state,\n paths: [],\n errors,\n isForceAccess,\n data: {},\n pageDefMap: this.pageDefMap,\n listDefMap: this.listDefMap,\n componentDefMap: this.componentDefMap,\n pageMap: this.pageMap,\n componentMap: this.componentMap,\n referenceNumber: getReferenceNumber(state),\n submittedVersionNumber: this.versionNumber\n }\n\n // Validate current page\n context = validateFormPayload(request, page, context)\n\n // Find start page\n let nextPage = findPage(this, startPath)\n\n this.initialiseContext(context)\n\n // Walk form pages from start\n while (nextPage) {\n // Add page to context\n context.relevantPages.push(nextPage)\n\n // Engine.V2 is excluded here as this will have already been done in initialiseContext()\n if (this.engine !== Engine.V2) {\n this.assignEvaluationState(context, nextPage)\n }\n\n this.assignRelevantState(context, nextPage)\n\n // Stop at current page\n if (\n this.pageStateIsInvalid(context, nextPage) ||\n nextPage.path === currentPath\n ) {\n break\n }\n\n // Apply conditions to determine next page\n nextPage = findPage(this, nextPage.getNextPath(context))\n }\n\n // Validate form state\n context = validateFormState(request, page, context)\n\n // Add paths for navigation\n this.assignPaths(context)\n\n return context\n }\n\n private initialiseContext(context: FormContext) {\n // For the V2 engine, we initialise `evaluationState` for all keys.\n // This is because the current condition evaluation library (eval-expr)\n // will throw if an expression uses a key that is undefined.\n if (this.engine === Engine.V2) {\n for (const page of this.pages) {\n this.assignEvaluationState(context, page)\n }\n }\n }\n\n private assignEvaluationState(\n context: FormContext,\n page: PageControllerClass\n ) {\n const { collection, pageDef } = page\n // Skip evaluation state for repeater pages\n\n if (!hasRepeater(pageDef)) {\n Object.assign(\n context.evaluationState,\n collection.getContextValueFromState(context.state)\n )\n }\n }\n\n private assignRelevantState(context: FormContext, page: PageControllerClass) {\n // Copy relevant state by expected keys\n for (const key of page.keys) {\n if (typeof context.state[key] !== 'undefined') {\n context.relevantState[key] = context.state[key]\n }\n }\n }\n\n private pageStateIsInvalid(context: FormContext, page: PageControllerClass) {\n // Get any list-bound fields on the page\n const listFields = page.collection.fields.filter(hasListFormField)\n\n // For each list field that is bound to a list that contains any conditional items,\n // we need to check any answers are still valid. Do this by evaluating the conditions\n // and ensuring any current answers are all included in the set of valid answers\n for (const field of listFields) {\n const list = field.list\n\n // Filter out YesNo as they can't be conditional\n if (list !== undefined && field.type !== ComponentType.YesNoField) {\n const hasOptionalItems =\n list.items.filter((item) => item.condition).length > 0\n\n if (hasOptionalItems) {\n return this.fieldStateIsInvalid(context, field, list)\n }\n }\n }\n }\n\n private fieldStateIsInvalid(\n context: FormContext,\n field: ListFormComponent,\n list: List\n ) {\n const { evaluationState, state } = context\n\n const validValues = list.items\n .filter((item) =>\n item.condition\n ? this.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n .map((item) => item.value)\n\n // Get the field state\n const fieldState = field.getFormValueFromState(state)\n\n if (fieldState !== undefined) {\n let isInvalid = false\n const isArray = Array.isArray(fieldState)\n\n // Check if any saved state value(s) are still valid\n // and return true if any are invalid\n if (isArray) {\n isInvalid = !fieldState.every((item) => validValues.includes(item))\n } else {\n isInvalid = !validValues.includes(fieldState)\n }\n\n if (isInvalid) {\n context.errors ??= []\n\n const text =\n 'Options are different because you changed a previous answer'\n\n context.errors.push({\n text,\n name: field.name,\n href: `#${field.name}`,\n path: [`#${field.name}`]\n })\n }\n\n return isInvalid\n }\n }\n\n private assignPaths(context: FormContext) {\n for (const { keys, path } of context.relevantPages) {\n context.paths.push(path)\n\n // Stop at page with errors\n if (\n context.errors?.some(({ name, path }) => {\n return keys.includes(name) || keys.some((key) => path.includes(key))\n })\n ) {\n break\n }\n }\n }\n\n getComponentById(componentId: string): ComponentDef | undefined {\n return this.componentDefIdMap.get(componentId)\n }\n\n getListById(listId: string): List | undefined {\n return this.listDefIdMap.get(listId)\n }\n\n /**\n * Returns a condition by its ID. O(n) lookup time.\n * @param conditionId\n * @returns\n */\n getConditionById(conditionId: string): ConditionWrapperV2 | undefined {\n return this.def.conditions\n .filter(isConditionWrapperV2)\n .find((condition) => condition.id === conditionId)\n }\n}\n\n/**\n * Validate current page only\n */\nfunction validateFormPayload(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { collection } = page\n const { payload, state } = context\n\n const { action } = page.getFormParams(request)\n\n // Skip validation GET requests or other actions\n if (\n !request.payload ||\n (action && ![FormAction.Validate, FormAction.SaveAndExit].includes(action))\n ) {\n return context\n }\n\n // For checkbox fields missing in the payload (i.e. unchecked),\n // explicitly set their value to undefined so that any previously\n // stored value is cleared and required field validation is enforced.\n const update = { ...request.payload }\n collection.fields.forEach((field) => {\n if (\n field.type === ComponentType.CheckboxesField &&\n !(field.name in update)\n ) {\n update[field.name] = undefined\n }\n })\n\n const { value, errors } = collection.validate({\n ...payload,\n ...update\n })\n\n // Add sanitised payload (ready to save)\n const formState = page.getStateFromValidForm(request, state, value)\n\n return {\n ...context,\n payload: merge(payload, value),\n state: merge(state, formState),\n errors\n }\n}\n\n/**\n * Validate entire form state\n */\nfunction validateFormState(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { errors = [], relevantPages, relevantState } = context\n\n // Exclude current page\n const previousPages = relevantPages.filter(\n (relevantPage) => relevantPage !== page\n )\n\n // Validate relevant state\n const { error } = page.model\n .makeFilteredSchema(previousPages)\n .validate(relevantState, { ...opts, stripUnknown: true })\n\n // Add relevant state errors\n if (error) {\n const errorsState = error.details.map(getError)\n return { ...context, errors: errors.concat(errorsState) }\n }\n\n return context\n}\n\nfunction getReferenceNumber(state: FormState): string {\n if (\n !state.$$__referenceNumber ||\n typeof state.$$__referenceNumber !== 'string'\n ) {\n throw Error('Reference number not found in form state')\n }\n\n return state.$$__referenceNumber\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getErrorMessage } from '@defra/forms-model';
|
|
1
2
|
import Joi from 'joi';
|
|
2
3
|
import { createLogger } from "../../common/helpers/logging/logger.js";
|
|
3
4
|
import { CacheService } from "../../services/index.js";
|
|
@@ -31,7 +32,7 @@ export function validatePluginOptions(options) {
|
|
|
31
32
|
abortEarly: false
|
|
32
33
|
});
|
|
33
34
|
if (result.error) {
|
|
34
|
-
logger.error(`Missing required properties in plugin options: ${result.error
|
|
35
|
+
logger.error(result.error, `Missing required properties in plugin options: ${getErrorMessage(result.error)}`);
|
|
35
36
|
throw new Error('Invalid plugin options', result.error);
|
|
36
37
|
}
|
|
37
38
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"options.js","names":["Joi","createLogger","CacheService","logger","pluginRegistrationOptionsSchema","object","model","optional","services","controllers","pattern","string","any","cache","alternatives","try","instance","globals","filters","pluginPath","nunjucks","baseLayoutPath","required","paths","array","items","viewContext","function","preparePageEventRequestOptions","onRequest","baseUrl","uri","saveAndExit","validatePluginOptions","options","result","validate","abortEarly","error","
|
|
1
|
+
{"version":3,"file":"options.js","names":["getErrorMessage","Joi","createLogger","CacheService","logger","pluginRegistrationOptionsSchema","object","model","optional","services","controllers","pattern","string","any","cache","alternatives","try","instance","globals","filters","pluginPath","nunjucks","baseLayoutPath","required","paths","array","items","viewContext","function","preparePageEventRequestOptions","onRequest","baseUrl","uri","saveAndExit","validatePluginOptions","options","result","validate","abortEarly","error","Error","value"],"sources":["../../../../src/server/plugins/engine/options.js"],"sourcesContent":["import { getErrorMessage } from '@defra/forms-model'\nimport Joi from 'joi'\n\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport { CacheService } from '~/src/server/services/index.js'\n\nconst logger = createLogger()\n\nconst pluginRegistrationOptionsSchema = Joi.object({\n model: Joi.object().optional(),\n services: Joi.object().optional(),\n controllers: Joi.object().pattern(Joi.string(), Joi.any()).optional(),\n cache: Joi.alternatives().try(\n Joi.object().instance(CacheService),\n Joi.string()\n ),\n globals: Joi.object().pattern(Joi.string(), Joi.any()).optional(),\n filters: Joi.object().pattern(Joi.string(), Joi.any()).optional(),\n pluginPath: Joi.string().optional(),\n nunjucks: Joi.object({\n baseLayoutPath: Joi.string().required(),\n paths: Joi.array().items(Joi.string()).required()\n }).required(),\n viewContext: Joi.function().required(),\n preparePageEventRequestOptions: Joi.function().optional(),\n onRequest: Joi.function().optional(),\n baseUrl: Joi.string().uri().required(),\n saveAndExit: Joi.function().optional()\n})\n\n/**\n * Validates the plugin options against the schema and returns the validated value.\n * @param {PluginOptions} options\n * @returns {PluginOptions}\n */\nexport function validatePluginOptions(options) {\n const result = pluginRegistrationOptionsSchema.validate(options, {\n abortEarly: false\n })\n\n if (result.error) {\n logger.error(\n result.error,\n `Missing required properties in plugin options: ${getErrorMessage(result.error)}`\n )\n throw new Error('Invalid plugin options', result.error)\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return result.value\n}\n\n/**\n * @import { PluginOptions } from '~/src/server/plugins/engine/types.js'\n */\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -267,8 +267,8 @@ export class FileUploadPageController extends QuestionPageController {
|
|
|
267
267
|
// Depth 1: 2000ms, Depth 2: 4000ms, Depth 3: 8000ms, Depth 4: 16000ms, Depth 5+: 30000ms (capped)
|
|
268
268
|
// A depth of 5 (or more) implies cumulative delays roughly reaching 55 seconds.
|
|
269
269
|
if (depth >= 5) {
|
|
270
|
-
const
|
|
271
|
-
request.logger.error(
|
|
270
|
+
const err = new Error(`Exceeded cumulative retry delay for ${uploadId} (depth: ${depth}). Re-initiating a new upload.`);
|
|
271
|
+
request.logger.error(err, `[uploadTimeout] Exceeded cumulative retry delay for uploadId: ${uploadId} at depth: ${depth} - re-initiating new upload`);
|
|
272
272
|
await this.initiateAndStoreNewUpload(request, state);
|
|
273
273
|
throw Boom.gatewayTimeout(`Timed out waiting for ${uploadId} after cumulative retries exceeding ${((CDP_UPLOAD_TIMEOUT_MS - 5000) / 1000).toFixed(0)} seconds`);
|
|
274
274
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileUploadPageController.js","names":["ComponentType","Boom","wait","tempItemSchema","getCacheService","getError","getExponentialBackoffDelay","QuestionPageController","getProxyUrlForLocalDevelopment","getUploadStatus","initiateUpload","FileStatus","UploadStatus","MAX_UPLOADS","CDP_UPLOAD_TIMEOUT_MS","prepareStatus","status","file","form","isPending","fileStatus","pending","errorMessage","prepareFileState","fileState","FileUploadPageController","fileUpload","fileDeleteViewName","constructor","model","pageDef","collection","fileUploads","fields","filter","field","type","FileUploadField","at","length","badImplementation","path","indexOf","name","viewName","getFormDataFromState","request","state","payload","files","getFilesFromState","undefined","getState","refreshUpload","uploadState","upload","getUploadFromState","makeGetItemDeleteRouteHandler","context","h","viewModel","params","fileToRemove","find","uploadId","itemId","notFound","filename","view","backLink","getBackLink","pageTitle","itemTitle","confirmation","text","buttonConfirm","buttonCancel","makePostItemDeleteRouteHandler","confirm","getFormParams","checkRemovedFiles","proceed","getErrors","details","errors","forEach","error","isUploadError","isUploadRootError","push","value","href","getViewModel","components","formComponent","id","index","proxyUrl","uploadUrl","formAction","componentsBefore","slice","checkUploadStatus","depth","initiateAndStoreNewUpload","statusResponse","badRequest","uploadStatus","initiated","Error","logger","gatewayTimeout","toFixed","delay","info","validationResult","validate","stripUnknown","complete","unshift","mergeState","cacheService","server","setFlash","filesUpdated","options","schema","max","Math","min","outputEmail","def","newUpload","accept"],"sources":["../../../../../src/server/plugins/engine/pageControllers/FileUploadPageController.ts"],"sourcesContent":["import { ComponentType, type PageFileUpload } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { wait } from '@hapi/hoek'\nimport { type ValidationErrorItem } from 'joi'\n\nimport {\n tempItemSchema,\n type FileUploadField\n} from '~/src/server/plugins/engine/components/FileUploadField.js'\nimport {\n getCacheService,\n getError,\n getExponentialBackoffDelay\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport { getProxyUrlForLocalDevelopment } from '~/src/server/plugins/engine/pageControllers/helpers/index.js'\nimport {\n getUploadStatus,\n initiateUpload\n} from '~/src/server/plugins/engine/services/uploadService.js'\nimport {\n FileStatus,\n UploadStatus,\n type AnyFormRequest,\n type FeaturedFormPageViewModel,\n type FileState,\n type FormContext,\n type FormContextRequest,\n type FormSubmissionError,\n type FormSubmissionState,\n type ItemDeletePageViewModel,\n type UploadInitiateResponse,\n type UploadStatusFileResponse\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\n\nconst MAX_UPLOADS = 25\nconst CDP_UPLOAD_TIMEOUT_MS = 60000 // 1 minute\n\nexport function prepareStatus(status: UploadStatusFileResponse) {\n const file = status.form.file\n const isPending = file.fileStatus === FileStatus.pending\n\n if (!file.errorMessage && isPending) {\n file.errorMessage = 'The selected file has not fully uploaded'\n }\n\n return status\n}\n\nfunction prepareFileState(fileState: FileState) {\n prepareStatus(fileState.status)\n\n return fileState\n}\n\nexport class FileUploadPageController extends QuestionPageController {\n declare pageDef: PageFileUpload\n\n fileUpload: FileUploadField\n fileDeleteViewName = 'item-delete'\n\n constructor(model: FormModel, pageDef: PageFileUpload) {\n super(model, pageDef)\n\n const { collection } = this\n\n // Get the file upload fields from the collection\n const fileUploads = collection.fields.filter(\n (field): field is FileUploadField =>\n field.type === ComponentType.FileUploadField\n )\n\n const fileUpload = fileUploads.at(0)\n\n // Assert we have exactly 1 file upload component\n if (!fileUpload || fileUploads.length > 1) {\n throw Boom.badImplementation(\n `Expected 1 FileUploadFieldComponent in FileUploadPageController '${pageDef.path}'`\n )\n }\n\n // Assert the file upload component is the first form component\n if (collection.fields.indexOf(fileUpload) !== 0) {\n throw Boom.badImplementation(\n `Expected '${fileUpload.name}' to be the first form component in FileUploadPageController '${pageDef.path}'`\n )\n }\n\n // Assign the file upload component to the controller\n this.fileUpload = fileUpload\n this.viewName = 'file-upload'\n }\n\n getFormDataFromState(\n request: FormContextRequest | undefined,\n state: FormSubmissionState\n ) {\n const { fileUpload } = this\n\n const payload = super.getFormDataFromState(request, state)\n const files = this.getFilesFromState(state)\n\n // Append the files to the payload\n payload[fileUpload.name] = files.length ? files : undefined\n\n return payload\n }\n\n async getState(request: AnyFormRequest) {\n const { fileUpload } = this\n\n // Get the actual state\n const state = await super.getState(request)\n const files = this.getFilesFromState(state)\n\n // Overwrite the files with those in the upload state\n state[fileUpload.name] = files\n\n return this.refreshUpload(request, state)\n }\n\n /**\n * Get the uploaded files from state.\n */\n getFilesFromState(state: FormSubmissionState) {\n const { path } = this\n\n const uploadState = state.upload?.[path]\n return uploadState?.files ?? []\n }\n\n /**\n * Get the initiated upload from state.\n */\n getUploadFromState(state: FormSubmissionState) {\n const { path } = this\n\n const uploadState = state.upload?.[path]\n return uploadState?.upload\n }\n\n makeGetItemDeleteRouteHandler() {\n return (\n request: FormRequest,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { viewModel } = this\n const { params } = request\n const { state } = context\n\n const files = this.getFilesFromState(state)\n\n const fileToRemove = files.find(\n ({ uploadId }) => uploadId === params.itemId\n )\n\n if (!fileToRemove) {\n throw Boom.notFound('File to delete not found')\n }\n\n const { filename } = fileToRemove.status.form.file\n\n return h.view(this.fileDeleteViewName, {\n ...viewModel,\n context,\n backLink: this.getBackLink(request, context),\n pageTitle: `Are you sure you want to remove this file?`,\n itemTitle: filename,\n confirmation: { text: 'You cannot recover removed files.' },\n buttonConfirm: { text: 'Remove file' },\n buttonCancel: { text: 'Cancel' }\n } satisfies ItemDeletePageViewModel)\n }\n }\n\n makePostItemDeleteRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { path } = this\n const { state } = context\n\n const { confirm } = this.getFormParams(request)\n\n // Check for any removed files in the POST payload\n if (confirm) {\n await this.checkRemovedFiles(request, state)\n return this.proceed(request, h, path)\n }\n\n return this.proceed(request, h)\n }\n }\n\n getErrors(details?: ValidationErrorItem[]) {\n const { fileUpload } = this\n\n if (details) {\n const errors: FormSubmissionError[] = []\n\n details.forEach((error) => {\n const isUploadError = error.path[0] === fileUpload.name\n const isUploadRootError = isUploadError && error.path.length === 1\n\n if (!isUploadError || isUploadRootError) {\n // The error is for the root of the upload or another\n // field on the page so defer to the getError helper\n errors.push(getError(error))\n } else {\n const { context, path, type } = error\n\n if (type === 'object.unknown' && path.at(-1) === 'errorMessage') {\n const value = context?.value as string | undefined\n\n if (value) {\n const name = fileUpload.name\n const text = typeof value === 'string' ? value : 'Unknown error'\n const href = `#${name}`\n\n errors.push({ path, href, name, text })\n }\n }\n }\n })\n\n return errors\n }\n }\n\n getViewModel(\n request: FormContextRequest,\n context: FormContext\n ): FeaturedFormPageViewModel {\n const { fileUpload } = this\n const { state } = context\n\n const upload = this.getUploadFromState(state)\n\n const viewModel = super.getViewModel(request, context)\n const { components } = viewModel\n\n // Featured form component\n const [formComponent] = components.filter(\n ({ model }) => model.id === fileUpload.name\n )\n\n const index = components.indexOf(formComponent)\n\n const proxyUrl = getProxyUrlForLocalDevelopment(upload?.uploadUrl)\n\n return {\n ...viewModel,\n formAction: upload?.uploadUrl,\n uploadId: upload?.uploadId,\n formComponent,\n\n // Split out components before/after\n componentsBefore: components.slice(0, index),\n components: components.slice(index),\n proxyUrl\n }\n }\n\n /**\n * Refreshes the CDP upload and files in the\n * state and checks for any removed files.\n *\n * If an upload exists and hasn't been consumed\n * it gets re-used, otherwise we initiate a new one.\n * @param request - the hapi request\n * @param state - the form state\n */\n private async refreshUpload(\n request: AnyFormRequest,\n state: FormSubmissionState\n ) {\n state = await this.checkUploadStatus(request, state)\n\n return state\n }\n\n /**\n * If an upload exists and hasn't been consumed\n * it gets re-used, otherwise a new one is initiated.\n * @param request - the hapi request\n * @param state - the form state\n * @param depth - the number of retries so far\n */\n private async checkUploadStatus(\n request: AnyFormRequest,\n state: FormSubmissionState,\n depth = 1\n ): Promise<FormSubmissionState> {\n const upload = this.getUploadFromState(state)\n const files = this.getFilesFromState(state)\n\n // If no upload exists, initiate a new one.\n if (!upload?.uploadId) {\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n const uploadId = upload.uploadId\n const statusResponse = await getUploadStatus(uploadId)\n if (!statusResponse) {\n throw Boom.badRequest(\n `Unexpected empty response from getUploadStatus for ${uploadId}`\n )\n }\n\n // Re-use the upload if it is still in the \"initiated\" state.\n if (statusResponse.uploadStatus === UploadStatus.initiated) {\n return state\n }\n\n if (statusResponse.uploadStatus === UploadStatus.pending) {\n // Using exponential backoff delays:\n // Depth 1: 2000ms, Depth 2: 4000ms, Depth 3: 8000ms, Depth 4: 16000ms, Depth 5+: 30000ms (capped)\n // A depth of 5 (or more) implies cumulative delays roughly reaching 55 seconds.\n if (depth >= 5) {\n const error = new Error(\n `Exceeded cumulative retry delay for ${uploadId} (depth: ${depth}). Re-initiating a new upload.`\n )\n request.logger.error(\n error,\n `[uploadTimeout] Exceeded cumulative retry delay for uploadId: ${uploadId} at depth: ${depth} - re-initiating new upload`\n )\n await this.initiateAndStoreNewUpload(request, state)\n throw Boom.gatewayTimeout(\n `Timed out waiting for ${uploadId} after cumulative retries exceeding ${((CDP_UPLOAD_TIMEOUT_MS - 5000) / 1000).toFixed(0)} seconds`\n )\n }\n const delay = getExponentialBackoffDelay(depth)\n request.logger.info(\n `[uploadRetry] Waiting ${delay / 1000} seconds for uploadId: ${uploadId} to complete (retry depth: ${depth})`\n )\n await wait(delay)\n return this.checkUploadStatus(request, state, depth + 1)\n }\n\n // Only add to files state if the file validates.\n // This secures against html tampering of the file input\n // by adding a 'multiple' attribute or it being\n // changed to a simple text field or similar.\n const validationResult = tempItemSchema.validate(\n { uploadId, status: statusResponse },\n { stripUnknown: true }\n )\n const error = validationResult.error\n const fileState = validationResult.value as FileState\n\n if (error) {\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n const file = fileState.status.form.file\n if (file.fileStatus === FileStatus.complete) {\n files.unshift(prepareFileState(fileState))\n await this.mergeState(request, state, {\n upload: { [this.path]: { files, upload } }\n })\n } else {\n // Flash the error message.\n const { fileUpload } = this\n const cacheService = getCacheService(request.server)\n const name = fileUpload.name\n const text = file.errorMessage ?? 'Unknown error'\n const errors: FormSubmissionError[] = [\n { path: [name], href: `#${name}`, name, text }\n ]\n cacheService.setFlash(request, { errors })\n }\n\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n /**\n * Checks the payload for a file getting removed\n * and removes it from the upload files if found\n * @param request - the hapi request\n * @param state - the form state\n * @returns updated state if any files have been removed\n */\n private async checkRemovedFiles(\n request: FormRequestPayload,\n state: FormSubmissionState\n ) {\n const { path } = this\n const { params } = request\n\n const upload = this.getUploadFromState(state)\n const files = this.getFilesFromState(state)\n\n const filesUpdated = files.filter(\n ({ uploadId }) => uploadId !== params.itemId\n )\n\n if (filesUpdated.length === files.length) {\n return\n }\n\n await this.mergeState(request, state, {\n upload: { [path]: { files: filesUpdated, upload } }\n })\n }\n\n /**\n * Initiates a CDP file upload and stores in the upload state\n * @param request - the hapi request\n * @param state - the form state\n */\n private async initiateAndStoreNewUpload(\n request: AnyFormRequest,\n state: FormSubmissionState\n ) {\n const { fileUpload, href, path } = this\n const { options, schema } = fileUpload\n\n const files = this.getFilesFromState(state)\n\n // Reset the upload in state\n let upload: UploadInitiateResponse | undefined\n\n // Don't initiate anymore after minimum of `schema.max` or MAX_UPLOADS\n const max = Math.min(schema.max ?? MAX_UPLOADS, MAX_UPLOADS)\n\n if (files.length < max) {\n const outputEmail =\n this.model.def.outputEmail ?? 'defraforms@defra.gov.uk'\n\n const newUpload = await initiateUpload(href, outputEmail, options.accept)\n\n if (newUpload === undefined) {\n throw Boom.badRequest('Unexpected empty response from initiateUpload')\n }\n\n upload = newUpload\n }\n\n return this.mergeState(request, state, {\n upload: { [path]: { files, upload } }\n })\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"FileUploadPageController.js","names":["ComponentType","Boom","wait","tempItemSchema","getCacheService","getError","getExponentialBackoffDelay","QuestionPageController","getProxyUrlForLocalDevelopment","getUploadStatus","initiateUpload","FileStatus","UploadStatus","MAX_UPLOADS","CDP_UPLOAD_TIMEOUT_MS","prepareStatus","status","file","form","isPending","fileStatus","pending","errorMessage","prepareFileState","fileState","FileUploadPageController","fileUpload","fileDeleteViewName","constructor","model","pageDef","collection","fileUploads","fields","filter","field","type","FileUploadField","at","length","badImplementation","path","indexOf","name","viewName","getFormDataFromState","request","state","payload","files","getFilesFromState","undefined","getState","refreshUpload","uploadState","upload","getUploadFromState","makeGetItemDeleteRouteHandler","context","h","viewModel","params","fileToRemove","find","uploadId","itemId","notFound","filename","view","backLink","getBackLink","pageTitle","itemTitle","confirmation","text","buttonConfirm","buttonCancel","makePostItemDeleteRouteHandler","confirm","getFormParams","checkRemovedFiles","proceed","getErrors","details","errors","forEach","error","isUploadError","isUploadRootError","push","value","href","getViewModel","components","formComponent","id","index","proxyUrl","uploadUrl","formAction","componentsBefore","slice","checkUploadStatus","depth","initiateAndStoreNewUpload","statusResponse","badRequest","uploadStatus","initiated","err","Error","logger","gatewayTimeout","toFixed","delay","info","validationResult","validate","stripUnknown","complete","unshift","mergeState","cacheService","server","setFlash","filesUpdated","options","schema","max","Math","min","outputEmail","def","newUpload","accept"],"sources":["../../../../../src/server/plugins/engine/pageControllers/FileUploadPageController.ts"],"sourcesContent":["import { ComponentType, type PageFileUpload } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { wait } from '@hapi/hoek'\nimport { type ValidationErrorItem } from 'joi'\n\nimport {\n tempItemSchema,\n type FileUploadField\n} from '~/src/server/plugins/engine/components/FileUploadField.js'\nimport {\n getCacheService,\n getError,\n getExponentialBackoffDelay\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport { getProxyUrlForLocalDevelopment } from '~/src/server/plugins/engine/pageControllers/helpers/index.js'\nimport {\n getUploadStatus,\n initiateUpload\n} from '~/src/server/plugins/engine/services/uploadService.js'\nimport {\n FileStatus,\n UploadStatus,\n type AnyFormRequest,\n type FeaturedFormPageViewModel,\n type FileState,\n type FormContext,\n type FormContextRequest,\n type FormSubmissionError,\n type FormSubmissionState,\n type ItemDeletePageViewModel,\n type UploadInitiateResponse,\n type UploadStatusFileResponse\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\n\nconst MAX_UPLOADS = 25\nconst CDP_UPLOAD_TIMEOUT_MS = 60000 // 1 minute\n\nexport function prepareStatus(status: UploadStatusFileResponse) {\n const file = status.form.file\n const isPending = file.fileStatus === FileStatus.pending\n\n if (!file.errorMessage && isPending) {\n file.errorMessage = 'The selected file has not fully uploaded'\n }\n\n return status\n}\n\nfunction prepareFileState(fileState: FileState) {\n prepareStatus(fileState.status)\n\n return fileState\n}\n\nexport class FileUploadPageController extends QuestionPageController {\n declare pageDef: PageFileUpload\n\n fileUpload: FileUploadField\n fileDeleteViewName = 'item-delete'\n\n constructor(model: FormModel, pageDef: PageFileUpload) {\n super(model, pageDef)\n\n const { collection } = this\n\n // Get the file upload fields from the collection\n const fileUploads = collection.fields.filter(\n (field): field is FileUploadField =>\n field.type === ComponentType.FileUploadField\n )\n\n const fileUpload = fileUploads.at(0)\n\n // Assert we have exactly 1 file upload component\n if (!fileUpload || fileUploads.length > 1) {\n throw Boom.badImplementation(\n `Expected 1 FileUploadFieldComponent in FileUploadPageController '${pageDef.path}'`\n )\n }\n\n // Assert the file upload component is the first form component\n if (collection.fields.indexOf(fileUpload) !== 0) {\n throw Boom.badImplementation(\n `Expected '${fileUpload.name}' to be the first form component in FileUploadPageController '${pageDef.path}'`\n )\n }\n\n // Assign the file upload component to the controller\n this.fileUpload = fileUpload\n this.viewName = 'file-upload'\n }\n\n getFormDataFromState(\n request: FormContextRequest | undefined,\n state: FormSubmissionState\n ) {\n const { fileUpload } = this\n\n const payload = super.getFormDataFromState(request, state)\n const files = this.getFilesFromState(state)\n\n // Append the files to the payload\n payload[fileUpload.name] = files.length ? files : undefined\n\n return payload\n }\n\n async getState(request: AnyFormRequest) {\n const { fileUpload } = this\n\n // Get the actual state\n const state = await super.getState(request)\n const files = this.getFilesFromState(state)\n\n // Overwrite the files with those in the upload state\n state[fileUpload.name] = files\n\n return this.refreshUpload(request, state)\n }\n\n /**\n * Get the uploaded files from state.\n */\n getFilesFromState(state: FormSubmissionState) {\n const { path } = this\n\n const uploadState = state.upload?.[path]\n return uploadState?.files ?? []\n }\n\n /**\n * Get the initiated upload from state.\n */\n getUploadFromState(state: FormSubmissionState) {\n const { path } = this\n\n const uploadState = state.upload?.[path]\n return uploadState?.upload\n }\n\n makeGetItemDeleteRouteHandler() {\n return (\n request: FormRequest,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { viewModel } = this\n const { params } = request\n const { state } = context\n\n const files = this.getFilesFromState(state)\n\n const fileToRemove = files.find(\n ({ uploadId }) => uploadId === params.itemId\n )\n\n if (!fileToRemove) {\n throw Boom.notFound('File to delete not found')\n }\n\n const { filename } = fileToRemove.status.form.file\n\n return h.view(this.fileDeleteViewName, {\n ...viewModel,\n context,\n backLink: this.getBackLink(request, context),\n pageTitle: `Are you sure you want to remove this file?`,\n itemTitle: filename,\n confirmation: { text: 'You cannot recover removed files.' },\n buttonConfirm: { text: 'Remove file' },\n buttonCancel: { text: 'Cancel' }\n } satisfies ItemDeletePageViewModel)\n }\n }\n\n makePostItemDeleteRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { path } = this\n const { state } = context\n\n const { confirm } = this.getFormParams(request)\n\n // Check for any removed files in the POST payload\n if (confirm) {\n await this.checkRemovedFiles(request, state)\n return this.proceed(request, h, path)\n }\n\n return this.proceed(request, h)\n }\n }\n\n getErrors(details?: ValidationErrorItem[]) {\n const { fileUpload } = this\n\n if (details) {\n const errors: FormSubmissionError[] = []\n\n details.forEach((error) => {\n const isUploadError = error.path[0] === fileUpload.name\n const isUploadRootError = isUploadError && error.path.length === 1\n\n if (!isUploadError || isUploadRootError) {\n // The error is for the root of the upload or another\n // field on the page so defer to the getError helper\n errors.push(getError(error))\n } else {\n const { context, path, type } = error\n\n if (type === 'object.unknown' && path.at(-1) === 'errorMessage') {\n const value = context?.value as string | undefined\n\n if (value) {\n const name = fileUpload.name\n const text = typeof value === 'string' ? value : 'Unknown error'\n const href = `#${name}`\n\n errors.push({ path, href, name, text })\n }\n }\n }\n })\n\n return errors\n }\n }\n\n getViewModel(\n request: FormContextRequest,\n context: FormContext\n ): FeaturedFormPageViewModel {\n const { fileUpload } = this\n const { state } = context\n\n const upload = this.getUploadFromState(state)\n\n const viewModel = super.getViewModel(request, context)\n const { components } = viewModel\n\n // Featured form component\n const [formComponent] = components.filter(\n ({ model }) => model.id === fileUpload.name\n )\n\n const index = components.indexOf(formComponent)\n\n const proxyUrl = getProxyUrlForLocalDevelopment(upload?.uploadUrl)\n\n return {\n ...viewModel,\n formAction: upload?.uploadUrl,\n uploadId: upload?.uploadId,\n formComponent,\n\n // Split out components before/after\n componentsBefore: components.slice(0, index),\n components: components.slice(index),\n proxyUrl\n }\n }\n\n /**\n * Refreshes the CDP upload and files in the\n * state and checks for any removed files.\n *\n * If an upload exists and hasn't been consumed\n * it gets re-used, otherwise we initiate a new one.\n * @param request - the hapi request\n * @param state - the form state\n */\n private async refreshUpload(\n request: AnyFormRequest,\n state: FormSubmissionState\n ) {\n state = await this.checkUploadStatus(request, state)\n\n return state\n }\n\n /**\n * If an upload exists and hasn't been consumed\n * it gets re-used, otherwise a new one is initiated.\n * @param request - the hapi request\n * @param state - the form state\n * @param depth - the number of retries so far\n */\n private async checkUploadStatus(\n request: AnyFormRequest,\n state: FormSubmissionState,\n depth = 1\n ): Promise<FormSubmissionState> {\n const upload = this.getUploadFromState(state)\n const files = this.getFilesFromState(state)\n\n // If no upload exists, initiate a new one.\n if (!upload?.uploadId) {\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n const uploadId = upload.uploadId\n const statusResponse = await getUploadStatus(uploadId)\n if (!statusResponse) {\n throw Boom.badRequest(\n `Unexpected empty response from getUploadStatus for ${uploadId}`\n )\n }\n\n // Re-use the upload if it is still in the \"initiated\" state.\n if (statusResponse.uploadStatus === UploadStatus.initiated) {\n return state\n }\n\n if (statusResponse.uploadStatus === UploadStatus.pending) {\n // Using exponential backoff delays:\n // Depth 1: 2000ms, Depth 2: 4000ms, Depth 3: 8000ms, Depth 4: 16000ms, Depth 5+: 30000ms (capped)\n // A depth of 5 (or more) implies cumulative delays roughly reaching 55 seconds.\n if (depth >= 5) {\n const err = new Error(\n `Exceeded cumulative retry delay for ${uploadId} (depth: ${depth}). Re-initiating a new upload.`\n )\n request.logger.error(\n err,\n `[uploadTimeout] Exceeded cumulative retry delay for uploadId: ${uploadId} at depth: ${depth} - re-initiating new upload`\n )\n await this.initiateAndStoreNewUpload(request, state)\n throw Boom.gatewayTimeout(\n `Timed out waiting for ${uploadId} after cumulative retries exceeding ${((CDP_UPLOAD_TIMEOUT_MS - 5000) / 1000).toFixed(0)} seconds`\n )\n }\n const delay = getExponentialBackoffDelay(depth)\n request.logger.info(\n `[uploadRetry] Waiting ${delay / 1000} seconds for uploadId: ${uploadId} to complete (retry depth: ${depth})`\n )\n await wait(delay)\n return this.checkUploadStatus(request, state, depth + 1)\n }\n\n // Only add to files state if the file validates.\n // This secures against html tampering of the file input\n // by adding a 'multiple' attribute or it being\n // changed to a simple text field or similar.\n const validationResult = tempItemSchema.validate(\n { uploadId, status: statusResponse },\n { stripUnknown: true }\n )\n const error = validationResult.error\n const fileState = validationResult.value as FileState\n\n if (error) {\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n const file = fileState.status.form.file\n if (file.fileStatus === FileStatus.complete) {\n files.unshift(prepareFileState(fileState))\n await this.mergeState(request, state, {\n upload: { [this.path]: { files, upload } }\n })\n } else {\n // Flash the error message.\n const { fileUpload } = this\n const cacheService = getCacheService(request.server)\n const name = fileUpload.name\n const text = file.errorMessage ?? 'Unknown error'\n const errors: FormSubmissionError[] = [\n { path: [name], href: `#${name}`, name, text }\n ]\n cacheService.setFlash(request, { errors })\n }\n\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n /**\n * Checks the payload for a file getting removed\n * and removes it from the upload files if found\n * @param request - the hapi request\n * @param state - the form state\n * @returns updated state if any files have been removed\n */\n private async checkRemovedFiles(\n request: FormRequestPayload,\n state: FormSubmissionState\n ) {\n const { path } = this\n const { params } = request\n\n const upload = this.getUploadFromState(state)\n const files = this.getFilesFromState(state)\n\n const filesUpdated = files.filter(\n ({ uploadId }) => uploadId !== params.itemId\n )\n\n if (filesUpdated.length === files.length) {\n return\n }\n\n await this.mergeState(request, state, {\n upload: { [path]: { files: filesUpdated, upload } }\n })\n }\n\n /**\n * Initiates a CDP file upload and stores in the upload state\n * @param request - the hapi request\n * @param state - the form state\n */\n private async initiateAndStoreNewUpload(\n request: AnyFormRequest,\n state: FormSubmissionState\n ) {\n const { fileUpload, href, path } = this\n const { options, schema } = fileUpload\n\n const files = this.getFilesFromState(state)\n\n // Reset the upload in state\n let upload: UploadInitiateResponse | undefined\n\n // Don't initiate anymore after minimum of `schema.max` or MAX_UPLOADS\n const max = Math.min(schema.max ?? MAX_UPLOADS, MAX_UPLOADS)\n\n if (files.length < max) {\n const outputEmail =\n this.model.def.outputEmail ?? 'defraforms@defra.gov.uk'\n\n const newUpload = await initiateUpload(href, outputEmail, options.accept)\n\n if (newUpload === undefined) {\n throw Boom.badRequest('Unexpected empty response from initiateUpload')\n }\n\n upload = newUpload\n }\n\n return this.mergeState(request, state, {\n upload: { [path]: { files, upload } }\n })\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -13,9 +13,8 @@ export async function getHandler(request, h) {
|
|
|
13
13
|
}).code(400);
|
|
14
14
|
}
|
|
15
15
|
return h.response(status);
|
|
16
|
-
} catch (
|
|
17
|
-
|
|
18
|
-
request.logger.error(errMsg, `[uploadStatusFailed] Upload status check failed for uploadId: ${uploadId} - ${errMsg}`);
|
|
16
|
+
} catch (err) {
|
|
17
|
+
request.logger.error(err, `[uploadStatusFailed] Upload status check failed for uploadId: ${uploadId} - ${getErrorMessage(err)}`);
|
|
19
18
|
return h.response({
|
|
20
19
|
error: 'Status check error'
|
|
21
20
|
}).code(500);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-upload.js","names":["getErrorMessage","Joi","getUploadStatus","getHandler","request","h","uploadId","params","status","response","error","code","
|
|
1
|
+
{"version":3,"file":"file-upload.js","names":["getErrorMessage","Joi","getUploadStatus","getHandler","request","h","uploadId","params","status","response","error","code","err","logger","getRoutes","method","path","handler","options","plugins","crumb","validate","object","keys","string","guid","required"],"sources":["../../../../../src/server/plugins/engine/routes/file-upload.ts"],"sourcesContent":["import { getErrorMessage } from '@defra/forms-model'\nimport { type ResponseToolkit, type ServerRoute } from '@hapi/hapi'\nimport Joi from 'joi'\n\nimport { getUploadStatus } from '~/src/server/plugins/engine/services/uploadService.js'\nimport {\n type FormRequest,\n type FormRequestRefs\n} from '~/src/server/routes/types.js'\n\nexport async function getHandler(\n request: FormRequest,\n h: Pick<ResponseToolkit, 'response'>\n) {\n const { uploadId } = request.params as unknown as {\n uploadId: string\n }\n try {\n const status = await getUploadStatus(uploadId)\n\n if (!status) {\n return h.response({ error: 'Status check failed' }).code(400)\n }\n\n return h.response(status)\n } catch (err) {\n request.logger.error(\n err,\n `[uploadStatusFailed] Upload status check failed for uploadId: ${uploadId} - ${getErrorMessage(err)}`\n )\n return h.response({ error: 'Status check error' }).code(500)\n }\n}\n\nexport function getRoutes(): ServerRoute<FormRequestRefs>[] {\n return [\n {\n method: 'get',\n path: '/upload-status/{uploadId}',\n handler: getHandler,\n options: {\n plugins: {\n crumb: false\n },\n validate: {\n params: Joi.object().keys({\n uploadId: Joi.string().guid().required()\n })\n }\n }\n }\n ]\n}\n"],"mappings":"AAAA,SAASA,eAAe,QAAQ,oBAAoB;AAEpD,OAAOC,GAAG,MAAM,KAAK;AAErB,SAASC,eAAe;AAMxB,OAAO,eAAeC,UAAUA,CAC9BC,OAAoB,EACpBC,CAAoC,EACpC;EACA,MAAM;IAAEC;EAAS,CAAC,GAAGF,OAAO,CAACG,MAE5B;EACD,IAAI;IACF,MAAMC,MAAM,GAAG,MAAMN,eAAe,CAACI,QAAQ,CAAC;IAE9C,IAAI,CAACE,MAAM,EAAE;MACX,OAAOH,CAAC,CAACI,QAAQ,CAAC;QAAEC,KAAK,EAAE;MAAsB,CAAC,CAAC,CAACC,IAAI,CAAC,GAAG,CAAC;IAC/D;IAEA,OAAON,CAAC,CAACI,QAAQ,CAACD,MAAM,CAAC;EAC3B,CAAC,CAAC,OAAOI,GAAG,EAAE;IACZR,OAAO,CAACS,MAAM,CAACH,KAAK,CAClBE,GAAG,EACH,iEAAiEN,QAAQ,MAAMN,eAAe,CAACY,GAAG,CAAC,EACrG,CAAC;IACD,OAAOP,CAAC,CAACI,QAAQ,CAAC;MAAEC,KAAK,EAAE;IAAqB,CAAC,CAAC,CAACC,IAAI,CAAC,GAAG,CAAC;EAC9D;AACF;AAEA,OAAO,SAASG,SAASA,CAAA,EAAmC;EAC1D,OAAO,CACL;IACEC,MAAM,EAAE,KAAK;IACbC,IAAI,EAAE,2BAA2B;IACjCC,OAAO,EAAEd,UAAU;IACnBe,OAAO,EAAE;MACPC,OAAO,EAAE;QACPC,KAAK,EAAE;MACT,CAAC;MACDC,QAAQ,EAAE;QACRd,MAAM,EAAEN,GAAG,CAACqB,MAAM,CAAC,CAAC,CAACC,IAAI,CAAC;UACxBjB,QAAQ,EAAEL,GAAG,CAACuB,MAAM,CAAC,CAAC,CAACC,IAAI,CAAC,CAAC,CAACC,QAAQ,CAAC;QACzC,CAAC;MACH;IACF;EACF,CAAC,CACF;AACH","ignoreList":[]}
|
|
@@ -45,8 +45,7 @@ export async function submit(context, request, model, emailAddress, items, submi
|
|
|
45
45
|
});
|
|
46
46
|
request.logger.info(logTags, 'Email sent successfully');
|
|
47
47
|
} catch (err) {
|
|
48
|
-
|
|
49
|
-
request.logger.error(errMsg, `[emailSendFailed] Error sending notification email - templateId: ${templateId} - recipient: ${emailAddress} - ${errMsg}`);
|
|
48
|
+
request.logger.error(err, `[emailSendFailed] Error sending notification email - templateId: ${templateId} - recipient: ${emailAddress} - ${getErrorMessage(err)}`);
|
|
50
49
|
throw err;
|
|
51
50
|
}
|
|
52
51
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notifyService.js","names":["getErrorMessage","config","escapeMarkdown","checkFormStatus","getFormatter","sendNotification","templateId","get","submit","context","request","model","emailAddress","items","submitResponse","formMetadata","Promise","resolve","logTags","formStatus","params","logger","info","formName","name","subject","isPreview","outputAudience","def","output","audience","outputVersion","version","outputFormatter","body","Buffer","from","toString","personalisation","err","
|
|
1
|
+
{"version":3,"file":"notifyService.js","names":["getErrorMessage","config","escapeMarkdown","checkFormStatus","getFormatter","sendNotification","templateId","get","submit","context","request","model","emailAddress","items","submitResponse","formMetadata","Promise","resolve","logTags","formStatus","params","logger","info","formName","name","subject","isPreview","outputAudience","def","output","audience","outputVersion","version","outputFormatter","body","Buffer","from","toString","personalisation","err","error"],"sources":["../../../../../src/server/plugins/engine/services/notifyService.ts"],"sourcesContent":["import {\n getErrorMessage,\n type FormMetadata,\n type SubmitResponsePayload\n} from '@defra/forms-model'\n\nimport { config } from '~/src/config/index.js'\nimport { escapeMarkdown } from '~/src/server/plugins/engine/components/helpers/index.js'\nimport { checkFormStatus } from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItem } from '~/src/server/plugins/engine/models/types.js'\nimport { getFormatter } from '~/src/server/plugins/engine/outputFormatters/index.js'\nimport { type FormContext } from '~/src/server/plugins/engine/types.js'\nimport { type FormRequestPayload } from '~/src/server/routes/types.js'\nimport { sendNotification } from '~/src/server/utils/notify.js'\n\nconst templateId = config.get('notifyTemplateId')\n\n/**\n * Optional GOV.UK Notify service for consumers who want email notifications\n * Can be disabled by not providing notifyTemplateId in config\n * Can be overridden by providing a custom outputService in the services config\n */\nexport async function submit(\n context: FormContext,\n request: FormRequestPayload,\n model: FormModel,\n emailAddress: string,\n items: DetailItem[],\n submitResponse: SubmitResponsePayload,\n formMetadata?: FormMetadata\n) {\n if (!templateId) {\n return Promise.resolve()\n }\n\n const logTags = ['submit', 'email']\n const formStatus = checkFormStatus(request.params)\n\n // Get submission email personalisation\n request.logger.info(logTags, 'Getting personalisation data')\n\n const formName = escapeMarkdown(model.name)\n const subject = formStatus.isPreview\n ? `TEST FORM SUBMISSION: ${formName}`\n : `Form submission: ${formName}`\n\n const outputAudience = model.def.output?.audience ?? 'human'\n const outputVersion = model.def.output?.version ?? '1'\n\n const outputFormatter = getFormatter(outputAudience, outputVersion)\n let body = outputFormatter(\n context,\n items,\n model,\n submitResponse,\n formStatus,\n formMetadata\n )\n\n // GOV.UK Notify transforms quotes into curly quotes, so we can't just send the raw payload\n // This is logic specific to Notify, so we include the logic here rather than in the formatter\n if (outputAudience === 'machine') {\n body = Buffer.from(body).toString('base64')\n }\n\n request.logger.info(logTags, 'Sending email')\n\n try {\n // Send submission email\n await sendNotification({\n templateId,\n emailAddress,\n personalisation: {\n subject,\n body\n }\n })\n\n request.logger.info(logTags, 'Email sent successfully')\n } catch (err) {\n request.logger.error(\n err,\n `[emailSendFailed] Error sending notification email - templateId: ${templateId} - recipient: ${emailAddress} - ${getErrorMessage(err)}`\n )\n\n throw err\n }\n}\n"],"mappings":"AAAA,SACEA,eAAe,QAGV,oBAAoB;AAE3B,SAASC,MAAM;AACf,SAASC,cAAc;AACvB,SAASC,eAAe;AAGxB,SAASC,YAAY;AAGrB,SAASC,gBAAgB;AAEzB,MAAMC,UAAU,GAAGL,MAAM,CAACM,GAAG,CAAC,kBAAkB,CAAC;;AAEjD;AACA;AACA;AACA;AACA;AACA,OAAO,eAAeC,MAAMA,CAC1BC,OAAoB,EACpBC,OAA2B,EAC3BC,KAAgB,EAChBC,YAAoB,EACpBC,KAAmB,EACnBC,cAAqC,EACrCC,YAA2B,EAC3B;EACA,IAAI,CAACT,UAAU,EAAE;IACf,OAAOU,OAAO,CAACC,OAAO,CAAC,CAAC;EAC1B;EAEA,MAAMC,OAAO,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC;EACnC,MAAMC,UAAU,GAAGhB,eAAe,CAACO,OAAO,CAACU,MAAM,CAAC;;EAElD;EACAV,OAAO,CAACW,MAAM,CAACC,IAAI,CAACJ,OAAO,EAAE,8BAA8B,CAAC;EAE5D,MAAMK,QAAQ,GAAGrB,cAAc,CAACS,KAAK,CAACa,IAAI,CAAC;EAC3C,MAAMC,OAAO,GAAGN,UAAU,CAACO,SAAS,GAChC,yBAAyBH,QAAQ,EAAE,GACnC,oBAAoBA,QAAQ,EAAE;EAElC,MAAMI,cAAc,GAAGhB,KAAK,CAACiB,GAAG,CAACC,MAAM,EAAEC,QAAQ,IAAI,OAAO;EAC5D,MAAMC,aAAa,GAAGpB,KAAK,CAACiB,GAAG,CAACC,MAAM,EAAEG,OAAO,IAAI,GAAG;EAEtD,MAAMC,eAAe,GAAG7B,YAAY,CAACuB,cAAc,EAAEI,aAAa,CAAC;EACnE,IAAIG,IAAI,GAAGD,eAAe,CACxBxB,OAAO,EACPI,KAAK,EACLF,KAAK,EACLG,cAAc,EACdK,UAAU,EACVJ,YACF,CAAC;;EAED;EACA;EACA,IAAIY,cAAc,KAAK,SAAS,EAAE;IAChCO,IAAI,GAAGC,MAAM,CAACC,IAAI,CAACF,IAAI,CAAC,CAACG,QAAQ,CAAC,QAAQ,CAAC;EAC7C;EAEA3B,OAAO,CAACW,MAAM,CAACC,IAAI,CAACJ,OAAO,EAAE,eAAe,CAAC;EAE7C,IAAI;IACF;IACA,MAAMb,gBAAgB,CAAC;MACrBC,UAAU;MACVM,YAAY;MACZ0B,eAAe,EAAE;QACfb,OAAO;QACPS;MACF;IACF,CAAC,CAAC;IAEFxB,OAAO,CAACW,MAAM,CAACC,IAAI,CAACJ,OAAO,EAAE,yBAAyB,CAAC;EACzD,CAAC,CAAC,OAAOqB,GAAG,EAAE;IACZ7B,OAAO,CAACW,MAAM,CAACmB,KAAK,CAClBD,GAAG,EACH,oEAAoEjC,UAAU,iBAAiBM,YAAY,MAAMZ,eAAe,CAACuC,GAAG,CAAC,EACvI,CAAC;IAED,MAAMA,GAAG;EACX;AACF","ignoreList":[]}
|
|
@@ -11,13 +11,13 @@ export default {
|
|
|
11
11
|
// An error was raised during
|
|
12
12
|
// processing the request
|
|
13
13
|
const statusCode = response.output.statusCode;
|
|
14
|
-
const
|
|
14
|
+
const err = {
|
|
15
15
|
statusCode,
|
|
16
16
|
message: response.message,
|
|
17
17
|
stack: response.stack
|
|
18
18
|
};
|
|
19
|
-
request.logger.error(
|
|
20
|
-
return h.response(
|
|
19
|
+
request.logger.error(err, `[httpError] HTTP ${statusCode} error occurred - ${response.message} - path: ${request.path} - method: ${request.method}`);
|
|
20
|
+
return h.response(err).code(statusCode);
|
|
21
21
|
}
|
|
22
22
|
return h.continue;
|
|
23
23
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errorPages.js","names":["plugin","name","register","server","ext","request","h","response","isBoom","statusCode","output","
|
|
1
|
+
{"version":3,"file":"errorPages.js","names":["plugin","name","register","server","ext","request","h","response","isBoom","statusCode","output","err","message","stack","logger","error","path","method","code","continue"],"sources":["../../../src/server/plugins/errorPages.ts"],"sourcesContent":["import {\n type Request,\n type ResponseToolkit,\n type ServerRegisterPluginObject\n} from '@hapi/hapi'\n\n/*\n * Add an `onPreResponse` listener to return error pages\n */\nexport default {\n plugin: {\n name: 'error-pages',\n register(server) {\n server.ext('onPreResponse', (request: Request, h: ResponseToolkit) => {\n const response = request.response\n\n if ('isBoom' in response && response.isBoom) {\n // An error was raised during\n // processing the request\n const statusCode = response.output.statusCode\n\n const err = {\n statusCode,\n message: response.message,\n stack: response.stack\n }\n\n request.logger.error(\n err,\n `[httpError] HTTP ${statusCode} error occurred - ${response.message} - path: ${request.path} - method: ${request.method}`\n )\n\n return h.response(err).code(statusCode)\n }\n\n return h.continue\n })\n }\n }\n} satisfies ServerRegisterPluginObject<void>\n"],"mappings":"AAMA;AACA;AACA;AACA,eAAe;EACbA,MAAM,EAAE;IACNC,IAAI,EAAE,aAAa;IACnBC,QAAQA,CAACC,MAAM,EAAE;MACfA,MAAM,CAACC,GAAG,CAAC,eAAe,EAAE,CAACC,OAAgB,EAAEC,CAAkB,KAAK;QACpE,MAAMC,QAAQ,GAAGF,OAAO,CAACE,QAAQ;QAEjC,IAAI,QAAQ,IAAIA,QAAQ,IAAIA,QAAQ,CAACC,MAAM,EAAE;UAC3C;UACA;UACA,MAAMC,UAAU,GAAGF,QAAQ,CAACG,MAAM,CAACD,UAAU;UAE7C,MAAME,GAAG,GAAG;YACVF,UAAU;YACVG,OAAO,EAAEL,QAAQ,CAACK,OAAO;YACzBC,KAAK,EAAEN,QAAQ,CAACM;UAClB,CAAC;UAEDR,OAAO,CAACS,MAAM,CAACC,KAAK,CAClBJ,GAAG,EACH,oBAAoBF,UAAU,qBAAqBF,QAAQ,CAACK,OAAO,YAAYP,OAAO,CAACW,IAAI,cAAcX,OAAO,CAACY,MAAM,EACzH,CAAC;UAED,OAAOX,CAAC,CAACC,QAAQ,CAACI,GAAG,CAAC,CAACO,IAAI,CAACT,UAAU,CAAC;QACzC;QAEA,OAAOH,CAAC,CAACa,QAAQ;MACnB,CAAC,CAAC;IACJ;EACF;AACF,CAAC","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defra/forms-engine-plugin",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.6",
|
|
4
4
|
"description": "Defra forms engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
},
|
|
71
71
|
"license": "SEE LICENSE IN LICENSE",
|
|
72
72
|
"dependencies": {
|
|
73
|
-
"@defra/forms-model": "^3.0.
|
|
73
|
+
"@defra/forms-model": "^3.0.555",
|
|
74
74
|
"@defra/hapi-tracing": "^1.26.0",
|
|
75
75
|
"@elastic/ecs-pino-format": "^1.5.0",
|
|
76
76
|
"@hapi/boom": "^10.0.1",
|
|
@@ -63,9 +63,11 @@ export function buildRedisClient() {
|
|
|
63
63
|
)
|
|
64
64
|
})
|
|
65
65
|
|
|
66
|
-
redisClient.on('error', (
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
redisClient.on('error', (err) => {
|
|
67
|
+
logger.error(
|
|
68
|
+
err,
|
|
69
|
+
`[redisConnectionError] Redis connection error - ${getErrorMessage(err)}`
|
|
70
|
+
)
|
|
69
71
|
})
|
|
70
72
|
|
|
71
73
|
return redisClient
|
|
@@ -149,10 +149,9 @@ export function encodeUrl(link?: string) {
|
|
|
149
149
|
try {
|
|
150
150
|
return new URL(link).toString() // escape the search params without breaking the ? and & reserved characters in rfc2368
|
|
151
151
|
} catch (err) {
|
|
152
|
-
const errMsg = getErrorMessage(err)
|
|
153
152
|
logger.error(
|
|
154
|
-
|
|
155
|
-
`[urlEncodingFailed] Failed to encode URL: ${link} - ${
|
|
153
|
+
err,
|
|
154
|
+
`[urlEncodingFailed] Failed to encode URL: ${link} - ${getErrorMessage(err)}`
|
|
156
155
|
)
|
|
157
156
|
throw err
|
|
158
157
|
}
|
|
@@ -363,7 +363,10 @@ export class FormModel {
|
|
|
363
363
|
// Add page to context
|
|
364
364
|
context.relevantPages.push(nextPage)
|
|
365
365
|
|
|
366
|
-
this
|
|
366
|
+
// Engine.V2 is excluded here as this will have already been done in initialiseContext()
|
|
367
|
+
if (this.engine !== Engine.V2) {
|
|
368
|
+
this.assignEvaluationState(context, nextPage)
|
|
369
|
+
}
|
|
367
370
|
|
|
368
371
|
this.assignRelevantState(context, nextPage)
|
|
369
372
|
|
|
@@ -389,14 +392,12 @@ export class FormModel {
|
|
|
389
392
|
}
|
|
390
393
|
|
|
391
394
|
private initialiseContext(context: FormContext) {
|
|
392
|
-
// For the V2 engine, we
|
|
393
|
-
//
|
|
394
|
-
//
|
|
395
|
+
// For the V2 engine, we initialise `evaluationState` for all keys.
|
|
396
|
+
// This is because the current condition evaluation library (eval-expr)
|
|
397
|
+
// will throw if an expression uses a key that is undefined.
|
|
395
398
|
if (this.engine === Engine.V2) {
|
|
396
399
|
for (const page of this.pages) {
|
|
397
|
-
|
|
398
|
-
context.evaluationState[key] = null
|
|
399
|
-
}
|
|
400
|
+
this.assignEvaluationState(context, page)
|
|
400
401
|
}
|
|
401
402
|
}
|
|
402
403
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getErrorMessage } from '@defra/forms-model'
|
|
1
2
|
import Joi from 'joi'
|
|
2
3
|
|
|
3
4
|
import { createLogger } from '~/src/server/common/helpers/logging/logger.js'
|
|
@@ -39,7 +40,8 @@ export function validatePluginOptions(options) {
|
|
|
39
40
|
|
|
40
41
|
if (result.error) {
|
|
41
42
|
logger.error(
|
|
42
|
-
|
|
43
|
+
result.error,
|
|
44
|
+
`Missing required properties in plugin options: ${getErrorMessage(result.error)}`
|
|
43
45
|
)
|
|
44
46
|
throw new Error('Invalid plugin options', result.error)
|
|
45
47
|
}
|
|
@@ -326,11 +326,11 @@ export class FileUploadPageController extends QuestionPageController {
|
|
|
326
326
|
// Depth 1: 2000ms, Depth 2: 4000ms, Depth 3: 8000ms, Depth 4: 16000ms, Depth 5+: 30000ms (capped)
|
|
327
327
|
// A depth of 5 (or more) implies cumulative delays roughly reaching 55 seconds.
|
|
328
328
|
if (depth >= 5) {
|
|
329
|
-
const
|
|
329
|
+
const err = new Error(
|
|
330
330
|
`Exceeded cumulative retry delay for ${uploadId} (depth: ${depth}). Re-initiating a new upload.`
|
|
331
331
|
)
|
|
332
332
|
request.logger.error(
|
|
333
|
-
|
|
333
|
+
err,
|
|
334
334
|
`[uploadTimeout] Exceeded cumulative retry delay for uploadId: ${uploadId} at depth: ${depth} - re-initiating new upload`
|
|
335
335
|
)
|
|
336
336
|
await this.initiateAndStoreNewUpload(request, state)
|
|
@@ -23,11 +23,10 @@ export async function getHandler(
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
return h.response(status)
|
|
26
|
-
} catch (
|
|
27
|
-
const errMsg = getErrorMessage(error)
|
|
26
|
+
} catch (err) {
|
|
28
27
|
request.logger.error(
|
|
29
|
-
|
|
30
|
-
`[uploadStatusFailed] Upload status check failed for uploadId: ${uploadId} - ${
|
|
28
|
+
err,
|
|
29
|
+
`[uploadStatusFailed] Upload status check failed for uploadId: ${uploadId} - ${getErrorMessage(err)}`
|
|
31
30
|
)
|
|
32
31
|
return h.response({ error: 'Status check error' }).code(500)
|
|
33
32
|
}
|
|
@@ -301,7 +301,7 @@ describe('notifyService', () => {
|
|
|
301
301
|
).rejects.toThrow('Notification service unavailable')
|
|
302
302
|
|
|
303
303
|
expect(mockRequest.logger.error).toHaveBeenCalledWith(
|
|
304
|
-
'Notification service unavailable',
|
|
304
|
+
new Error('Notification service unavailable'),
|
|
305
305
|
expect.stringContaining(
|
|
306
306
|
'[emailSendFailed] Error sending notification email'
|
|
307
307
|
)
|
|
@@ -79,10 +79,9 @@ export async function submit(
|
|
|
79
79
|
|
|
80
80
|
request.logger.info(logTags, 'Email sent successfully')
|
|
81
81
|
} catch (err) {
|
|
82
|
-
const errMsg = getErrorMessage(err)
|
|
83
82
|
request.logger.error(
|
|
84
|
-
|
|
85
|
-
`[emailSendFailed] Error sending notification email - templateId: ${templateId} - recipient: ${emailAddress} - ${
|
|
83
|
+
err,
|
|
84
|
+
`[emailSendFailed] Error sending notification email - templateId: ${templateId} - recipient: ${emailAddress} - ${getErrorMessage(err)}`
|
|
86
85
|
)
|
|
87
86
|
|
|
88
87
|
throw err
|
|
@@ -19,18 +19,18 @@ export default {
|
|
|
19
19
|
// processing the request
|
|
20
20
|
const statusCode = response.output.statusCode
|
|
21
21
|
|
|
22
|
-
const
|
|
22
|
+
const err = {
|
|
23
23
|
statusCode,
|
|
24
24
|
message: response.message,
|
|
25
25
|
stack: response.stack
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
request.logger.error(
|
|
29
|
-
|
|
29
|
+
err,
|
|
30
30
|
`[httpError] HTTP ${statusCode} error occurred - ${response.message} - path: ${request.path} - method: ${request.method}`
|
|
31
31
|
)
|
|
32
32
|
|
|
33
|
-
return h.response(
|
|
33
|
+
return h.response(err).code(statusCode)
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
return h.continue
|