@dwp/govuk-casa 8.16.2 → 8.16.4
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/dist/assets/css/casa-ie8.css +1 -1
- package/dist/assets/css/casa.css +1 -1
- package/dist/casa.d.ts +13 -13
- package/dist/casa.js +17 -7
- package/dist/casa.js.map +1 -1
- package/dist/lib/CasaTemplateLoader.d.ts +1 -1
- package/dist/lib/CasaTemplateLoader.js +13 -14
- package/dist/lib/CasaTemplateLoader.js.map +1 -1
- package/dist/lib/JourneyContext.d.ts +10 -4
- package/dist/lib/JourneyContext.js +57 -47
- package/dist/lib/JourneyContext.js.map +1 -1
- package/dist/lib/MutableRouter.d.ts +1 -1
- package/dist/lib/MutableRouter.js +22 -23
- package/dist/lib/MutableRouter.js.map +1 -1
- package/dist/lib/Plan.d.ts +5 -5
- package/dist/lib/Plan.js +49 -36
- package/dist/lib/Plan.js.map +1 -1
- package/dist/lib/ValidationError.d.ts +1 -1
- package/dist/lib/ValidationError.js +9 -9
- package/dist/lib/ValidationError.js.map +1 -1
- package/dist/lib/ValidatorFactory.js +4 -7
- package/dist/lib/ValidatorFactory.js.map +1 -1
- package/dist/lib/configuration-ingestor.d.ts +75 -14
- package/dist/lib/configuration-ingestor.js +156 -64
- package/dist/lib/configuration-ingestor.js.map +1 -1
- package/dist/lib/configure.js +11 -10
- package/dist/lib/configure.js.map +1 -1
- package/dist/lib/constants.js +8 -8
- package/dist/lib/context-id-generators.d.ts +1 -1
- package/dist/lib/context-id-generators.js +7 -4
- package/dist/lib/context-id-generators.js.map +1 -1
- package/dist/lib/end-session.js +2 -2
- package/dist/lib/field.d.ts +6 -6
- package/dist/lib/field.js +15 -21
- package/dist/lib/field.js.map +1 -1
- package/dist/lib/index.d.ts +13 -13
- package/dist/lib/index.js +17 -7
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/logger.js +7 -7
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/mount.js +3 -3
- package/dist/lib/mount.js.map +1 -1
- package/dist/lib/nunjucks-filters.d.ts +5 -1
- package/dist/lib/nunjucks-filters.js +37 -23
- package/dist/lib/nunjucks-filters.js.map +1 -1
- package/dist/lib/nunjucks.d.ts +2 -2
- package/dist/lib/nunjucks.js +6 -7
- package/dist/lib/nunjucks.js.map +1 -1
- package/dist/lib/utils.js +52 -42
- package/dist/lib/utils.js.map +1 -1
- package/dist/lib/validators/dateObject.d.ts +3 -3
- package/dist/lib/validators/dateObject.js +44 -37
- package/dist/lib/validators/dateObject.js.map +1 -1
- package/dist/lib/validators/email.d.ts +2 -2
- package/dist/lib/validators/email.js +4 -5
- package/dist/lib/validators/email.js.map +1 -1
- package/dist/lib/validators/inArray.d.ts +2 -2
- package/dist/lib/validators/inArray.js +5 -6
- package/dist/lib/validators/inArray.js.map +1 -1
- package/dist/lib/validators/index.d.ts +10 -10
- package/dist/lib/validators/index.js.map +1 -1
- package/dist/lib/validators/nino.d.ts +2 -2
- package/dist/lib/validators/nino.js +10 -7
- package/dist/lib/validators/nino.js.map +1 -1
- package/dist/lib/validators/postalAddressObject.d.ts +2 -2
- package/dist/lib/validators/postalAddressObject.js +52 -39
- package/dist/lib/validators/postalAddressObject.js.map +1 -1
- package/dist/lib/validators/range.d.ts +2 -2
- package/dist/lib/validators/range.js +6 -7
- package/dist/lib/validators/range.js.map +1 -1
- package/dist/lib/validators/regex.d.ts +2 -2
- package/dist/lib/validators/regex.js +4 -5
- package/dist/lib/validators/regex.js.map +1 -1
- package/dist/lib/validators/required.d.ts +2 -2
- package/dist/lib/validators/required.js +6 -9
- package/dist/lib/validators/required.js.map +1 -1
- package/dist/lib/validators/strlen.d.ts +2 -2
- package/dist/lib/validators/strlen.js +8 -9
- package/dist/lib/validators/strlen.js.map +1 -1
- package/dist/lib/validators/wordCount.d.ts +2 -2
- package/dist/lib/validators/wordCount.js +10 -9
- package/dist/lib/validators/wordCount.js.map +1 -1
- package/dist/lib/waypoint-url.d.ts +4 -4
- package/dist/lib/waypoint-url.js +23 -23
- package/dist/lib/waypoint-url.js.map +1 -1
- package/dist/middleware/body-parser.d.ts +27 -5
- package/dist/middleware/body-parser.js +37 -6
- package/dist/middleware/body-parser.js.map +1 -1
- package/dist/middleware/csrf.d.ts +3 -0
- package/dist/middleware/csrf.js +3 -0
- package/dist/middleware/csrf.js.map +1 -1
- package/dist/middleware/data.d.ts +22 -5
- package/dist/middleware/data.js +37 -7
- package/dist/middleware/data.js.map +1 -1
- package/dist/middleware/gather-fields.d.ts +1 -1
- package/dist/middleware/gather-fields.js +4 -3
- package/dist/middleware/gather-fields.js.map +1 -1
- package/dist/middleware/i18n.d.ts +11 -2
- package/dist/middleware/i18n.js +26 -17
- package/dist/middleware/i18n.js.map +1 -1
- package/dist/middleware/post.d.ts +3 -1
- package/dist/middleware/post.js +35 -18
- package/dist/middleware/post.js.map +1 -1
- package/dist/middleware/pre.d.ts +1 -1
- package/dist/middleware/pre.js +43 -21
- package/dist/middleware/pre.js.map +1 -1
- package/dist/middleware/progress-journey.d.ts +1 -1
- package/dist/middleware/progress-journey.js +5 -5
- package/dist/middleware/progress-journey.js.map +1 -1
- package/dist/middleware/sanitise-fields.d.ts +2 -2
- package/dist/middleware/sanitise-fields.js +13 -11
- package/dist/middleware/sanitise-fields.js.map +1 -1
- package/dist/middleware/serve-first-waypoint.d.ts +1 -1
- package/dist/middleware/serve-first-waypoint.js +6 -4
- package/dist/middleware/serve-first-waypoint.js.map +1 -1
- package/dist/middleware/session.d.ts +27 -8
- package/dist/middleware/session.js +53 -25
- package/dist/middleware/session.js.map +1 -1
- package/dist/middleware/skip-waypoint.d.ts +1 -1
- package/dist/middleware/skip-waypoint.js +3 -3
- package/dist/middleware/skip-waypoint.js.map +1 -1
- package/dist/middleware/steer-journey.d.ts +1 -1
- package/dist/middleware/steer-journey.js +15 -13
- package/dist/middleware/steer-journey.js.map +1 -1
- package/dist/middleware/strip-proxy-path.d.ts +1 -1
- package/dist/middleware/strip-proxy-path.js +3 -3
- package/dist/middleware/strip-proxy-path.js.map +1 -1
- package/dist/middleware/validate-fields.d.ts +2 -2
- package/dist/middleware/validate-fields.js +2 -5
- package/dist/middleware/validate-fields.js.map +1 -1
- package/dist/routes/ancillary.d.ts +2 -2
- package/dist/routes/ancillary.js +3 -3
- package/dist/routes/ancillary.js.map +1 -1
- package/dist/routes/journey.d.ts +1 -1
- package/dist/routes/journey.js +85 -31
- package/dist/routes/journey.js.map +1 -1
- package/dist/routes/static.d.ts +13 -4
- package/dist/routes/static.js +21 -19
- package/dist/routes/static.js.map +1 -1
- package/package.json +33 -36
- package/src/casa.js +13 -13
- package/src/lib/CasaTemplateLoader.js +21 -17
- package/src/lib/JourneyContext.js +118 -79
- package/src/lib/MutableRouter.js +30 -26
- package/src/lib/Plan.js +109 -62
- package/src/lib/ValidationError.js +13 -10
- package/src/lib/ValidatorFactory.js +7 -8
- package/src/lib/configuration-ingestor.js +200 -74
- package/src/lib/configure.js +31 -30
- package/src/lib/constants.js +8 -8
- package/src/lib/context-id-generators.js +39 -38
- package/src/lib/end-session.js +3 -3
- package/src/lib/field.js +48 -32
- package/src/lib/index.js +12 -12
- package/src/lib/logger.js +9 -9
- package/src/lib/mount.js +68 -73
- package/src/lib/nunjucks-filters.js +57 -44
- package/src/lib/nunjucks.js +20 -16
- package/src/lib/utils.js +69 -44
- package/src/lib/validators/dateObject.js +57 -48
- package/src/lib/validators/email.js +8 -9
- package/src/lib/validators/inArray.js +8 -9
- package/src/lib/validators/index.js +11 -11
- package/src/lib/validators/nino.js +25 -12
- package/src/lib/validators/postalAddressObject.js +73 -55
- package/src/lib/validators/range.js +9 -11
- package/src/lib/validators/regex.js +7 -8
- package/src/lib/validators/required.js +13 -14
- package/src/lib/validators/strlen.js +11 -12
- package/src/lib/validators/wordCount.js +17 -12
- package/src/lib/waypoint-url.js +48 -33
- package/src/middleware/body-parser.js +44 -10
- package/src/middleware/csrf.js +4 -1
- package/src/middleware/data.js +62 -25
- package/src/middleware/gather-fields.js +8 -8
- package/src/middleware/i18n.js +49 -39
- package/src/middleware/post.js +47 -21
- package/src/middleware/pre.js +59 -35
- package/src/middleware/progress-journey.js +32 -18
- package/src/middleware/sanitise-fields.js +43 -20
- package/src/middleware/serve-first-waypoint.js +12 -10
- package/src/middleware/session.js +97 -65
- package/src/middleware/skip-waypoint.js +7 -9
- package/src/middleware/steer-journey.js +39 -27
- package/src/middleware/strip-proxy-path.js +8 -7
- package/src/middleware/validate-fields.js +5 -12
- package/src/routes/ancillary.js +4 -6
- package/src/routes/journey.js +158 -78
- package/src/routes/static.js +64 -26
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import merge from
|
|
2
|
-
import { DateTime } from
|
|
3
|
-
import nunjucks from
|
|
1
|
+
import merge from "deepmerge";
|
|
2
|
+
import { DateTime } from "luxon";
|
|
3
|
+
import nunjucks from "nunjucks";
|
|
4
4
|
|
|
5
5
|
const { all: deepmergeAll } = merge;
|
|
6
6
|
|
|
@@ -9,54 +9,66 @@ const { all: deepmergeAll } = merge;
|
|
|
9
9
|
// ref: https://www.npmjs.com/package/deepmerge
|
|
10
10
|
|
|
11
11
|
const combineMerge = (target, source, options) => {
|
|
12
|
-
const destination = target.slice()
|
|
12
|
+
const destination = target.slice();
|
|
13
13
|
|
|
14
14
|
source.forEach((item, index) => {
|
|
15
15
|
// ESLint disabled as `index` is only an integer
|
|
16
16
|
/* eslint-disable security/detect-object-injection */
|
|
17
|
-
if (typeof destination[index] ===
|
|
18
|
-
destination[index] = options.cloneUnlessOtherwiseSpecified(item, options)
|
|
17
|
+
if (typeof destination[index] === "undefined") {
|
|
18
|
+
destination[index] = options.cloneUnlessOtherwiseSpecified(item, options);
|
|
19
19
|
} else if (options.isMergeableObject(item)) {
|
|
20
|
-
destination[index] = merge(target[index], item, options)
|
|
20
|
+
destination[index] = merge(target[index], item, options);
|
|
21
21
|
} else if (target.indexOf(item) === -1) {
|
|
22
|
-
destination.push(item)
|
|
22
|
+
destination.push(item);
|
|
23
23
|
}
|
|
24
24
|
/* eslint-enable security/detect-object-injection */
|
|
25
|
-
})
|
|
26
|
-
return destination
|
|
27
|
-
}
|
|
25
|
+
});
|
|
26
|
+
return destination;
|
|
27
|
+
};
|
|
28
28
|
|
|
29
29
|
// Allows objects to be deepmerged and retain their type, without becoming [object Object]
|
|
30
30
|
// ref: https://github.com/jonschlinkert/is-plain-object/blob/master/is-plain-object.js
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* @param {any} o Value to test
|
|
34
|
+
* @returns {boolean} True if an object
|
|
35
|
+
*/
|
|
32
36
|
function isObject(o) {
|
|
33
|
-
return Object.prototype.toString.call(o) ===
|
|
37
|
+
return Object.prototype.toString.call(o) === "[object Object]";
|
|
34
38
|
}
|
|
35
39
|
|
|
40
|
+
/**
|
|
41
|
+
* @param {any} o Value to test
|
|
42
|
+
* @returns {boolean} True if a plain object or array
|
|
43
|
+
*/
|
|
36
44
|
function isPlainObjectOrArray(o) {
|
|
37
45
|
if (Array.isArray(o)) {
|
|
38
|
-
return true
|
|
46
|
+
return true;
|
|
39
47
|
}
|
|
40
48
|
if (isObject(o) === false) {
|
|
41
|
-
return false
|
|
49
|
+
return false;
|
|
42
50
|
}
|
|
43
51
|
const ctor = o.constructor;
|
|
44
52
|
if (ctor === undefined) {
|
|
45
|
-
return true
|
|
53
|
+
return true;
|
|
46
54
|
}
|
|
47
55
|
const prot = ctor.prototype;
|
|
48
56
|
if (isObject(prot) === false) {
|
|
49
|
-
return false
|
|
57
|
+
return false;
|
|
50
58
|
}
|
|
51
59
|
// eslint-disable-next-line no-prototype-builtins
|
|
52
|
-
return prot.hasOwnProperty(
|
|
60
|
+
return prot.hasOwnProperty("isPrototypeOf");
|
|
53
61
|
}
|
|
54
62
|
|
|
63
|
+
/**
|
|
64
|
+
* @param {...any} objects Objects to merge
|
|
65
|
+
* @returns {object} Merged object
|
|
66
|
+
*/
|
|
55
67
|
function mergeObjects(...objects) {
|
|
56
|
-
return deepmergeAll([
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
68
|
+
return deepmergeAll([Object.create(null), ...objects], {
|
|
69
|
+
arrayMerge: combineMerge,
|
|
70
|
+
isMergeableObject: isPlainObjectOrArray,
|
|
71
|
+
});
|
|
60
72
|
}
|
|
61
73
|
/**
|
|
62
74
|
* Determine whether a value exists in a list.
|
|
@@ -66,7 +78,7 @@ function mergeObjects(...objects) {
|
|
|
66
78
|
* @param {any} search Item to search within the `source`
|
|
67
79
|
* @returns {boolean} True if the search item was found
|
|
68
80
|
*/
|
|
69
|
-
function includes(source = [], search =
|
|
81
|
+
function includes(source = [], search = "") {
|
|
70
82
|
return source.includes(search);
|
|
71
83
|
}
|
|
72
84
|
|
|
@@ -89,21 +101,23 @@ function includes(source = [], search = '') {
|
|
|
89
101
|
* @returns {string} Formatted date
|
|
90
102
|
*/
|
|
91
103
|
function formatDateObject(date, config = {}) {
|
|
92
|
-
const { locale =
|
|
104
|
+
const { locale = "en", format = "d MMMM yyyy" } = config;
|
|
93
105
|
|
|
94
106
|
if (
|
|
95
|
-
Object.prototype.toString.call(date) ===
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
107
|
+
Object.prototype.toString.call(date) === "[object Object]" &&
|
|
108
|
+
"yyyy" in date &&
|
|
109
|
+
"mm" in date &&
|
|
110
|
+
"dd" in date
|
|
99
111
|
) {
|
|
100
112
|
return DateTime.fromObject({
|
|
101
113
|
year: Math.max(0, parseInt(date.yyyy, 10)),
|
|
102
114
|
month: Math.max(0, parseInt(date.mm, 10)),
|
|
103
115
|
day: Math.max(1, parseInt(date.dd, 10)),
|
|
104
|
-
})
|
|
116
|
+
})
|
|
117
|
+
.setLocale(locale)
|
|
118
|
+
.toFormat(format);
|
|
105
119
|
}
|
|
106
|
-
return
|
|
120
|
+
return "INVALID DATE OBJECT";
|
|
107
121
|
}
|
|
108
122
|
|
|
109
123
|
/**
|
|
@@ -119,32 +133,31 @@ function formatDateObject(date, config = {}) {
|
|
|
119
133
|
*/
|
|
120
134
|
function renderAsAttributes(attrsObject) {
|
|
121
135
|
const attrsList = [];
|
|
122
|
-
if (typeof attrsObject ===
|
|
136
|
+
if (typeof attrsObject === "object") {
|
|
123
137
|
Object.keys(attrsObject).forEach((key) => {
|
|
124
138
|
// ESLint disable as `attrsObject` is dev-controlled, `Object.keys()` has
|
|
125
139
|
// been used (to get "own" properties) and `m` is one of the characters
|
|
126
140
|
// found by the regex.
|
|
127
141
|
/* eslint-disable security/detect-object-injection */
|
|
128
|
-
const value = String(attrsObject[key]).replace(
|
|
129
|
-
'
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
142
|
+
const value = String(attrsObject[key]).replace(
|
|
143
|
+
/[<>"'&]/g,
|
|
144
|
+
(m) =>
|
|
145
|
+
({
|
|
146
|
+
"<": "<",
|
|
147
|
+
">": ">",
|
|
148
|
+
'"': """,
|
|
149
|
+
"'": "'",
|
|
150
|
+
"&": "&",
|
|
151
|
+
})[m],
|
|
152
|
+
);
|
|
135
153
|
/* eslint-enable security/detect-object-injection */
|
|
136
154
|
attrsList.push(`${key}="${value}"`);
|
|
137
155
|
});
|
|
138
156
|
}
|
|
139
|
-
return new nunjucks.runtime.SafeString(attrsList.join(
|
|
157
|
+
return new nunjucks.runtime.SafeString(attrsList.join(" "));
|
|
140
158
|
}
|
|
141
159
|
|
|
142
160
|
/**
|
|
143
161
|
* @namespace NunjucksFilters
|
|
144
162
|
*/
|
|
145
|
-
export {
|
|
146
|
-
mergeObjects,
|
|
147
|
-
includes,
|
|
148
|
-
formatDateObject,
|
|
149
|
-
renderAsAttributes,
|
|
150
|
-
};
|
|
163
|
+
export { mergeObjects, includes, formatDateObject, renderAsAttributes };
|
package/src/lib/nunjucks.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { readFileSync } from
|
|
2
|
-
import { resolve } from
|
|
3
|
-
import { Environment } from
|
|
4
|
-
import dirname from
|
|
5
|
-
import CasaTemplateLoader from
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { resolve } from "path";
|
|
3
|
+
import { Environment } from "nunjucks";
|
|
4
|
+
import dirname from "./dirname.cjs";
|
|
5
|
+
import CasaTemplateLoader from "./CasaTemplateLoader.js";
|
|
6
6
|
import {
|
|
7
|
-
mergeObjects,
|
|
8
|
-
|
|
7
|
+
mergeObjects,
|
|
8
|
+
includes,
|
|
9
|
+
renderAsAttributes,
|
|
10
|
+
formatDateObject,
|
|
11
|
+
} from "./nunjucks-filters.js";
|
|
9
12
|
|
|
10
13
|
/**
|
|
11
14
|
* @typedef {object} NunjucksOptions
|
|
@@ -19,9 +22,7 @@ import {
|
|
|
19
22
|
* @param {NunjucksOptions} options Nunjucks options
|
|
20
23
|
* @returns {Environment} Nunjucks Environment instance
|
|
21
24
|
*/
|
|
22
|
-
export default function nunjucksConfig({
|
|
23
|
-
views = [],
|
|
24
|
-
}) {
|
|
25
|
+
export default function nunjucksConfig({ views = [] }) {
|
|
25
26
|
// Prepare a single Nunjucks environment for all responses to use. Note that
|
|
26
27
|
// we cannot prepare response-specific global functions/filters if we use a
|
|
27
28
|
// single environment, but the performance gains of doing so are significant.
|
|
@@ -42,13 +43,16 @@ export default function nunjucksConfig({
|
|
|
42
43
|
|
|
43
44
|
// Globals
|
|
44
45
|
// These can't be modified once set. But they can be overridden by res.locals.
|
|
45
|
-
/* eslint-disable-next-line security/detect-non-literal-fs-filename */
|
|
46
|
-
env.addGlobal('casaVersion', JSON.parse(readFileSync(resolve(dirname, '../../package.json'))).version);
|
|
47
46
|
|
|
48
|
-
env.addGlobal(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
env.addGlobal(
|
|
48
|
+
"casaVersion",
|
|
49
|
+
JSON.parse(readFileSync(resolve(dirname, "../../package.json"))).version,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
env.addGlobal("mergeObjects", mergeObjects);
|
|
53
|
+
env.addGlobal("includes", includes);
|
|
54
|
+
env.addGlobal("formatDateObject", formatDateObject);
|
|
55
|
+
env.addGlobal("renderAsAttributes", renderAsAttributes);
|
|
52
56
|
|
|
53
57
|
return env;
|
|
54
58
|
}
|
package/src/lib/utils.js
CHANGED
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
*/
|
|
13
13
|
export function isEmpty(val) {
|
|
14
14
|
if (
|
|
15
|
-
val === null
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
val === null ||
|
|
16
|
+
typeof val === "undefined" ||
|
|
17
|
+
(typeof val === "string" && val === "")
|
|
18
18
|
) {
|
|
19
19
|
return true;
|
|
20
20
|
}
|
|
21
|
-
if (Array.isArray(val) || typeof val ===
|
|
21
|
+
if (Array.isArray(val) || typeof val === "object") {
|
|
22
22
|
// ESLint disabled as `k` is an "own property" (thanks to `Object.keys()`)
|
|
23
23
|
/* eslint-disable-next-line security/detect-object-injection */
|
|
24
24
|
return Object.keys(val).filter((k) => !isEmpty(val[k])).length === 0;
|
|
@@ -34,7 +34,7 @@ export function isEmpty(val) {
|
|
|
34
34
|
* @returns {boolean} Whether the value is stringable or not
|
|
35
35
|
*/
|
|
36
36
|
export function isStringable(value) {
|
|
37
|
-
return typeof value ===
|
|
37
|
+
return typeof value === "string" || typeof value === "number";
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
@@ -48,9 +48,14 @@ export function isStringable(value) {
|
|
|
48
48
|
* @returns {Function[]} An array of middleware that should be applied
|
|
49
49
|
*/
|
|
50
50
|
export function resolveMiddlewareHooks(hookName, path, hooks = []) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
const pathMatch = (h) =>
|
|
52
|
+
h.path === undefined ||
|
|
53
|
+
(h.path instanceof RegExp && h.path.test(path)) ||
|
|
54
|
+
h.path === path;
|
|
55
|
+
return hooks
|
|
56
|
+
.filter((h) => h.hook === hookName)
|
|
57
|
+
.filter(pathMatch)
|
|
58
|
+
.map((h) => h.middleware);
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
/**
|
|
@@ -63,7 +68,10 @@ export function resolveMiddlewareHooks(hookName, path, hooks = []) {
|
|
|
63
68
|
*/
|
|
64
69
|
export function stringifyInput(input, fallback) {
|
|
65
70
|
// Not using param defaults here as the fallback may be explicitly "undefined"
|
|
66
|
-
const fb =
|
|
71
|
+
const fb =
|
|
72
|
+
arguments.length === 2 && (isStringable(fallback) || fallback === undefined)
|
|
73
|
+
? fallback
|
|
74
|
+
: "";
|
|
67
75
|
return isStringable(input) ? String(input) : fb;
|
|
68
76
|
}
|
|
69
77
|
|
|
@@ -88,32 +96,37 @@ export function coerceInputToInteger(input) {
|
|
|
88
96
|
*/
|
|
89
97
|
export function stripWhitespace(value, options) {
|
|
90
98
|
const opts = {
|
|
91
|
-
leading:
|
|
92
|
-
trailing:
|
|
93
|
-
nested:
|
|
99
|
+
leading: "",
|
|
100
|
+
trailing: "",
|
|
101
|
+
nested: " ",
|
|
94
102
|
...options,
|
|
95
103
|
};
|
|
96
104
|
|
|
97
|
-
if (typeof value !==
|
|
98
|
-
throw new TypeError(
|
|
105
|
+
if (typeof value !== "string") {
|
|
106
|
+
throw new TypeError("value must be a string");
|
|
99
107
|
}
|
|
100
108
|
|
|
101
|
-
if (typeof opts.leading !==
|
|
102
|
-
throw new TypeError(
|
|
109
|
+
if (typeof opts.leading !== "string") {
|
|
110
|
+
throw new TypeError("leading must be a string");
|
|
103
111
|
}
|
|
104
112
|
|
|
105
|
-
if (typeof opts.trailing !==
|
|
106
|
-
throw new TypeError(
|
|
113
|
+
if (typeof opts.trailing !== "string") {
|
|
114
|
+
throw new TypeError("trailing must be a string");
|
|
107
115
|
}
|
|
108
116
|
|
|
109
|
-
if (typeof opts.nested !==
|
|
110
|
-
throw new TypeError(
|
|
117
|
+
if (typeof opts.nested !== "string") {
|
|
118
|
+
throw new TypeError("nested must be a string");
|
|
111
119
|
}
|
|
112
120
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
121
|
+
// This approach avoids using `/s+$/` regex, which triggers the
|
|
122
|
+
// `sonarjs/slow-regex` eslint rule
|
|
123
|
+
let newValue = value.replace(/^\s+/, opts.leading);
|
|
124
|
+
if (newValue.match(/\s$/)) {
|
|
125
|
+
newValue = `${newValue.trimEnd()}${opts.trailing}`;
|
|
126
|
+
}
|
|
127
|
+
newValue = newValue.replace(/\s+/g, opts.nested);
|
|
128
|
+
|
|
129
|
+
return newValue;
|
|
117
130
|
}
|
|
118
131
|
|
|
119
132
|
/* ------------------------------------------------ validation / sanitisation */
|
|
@@ -127,8 +140,12 @@ export function stripWhitespace(value, options) {
|
|
|
127
140
|
* @throws {Error} if proposed key is an invalid keyword
|
|
128
141
|
*/
|
|
129
142
|
export function notProto(key) {
|
|
130
|
-
if (
|
|
131
|
-
|
|
143
|
+
if (
|
|
144
|
+
["__proto__", "constructor", "prototype"].includes(
|
|
145
|
+
String(key).toLowerCase(),
|
|
146
|
+
)
|
|
147
|
+
) {
|
|
148
|
+
throw new Error("Attempt to use prototype key disallowed");
|
|
132
149
|
}
|
|
133
150
|
return key;
|
|
134
151
|
}
|
|
@@ -143,16 +160,18 @@ export function notProto(key) {
|
|
|
143
160
|
* @throws {SyntaxError}
|
|
144
161
|
*/
|
|
145
162
|
export function validateHookName(hookName) {
|
|
146
|
-
if (typeof hookName !==
|
|
147
|
-
throw new TypeError(
|
|
163
|
+
if (typeof hookName !== "string") {
|
|
164
|
+
throw new TypeError("Hook name must be a string");
|
|
148
165
|
}
|
|
149
166
|
|
|
150
167
|
if (!hookName.length) {
|
|
151
|
-
throw new SyntaxError(
|
|
168
|
+
throw new SyntaxError("Hook name must not be empty");
|
|
152
169
|
}
|
|
153
170
|
|
|
154
171
|
if (!hookName.match(/^([a-z_]+\.|)[a-z_]+$/i)) {
|
|
155
|
-
throw new SyntaxError(
|
|
172
|
+
throw new SyntaxError(
|
|
173
|
+
"Hook name must match either <scope>.<hookname> or <hookname> formats",
|
|
174
|
+
);
|
|
156
175
|
}
|
|
157
176
|
}
|
|
158
177
|
|
|
@@ -165,8 +184,8 @@ export function validateHookName(hookName) {
|
|
|
165
184
|
* @throws {TypeError}
|
|
166
185
|
*/
|
|
167
186
|
export function validateHookPath(path) {
|
|
168
|
-
if (typeof path !==
|
|
169
|
-
throw new TypeError(
|
|
187
|
+
if (typeof path !== "string" && !(path instanceof RegExp)) {
|
|
188
|
+
throw new TypeError("Hook path must be a string or RegExp");
|
|
170
189
|
}
|
|
171
190
|
}
|
|
172
191
|
|
|
@@ -180,16 +199,18 @@ export function validateHookPath(path) {
|
|
|
180
199
|
* @throws {SyntaxError}
|
|
181
200
|
*/
|
|
182
201
|
export function validateUrlPath(path) {
|
|
183
|
-
if (typeof path !==
|
|
184
|
-
throw new TypeError(
|
|
202
|
+
if (typeof path !== "string") {
|
|
203
|
+
throw new TypeError("URL path must be a string");
|
|
185
204
|
}
|
|
186
205
|
|
|
187
206
|
if (path.match(/[^/a-z0-9_-]/)) {
|
|
188
|
-
throw new SyntaxError(
|
|
207
|
+
throw new SyntaxError(
|
|
208
|
+
"URL path must contain only a-z, 0-9, -, _ and / characters",
|
|
209
|
+
);
|
|
189
210
|
}
|
|
190
211
|
|
|
191
212
|
if (path.match(/\/{2,}/)) {
|
|
192
|
-
throw new SyntaxError(
|
|
213
|
+
throw new SyntaxError("URL path must not contain consecutive /");
|
|
193
214
|
}
|
|
194
215
|
|
|
195
216
|
return path;
|
|
@@ -205,16 +226,18 @@ export function validateUrlPath(path) {
|
|
|
205
226
|
* @throws {SyntaxError}
|
|
206
227
|
*/
|
|
207
228
|
export function validateView(view) {
|
|
208
|
-
if (typeof view !==
|
|
209
|
-
throw new TypeError(
|
|
229
|
+
if (typeof view !== "string") {
|
|
230
|
+
throw new TypeError("View must be a string");
|
|
210
231
|
}
|
|
211
232
|
|
|
212
233
|
if (!view.length) {
|
|
213
|
-
throw new SyntaxError(
|
|
234
|
+
throw new SyntaxError("View must not be empty");
|
|
214
235
|
}
|
|
215
236
|
|
|
216
237
|
if (!view.match(/^[a-z0-9/_-]+\.njk$/i)) {
|
|
217
|
-
throw new SyntaxError(
|
|
238
|
+
throw new SyntaxError(
|
|
239
|
+
"View must contain only a-z, 0-9, -, _ and / characters, and end in .njk",
|
|
240
|
+
);
|
|
218
241
|
}
|
|
219
242
|
}
|
|
220
243
|
|
|
@@ -228,15 +251,17 @@ export function validateView(view) {
|
|
|
228
251
|
* @throws {SyntaxError}
|
|
229
252
|
*/
|
|
230
253
|
export function validateWaypoint(waypoint) {
|
|
231
|
-
if (typeof waypoint !==
|
|
232
|
-
throw new TypeError(
|
|
254
|
+
if (typeof waypoint !== "string") {
|
|
255
|
+
throw new TypeError("Waypoint must be a string");
|
|
233
256
|
}
|
|
234
257
|
|
|
235
258
|
if (!waypoint.length) {
|
|
236
|
-
throw new SyntaxError(
|
|
259
|
+
throw new SyntaxError("Waypoint must not be empty");
|
|
237
260
|
}
|
|
238
261
|
|
|
239
262
|
if (waypoint.match(/[^/a-z0-9_-]/)) {
|
|
240
|
-
throw new SyntaxError(
|
|
263
|
+
throw new SyntaxError(
|
|
264
|
+
"Waypoint must contain only a-z, 0-9, -, _ and / characters",
|
|
265
|
+
);
|
|
241
266
|
}
|
|
242
267
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import { stringifyInput, stripWhitespace } from '../utils.js';
|
|
1
|
+
import { DateTime } from "luxon";
|
|
2
|
+
import lodash from "lodash";
|
|
3
|
+
import ValidationError from "../ValidationError.js";
|
|
4
|
+
import ValidatorFactory from "../ValidatorFactory.js";
|
|
5
|
+
import { stringifyInput, stripWhitespace } from "../utils.js";
|
|
7
6
|
|
|
8
7
|
const { isPlainObject } = lodash;
|
|
9
8
|
|
|
@@ -43,21 +42,21 @@ const { isPlainObject } = lodash;
|
|
|
43
42
|
*/
|
|
44
43
|
export default class DateObject extends ValidatorFactory {
|
|
45
44
|
/** @property {string} name Validator name ("dateObject") */
|
|
46
|
-
name =
|
|
45
|
+
name = "dateObject";
|
|
47
46
|
|
|
48
47
|
validate(value, dataContext = {}) {
|
|
49
48
|
const config = {
|
|
50
49
|
errorMsg: {
|
|
51
|
-
inline:
|
|
52
|
-
summary:
|
|
50
|
+
inline: "validation:rule.dateObject.inline",
|
|
51
|
+
summary: "validation:rule.dateObject.summary",
|
|
53
52
|
},
|
|
54
53
|
errorMsgAfterOffset: {
|
|
55
|
-
inline:
|
|
56
|
-
summary:
|
|
54
|
+
inline: "validation:rule.dateObject.afterOffset.inline",
|
|
55
|
+
summary: "validation:rule.dateObject.afterOffset.summary",
|
|
57
56
|
},
|
|
58
57
|
errorMsgBeforeOffset: {
|
|
59
|
-
inline:
|
|
60
|
-
summary:
|
|
58
|
+
inline: "validation:rule.dateObject.beforeOffset.inline",
|
|
59
|
+
summary: "validation:rule.dateObject.beforeOffset.summary",
|
|
61
60
|
},
|
|
62
61
|
now: DateTime.local(),
|
|
63
62
|
allowSingleDigitDay: false,
|
|
@@ -71,38 +70,44 @@ export default class DateObject extends ValidatorFactory {
|
|
|
71
70
|
let valid = false;
|
|
72
71
|
let { errorMsg } = config;
|
|
73
72
|
let luxonDate;
|
|
74
|
-
const NOW = config.now.startOf(
|
|
73
|
+
const NOW = config.now.startOf("day");
|
|
75
74
|
|
|
76
75
|
// Accepted formats
|
|
77
|
-
let formats = [
|
|
78
|
-
const formatTests = [
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
76
|
+
let formats = ["dd-MM-yyyy"];
|
|
77
|
+
const formatTests = [
|
|
78
|
+
{
|
|
79
|
+
flags: [config.allowSingleDigitDay],
|
|
80
|
+
formats: ["d-MM-yyyy"],
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
flags: [config.allowSingleDigitDay, config.allowSingleDigitMonth],
|
|
84
|
+
formats: ["d-M-yyyy"],
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
flags: [config.allowSingleDigitDay, config.allowMonthNames],
|
|
88
|
+
formats: ["d-MMM-yyyy", "d-MMMM-yyyy"],
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
flags: [config.allowSingleDigitMonth],
|
|
92
|
+
formats: ["dd-M-yyyy"],
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
flags: [config.allowMonthNames],
|
|
96
|
+
formats: ["dd-MMM-yyyy", "dd-MMMM-yyyy"],
|
|
97
|
+
},
|
|
98
|
+
];
|
|
94
99
|
formatTests.forEach((test) => {
|
|
95
100
|
if (test.flags.every((v) => v === true)) {
|
|
96
101
|
formats = [...formats, ...test.formats];
|
|
97
102
|
}
|
|
98
103
|
});
|
|
99
104
|
|
|
100
|
-
if (typeof value ===
|
|
105
|
+
if (typeof value === "object") {
|
|
101
106
|
formats.find((format) => {
|
|
102
107
|
luxonDate = DateTime.fromFormat(
|
|
103
|
-
[value.dd, value.mm, value.yyyy].join(
|
|
108
|
+
[value.dd, value.mm, value.yyyy].join("-"),
|
|
104
109
|
format,
|
|
105
|
-
).startOf(
|
|
110
|
+
).startOf("day");
|
|
106
111
|
|
|
107
112
|
valid = luxonDate.isValid;
|
|
108
113
|
|
|
@@ -113,7 +118,7 @@ export default class DateObject extends ValidatorFactory {
|
|
|
113
118
|
// Check date is after the specified duration from now.
|
|
114
119
|
// Need to use UTC() otherwise DST shifts can affect the calculated offset
|
|
115
120
|
if (config.afterOffsetFromNow) {
|
|
116
|
-
const offsetDate = NOW.plus(config.afterOffsetFromNow).startOf(
|
|
121
|
+
const offsetDate = NOW.plus(config.afterOffsetFromNow).startOf("day");
|
|
117
122
|
|
|
118
123
|
if (luxonDate <= offsetDate) {
|
|
119
124
|
valid = false;
|
|
@@ -124,7 +129,9 @@ export default class DateObject extends ValidatorFactory {
|
|
|
124
129
|
// Check date is before the specified duration from now
|
|
125
130
|
// Need to use UTC() otherwise DST shifts can affect the calculated offset
|
|
126
131
|
if (config.beforeOffsetFromNow) {
|
|
127
|
-
const offsetDate = NOW.plus(config.beforeOffsetFromNow).startOf(
|
|
132
|
+
const offsetDate = NOW.plus(config.beforeOffsetFromNow).startOf(
|
|
133
|
+
"day",
|
|
134
|
+
);
|
|
128
135
|
|
|
129
136
|
if (luxonDate >= offsetDate) {
|
|
130
137
|
valid = false;
|
|
@@ -136,20 +143,20 @@ export default class DateObject extends ValidatorFactory {
|
|
|
136
143
|
// Check presence of each object component (dd, mm, yyyy) in order to log
|
|
137
144
|
// which specific parts are in error
|
|
138
145
|
errorMsg.focusSuffix = [];
|
|
139
|
-
if (!Object.prototype.hasOwnProperty.call(value,
|
|
140
|
-
errorMsg.focusSuffix.push(
|
|
146
|
+
if (!Object.prototype.hasOwnProperty.call(value, "dd") || !value.dd) {
|
|
147
|
+
errorMsg.focusSuffix.push("[dd]");
|
|
141
148
|
}
|
|
142
|
-
if (!Object.prototype.hasOwnProperty.call(value,
|
|
143
|
-
errorMsg.focusSuffix.push(
|
|
149
|
+
if (!Object.prototype.hasOwnProperty.call(value, "mm") || !value.mm) {
|
|
150
|
+
errorMsg.focusSuffix.push("[mm]");
|
|
144
151
|
}
|
|
145
|
-
if (!Object.prototype.hasOwnProperty.call(value,
|
|
146
|
-
errorMsg.focusSuffix.push(
|
|
152
|
+
if (!Object.prototype.hasOwnProperty.call(value, "yyyy") || !value.yyyy) {
|
|
153
|
+
errorMsg.focusSuffix.push("[yyyy]");
|
|
147
154
|
}
|
|
148
155
|
|
|
149
156
|
// If the date is invalid, but not specific parts have been highlighted in
|
|
150
157
|
// error, then highlight all inputs, focusing on the [dd] first
|
|
151
158
|
if (!valid && !errorMsg.focusSuffix.length) {
|
|
152
|
-
errorMsg.focusSuffix = [
|
|
159
|
+
errorMsg.focusSuffix = ["[dd]", "[mm]", "[yyyy]"];
|
|
153
160
|
}
|
|
154
161
|
}
|
|
155
162
|
|
|
@@ -158,11 +165,13 @@ export default class DateObject extends ValidatorFactory {
|
|
|
158
165
|
|
|
159
166
|
sanitise(value) {
|
|
160
167
|
if (value !== undefined) {
|
|
161
|
-
return isPlainObject(value)
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
168
|
+
return isPlainObject(value)
|
|
169
|
+
? {
|
|
170
|
+
dd: stripWhitespace(stringifyInput(value.dd)),
|
|
171
|
+
mm: stripWhitespace(stringifyInput(value.mm)),
|
|
172
|
+
yyyy: stripWhitespace(stringifyInput(value.yyyy)),
|
|
173
|
+
}
|
|
174
|
+
: Object.create(null);
|
|
166
175
|
}
|
|
167
176
|
return undefined;
|
|
168
177
|
}
|