@dwp/govuk-casa 7.0.6 → 8.0.0-alpha1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -17
- package/dist/{casa → assets}/css/casa-ie8.css +1 -1
- package/dist/assets/css/casa.css +1 -0
- package/dist/casa.d.ts +10 -0
- package/dist/casa.js +44 -0
- package/dist/lib/CasaTemplateLoader.d.ts +19 -0
- package/dist/lib/CasaTemplateLoader.js +57 -0
- package/dist/lib/JourneyContext.d.ts +255 -0
- package/dist/lib/JourneyContext.js +681 -0
- package/dist/lib/MutableRouter.d.ts +155 -0
- package/dist/lib/MutableRouter.js +272 -0
- package/dist/lib/Plan.d.ts +119 -0
- package/dist/lib/Plan.js +382 -0
- package/dist/lib/ValidationError.d.ts +70 -0
- package/dist/lib/ValidationError.js +156 -0
- package/dist/lib/ValidatorFactory.d.ts +24 -0
- package/dist/lib/ValidatorFactory.js +87 -0
- package/dist/lib/configure.d.ts +205 -0
- package/dist/lib/configure.js +215 -0
- package/dist/lib/dirname.cjs +1 -0
- package/dist/lib/end-session.d.ts +12 -0
- package/dist/lib/end-session.js +24 -0
- package/dist/lib/field.d.ts +79 -0
- package/dist/lib/field.js +223 -0
- package/dist/lib/logger.d.ts +8 -0
- package/dist/lib/logger.js +19 -0
- package/dist/lib/nunjucks-filters.d.ts +26 -0
- package/dist/lib/nunjucks-filters.js +112 -0
- package/dist/lib/nunjucks.d.ts +22 -0
- package/dist/lib/nunjucks.js +49 -0
- package/dist/lib/utils.d.ts +22 -0
- package/dist/lib/utils.js +44 -0
- package/dist/lib/validators/dateObject.d.ts +4 -0
- package/dist/lib/validators/dateObject.js +135 -0
- package/dist/lib/validators/email.d.ts +4 -0
- package/dist/lib/validators/email.js +46 -0
- package/dist/lib/validators/inArray.d.ts +4 -0
- package/dist/lib/validators/inArray.js +60 -0
- package/dist/lib/validators/index.d.ts +21 -0
- package/dist/lib/validators/index.js +47 -0
- package/dist/lib/validators/nino.d.ts +4 -0
- package/dist/lib/validators/nino.js +46 -0
- package/dist/lib/validators/postalAddressObject.d.ts +4 -0
- package/dist/lib/validators/postalAddressObject.js +123 -0
- package/dist/lib/validators/regex.d.ts +4 -0
- package/dist/lib/validators/regex.js +40 -0
- package/dist/lib/validators/required.d.ts +4 -0
- package/dist/lib/validators/required.js +56 -0
- package/dist/lib/validators/strlen.d.ts +4 -0
- package/dist/lib/validators/strlen.js +51 -0
- package/dist/lib/validators/wordCount.d.ts +5 -0
- package/dist/lib/validators/wordCount.js +54 -0
- package/dist/lib/waypoint-url.d.ts +17 -0
- package/dist/lib/waypoint-url.js +46 -0
- package/dist/middleware/body-parser.d.ts +1 -0
- package/dist/middleware/body-parser.js +24 -0
- package/dist/middleware/csrf.d.ts +1 -0
- package/dist/middleware/csrf.js +31 -0
- package/dist/middleware/data.d.ts +6 -0
- package/dist/middleware/data.js +53 -0
- package/dist/middleware/dirname.cjs +1 -0
- package/dist/middleware/gather-fields.d.ts +5 -0
- package/dist/middleware/gather-fields.js +39 -0
- package/dist/middleware/i18n.d.ts +4 -0
- package/dist/middleware/i18n.js +87 -0
- package/dist/middleware/post.d.ts +1 -0
- package/dist/middleware/post.js +42 -0
- package/dist/middleware/pre.d.ts +3 -0
- package/dist/middleware/pre.js +38 -0
- package/dist/middleware/progress-journey.d.ts +6 -0
- package/dist/middleware/progress-journey.js +82 -0
- package/dist/middleware/sanitise-fields.d.ts +5 -0
- package/dist/middleware/sanitise-fields.js +48 -0
- package/dist/middleware/session.d.ts +10 -0
- package/dist/middleware/session.js +115 -0
- package/dist/middleware/skip-waypoint.d.ts +5 -0
- package/dist/middleware/skip-waypoint.js +40 -0
- package/dist/middleware/steer-journey.d.ts +6 -0
- package/dist/middleware/steer-journey.js +44 -0
- package/dist/middleware/validate-fields.d.ts +7 -0
- package/dist/middleware/validate-fields.js +76 -0
- package/dist/mjs/esm-wrapper.js +10 -0
- package/dist/mjs/package.json +3 -0
- package/dist/package.json +3 -0
- package/dist/routes/ancillary.d.ts +4 -0
- package/dist/routes/ancillary.js +19 -0
- package/dist/routes/dirname.cjs +1 -0
- package/dist/routes/journey.d.ts +8 -0
- package/dist/routes/journey.js +130 -0
- package/dist/routes/static.d.ts +26 -0
- package/dist/routes/static.js +67 -0
- package/package.json +45 -86
- package/views/casa/components/checkboxes/template.njk +4 -1
- package/views/casa/components/date-input/template.njk +3 -3
- package/views/casa/components/journey-form/README.md +3 -1
- package/views/casa/components/journey-form/template.njk +1 -1
- package/views/casa/components/postal-address-object/template.njk +5 -5
- package/views/casa/components/radios/template.njk +1 -1
- package/views/casa/layouts/journey.njk +26 -9
- package/views/casa/layouts/main.njk +6 -19
- package/views/casa/partials/scripts.njk +8 -3
- package/views/casa/partials/styles.njk +2 -2
- package/casa.js +0 -208
- package/definitions/review-page.js +0 -60
- package/dist/casa/css/casa.css +0 -1
- package/dist/casa/js/casa.js +0 -1
- package/index.d.ts +0 -121
- package/lib/ConfigIngestor.js +0 -588
- package/lib/GatherModifier.js +0 -14
- package/lib/I18n.js +0 -160
- package/lib/JourneyContext.d.ts +0 -97
- package/lib/JourneyContext.js +0 -552
- package/lib/JourneyMap.js +0 -233
- package/lib/JourneyRoad.js +0 -330
- package/lib/Logger.js +0 -59
- package/lib/PageDictionary.d.ts +0 -11
- package/lib/PageDirectory.js +0 -77
- package/lib/Plan.js +0 -423
- package/lib/RoadConverter.js +0 -153
- package/lib/UserJourney.js +0 -8
- package/lib/Util.js +0 -227
- package/lib/Validation.js +0 -20
- package/lib/bootstrap/end-session.js +0 -44
- package/lib/bootstrap/load-definitions.js +0 -64
- package/lib/commonBodyParser.js +0 -15
- package/lib/enums.js +0 -6
- package/lib/gather-modifiers/index.js +0 -7
- package/lib/gather-modifiers/trimPostalAddressObject.js +0 -75
- package/lib/gather-modifiers/trimWhitespace.js +0 -16
- package/lib/utils/createGetRequest.d.ts +0 -5
- package/lib/utils/createGetRequest.js +0 -59
- package/lib/utils/index.js +0 -11
- package/lib/utils/parseRequest.d.ts +0 -5
- package/lib/utils/parseRequest.js +0 -72
- package/lib/utils/sanitise.js +0 -74
- package/lib/utils/validate.js +0 -32
- package/lib/validation/ArrayObjectField.js +0 -49
- package/lib/validation/ObjectField.js +0 -53
- package/lib/validation/SimpleField.d.ts +0 -11
- package/lib/validation/SimpleField.js +0 -46
- package/lib/validation/ValidationError.d.ts +0 -14
- package/lib/validation/ValidationError.js +0 -170
- package/lib/validation/ValidatorFactory.d.ts +0 -32
- package/lib/validation/ValidatorFactory.js +0 -91
- package/lib/validation/index.js +0 -22
- package/lib/validation/processor/flattenErrorArray.js +0 -24
- package/lib/validation/processor/queue.js +0 -214
- package/lib/validation/processor.js +0 -84
- package/lib/validation/rules/README.md +0 -3
- package/lib/validation/rules/ValidationRules.d.ts +0 -22
- package/lib/validation/rules/dateObject.js +0 -156
- package/lib/validation/rules/email.js +0 -44
- package/lib/validation/rules/inArray.js +0 -61
- package/lib/validation/rules/index.js +0 -23
- package/lib/validation/rules/nino.js +0 -48
- package/lib/validation/rules/optional.js +0 -14
- package/lib/validation/rules/postalAddressObject.js +0 -142
- package/lib/validation/rules/regex.js +0 -39
- package/lib/validation/rules/required.js +0 -57
- package/lib/validation/rules/strlen.js +0 -57
- package/lib/validation/rules/wordCount.js +0 -61
- package/lib/view-filters/formatDateObject.js +0 -35
- package/lib/view-filters/includes.js +0 -10
- package/lib/view-filters/index.js +0 -23
- package/lib/view-filters/mergeObjectsDeep.js +0 -21
- package/lib/view-filters/renderAsAttributes.js +0 -33
- package/middleware/errors/404.js +0 -12
- package/middleware/errors/catch-all.js +0 -27
- package/middleware/errors/index.js +0 -9
- package/middleware/headers/config-defaults.js +0 -57
- package/middleware/headers/headers.js +0 -40
- package/middleware/headers/index.js +0 -9
- package/middleware/i18n/i18n.js +0 -56
- package/middleware/i18n/index.js +0 -16
- package/middleware/index.js +0 -55
- package/middleware/mount/index.js +0 -9
- package/middleware/mount/mount.js +0 -10
- package/middleware/nunjucks/environment.js +0 -57
- package/middleware/nunjucks/index.js +0 -8
- package/middleware/page/csrf.js +0 -37
- package/middleware/page/edit-mode.js +0 -52
- package/middleware/page/gather.js +0 -75
- package/middleware/page/index.js +0 -103
- package/middleware/page/journey-continue.js +0 -157
- package/middleware/page/journey-rails.js +0 -102
- package/middleware/page/prepare-request.js +0 -77
- package/middleware/page/render.js +0 -75
- package/middleware/page/skip.js +0 -72
- package/middleware/page/utils.js +0 -206
- package/middleware/page/validate.js +0 -67
- package/middleware/session/expiry.js +0 -95
- package/middleware/session/genid.js +0 -18
- package/middleware/session/index.js +0 -18
- package/middleware/session/init.js +0 -25
- package/middleware/session/seed.js +0 -50
- package/middleware/session/timeout.js +0 -5
- package/middleware/static/asset-versions.js +0 -23
- package/middleware/static/index.js +0 -104
- package/middleware/static/prepare-assets.js +0 -51
- package/middleware/static/serve-assets.js +0 -58
- package/middleware/variables/index.js +0 -12
- package/middleware/variables/variables.js +0 -35
- package/src/browserconfig.xml +0 -5
- package/src/js/casa.js +0 -132
- package/src/scss/_casaElements.scss +0 -11
- package/src/scss/_casaGovukTemplateJinjaPolyfill.scss +0 -39
- package/src/scss/_casaMountUrl.scss +0 -8
- package/src/scss/casa-ie8.scss +0 -3
- package/src/scss/casa.scss +0 -14
- package/test/unit/templates/README.md +0 -5
- package/test/utils/BaseTestWaypoint.js +0 -106
- package/test/utils/concatWaypoints.js +0 -26
- package/test/utils/index.js +0 -6
- package/test/utils/testTraversal.js +0 -90
- package/views/casa/partials/cookie_message.njk +0 -3
- package/views/casa/partials/phase_banner_alpha.njk +0 -8
- package/views/casa/partials/phase_banner_beta.njk +0 -8
- package/views/casa/review/page-block.njk +0 -8
- package/views/casa/review/review.njk +0 -47
package/dist/lib/Plan.js
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const graphlib_1 = require("graphlib");
|
|
7
|
+
const JourneyContext_js_1 = __importDefault(require("./JourneyContext.js"));
|
|
8
|
+
const logger_js_1 = __importDefault(require("./logger.js"));
|
|
9
|
+
const log = (0, logger_js_1.default)('class:plan');
|
|
10
|
+
/**
|
|
11
|
+
* Will check if the source waypoint has specifically passed validation, i.e
|
|
12
|
+
* there is a "null" validation entry for the route source.
|
|
13
|
+
*
|
|
14
|
+
* @param {object} r Route meta.
|
|
15
|
+
* @param {JourneyContext} context Journey Context.
|
|
16
|
+
* @returns {boolean} Condition result.
|
|
17
|
+
*/
|
|
18
|
+
function defaultNextFollow(r, context) {
|
|
19
|
+
const { validation: v = {} } = context.toObject();
|
|
20
|
+
return Object.prototype.hasOwnProperty.call(v, r.source) && v[r.source] === null;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Will check if the target waypoint (the one we're moving back to) has
|
|
24
|
+
* specifically passed validation.
|
|
25
|
+
*
|
|
26
|
+
* @param {object} r Route meta.
|
|
27
|
+
* @param {JourneyContext} context Journey context.
|
|
28
|
+
* @returns {boolean} Condition result.
|
|
29
|
+
*/
|
|
30
|
+
function defaultPrevFollow(r, context) {
|
|
31
|
+
const { validation: v = {} } = context.toObject();
|
|
32
|
+
return Object.prototype.hasOwnProperty.call(v, r.target) && v[r.target] === null;
|
|
33
|
+
}
|
|
34
|
+
function validateWaypointId(val) {
|
|
35
|
+
if (typeof val !== 'string') {
|
|
36
|
+
throw new TypeError(`Expected waypoint id to be a string, got ${typeof val}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function validateRouteName(val) {
|
|
40
|
+
if (typeof val !== 'string') {
|
|
41
|
+
throw new TypeError(`Expected route name to be a string, got ${typeof val}`);
|
|
42
|
+
}
|
|
43
|
+
else if (!['next', 'prev'].includes(val)) {
|
|
44
|
+
throw new ReferenceError(`Expected route name to be one of next or prev. Got ${val}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function validateRouteCondition(val) {
|
|
48
|
+
if (!(val instanceof Function)) {
|
|
49
|
+
throw new TypeError(`Expected route condition to be a function, got ${typeof val}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Creates a user friendly route structure from a given graph edge which will
|
|
54
|
+
* be used in userland. This is the object that will be passed into follow
|
|
55
|
+
* functions too as the "route" parameter.
|
|
56
|
+
*
|
|
57
|
+
* @param {object} dgraph Directed graph instance.
|
|
58
|
+
* @param {object} edge Graph edge object.
|
|
59
|
+
* @returns {object} Route.
|
|
60
|
+
*/
|
|
61
|
+
const makeRouteObject = (dgraph, edge) => {
|
|
62
|
+
const label = dgraph.edge(edge) || {};
|
|
63
|
+
return {
|
|
64
|
+
source: edge.v,
|
|
65
|
+
target: edge.w,
|
|
66
|
+
name: edge.name,
|
|
67
|
+
// label: {},
|
|
68
|
+
label,
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Exit nodes begin with a protocol format, such as `url://`, `http://`, etc
|
|
73
|
+
*/
|
|
74
|
+
const reExitNodeProtocol = /^[a-z]+:\/\//i;
|
|
75
|
+
const priv = new WeakMap();
|
|
76
|
+
class Plan {
|
|
77
|
+
static isExitNode(name) {
|
|
78
|
+
return reExitNodeProtocol.test(name);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* @param {object} opts
|
|
82
|
+
*/
|
|
83
|
+
constructor(opts = {}) {
|
|
84
|
+
// This is our directed, multigraph representation
|
|
85
|
+
const dgraph = new graphlib_1.Graph({
|
|
86
|
+
directed: true,
|
|
87
|
+
multigraph: true,
|
|
88
|
+
compound: false,
|
|
89
|
+
});
|
|
90
|
+
// Gather options
|
|
91
|
+
const options = Object.assign(Object.create(null), {
|
|
92
|
+
// When true, the validation state of the source node must be `null` (i.e.
|
|
93
|
+
// no validation errors) before any custom route conditions are evaluated.
|
|
94
|
+
validateBeforeRouteCondition: true,
|
|
95
|
+
// Traversal arbitration
|
|
96
|
+
arbiter: undefined,
|
|
97
|
+
}, opts);
|
|
98
|
+
Object.freeze(options);
|
|
99
|
+
priv.set(this, {
|
|
100
|
+
dgraph,
|
|
101
|
+
follows: {
|
|
102
|
+
next: {},
|
|
103
|
+
prev: {},
|
|
104
|
+
},
|
|
105
|
+
options,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
getOptions() {
|
|
109
|
+
return priv.get(this).options;
|
|
110
|
+
}
|
|
111
|
+
getWaypoints() {
|
|
112
|
+
return priv.get(this).dgraph.nodes();
|
|
113
|
+
}
|
|
114
|
+
containsWaypoint(waypoint) {
|
|
115
|
+
return this.getWaypoints().includes(waypoint);
|
|
116
|
+
}
|
|
117
|
+
getRoutes() {
|
|
118
|
+
const self = priv.get(this);
|
|
119
|
+
return self.dgraph.edges().map((edge) => makeRouteObject(self.dgraph, edge));
|
|
120
|
+
}
|
|
121
|
+
getRouteCondition(src, tgt, name) {
|
|
122
|
+
return priv.get(this).follows[name][`${src}/${tgt}`];
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Return all outward routes (out-edges) from the given waypoint, to the
|
|
126
|
+
* optional target waypoint.
|
|
127
|
+
*
|
|
128
|
+
* @param {string} src Source waypoint.
|
|
129
|
+
* @param {string} tgt Target waypoint (optional).
|
|
130
|
+
* @returns {Array<object>} Route objects found.
|
|
131
|
+
*/
|
|
132
|
+
getOutwardRoutes(src, tgt = null) {
|
|
133
|
+
const self = priv.get(this);
|
|
134
|
+
return self.dgraph.outEdges(src, tgt).map((e) => makeRouteObject(self.dgraph, e));
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Return all outward routes (out-edges) from the given waypoint, to the
|
|
138
|
+
* optional target waypoint, matching the "prev" name.
|
|
139
|
+
*
|
|
140
|
+
* @param {string} src Source waypoint.
|
|
141
|
+
* @param {string} tgt Target waypoint (optional).
|
|
142
|
+
* @returns {Array<object>} Route objects found.
|
|
143
|
+
*/
|
|
144
|
+
getPrevOutwardRoutes(src, tgt = null) {
|
|
145
|
+
return this.getOutwardRoutes(src, tgt).filter((r) => r.name === 'prev');
|
|
146
|
+
}
|
|
147
|
+
addSequence(...waypoints) {
|
|
148
|
+
// Setup simple double routes (next/prev) between all waypoints in this list
|
|
149
|
+
for (let i = 0, l = waypoints.length - 1; i < l; i += 1) {
|
|
150
|
+
this.setRoute(waypoints[i], waypoints[i + 1]);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
setNextRoute(src, tgt, follow) {
|
|
154
|
+
return this.setNamedRoute(src, tgt, 'next', follow);
|
|
155
|
+
}
|
|
156
|
+
setPrevRoute(src, tgt, follow) {
|
|
157
|
+
return this.setNamedRoute(src, tgt, 'prev', follow);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Adds both a "next" and "prev" route between the two waypoints.
|
|
161
|
+
*
|
|
162
|
+
* By default, the "prev" route will use the same "follow" test as the "next"
|
|
163
|
+
* route. This makes sense in that in order to get the target, the test must
|
|
164
|
+
* have been true, and so to reverse the direction we also need that same test
|
|
165
|
+
* to be true.
|
|
166
|
+
*
|
|
167
|
+
* However, if the condition function uses the `source`/`target`
|
|
168
|
+
* of the route in some way, then we must reverse these before passing to the
|
|
169
|
+
* condition on the "prev" route because `source` in the condition will almost
|
|
170
|
+
* certainly be referring to the source of the "next" route.
|
|
171
|
+
*
|
|
172
|
+
* If `tgt` is an egress node, do not create a `prev` route for it, because
|
|
173
|
+
* there's no way back from that point to this Plan.
|
|
174
|
+
*
|
|
175
|
+
* @param {string} src Source waypoint.
|
|
176
|
+
* @param {string} tgt Target waypoint.
|
|
177
|
+
* @param {Function} followNext Follow test function.
|
|
178
|
+
* @param {Function} followPrev Follow test function.
|
|
179
|
+
* @returns {Plan} Self.
|
|
180
|
+
*/
|
|
181
|
+
setRoute(src, tgt, followNext = undefined, followPrev = undefined) {
|
|
182
|
+
this.setNamedRoute(src, tgt, 'next', followNext);
|
|
183
|
+
if (followPrev === undefined) {
|
|
184
|
+
followPrev = followNext === undefined ? undefined : (r, c) => {
|
|
185
|
+
const invertedRoute = Object.assign(Object.assign({}, r), { source: r.target, target: r.source });
|
|
186
|
+
return followNext(invertedRoute, c);
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
this.setNamedRoute(tgt, src, 'prev', followPrev);
|
|
190
|
+
return this;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Create a named route between two waypoints, and give that route a function
|
|
194
|
+
* that determine whether it should be followed during traversal operations.
|
|
195
|
+
* Note that the source waypoint must be in a successful validation state
|
|
196
|
+
* to be considered for traversal, regardless of what the custom function
|
|
197
|
+
* determines.
|
|
198
|
+
*
|
|
199
|
+
* You may also define routes that take the user to any generic URL within the
|
|
200
|
+
* same domain by using the `url://` protocol. These are considered
|
|
201
|
+
* "exit nodes".
|
|
202
|
+
*
|
|
203
|
+
* setNamedRoute("my-waypoint", "url:///some/absolute/url");
|
|
204
|
+
*
|
|
205
|
+
* @param {string} src Source waypoint.
|
|
206
|
+
* @param {string} tgt Target waypoint.
|
|
207
|
+
* @param {string} name Name of the route (must be unique for this waypoint pairing).
|
|
208
|
+
* @param {Function} follow Test function to determine if route can be followed.
|
|
209
|
+
* @returns {Plan} Chain
|
|
210
|
+
* @throws {Error} If attempting to create a "next" route from an exit node
|
|
211
|
+
*/
|
|
212
|
+
setNamedRoute(src, tgt, name, follow) {
|
|
213
|
+
const self = priv.get(this);
|
|
214
|
+
// Validate
|
|
215
|
+
validateWaypointId(src);
|
|
216
|
+
validateWaypointId(tgt);
|
|
217
|
+
validateRouteName(name);
|
|
218
|
+
if (follow !== undefined) {
|
|
219
|
+
validateRouteCondition(follow);
|
|
220
|
+
}
|
|
221
|
+
// Get routing function name to label edge
|
|
222
|
+
const label = follow && follow.name;
|
|
223
|
+
// Warn if we're overwriting an existing edge on the same name
|
|
224
|
+
if (self.dgraph.hasEdge(src, tgt, name)) {
|
|
225
|
+
log.warn('Setting a route that already exists (%s, %s, %s). Will be overridden', src, tgt, name);
|
|
226
|
+
}
|
|
227
|
+
self.dgraph.setEdge(src, tgt, { label }, name);
|
|
228
|
+
// Determine which follow function to use
|
|
229
|
+
let followFunc;
|
|
230
|
+
if (follow) {
|
|
231
|
+
if (!self.options.validateBeforeRouteCondition) {
|
|
232
|
+
followFunc = follow;
|
|
233
|
+
}
|
|
234
|
+
else if (name === 'next') {
|
|
235
|
+
// Retain the original function name of route condition
|
|
236
|
+
followFunc = {
|
|
237
|
+
[follow.name]: (r, c) => (defaultNextFollow(r, c) && follow(r, c)),
|
|
238
|
+
}[follow.name];
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
// Retain the original function name of route condition
|
|
242
|
+
followFunc = {
|
|
243
|
+
[follow.name]: (r, c) => (defaultPrevFollow(r, c) && follow(r, c)),
|
|
244
|
+
}[follow.name];
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
else if (name === 'next') {
|
|
248
|
+
followFunc = defaultNextFollow;
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
followFunc = defaultPrevFollow;
|
|
252
|
+
}
|
|
253
|
+
self.follows[name][`${src}/${tgt}`] = followFunc;
|
|
254
|
+
return this;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* This is a convenience method for traversing all "next" routes, and returning
|
|
258
|
+
* the IDs of all waypoints visited along the way.
|
|
259
|
+
*
|
|
260
|
+
* @param {JourneyContext} context Journey Context.
|
|
261
|
+
* @param {object} options Options.
|
|
262
|
+
* @returns {Array<string>} List of traversed waypoints.
|
|
263
|
+
*/
|
|
264
|
+
traverse(context, options = {}) {
|
|
265
|
+
return this.traverseNextRoutes(context, options).map((e) => e.source);
|
|
266
|
+
}
|
|
267
|
+
traverseNextRoutes(context, options = {}) {
|
|
268
|
+
return this.traverseRoutes(context, Object.assign(Object.assign({}, options), { routeName: 'next' }));
|
|
269
|
+
}
|
|
270
|
+
traversePrevRoutes(context, options = {}) {
|
|
271
|
+
return this.traverseRoutes(context, Object.assign(Object.assign({}, options), { routeName: 'prev' }));
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Traverse through the plan from a particular starting waypoint. This is a
|
|
275
|
+
* non-exhaustive Graph Exploration.
|
|
276
|
+
*
|
|
277
|
+
* The last route in the list will contain the source of the last waypoint that
|
|
278
|
+
* can be reached, i.e. The waypoint that has no further satisfiable out-edges.
|
|
279
|
+
*
|
|
280
|
+
* If a cyclical set of routes are encountered, traversal will stop after
|
|
281
|
+
* reaching the first repeated waypoint.
|
|
282
|
+
*
|
|
283
|
+
* Options:
|
|
284
|
+
* string startWaypoint = Waypoint from which to start traversal
|
|
285
|
+
* string routeName = Follow routes matching this name (next | prev)
|
|
286
|
+
* Map history = Used to detect loops in traversal (internal use)
|
|
287
|
+
* function stopCondition = Condition that, if true, will stop traversal (useful for performance)
|
|
288
|
+
* function|string arbiter = When mutliple target routes are found, this will decide which one to choose (if any)
|
|
289
|
+
*
|
|
290
|
+
* @param {JourneyContext} context Journey context
|
|
291
|
+
* @param {object} options Options
|
|
292
|
+
* @returns {Array<object>} Routes that were traversed
|
|
293
|
+
* @throws {TypeError} When context is not a JourneyContext
|
|
294
|
+
*/
|
|
295
|
+
traverseRoutes(context, options = {}) {
|
|
296
|
+
if (!(context instanceof JourneyContext_js_1.default)) {
|
|
297
|
+
throw new TypeError(`Expected context to be an instance of JourneyContext, got ${typeof context}`);
|
|
298
|
+
}
|
|
299
|
+
const self = priv.get(this);
|
|
300
|
+
const { startWaypoint = this.getWaypoints()[0], stopCondition = () => (false), arbiter = self.options.arbiter, routeName, } = options;
|
|
301
|
+
if (!self.dgraph.hasNode(startWaypoint)) {
|
|
302
|
+
throw new ReferenceError(`Plan does not contain waypoint '${startWaypoint}'`);
|
|
303
|
+
}
|
|
304
|
+
if (routeName === undefined) {
|
|
305
|
+
throw new ReferenceError('Route name must be provided');
|
|
306
|
+
}
|
|
307
|
+
const history = new Map();
|
|
308
|
+
const traverse = (startWP) => {
|
|
309
|
+
let target = self.dgraph.outEdges(startWP).filter((e) => {
|
|
310
|
+
if (e.name !== routeName) {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
const route = makeRouteObject(self.dgraph, e);
|
|
314
|
+
try {
|
|
315
|
+
return self.follows[routeName][`${e.v}/${e.w}`](route, context);
|
|
316
|
+
}
|
|
317
|
+
catch (ex) {
|
|
318
|
+
log.warn('Route follow function threw an exception, "%s" (%s)', ex.message, `${e.v}/${e.w}`);
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
// When there's more than one candidate route to take, we need help to choose
|
|
323
|
+
if (target.length > 1) {
|
|
324
|
+
const satisifed = target.map((t) => `${t.v} -> ${t.w}`);
|
|
325
|
+
log.debug(`Multiple routes were satisfied for "${routeName}" from "${startWP}" (${satisifed.join(' / ')}). Deciding how to resolve ...`);
|
|
326
|
+
if (arbiter === 'auto') {
|
|
327
|
+
log.debug('Using automatic arbitration process');
|
|
328
|
+
const targetNames = target.map(({ w }) => w);
|
|
329
|
+
const forwardTraversal = this.traverseNextRoutes(context, {
|
|
330
|
+
stopCondition: ({ source }) => targetNames.includes(source),
|
|
331
|
+
});
|
|
332
|
+
const resolved = forwardTraversal.pop();
|
|
333
|
+
target = target.filter(t => t.w === resolved.source);
|
|
334
|
+
}
|
|
335
|
+
else if (arbiter instanceof Function) {
|
|
336
|
+
log.debug('Using custom arbitration process');
|
|
337
|
+
target = arbiter(target, Object.assign({ context }, options));
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
log.warn('Unable to arbitrate');
|
|
341
|
+
target = [];
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (target.length === 1) {
|
|
345
|
+
const route = makeRouteObject(self.dgraph, target[0]);
|
|
346
|
+
const routeHash = `${route.name}/${route.source}/${route.target}`;
|
|
347
|
+
if (stopCondition(route)) {
|
|
348
|
+
return [route];
|
|
349
|
+
}
|
|
350
|
+
if (!history.has(routeHash)) {
|
|
351
|
+
history.set(routeHash, null);
|
|
352
|
+
const traversed = traverse(target[0].w);
|
|
353
|
+
const totalTrav = traversed.length;
|
|
354
|
+
const results = new Array(totalTrav + 1);
|
|
355
|
+
results[0] = route;
|
|
356
|
+
for (let i = 0; i < totalTrav; i++) {
|
|
357
|
+
results[i + 1] = traversed[i];
|
|
358
|
+
}
|
|
359
|
+
return results;
|
|
360
|
+
}
|
|
361
|
+
log.debug('Encountered loop (%s). Stopping traversal.', `${route.source} -> ${route.target}`);
|
|
362
|
+
}
|
|
363
|
+
return [makeRouteObject(self.dgraph, {
|
|
364
|
+
v: startWP,
|
|
365
|
+
w: null,
|
|
366
|
+
name: routeName,
|
|
367
|
+
label: {},
|
|
368
|
+
})];
|
|
369
|
+
};
|
|
370
|
+
return traverse(startWaypoint);
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Get raw graph data structure. This can be used with other libraries to
|
|
374
|
+
* generate graph visualisations, for example.
|
|
375
|
+
*
|
|
376
|
+
* @returns {graphlib.Graph} Graph data structure.
|
|
377
|
+
*/
|
|
378
|
+
getGraphStructure() {
|
|
379
|
+
return priv.get(this).dgraph;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
exports.default = Plan;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export default class ValidationError {
|
|
2
|
+
/**
|
|
3
|
+
* Make a ValidationError instance from a primitive object (or a function that
|
|
4
|
+
* returns a primitive object) that is specific to the given journey context.
|
|
5
|
+
*
|
|
6
|
+
* The returned `error` (or the function that returns the equivalent) must
|
|
7
|
+
* match the structure required by the ValidationError constructor.
|
|
8
|
+
*
|
|
9
|
+
* `errorMsg` can be one of these formats:
|
|
10
|
+
* String => 'common:errors.my-error-message'
|
|
11
|
+
* Object => (see constructor argument for structure of this object)
|
|
12
|
+
* Function => Function returns object suitable for constructor (see example below)
|
|
13
|
+
* Error => A JavaScript error. It's `message` will be used as the error.
|
|
14
|
+
*
|
|
15
|
+
* `dataContext` is an object containing the same data passed to all validator
|
|
16
|
+
* functions, and contains:
|
|
17
|
+
* waypointId => The current waypoint being requested
|
|
18
|
+
* fieldName => Name of the field being validated
|
|
19
|
+
* journeyContext => The full JourneyContext of the current request.
|
|
20
|
+
*
|
|
21
|
+
* Example function signature that can be used for `errorMsg`:
|
|
22
|
+
* ({ waypointId, fieldName, journeyContext }) => ({
|
|
23
|
+
* summary: 'my-waypoint:some.key.to.say.hello',
|
|
24
|
+
* variables: {
|
|
25
|
+
* name: journeyContext.getDataForPage(waypointId).name,
|
|
26
|
+
* }
|
|
27
|
+
* });
|
|
28
|
+
*
|
|
29
|
+
* @param {object} args See args above
|
|
30
|
+
* @param {any} args.errorMsg Error message to seed the ValidationError
|
|
31
|
+
* @param {object} args.dataContext Validation context
|
|
32
|
+
* @returns {object} Primitive error matching structure above
|
|
33
|
+
* @throws {TypeError} If errorMsg is not in a valid type
|
|
34
|
+
*/
|
|
35
|
+
static make({ errorMsg, dataContext }: {
|
|
36
|
+
errorMsg: any;
|
|
37
|
+
dataContext: object;
|
|
38
|
+
}): object;
|
|
39
|
+
/**
|
|
40
|
+
* `error` may be a simple string, in which case that string reppresents the
|
|
41
|
+
* error mesaage (equivalent to `error.summary` in the structure below).
|
|
42
|
+
*
|
|
43
|
+
* `error`, when passed as an object, must match this structure:
|
|
44
|
+
*
|
|
45
|
+
* {
|
|
46
|
+
* summary: "", // required
|
|
47
|
+
* inline: "", // optional, may be deprecated in future
|
|
48
|
+
* focusSuffix: "", // optional
|
|
49
|
+
* fieldKeySuffix: "", // optional
|
|
50
|
+
* variables: { // optional
|
|
51
|
+
* myVariable: 'a value'
|
|
52
|
+
* }
|
|
53
|
+
* }
|
|
54
|
+
*
|
|
55
|
+
* @param {object|string} errorParam See object structure above
|
|
56
|
+
*/
|
|
57
|
+
constructor(errorParam?: object | string);
|
|
58
|
+
/**
|
|
59
|
+
* Modifies the error to reflect the given context.
|
|
60
|
+
*
|
|
61
|
+
* @param {ValidateContext} context See structure above
|
|
62
|
+
* @returns {ValidationError} Chain
|
|
63
|
+
*/
|
|
64
|
+
withContext(context: any): ValidationError;
|
|
65
|
+
variables: any;
|
|
66
|
+
field: any;
|
|
67
|
+
fieldHref: string | undefined;
|
|
68
|
+
focusSuffix: any;
|
|
69
|
+
validator: any;
|
|
70
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
7
|
+
const { isPlainObject } = lodash_1.default; // CommonJS
|
|
8
|
+
const params = new WeakMap();
|
|
9
|
+
class ValidationError {
|
|
10
|
+
/**
|
|
11
|
+
* Make a ValidationError instance from a primitive object (or a function that
|
|
12
|
+
* returns a primitive object) that is specific to the given journey context.
|
|
13
|
+
*
|
|
14
|
+
* The returned `error` (or the function that returns the equivalent) must
|
|
15
|
+
* match the structure required by the ValidationError constructor.
|
|
16
|
+
*
|
|
17
|
+
* `errorMsg` can be one of these formats:
|
|
18
|
+
* String => 'common:errors.my-error-message'
|
|
19
|
+
* Object => (see constructor argument for structure of this object)
|
|
20
|
+
* Function => Function returns object suitable for constructor (see example below)
|
|
21
|
+
* Error => A JavaScript error. It's `message` will be used as the error.
|
|
22
|
+
*
|
|
23
|
+
* `dataContext` is an object containing the same data passed to all validator
|
|
24
|
+
* functions, and contains:
|
|
25
|
+
* waypointId => The current waypoint being requested
|
|
26
|
+
* fieldName => Name of the field being validated
|
|
27
|
+
* journeyContext => The full JourneyContext of the current request.
|
|
28
|
+
*
|
|
29
|
+
* Example function signature that can be used for `errorMsg`:
|
|
30
|
+
* ({ waypointId, fieldName, journeyContext }) => ({
|
|
31
|
+
* summary: 'my-waypoint:some.key.to.say.hello',
|
|
32
|
+
* variables: {
|
|
33
|
+
* name: journeyContext.getDataForPage(waypointId).name,
|
|
34
|
+
* }
|
|
35
|
+
* });
|
|
36
|
+
*
|
|
37
|
+
* @param {object} args See args above
|
|
38
|
+
* @param {any} args.errorMsg Error message to seed the ValidationError
|
|
39
|
+
* @param {object} args.dataContext Validation context
|
|
40
|
+
* @returns {object} Primitive error matching structure above
|
|
41
|
+
* @throws {TypeError} If errorMsg is not in a valid type
|
|
42
|
+
*/
|
|
43
|
+
static make({ errorMsg, dataContext = {} }) {
|
|
44
|
+
// Convert strings to the most basic object primitive
|
|
45
|
+
if (typeof errorMsg === 'string') {
|
|
46
|
+
return new ValidationError({
|
|
47
|
+
summary: errorMsg,
|
|
48
|
+
inline: errorMsg,
|
|
49
|
+
focusSuffix: [],
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
// No contextual changes applicable; return ValidationErorr made from the
|
|
53
|
+
// original object
|
|
54
|
+
if (isPlainObject(errorMsg)) {
|
|
55
|
+
return new ValidationError(errorMsg);
|
|
56
|
+
}
|
|
57
|
+
// Use the user-defined function to generate an error primitive for the
|
|
58
|
+
// given context
|
|
59
|
+
if (typeof errorMsg === 'function') {
|
|
60
|
+
return new ValidationError(errorMsg.call(null, Object.assign({}, dataContext)));
|
|
61
|
+
}
|
|
62
|
+
// Core Error
|
|
63
|
+
if (errorMsg instanceof Error) {
|
|
64
|
+
return new ValidationError({
|
|
65
|
+
summary: errorMsg.message,
|
|
66
|
+
inline: errorMsg.message,
|
|
67
|
+
focusSuffix: errorMsg.focusSuffix || [],
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
// Unsupported
|
|
71
|
+
throw new TypeError('errorMsg must be a string, Error, primitive object or function that generates a primitive object');
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* `error` may be a simple string, in which case that string reppresents the
|
|
75
|
+
* error mesaage (equivalent to `error.summary` in the structure below).
|
|
76
|
+
*
|
|
77
|
+
* `error`, when passed as an object, must match this structure:
|
|
78
|
+
*
|
|
79
|
+
* {
|
|
80
|
+
* summary: "", // required
|
|
81
|
+
* inline: "", // optional, may be deprecated in future
|
|
82
|
+
* focusSuffix: "", // optional
|
|
83
|
+
* fieldKeySuffix: "", // optional
|
|
84
|
+
* variables: { // optional
|
|
85
|
+
* myVariable: 'a value'
|
|
86
|
+
* }
|
|
87
|
+
* }
|
|
88
|
+
*
|
|
89
|
+
* @param {object|string} errorParam See object structure above
|
|
90
|
+
*/
|
|
91
|
+
constructor(errorParam = {}) {
|
|
92
|
+
if (!isPlainObject(errorParam) && typeof errorParam !== 'string') {
|
|
93
|
+
throw new TypeError('Constructor requires a string or object');
|
|
94
|
+
}
|
|
95
|
+
const error = typeof errorParam === 'string' ? { summary: errorParam } : errorParam;
|
|
96
|
+
// Store parameters for later use in applying contexts
|
|
97
|
+
const originals = {
|
|
98
|
+
summary: error.summary,
|
|
99
|
+
// may be deprecated; summary and inline should always match
|
|
100
|
+
inline: error.inline || error.summary,
|
|
101
|
+
focusSuffix: error.focusSuffix || [],
|
|
102
|
+
fieldKeySuffix: error.fieldKeySuffix || undefined,
|
|
103
|
+
variables: error.variables || {},
|
|
104
|
+
message: error.summary,
|
|
105
|
+
field: error.field || undefined,
|
|
106
|
+
fieldHref: error.fieldHref || undefined,
|
|
107
|
+
validator: error.validator || undefined,
|
|
108
|
+
};
|
|
109
|
+
params.set(this, originals);
|
|
110
|
+
// Duplicate parameters to make them available in public scope. These are
|
|
111
|
+
// the values that will be readable, and reflect any context that may have
|
|
112
|
+
// been applied
|
|
113
|
+
Object.keys(originals).forEach((o) => {
|
|
114
|
+
Object.defineProperty(this, o, {
|
|
115
|
+
value: originals[o],
|
|
116
|
+
enumerable: true,
|
|
117
|
+
writable: true,
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Modifies the error to reflect the given context.
|
|
123
|
+
*
|
|
124
|
+
* @param {ValidateContext} context See structure above
|
|
125
|
+
* @returns {ValidationError} Chain
|
|
126
|
+
*/
|
|
127
|
+
withContext(context) {
|
|
128
|
+
// Get original constructor parameters
|
|
129
|
+
const originals = params.get(this);
|
|
130
|
+
// Expand variables
|
|
131
|
+
if (typeof originals.variables === 'function') {
|
|
132
|
+
this.variables = originals.variables.call(this, context);
|
|
133
|
+
}
|
|
134
|
+
// Set field name
|
|
135
|
+
if (context.fieldName) {
|
|
136
|
+
let focusSuffix;
|
|
137
|
+
let fieldHref = `#f-${context.fieldName}`;
|
|
138
|
+
if (originals.fieldKeySuffix) {
|
|
139
|
+
fieldHref += originals.fieldKeySuffix;
|
|
140
|
+
}
|
|
141
|
+
else if (originals.focusSuffix && originals.focusSuffix.length) {
|
|
142
|
+
focusSuffix = Array.isArray(originals.focusSuffix)
|
|
143
|
+
? originals.focusSuffix
|
|
144
|
+
: [originals.focusSuffix];
|
|
145
|
+
fieldHref += focusSuffix[0];
|
|
146
|
+
}
|
|
147
|
+
this.field = context.fieldName + (originals.fieldKeySuffix || '');
|
|
148
|
+
this.fieldHref = fieldHref;
|
|
149
|
+
this.focusSuffix = focusSuffix || [];
|
|
150
|
+
}
|
|
151
|
+
// Set validator name
|
|
152
|
+
this.validator = context.validator || undefined;
|
|
153
|
+
return this;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
exports.default = ValidationError;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export default class ValidatorFactory {
|
|
2
|
+
/**
|
|
3
|
+
* This is a convenience method that will bind and return this class'
|
|
4
|
+
* `validate()` function, so you can call it directly rather than calling the
|
|
5
|
+
* method. i.e.
|
|
6
|
+
*
|
|
7
|
+
* MyValidator.make()('value to validate')
|
|
8
|
+
* versus
|
|
9
|
+
* (new MyValidator()).validate('value to validate')
|
|
10
|
+
*
|
|
11
|
+
* It also attaches the `sanitise()` method as a static property to that
|
|
12
|
+
* function.
|
|
13
|
+
*
|
|
14
|
+
* @param {object} config Validator config
|
|
15
|
+
* @returns {object} Validator object
|
|
16
|
+
* @throws {TypeError} When configurarion is invalid.
|
|
17
|
+
*/
|
|
18
|
+
static make(config?: object): object;
|
|
19
|
+
static coerceToValidatorObject(input: any): any;
|
|
20
|
+
constructor(config?: {});
|
|
21
|
+
config: {};
|
|
22
|
+
validate(fieldValue: any, context: any): void;
|
|
23
|
+
sanitise(fieldValue: any): any;
|
|
24
|
+
}
|