Skip to content

Commit 86d40a9

Browse files
Merge pull request #320 from getodk/fix/relax-preload-parse-spec-enforcement
engine (fix): Forms load with any `jr:preload` or `jr:preloadParams` value
2 parents d7ecc33 + 2bf859e commit 86d40a9

File tree

3 files changed

+33
-101
lines changed

3 files changed

+33
-101
lines changed

.changeset/six-moose-leave.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@getodk/xforms-engine': patch
3+
---
4+
5+
Fix: relax parsing of `jr:preload` and `jr:preloadParams`. Any value for either attribute is accepted. Known (specified in ODK XForms, at time of writing) values are provided as type hints, similarly to how known appearances are specified.

packages/xforms-engine/src/error/UnknownPreloadAttributeValueNotice.ts

-35
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,29 @@
11
import { JAVAROSA_NAMESPACE_URI } from '@getodk/common/constants/xmlns.ts';
2-
import { getPropertyKeys } from '@getodk/common/lib/objects/structure.ts';
3-
import { UnknownPreloadAttributeValueNotice } from '../../error/UnknownPreloadAttributeValueNotice.ts';
2+
import type { PartiallyKnownString } from '@getodk/common/types/string/PartiallyKnownString.ts';
43
import type { BindElement } from './BindElement.ts';
54

6-
const preloadParametersByType = {
7-
uid: [null],
8-
date: ['today'],
9-
timestamp: ['start', 'end'],
10-
property: ['deviceid', 'email', 'username', 'phonenumber'],
11-
} as const;
12-
13-
const preloadParameterTypes = getPropertyKeys(preloadParametersByType);
14-
15-
type PreloadParametersByType = typeof preloadParametersByType;
16-
17-
type PreloadType = keyof PreloadParametersByType;
18-
19-
type AssertPreloadType = (type: string) => asserts type is PreloadType;
20-
21-
const assertPreloadType: AssertPreloadType = (type) => {
22-
if (!preloadParameterTypes.includes(type as PreloadType)) {
23-
throw new UnknownPreloadAttributeValueNotice('jr:preload', preloadParameterTypes, type);
24-
}
25-
};
26-
27-
const getPreloadType = (bindElement: BindElement): PreloadType | null => {
28-
const type = bindElement.getAttributeNS(JAVAROSA_NAMESPACE_URI, 'preload');
29-
30-
if (type == null) {
31-
return null;
32-
}
33-
34-
assertPreloadType(type);
35-
36-
return type;
37-
};
38-
39-
type PreloadParameter<Type extends PreloadType> = PreloadParametersByType[Type][number];
40-
41-
type AssertPreloadParameter = <Type extends PreloadType>(
42-
type: Type,
43-
parameter: string | null
44-
) => asserts parameter is PreloadParameter<Type>;
5+
type PartiallyKnownPreloadParameter<Known extends string> =
6+
// eslint-disable-next-line @typescript-eslint/sort-type-constituents
7+
PartiallyKnownString<NonNullable<Known>> | Extract<Known, null>;
458

46-
const assertPreloadParameter: AssertPreloadParameter = <Type extends PreloadType>(
47-
type: Type,
48-
parameter: string | null
49-
) => {
50-
const parameters: ReadonlyArray<PreloadParameter<Type>> = preloadParametersByType[type];
9+
interface PreloadParametersByType {
10+
readonly uid: string | null;
11+
readonly date: PartiallyKnownPreloadParameter<'today'>;
12+
readonly timestamp: PartiallyKnownPreloadParameter<'end' | 'start'>;
5113

52-
if (!parameters.includes(parameter as PreloadParameter<Type>)) {
53-
throw new UnknownPreloadAttributeValueNotice('jr:preloadParams', parameters, parameter);
54-
}
55-
};
56-
57-
const getPreloadParameter = <Type extends PreloadType>(
58-
bindElement: BindElement,
59-
type: Type
60-
): PreloadParameter<Type> => {
61-
const parameter = bindElement.getAttributeNS(JAVAROSA_NAMESPACE_URI, 'preloadParams');
14+
readonly property: PartiallyKnownPreloadParameter<
15+
// prettier-ignore
16+
'deviceid' | 'email' | 'phonenumber' | 'username'
17+
>;
18+
}
6219

63-
assertPreloadParameter(type, parameter);
20+
type PreloadType = PartiallyKnownString<keyof PreloadParametersByType>;
6421

65-
return parameter;
66-
};
22+
// prettier-ignore
23+
type PreloadParameter<Type extends PreloadType> =
24+
Type extends keyof PreloadParametersByType
25+
? PreloadParametersByType[Type]
26+
: string | null;
6727

6828
interface PreloadInput<Type extends PreloadType> {
6929
readonly type: Type;
@@ -75,20 +35,21 @@ type AnyPreloadInput = {
7535
}[PreloadType];
7636

7737
const getPreloadInput = (bindElement: BindElement): AnyPreloadInput | null => {
78-
const type = getPreloadType(bindElement);
38+
const type = bindElement.getAttributeNS(JAVAROSA_NAMESPACE_URI, 'preload');
7939

8040
if (type == null) {
8141
return null;
8242
}
8343

84-
type Type = typeof type;
85-
86-
const parameter: PreloadParameter<Type> = getPreloadParameter(bindElement, type);
44+
const parameter: PreloadParameter<typeof type> = bindElement.getAttributeNS(
45+
JAVAROSA_NAMESPACE_URI,
46+
'preloadParams'
47+
);
8748

8849
return {
8950
type,
9051
parameter,
91-
} satisfies PreloadInput<Type> as AnyPreloadInput;
52+
};
9253
};
9354

9455
/**
@@ -118,7 +79,7 @@ export class BindPreloadDefinition<Type extends PreloadType> implements PreloadI
11879
return null;
11980
}
12081

121-
return new this(input) satisfies BindPreloadDefinition<PreloadType> as AnyBindPreloadDefinition;
82+
return new this(input);
12283
}
12384

12485
readonly type: Type;
@@ -135,4 +96,5 @@ export type AnyBindPreloadDefinition =
13596
// eslint-disable-next-line @typescript-eslint/sort-type-constituents
13697
| BindPreloadDefinition<'uid'>
13798
| BindPreloadDefinition<'timestamp'>
138-
| BindPreloadDefinition<'property'>;
99+
| BindPreloadDefinition<'property'>
100+
| BindPreloadDefinition<string>;

0 commit comments

Comments
 (0)