@inseefr/lunatic 3.4.21 → 3.5.0-rc.1
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/esm/type.source.d.ts +5 -1
- package/esm/use-lunatic/commons/variables/behaviours/cleaning-behaviour.d.ts +1 -1
- package/esm/use-lunatic/commons/variables/behaviours/cleaning-behaviour.js +111 -14
- package/esm/use-lunatic/commons/variables/behaviours/cleaning-behaviour.js.map +1 -1
- package/esm/use-lunatic/commons/variables/lunatic-variables-store.d.ts +1 -1
- package/esm/use-lunatic/commons/variables/lunatic-variables-store.spec.js +93 -0
- package/esm/use-lunatic/commons/variables/lunatic-variables-store.spec.js.map +1 -1
- package/esm/use-lunatic/use-lunatic.d.ts +1 -2
- package/esm/use-lunatic/use-lunatic.js.map +1 -1
- package/esm/utils/cast.d.ts +19 -0
- package/esm/utils/cast.js +63 -0
- package/esm/utils/cast.js.map +1 -0
- package/package.json +10 -1
- package/src/stories/behaviour/cleaning/cleaning.stories.jsx +11 -0
- package/src/stories/behaviour/cleaning/loop.json +246 -0
- package/src/stories/behaviour/performance/performance.stories.jsx +8 -0
- package/src/stories/behaviour/performance/srcv.json +44747 -0
- package/src/type.source.ts +7 -1
- package/src/use-lunatic/commons/variables/behaviours/cleaning-behaviour.ts +182 -24
- package/src/use-lunatic/commons/variables/lunatic-variables-store.spec.ts +111 -0
- package/src/use-lunatic/commons/variables/lunatic-variables-store.ts +1 -1
- package/src/use-lunatic/use-lunatic.ts +5 -4
- package/src/utils/cast.ts +67 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/type.source.d.ts +5 -1
- package/use-lunatic/commons/variables/behaviours/cleaning-behaviour.d.ts +1 -1
- package/use-lunatic/commons/variables/behaviours/cleaning-behaviour.js +111 -14
- package/use-lunatic/commons/variables/behaviours/cleaning-behaviour.js.map +1 -1
- package/use-lunatic/commons/variables/lunatic-variables-store.d.ts +1 -1
- package/use-lunatic/commons/variables/lunatic-variables-store.spec.js +93 -0
- package/use-lunatic/commons/variables/lunatic-variables-store.spec.js.map +1 -1
- package/use-lunatic/use-lunatic.d.ts +1 -2
- package/use-lunatic/use-lunatic.js.map +1 -1
- package/utils/cast.d.ts +19 -0
- package/utils/cast.js +70 -0
- package/utils/cast.js.map +1 -0
package/src/type.source.ts
CHANGED
|
@@ -272,7 +272,13 @@ export type LunaticSource = {
|
|
|
272
272
|
suggesters?: SuggesterDefinition[];
|
|
273
273
|
cleaning?: {
|
|
274
274
|
[k: string]: {
|
|
275
|
-
[k: string]:
|
|
275
|
+
[k: string]:
|
|
276
|
+
| string
|
|
277
|
+
| {
|
|
278
|
+
expression: string;
|
|
279
|
+
shapeFrom?: string;
|
|
280
|
+
isAggregatorUsed: boolean;
|
|
281
|
+
}[];
|
|
276
282
|
};
|
|
277
283
|
};
|
|
278
284
|
missingBlock?: {
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
IterationLevel,
|
|
3
|
+
LunaticVariablesStore,
|
|
4
|
+
} from '../lunatic-variables-store';
|
|
2
5
|
import type { LunaticSource } from '../../../type';
|
|
3
6
|
import { depth } from '../../../../utils/array';
|
|
7
|
+
import { castBool } from '../../../../utils/cast';
|
|
4
8
|
|
|
5
9
|
/**
|
|
6
10
|
* Cleaning behaviour for the store
|
|
@@ -9,13 +13,30 @@ import { depth } from '../../../../utils/array';
|
|
|
9
13
|
export function cleaningBehaviour(
|
|
10
14
|
store: LunaticVariablesStore,
|
|
11
15
|
cleaning: LunaticSource['cleaning'],
|
|
12
|
-
// Value used as default when cleaning a variable
|
|
13
|
-
|
|
16
|
+
// Value used as default when cleaning a variable, correspoding to value of variable in source.json (not data)
|
|
17
|
+
sourceValues: Record<string, unknown> = {}
|
|
14
18
|
) {
|
|
15
19
|
if (!cleaning) {
|
|
16
20
|
return;
|
|
17
21
|
}
|
|
18
22
|
|
|
23
|
+
// Create calculated variables from cleaning expressions
|
|
24
|
+
for (const source in cleaning) {
|
|
25
|
+
for (const target in cleaning[source]) {
|
|
26
|
+
if (Array.isArray(cleaning[source][target])) {
|
|
27
|
+
for (const cleaningInfo of cleaning[source][target]) {
|
|
28
|
+
store.setCalculated(
|
|
29
|
+
cleaningInfo.expression,
|
|
30
|
+
cleaningInfo.expression,
|
|
31
|
+
{
|
|
32
|
+
shapeFrom: cleaningInfo.shapeFrom,
|
|
33
|
+
}
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
19
40
|
// Create a map to improve performance
|
|
20
41
|
const cleaningMap = new Map(Object.entries(cleaning));
|
|
21
42
|
|
|
@@ -30,28 +51,28 @@ export function cleaningBehaviour(
|
|
|
30
51
|
|
|
31
52
|
for (const variableName in cleaningInfo) {
|
|
32
53
|
try {
|
|
33
|
-
|
|
34
|
-
|
|
54
|
+
// First: check if variable is already cleaned i.e, value is `null`, empty or list of `null`
|
|
55
|
+
if (isAlreadyCleaned(store, variableName)) continue;
|
|
56
|
+
// Second: check if variable should be clean i.e one of expressions is true
|
|
57
|
+
|
|
58
|
+
// shouldClean is simple boolean or array of boolean if there have a shapeFrom
|
|
59
|
+
const shouldCleanResult = shouldClean(store, {
|
|
60
|
+
expressions: cleaningInfo[variableName],
|
|
61
|
+
iteration: iteration,
|
|
62
|
+
isResizing: e.detail.cause === 'resizing',
|
|
35
63
|
});
|
|
36
|
-
|
|
64
|
+
|
|
65
|
+
if (Array.isArray(shouldCleanResult)) {
|
|
66
|
+
cleanArrayVariableAccordingCondition(
|
|
67
|
+
store,
|
|
68
|
+
sourceValues,
|
|
69
|
+
variableName,
|
|
70
|
+
shouldCleanResult
|
|
71
|
+
);
|
|
37
72
|
continue;
|
|
38
73
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const variableDepth = depth(initialValues[variableName]);
|
|
42
|
-
const variableIteration =
|
|
43
|
-
variableDepth === 0
|
|
44
|
-
? undefined
|
|
45
|
-
: iteration?.slice(0, depth(initialValues[variableName]));
|
|
46
|
-
|
|
47
|
-
store.set(
|
|
48
|
-
variableName,
|
|
49
|
-
getValueAtIteration(initialValues[variableName], variableIteration),
|
|
50
|
-
{
|
|
51
|
-
iteration: variableIteration,
|
|
52
|
-
cause: 'cleaning',
|
|
53
|
-
}
|
|
54
|
-
);
|
|
74
|
+
if (shouldCleanResult)
|
|
75
|
+
cleanVariable(store, sourceValues, variableName, iteration);
|
|
55
76
|
} catch (e) {
|
|
56
77
|
// If we have an error, skip this cleaning
|
|
57
78
|
console.error(e);
|
|
@@ -60,14 +81,151 @@ export function cleaningBehaviour(
|
|
|
60
81
|
});
|
|
61
82
|
}
|
|
62
83
|
|
|
84
|
+
function isAlreadyCleaned(store: LunaticVariablesStore, variableName: string) {
|
|
85
|
+
const value = store.get(variableName);
|
|
86
|
+
if (Array.isArray(value)) return value.every((v) => v === null);
|
|
87
|
+
if (value === null) return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Check if a variable need to be cleaned
|
|
92
|
+
*/
|
|
93
|
+
function shouldClean(
|
|
94
|
+
store: LunaticVariablesStore,
|
|
95
|
+
{
|
|
96
|
+
// The expressions are a list of condition filter to display the variable, so we should clean if the filter is evaluated to false (false = variable is not visible)
|
|
97
|
+
expressions,
|
|
98
|
+
iteration,
|
|
99
|
+
isResizing,
|
|
100
|
+
}: {
|
|
101
|
+
expressions:
|
|
102
|
+
| string
|
|
103
|
+
| {
|
|
104
|
+
expression: string;
|
|
105
|
+
shapeFrom?: string;
|
|
106
|
+
isAggregatorUsed: boolean;
|
|
107
|
+
}[];
|
|
108
|
+
iteration?: number[];
|
|
109
|
+
isResizing: boolean;
|
|
110
|
+
}
|
|
111
|
+
) {
|
|
112
|
+
// Legacy cleaning used a simple string
|
|
113
|
+
if (typeof expressions === 'string') {
|
|
114
|
+
return !castBool(
|
|
115
|
+
store.run(expressions, {
|
|
116
|
+
iteration,
|
|
117
|
+
})
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// New format use tuples { expression, shapeFrom, isAggregatorUsed }
|
|
122
|
+
if (isResizing) {
|
|
123
|
+
// If we are resizing a variable, only run expression containing aggregators (count(), sum()...)
|
|
124
|
+
expressions = expressions.filter((expr) => expr.isAggregatorUsed);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// here, value has change in root scope, but we have to to check for each iteration of variable
|
|
128
|
+
if (hasShapeFrom(expressions) && !iteration) {
|
|
129
|
+
const shapeFromVariable = store.get(
|
|
130
|
+
expressions[0].shapeFrom as string
|
|
131
|
+
) as Array<unknown>;
|
|
132
|
+
|
|
133
|
+
const shouldCleanArray = new Array(shapeFromVariable.length).fill(
|
|
134
|
+
false
|
|
135
|
+
) as Array<boolean>;
|
|
136
|
+
|
|
137
|
+
for (const [iterationIndex] of shouldCleanArray.entries()) {
|
|
138
|
+
shouldCleanArray[iterationIndex] = shouldClean(store, {
|
|
139
|
+
expressions,
|
|
140
|
+
iteration: [iterationIndex],
|
|
141
|
+
isResizing,
|
|
142
|
+
}) as boolean;
|
|
143
|
+
}
|
|
144
|
+
return shouldCleanArray;
|
|
145
|
+
} else {
|
|
146
|
+
// if only one expression is false, we have to clean (condition is display condition)
|
|
147
|
+
for (const expression of expressions) {
|
|
148
|
+
// Run the expression to check if cleaning should happen
|
|
149
|
+
if (
|
|
150
|
+
!store.run(expression.expression, {
|
|
151
|
+
iteration,
|
|
152
|
+
})
|
|
153
|
+
)
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
63
161
|
function getValueAtIteration(value: unknown, iteration?: number[]) {
|
|
64
162
|
if (!iteration || iteration.length === 0) {
|
|
65
163
|
return value ?? null;
|
|
66
164
|
}
|
|
67
|
-
|
|
68
165
|
if (!Array.isArray(value)) {
|
|
69
166
|
return null;
|
|
70
167
|
}
|
|
71
|
-
|
|
72
168
|
return getValueAtIteration(value[iteration[0]], iteration.slice(1));
|
|
73
169
|
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* hasShapeFrom
|
|
173
|
+
* actually, in cleaning modelisation,
|
|
174
|
+
* all expressions have no shapeFrom or have the **same** shapeFrom
|
|
175
|
+
* @param expressions
|
|
176
|
+
* @returns boolean if all expression has shapeFrom
|
|
177
|
+
*
|
|
178
|
+
*/
|
|
179
|
+
function hasShapeFrom(
|
|
180
|
+
expressions: {
|
|
181
|
+
expression: string;
|
|
182
|
+
shapeFrom?: string;
|
|
183
|
+
isAggregatorUsed: boolean;
|
|
184
|
+
}[]
|
|
185
|
+
) {
|
|
186
|
+
return expressions.every(
|
|
187
|
+
(expression) =>
|
|
188
|
+
expression.shapeFrom !== null && expression.shapeFrom !== undefined
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function cleanArrayVariableAccordingCondition(
|
|
193
|
+
store: LunaticVariablesStore,
|
|
194
|
+
sourceValues: Record<string, unknown>,
|
|
195
|
+
variableName: string,
|
|
196
|
+
shouldClean: boolean[]
|
|
197
|
+
) {
|
|
198
|
+
for (const [iteration, shouldCleanByIteration] of shouldClean.entries()) {
|
|
199
|
+
if (shouldCleanByIteration)
|
|
200
|
+
cleanVariable(store, sourceValues, variableName, [iteration]);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* cleanVariable: this function set to null (and not initalValue) the variable at iteration
|
|
206
|
+
* @param store
|
|
207
|
+
* @param variableName
|
|
208
|
+
* @param iteration
|
|
209
|
+
*/
|
|
210
|
+
function cleanVariable(
|
|
211
|
+
store: LunaticVariablesStore,
|
|
212
|
+
sourceValues: Record<string, unknown>,
|
|
213
|
+
variableName: string,
|
|
214
|
+
iteration: IterationLevel | undefined
|
|
215
|
+
) {
|
|
216
|
+
// Variable may be top level, so we need to deduce expected iteration
|
|
217
|
+
const variableDepth = depth(sourceValues[variableName]);
|
|
218
|
+
const variableIteration =
|
|
219
|
+
variableDepth === 0
|
|
220
|
+
? undefined
|
|
221
|
+
: iteration?.slice(0, depth(sourceValues[variableName]));
|
|
222
|
+
|
|
223
|
+
store.set(
|
|
224
|
+
variableName,
|
|
225
|
+
getValueAtIteration(sourceValues[variableName], variableIteration),
|
|
226
|
+
{
|
|
227
|
+
iteration: variableIteration,
|
|
228
|
+
cause: 'cleaning',
|
|
229
|
+
}
|
|
230
|
+
);
|
|
231
|
+
}
|
|
@@ -104,6 +104,17 @@ describe('lunatic-variables-store', () => {
|
|
|
104
104
|
expect(() => variables.run('Hello world')).toThrowError();
|
|
105
105
|
});
|
|
106
106
|
|
|
107
|
+
it('should handle calculated with aggregates', () => {
|
|
108
|
+
variables.set('AGES', [1, 2, 3]);
|
|
109
|
+
variables.setCalculated('NB_HAB', 'count(AGES)');
|
|
110
|
+
variables.setCalculated('AGES_PLUS_NBHAB', 'NB_HAB + AGES', {
|
|
111
|
+
shapeFrom: 'AGES',
|
|
112
|
+
});
|
|
113
|
+
expect(variables.get('NB_HAB')).toBe(3);
|
|
114
|
+
expect(variables.get('AGES_PLUS_NBHAB', [0])).toBe(4);
|
|
115
|
+
expect(variables.get('AGES_PLUS_NBHAB')).toEqual([4, 5, 6]);
|
|
116
|
+
});
|
|
117
|
+
|
|
107
118
|
describe('event listener', () => {
|
|
108
119
|
it('should trigger onChange', () => {
|
|
109
120
|
variables.set('FIRSTNAME', 'John');
|
|
@@ -415,6 +426,106 @@ describe('lunatic-variables-store', () => {
|
|
|
415
426
|
variables.set('READY', false, { iteration: [1] });
|
|
416
427
|
expect(variables.get('PRENOM')).toEqual(null);
|
|
417
428
|
});
|
|
429
|
+
it('should handle the new array format', () => {
|
|
430
|
+
variables.set('PRENOM', ['John', 'Jane', 'Marc']);
|
|
431
|
+
variables.set('READY', [true, true, true]);
|
|
432
|
+
cleaningBehaviour(
|
|
433
|
+
variables,
|
|
434
|
+
{
|
|
435
|
+
READY: {
|
|
436
|
+
PRENOM: [
|
|
437
|
+
{
|
|
438
|
+
expression: 'READY',
|
|
439
|
+
shapeFrom: 'READY',
|
|
440
|
+
isAggregatorUsed: false,
|
|
441
|
+
},
|
|
442
|
+
],
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
{ PRENOM: [null] }
|
|
446
|
+
);
|
|
447
|
+
variables.set('READY', false, { iteration: [1] });
|
|
448
|
+
expect(variables.get('PRENOM')).toEqual(['John', null, 'Marc']);
|
|
449
|
+
});
|
|
450
|
+
it('should handle cleaning on aggregations', () => {
|
|
451
|
+
variables.set('PRENOM', ['John', 'Jane', 'Marc']);
|
|
452
|
+
variables.set('READY', [true, true, true]);
|
|
453
|
+
variables.setCalculated('NB_HAB', 'count(PRENOM)');
|
|
454
|
+
cleaningBehaviour(
|
|
455
|
+
variables,
|
|
456
|
+
{
|
|
457
|
+
READY: {
|
|
458
|
+
PRENOM: [
|
|
459
|
+
{
|
|
460
|
+
expression: 'READY',
|
|
461
|
+
shapeFrom: 'READY',
|
|
462
|
+
isAggregatorUsed: false,
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
expression: 'NB_HAB > 1',
|
|
466
|
+
isAggregatorUsed: true,
|
|
467
|
+
},
|
|
468
|
+
],
|
|
469
|
+
},
|
|
470
|
+
},
|
|
471
|
+
{ PRENOM: [null] }
|
|
472
|
+
);
|
|
473
|
+
variables.set('READY', false, { iteration: [1], cause: 'resizing' });
|
|
474
|
+
expect(variables.get('PRENOM')).toEqual(['John', 'Jane', 'Marc']);
|
|
475
|
+
variables.set('PRENOM', ['John'], { cause: 'resizing' });
|
|
476
|
+
variables.set('READY', [true], { cause: 'resizing' });
|
|
477
|
+
expect(variables.get('PRENOM')).toEqual([null]);
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
it('should evaluate cleaning for each iteration', () => {
|
|
481
|
+
variables.set('PRENOM', ['John', 'Jane', 'Marc']);
|
|
482
|
+
variables.set('READY', [true, true, true]);
|
|
483
|
+
cleaningBehaviour(
|
|
484
|
+
variables,
|
|
485
|
+
{
|
|
486
|
+
READY: {
|
|
487
|
+
PRENOM: [
|
|
488
|
+
{
|
|
489
|
+
expression: 'READY',
|
|
490
|
+
shapeFrom: 'READY',
|
|
491
|
+
isAggregatorUsed: false,
|
|
492
|
+
},
|
|
493
|
+
],
|
|
494
|
+
},
|
|
495
|
+
},
|
|
496
|
+
{ PRENOM: [null] }
|
|
497
|
+
);
|
|
498
|
+
variables.set('READY', [true, true, false]);
|
|
499
|
+
expect(variables.get('PRENOM')).toEqual(['John', 'Jane', null]);
|
|
500
|
+
});
|
|
501
|
+
it('should evaluate cleaning for each iteration at root levl', () => {
|
|
502
|
+
variables.set('PRENOM', ['John', 'Jane', 'Marc']);
|
|
503
|
+
variables.set('AGE', [18, 21, 23]);
|
|
504
|
+
variables.setCalculated('NB_HAB', 'count(PRENOM)');
|
|
505
|
+
resizingBehaviour(variables, {
|
|
506
|
+
PRENOM: {
|
|
507
|
+
size: 'count(PRENOM)',
|
|
508
|
+
variables: ['AGE'],
|
|
509
|
+
},
|
|
510
|
+
});
|
|
511
|
+
cleaningBehaviour(
|
|
512
|
+
variables,
|
|
513
|
+
{
|
|
514
|
+
PRENOM: {
|
|
515
|
+
AGE: [
|
|
516
|
+
{
|
|
517
|
+
expression: 'NB_HAB >= 3',
|
|
518
|
+
shapeFrom: 'PRENOM',
|
|
519
|
+
isAggregatorUsed: true,
|
|
520
|
+
},
|
|
521
|
+
],
|
|
522
|
+
},
|
|
523
|
+
},
|
|
524
|
+
{ PRENOM: [], AGE: [] }
|
|
525
|
+
);
|
|
526
|
+
variables.set('PRENOM', ['John', 'Jane']);
|
|
527
|
+
expect(variables.get('AGE')).toEqual([null, null]);
|
|
528
|
+
});
|
|
418
529
|
});
|
|
419
530
|
|
|
420
531
|
describe('missing', () => {
|
|
@@ -24,7 +24,7 @@ let interpretCount = 0;
|
|
|
24
24
|
/** Special variable that will take the current iteration value. */
|
|
25
25
|
const iterationVariableName = 'GLOBAL_ITERATION_INDEX';
|
|
26
26
|
|
|
27
|
-
type IterationLevel = number[];
|
|
27
|
+
export type IterationLevel = number[];
|
|
28
28
|
export type EventArgs = {
|
|
29
29
|
change: {
|
|
30
30
|
/** Name of the changed variable. */
|
|
@@ -6,17 +6,18 @@ import {
|
|
|
6
6
|
handleChangesAction,
|
|
7
7
|
} from './actions';
|
|
8
8
|
import { getPageTag, isFirstLastPage } from './commons';
|
|
9
|
+
|
|
10
|
+
import D from '../i18n';
|
|
11
|
+
import { COLLECTED } from '../utils/constants';
|
|
12
|
+
import { createLunaticProvider } from './lunatic-context';
|
|
9
13
|
import type {
|
|
14
|
+
LunaticSource,
|
|
10
15
|
LunaticChangesHandler,
|
|
11
16
|
LunaticData,
|
|
12
17
|
LunaticOptions,
|
|
13
18
|
LunaticState,
|
|
14
19
|
PageTag,
|
|
15
20
|
} from './type';
|
|
16
|
-
import D from '../i18n';
|
|
17
|
-
import { COLLECTED } from '../utils/constants';
|
|
18
|
-
import { createLunaticProvider } from './lunatic-context';
|
|
19
|
-
import type { LunaticSource } from './type';
|
|
20
21
|
import { compileControls as compileControlsLib } from './commons/compile-controls';
|
|
21
22
|
import { useLoopVariables } from './hooks/use-loop-variables';
|
|
22
23
|
import { getQuestionnaireData } from './commons/variables/get-questionnaire-data';
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cast functions that force an unknown type to a specific type
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Force a number (throw if it cannot be converted)
|
|
7
|
+
*/
|
|
8
|
+
export function castNumber(v: unknown): number {
|
|
9
|
+
if (typeof v === 'number') {
|
|
10
|
+
return v;
|
|
11
|
+
}
|
|
12
|
+
if (typeof v === 'string') {
|
|
13
|
+
return parseInt(v, 10);
|
|
14
|
+
}
|
|
15
|
+
if (Array.isArray(v) && v.length > 0) {
|
|
16
|
+
return castNumber(v[0]);
|
|
17
|
+
}
|
|
18
|
+
throw new Error(`Cannot cast "${v}" to number`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Force a number, with a default value if an unmanageable type is encountered
|
|
23
|
+
*/
|
|
24
|
+
export const castNumberWithDefault =
|
|
25
|
+
(initial: number = 0) =>
|
|
26
|
+
(v: unknown): number => {
|
|
27
|
+
try {
|
|
28
|
+
return castNumber(v);
|
|
29
|
+
} catch {
|
|
30
|
+
return initial;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Force a bool
|
|
36
|
+
*/
|
|
37
|
+
export function castBool(v: unknown): boolean {
|
|
38
|
+
if (typeof v === 'boolean') {
|
|
39
|
+
return v;
|
|
40
|
+
}
|
|
41
|
+
if (Array.isArray(v) && v.length > 0) {
|
|
42
|
+
return castBool(v[0]);
|
|
43
|
+
}
|
|
44
|
+
if (Array.isArray(v)) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
return Boolean(v);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Force a string
|
|
52
|
+
*/
|
|
53
|
+
export function castString(v: unknown): string {
|
|
54
|
+
if (typeof v === 'string') {
|
|
55
|
+
return v;
|
|
56
|
+
}
|
|
57
|
+
if (typeof v === 'number') {
|
|
58
|
+
return v.toString();
|
|
59
|
+
}
|
|
60
|
+
if (Array.isArray(v)) {
|
|
61
|
+
return v.map(castString).join(', ');
|
|
62
|
+
}
|
|
63
|
+
if (!v) {
|
|
64
|
+
return '';
|
|
65
|
+
}
|
|
66
|
+
return v.toString();
|
|
67
|
+
}
|