@medplum/core 2.0.23 → 2.0.25
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/cjs/index.cjs +6 -13364
- package/dist/cjs/index.cjs.map +7 -1
- package/dist/esm/index.mjs +31 -26
- package/dist/esm/index.mjs.map +7 -1
- package/dist/types/access.d.ts +48 -0
- package/dist/types/client.d.ts +15 -20
- package/dist/types/fhirlexer/parse.d.ts +5 -4
- package/dist/types/fhirpath/functions.d.ts +1 -3
- package/dist/types/index.d.ts +3 -0
- package/dist/types/outcomes.d.ts +3 -1
- package/dist/types/search/search.d.ts +7 -0
- package/dist/types/typeschema/types.d.ts +5 -2
- package/dist/types/typeschema/validation.d.ts +2 -0
- package/dist/types/utils.d.ts +14 -0
- package/package.json +2 -2
- package/dist/cjs/index.min.cjs +0 -1
- package/dist/esm/base-schema.json.mjs +0 -4408
- package/dist/esm/base-schema.json.mjs.map +0 -1
- package/dist/esm/base64.mjs +0 -33
- package/dist/esm/base64.mjs.map +0 -1
- package/dist/esm/bundle.mjs +0 -36
- package/dist/esm/bundle.mjs.map +0 -1
- package/dist/esm/cache.mjs +0 -64
- package/dist/esm/cache.mjs.map +0 -1
- package/dist/esm/client.mjs +0 -2144
- package/dist/esm/client.mjs.map +0 -1
- package/dist/esm/crypto.mjs +0 -22
- package/dist/esm/crypto.mjs.map +0 -1
- package/dist/esm/eventtarget.mjs +0 -36
- package/dist/esm/eventtarget.mjs.map +0 -1
- package/dist/esm/fhirlexer/parse.mjs +0 -122
- package/dist/esm/fhirlexer/parse.mjs.map +0 -1
- package/dist/esm/fhirlexer/tokenize.mjs +0 -231
- package/dist/esm/fhirlexer/tokenize.mjs.map +0 -1
- package/dist/esm/fhirmapper/parse.mjs +0 -329
- package/dist/esm/fhirmapper/parse.mjs.map +0 -1
- package/dist/esm/fhirmapper/tokenize.mjs +0 -13
- package/dist/esm/fhirmapper/tokenize.mjs.map +0 -1
- package/dist/esm/fhirpath/atoms.mjs +0 -347
- package/dist/esm/fhirpath/atoms.mjs.map +0 -1
- package/dist/esm/fhirpath/date.mjs +0 -24
- package/dist/esm/fhirpath/date.mjs.map +0 -1
- package/dist/esm/fhirpath/functions.mjs +0 -1626
- package/dist/esm/fhirpath/functions.mjs.map +0 -1
- package/dist/esm/fhirpath/parse.mjs +0 -144
- package/dist/esm/fhirpath/parse.mjs.map +0 -1
- package/dist/esm/fhirpath/tokenize.mjs +0 -10
- package/dist/esm/fhirpath/tokenize.mjs.map +0 -1
- package/dist/esm/fhirpath/utils.mjs +0 -378
- package/dist/esm/fhirpath/utils.mjs.map +0 -1
- package/dist/esm/filter/parse.mjs +0 -101
- package/dist/esm/filter/parse.mjs.map +0 -1
- package/dist/esm/filter/tokenize.mjs +0 -16
- package/dist/esm/filter/tokenize.mjs.map +0 -1
- package/dist/esm/filter/types.mjs +0 -34
- package/dist/esm/filter/types.mjs.map +0 -1
- package/dist/esm/format.mjs +0 -390
- package/dist/esm/format.mjs.map +0 -1
- package/dist/esm/hl7.mjs +0 -242
- package/dist/esm/hl7.mjs.map +0 -1
- package/dist/esm/index.min.mjs +0 -1
- package/dist/esm/jwt.mjs +0 -30
- package/dist/esm/jwt.mjs.map +0 -1
- package/dist/esm/node_modules/tslib/package.json +0 -1
- package/dist/esm/outcomes.mjs +0 -271
- package/dist/esm/outcomes.mjs.map +0 -1
- package/dist/esm/readablepromise.mjs +0 -82
- package/dist/esm/readablepromise.mjs.map +0 -1
- package/dist/esm/schema.mjs +0 -417
- package/dist/esm/schema.mjs.map +0 -1
- package/dist/esm/search/details.mjs +0 -164
- package/dist/esm/search/details.mjs.map +0 -1
- package/dist/esm/search/match.mjs +0 -164
- package/dist/esm/search/match.mjs.map +0 -1
- package/dist/esm/search/search.mjs +0 -369
- package/dist/esm/search/search.mjs.map +0 -1
- package/dist/esm/sftp.mjs +0 -24
- package/dist/esm/sftp.mjs.map +0 -1
- package/dist/esm/storage.mjs +0 -95
- package/dist/esm/storage.mjs.map +0 -1
- package/dist/esm/types.mjs +0 -370
- package/dist/esm/types.mjs.map +0 -1
- package/dist/esm/utils.mjs +0 -632
- package/dist/esm/utils.mjs.map +0 -1
package/dist/esm/schema.mjs
DELETED
|
@@ -1,417 +0,0 @@
|
|
|
1
|
-
import { globalSchema, PropertyType } from './types.mjs';
|
|
2
|
-
import { isEmpty, isLowerCase, getExtensionValue, capitalize } from './utils.mjs';
|
|
3
|
-
import { toTypedValue, getTypedPropertyValue } from './fhirpath/utils.mjs';
|
|
4
|
-
import './fhirpath/parse.mjs';
|
|
5
|
-
import { OperationOutcomeError, validationError } from './outcomes.mjs';
|
|
6
|
-
|
|
7
|
-
/*
|
|
8
|
-
* This file provides schema validation utilities for FHIR JSON objects.
|
|
9
|
-
*
|
|
10
|
-
* See: [JSON Representation of Resources](https://hl7.org/fhir/json.html)
|
|
11
|
-
* See: [FHIR Data Types](https://www.hl7.org/fhir/datatypes.html)
|
|
12
|
-
*/
|
|
13
|
-
const fhirTypeToJsType = {
|
|
14
|
-
base64Binary: 'string',
|
|
15
|
-
boolean: 'boolean',
|
|
16
|
-
canonical: 'string',
|
|
17
|
-
code: 'string',
|
|
18
|
-
date: 'string',
|
|
19
|
-
dateTime: 'string',
|
|
20
|
-
decimal: 'number',
|
|
21
|
-
id: 'string',
|
|
22
|
-
instant: 'string',
|
|
23
|
-
integer: 'number',
|
|
24
|
-
markdown: 'string',
|
|
25
|
-
oid: 'string',
|
|
26
|
-
positiveInt: 'number',
|
|
27
|
-
string: 'string',
|
|
28
|
-
time: 'string',
|
|
29
|
-
unsignedInt: 'number',
|
|
30
|
-
uri: 'string',
|
|
31
|
-
url: 'string',
|
|
32
|
-
uuid: 'string',
|
|
33
|
-
xhtml: 'string',
|
|
34
|
-
'http://hl7.org/fhirpath/System.String': 'string',
|
|
35
|
-
};
|
|
36
|
-
const baseResourceProperties = new Set([
|
|
37
|
-
// Resource
|
|
38
|
-
'resourceType',
|
|
39
|
-
'id',
|
|
40
|
-
'meta',
|
|
41
|
-
'implicitRules',
|
|
42
|
-
'language',
|
|
43
|
-
// DomainResource
|
|
44
|
-
'text',
|
|
45
|
-
'contained',
|
|
46
|
-
'extension',
|
|
47
|
-
'modifierExtension',
|
|
48
|
-
]);
|
|
49
|
-
/**
|
|
50
|
-
* Returns true if the given string is a valid FHIR resource type.
|
|
51
|
-
*
|
|
52
|
-
* ```ts
|
|
53
|
-
* isResourceType('Patient'); // true
|
|
54
|
-
* isResourceType('XYZ'); // false
|
|
55
|
-
* ```
|
|
56
|
-
*
|
|
57
|
-
* Note that this depends on globalSchema, which is populated by the StructureDefinition loader.
|
|
58
|
-
*
|
|
59
|
-
* In a server context, you can load all schema definitions:
|
|
60
|
-
*
|
|
61
|
-
* ```ts
|
|
62
|
-
* import { indexStructureDefinitionBundle } from '@medplum/core';
|
|
63
|
-
* import { readJson } from '@medplum/definitions';
|
|
64
|
-
* import { Bundle } from '@medplum/fhirtypes';
|
|
65
|
-
*
|
|
66
|
-
* indexStructureDefinitionBundle(readJson('fhir/r4/profiles-resources.json') as Bundle);
|
|
67
|
-
* ```
|
|
68
|
-
*
|
|
69
|
-
* In a client context, you can load the schema definitions using MedplumClient:
|
|
70
|
-
*
|
|
71
|
-
* ```ts
|
|
72
|
-
* import { MedplumClient } from '@medplum/core';
|
|
73
|
-
*
|
|
74
|
-
* const medplum = new MedplumClient();
|
|
75
|
-
* await medplum.requestSchema('Patient');
|
|
76
|
-
* ```
|
|
77
|
-
* @param resourceType The candidate resource type string.
|
|
78
|
-
* @returns True if the resource type is a valid FHIR resource type.
|
|
79
|
-
*/
|
|
80
|
-
function isResourceType(resourceType) {
|
|
81
|
-
const typeSchema = globalSchema.types[resourceType];
|
|
82
|
-
return (typeSchema &&
|
|
83
|
-
typeSchema.structureDefinition.id === resourceType &&
|
|
84
|
-
typeSchema.structureDefinition.kind === 'resource');
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Validates that the given string is a valid FHIR resource type.
|
|
88
|
-
* On success, silently returns void.
|
|
89
|
-
* On failure, throws an OperationOutcomeError.
|
|
90
|
-
*
|
|
91
|
-
* ```ts
|
|
92
|
-
* validateResourceType('Patient'); // nothing
|
|
93
|
-
* validateResourceType('XYZ'); // throws OperationOutcomeError
|
|
94
|
-
* ```
|
|
95
|
-
*
|
|
96
|
-
* Note that this depends on globalSchema, which is populated by the StructureDefinition loader.
|
|
97
|
-
*
|
|
98
|
-
* In a server context, you can load all schema definitions:
|
|
99
|
-
*
|
|
100
|
-
* ```ts
|
|
101
|
-
* import { indexStructureDefinitionBundle } from '@medplum/core';
|
|
102
|
-
* import { readJson } from '@medplum/definitions';
|
|
103
|
-
* import { Bundle } from '@medplum/fhirtypes';
|
|
104
|
-
*
|
|
105
|
-
* indexStructureDefinitionBundle(readJson('fhir/r4/profiles-resources.json') as Bundle);
|
|
106
|
-
* ```
|
|
107
|
-
*
|
|
108
|
-
* In a client context, you can load the schema definitions using MedplumClient:
|
|
109
|
-
*
|
|
110
|
-
* ```ts
|
|
111
|
-
* import { MedplumClient } from '@medplum/core';
|
|
112
|
-
*
|
|
113
|
-
* const medplum = new MedplumClient();
|
|
114
|
-
* await medplum.requestSchema('Patient');
|
|
115
|
-
* ```
|
|
116
|
-
* @param resourceType The candidate resource type string.
|
|
117
|
-
*/
|
|
118
|
-
function validateResourceType(resourceType) {
|
|
119
|
-
if (!resourceType) {
|
|
120
|
-
throw new OperationOutcomeError(validationError('Resource type is null'));
|
|
121
|
-
}
|
|
122
|
-
if (!isResourceType(resourceType)) {
|
|
123
|
-
throw new OperationOutcomeError(validationError('Unknown resource type'));
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Validates a candidate FHIR resource object.
|
|
128
|
-
* On success, silently returns void.
|
|
129
|
-
* On failure, throws an OperationOutcomeError with issues for each violation.
|
|
130
|
-
*
|
|
131
|
-
* ```ts
|
|
132
|
-
* validateResource({ resourceType: 'Patient' }); // nothing
|
|
133
|
-
* validateResource({ resourceType: 'XYZ' }); // throws OperationOutcomeError
|
|
134
|
-
* ```
|
|
135
|
-
*
|
|
136
|
-
* Note that this depends on globalSchema, which is populated by the StructureDefinition loader.
|
|
137
|
-
*
|
|
138
|
-
* In a server context, you can load all schema definitions:
|
|
139
|
-
*
|
|
140
|
-
* ```ts
|
|
141
|
-
* import { indexStructureDefinitionBundle } from '@medplum/core';
|
|
142
|
-
* import { readJson } from '@medplum/definitions';
|
|
143
|
-
* import { Bundle } from '@medplum/fhirtypes';
|
|
144
|
-
*
|
|
145
|
-
* indexStructureDefinitionBundle(readJson('fhir/r4/profiles-resources.json') as Bundle);
|
|
146
|
-
* ```
|
|
147
|
-
*
|
|
148
|
-
* In a client context, you can load the schema definitions using MedplumClient:
|
|
149
|
-
*
|
|
150
|
-
* ```ts
|
|
151
|
-
* import { MedplumClient } from '@medplum/core';
|
|
152
|
-
*
|
|
153
|
-
* const medplum = new MedplumClient();
|
|
154
|
-
* await medplum.requestSchema('Patient');
|
|
155
|
-
* ```
|
|
156
|
-
* @param resource The candidate resource.
|
|
157
|
-
*/
|
|
158
|
-
function validateResource(resource) {
|
|
159
|
-
new FhirSchemaValidator(resource).validate();
|
|
160
|
-
}
|
|
161
|
-
class FhirSchemaValidator {
|
|
162
|
-
constructor(root) {
|
|
163
|
-
this.issues = [];
|
|
164
|
-
this.root = root;
|
|
165
|
-
}
|
|
166
|
-
validate() {
|
|
167
|
-
const resource = this.root;
|
|
168
|
-
if (!resource) {
|
|
169
|
-
throw new OperationOutcomeError(validationError('Resource is null'));
|
|
170
|
-
}
|
|
171
|
-
const resourceType = resource.resourceType;
|
|
172
|
-
if (!resourceType) {
|
|
173
|
-
throw new OperationOutcomeError(validationError('Missing resource type'));
|
|
174
|
-
}
|
|
175
|
-
// Check for "null" once for the entire object hierarchy
|
|
176
|
-
checkForNull(resource, '', this.issues);
|
|
177
|
-
this.validateObject(toTypedValue(resource), resourceType);
|
|
178
|
-
if (this.issues.length > 0) {
|
|
179
|
-
throw new OperationOutcomeError({
|
|
180
|
-
resourceType: 'OperationOutcome',
|
|
181
|
-
issue: this.issues,
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
validateObject(typedValue, path) {
|
|
186
|
-
const definition = globalSchema.types[typedValue.type];
|
|
187
|
-
if (!definition) {
|
|
188
|
-
throw new OperationOutcomeError(validationError('Unknown type: ' + typedValue.type));
|
|
189
|
-
}
|
|
190
|
-
const propertyDefinitions = definition.properties;
|
|
191
|
-
this.checkProperties(path, propertyDefinitions, typedValue);
|
|
192
|
-
this.checkAdditionalProperties(path, typedValue, propertyDefinitions);
|
|
193
|
-
}
|
|
194
|
-
checkProperties(path, propertyDefinitions, typedValue) {
|
|
195
|
-
for (const [key, elementDefinition] of Object.entries(propertyDefinitions)) {
|
|
196
|
-
this.checkProperty(path + '.' + key, elementDefinition, typedValue);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
checkProperty(path, elementDefinition, typedValue) {
|
|
200
|
-
const propertyName = path.split('.').pop();
|
|
201
|
-
const value = getTypedPropertyValue(typedValue, propertyName);
|
|
202
|
-
if (isEmpty(value)) {
|
|
203
|
-
if (elementDefinition.min !== undefined && elementDefinition.min > 0) {
|
|
204
|
-
this.issues.push(createStructureIssue(path, 'Missing required property'));
|
|
205
|
-
}
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
if (elementDefinition.max === '*') {
|
|
209
|
-
if (!Array.isArray(value)) {
|
|
210
|
-
this.issues.push(createStructureIssue(path, 'Expected array for property'));
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
for (const item of value) {
|
|
214
|
-
this.checkPropertyValue(path, elementDefinition, item);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
else {
|
|
218
|
-
if (Array.isArray(value)) {
|
|
219
|
-
this.issues.push(createStructureIssue(path, 'Expected single value for property'));
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
this.checkPropertyValue(path, elementDefinition, value);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
checkPropertyValue(path, elementDefinition, typedValue) {
|
|
226
|
-
if (typedValue.value === null) {
|
|
227
|
-
// Null handled separately
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
if (isLowerCase(typedValue.type.charAt(0))) {
|
|
231
|
-
this.validatePrimitiveType(elementDefinition, typedValue);
|
|
232
|
-
}
|
|
233
|
-
else {
|
|
234
|
-
this.validateObject(typedValue, path);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
validatePrimitiveType(elementDefinition, typedValue) {
|
|
238
|
-
const { type, value } = typedValue;
|
|
239
|
-
if (value === null) {
|
|
240
|
-
// Null handled separately, so this code should never be reached
|
|
241
|
-
// Leaving this check in place for now, in case we change the null handling
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
// First, make sure the value is the correct JS type
|
|
245
|
-
const expectedType = fhirTypeToJsType[typedValue.type];
|
|
246
|
-
if (typeof value !== expectedType) {
|
|
247
|
-
this.createIssue(elementDefinition, 'Invalid type for ' + type);
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
// Then, perform additional checks for specialty types
|
|
251
|
-
if (expectedType === 'string') {
|
|
252
|
-
this.validateString(elementDefinition, type, value);
|
|
253
|
-
}
|
|
254
|
-
else if (expectedType === 'number') {
|
|
255
|
-
this.validateNumber(elementDefinition, type, value);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
validateString(elementDefinition, type, value) {
|
|
259
|
-
if (!value.trim()) {
|
|
260
|
-
this.createIssue(elementDefinition, 'Invalid empty string');
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
// Try to get the regex
|
|
264
|
-
const valueDefinition = globalSchema.types[type]?.properties?.['value'];
|
|
265
|
-
if (valueDefinition?.type) {
|
|
266
|
-
const regex = getExtensionValue(valueDefinition.type[0], 'http://hl7.org/fhir/StructureDefinition/regex');
|
|
267
|
-
if (regex) {
|
|
268
|
-
if (!value.match(new RegExp(regex))) {
|
|
269
|
-
this.createIssue(elementDefinition, 'Invalid ' + type + ' format');
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
validateNumber(elementDefinition, type, value) {
|
|
275
|
-
if (isNaN(value) || !isFinite(value)) {
|
|
276
|
-
this.createIssue(elementDefinition, 'Invalid ' + type + ' value');
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
if (isIntegerType(type) && !Number.isInteger(value)) {
|
|
280
|
-
this.createIssue(elementDefinition, 'Number is not an integer');
|
|
281
|
-
}
|
|
282
|
-
if (type === PropertyType.positiveInt && value <= 0) {
|
|
283
|
-
this.createIssue(elementDefinition, 'Number is less than or equal to zero');
|
|
284
|
-
}
|
|
285
|
-
if (type === PropertyType.unsignedInt && value < 0) {
|
|
286
|
-
this.createIssue(elementDefinition, 'Number is negative');
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
checkAdditionalProperties(path, typedValue, propertyDefinitions) {
|
|
290
|
-
const object = typedValue.value;
|
|
291
|
-
for (const key of Object.keys(object)) {
|
|
292
|
-
this.checkAdditionalProperty(path, key, typedValue, propertyDefinitions);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
/**
|
|
296
|
-
* Checks if the given property is allowed on the given object.
|
|
297
|
-
* @param path The path of the current object.
|
|
298
|
-
* @param key The key of a property to check.
|
|
299
|
-
* @param typedValue The current object.
|
|
300
|
-
* @param propertyDefinitions The property definitions of the current object.
|
|
301
|
-
*/
|
|
302
|
-
checkAdditionalProperty(path, key, typedValue, propertyDefinitions) {
|
|
303
|
-
if (!baseResourceProperties.has(key) &&
|
|
304
|
-
!(key in propertyDefinitions) &&
|
|
305
|
-
!isChoiceOfType(key, typedValue, propertyDefinitions) &&
|
|
306
|
-
!this.checkPrimitiveElement(path, key, typedValue)) {
|
|
307
|
-
const expression = `${path}.${key}`;
|
|
308
|
-
this.issues.push(createStructureIssue(expression, `Invalid additional property "${expression}"`));
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
/**
|
|
312
|
-
* Checks the element for a primitive.
|
|
313
|
-
*
|
|
314
|
-
* FHIR elements with primitive data types are represented in two parts:
|
|
315
|
-
* 1) A JSON property with the name of the element, which has a JSON type of number, boolean, or string
|
|
316
|
-
* 2) a JSON property with _ prepended to the name of the element, which, if present, contains the value's id and/or extensions
|
|
317
|
-
*
|
|
318
|
-
* See: https://hl7.org/fhir/json.html#primitive
|
|
319
|
-
* @param path The path to the property
|
|
320
|
-
* @param key The key in the current typed value.
|
|
321
|
-
* @param typedValue The current typed value.
|
|
322
|
-
* @returns True if the primitive element is valid.
|
|
323
|
-
*/
|
|
324
|
-
checkPrimitiveElement(path, key, typedValue) {
|
|
325
|
-
// Primitive element starts with underscore
|
|
326
|
-
if (!key.startsWith('_')) {
|
|
327
|
-
return false;
|
|
328
|
-
}
|
|
329
|
-
// Validate the non-underscore property exists
|
|
330
|
-
const primitiveKey = key.slice(1);
|
|
331
|
-
if (!(primitiveKey in typedValue.value)) {
|
|
332
|
-
return false;
|
|
333
|
-
}
|
|
334
|
-
// Then validate the element
|
|
335
|
-
this.validateObject({ type: 'Element', value: typedValue.value[key] }, path);
|
|
336
|
-
return true;
|
|
337
|
-
}
|
|
338
|
-
createIssue(elementDefinition, message) {
|
|
339
|
-
this.issues.push(createStructureIssue(elementDefinition.path, message));
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
function isIntegerType(propertyType) {
|
|
343
|
-
return (propertyType === PropertyType.integer ||
|
|
344
|
-
propertyType === PropertyType.positiveInt ||
|
|
345
|
-
propertyType === PropertyType.unsignedInt);
|
|
346
|
-
}
|
|
347
|
-
function isChoiceOfType(key, typedValue, propertyDefinitions) {
|
|
348
|
-
for (const propertyName of Object.keys(propertyDefinitions)) {
|
|
349
|
-
if (!propertyName.endsWith('[x]')) {
|
|
350
|
-
continue;
|
|
351
|
-
}
|
|
352
|
-
const basePropertyName = propertyName.replace('[x]', '');
|
|
353
|
-
if (!key.startsWith(basePropertyName)) {
|
|
354
|
-
continue;
|
|
355
|
-
}
|
|
356
|
-
let typedPropertyValue = getTypedPropertyValue(typedValue, propertyName);
|
|
357
|
-
if (!typedPropertyValue) {
|
|
358
|
-
continue;
|
|
359
|
-
}
|
|
360
|
-
if (Array.isArray(typedPropertyValue)) {
|
|
361
|
-
// At present, there are no choice types that are arrays in the FHIR spec
|
|
362
|
-
// Leaving this here to make TypeScript happy, and in case that changes
|
|
363
|
-
typedPropertyValue = typedPropertyValue[0];
|
|
364
|
-
}
|
|
365
|
-
if (typedPropertyValue && key === basePropertyName + capitalize(typedPropertyValue.type)) {
|
|
366
|
-
return true;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
return false;
|
|
370
|
-
}
|
|
371
|
-
/**
|
|
372
|
-
* Recursively checks for null values in an object.
|
|
373
|
-
*
|
|
374
|
-
* Note that "null" is a special value in JSON that is not allowed in FHIR.
|
|
375
|
-
* @param value Input value of any type.
|
|
376
|
-
* @param path Path string to the value for OperationOutcome.
|
|
377
|
-
* @param issues Output list of issues.
|
|
378
|
-
*/
|
|
379
|
-
function checkForNull(value, path, issues) {
|
|
380
|
-
if (value === null) {
|
|
381
|
-
issues.push(createStructureIssue(path, 'Invalid null value'));
|
|
382
|
-
}
|
|
383
|
-
else if (Array.isArray(value)) {
|
|
384
|
-
checkArrayForNull(value, path, issues);
|
|
385
|
-
}
|
|
386
|
-
else if (typeof value === 'object') {
|
|
387
|
-
checkObjectForNull(value, path, issues);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
function checkArrayForNull(array, path, issues) {
|
|
391
|
-
for (let i = 0; i < array.length; i++) {
|
|
392
|
-
if (array[i] === undefined) {
|
|
393
|
-
issues.push(createStructureIssue(`${path}[${i}]`, 'Invalid undefined value'));
|
|
394
|
-
}
|
|
395
|
-
else {
|
|
396
|
-
checkForNull(array[i], `${path}[${i}]`, issues);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
function checkObjectForNull(obj, path, issues) {
|
|
401
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
402
|
-
checkForNull(value, `${path}${path ? '.' : ''}${key}`, issues);
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
function createStructureIssue(expression, details) {
|
|
406
|
-
return {
|
|
407
|
-
severity: 'error',
|
|
408
|
-
code: 'structure',
|
|
409
|
-
details: {
|
|
410
|
-
text: details,
|
|
411
|
-
},
|
|
412
|
-
expression: [expression],
|
|
413
|
-
};
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
export { FhirSchemaValidator, checkForNull, createStructureIssue, isResourceType, validateResource, validateResourceType };
|
|
417
|
-
//# sourceMappingURL=schema.mjs.map
|
package/dist/esm/schema.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"schema.mjs","sources":["../../src/schema.ts"],"sourcesContent":["import { ElementDefinition, OperationOutcomeIssue, Resource } from '@medplum/fhirtypes';\nimport { getTypedPropertyValue, toTypedValue } from './fhirpath';\nimport { OperationOutcomeError, validationError } from './outcomes';\nimport { globalSchema, PropertyType, TypedValue } from './types';\nimport { capitalize, getExtensionValue, isEmpty, isLowerCase } from './utils';\n\n/*\n * This file provides schema validation utilities for FHIR JSON objects.\n *\n * See: [JSON Representation of Resources](https://hl7.org/fhir/json.html)\n * See: [FHIR Data Types](https://www.hl7.org/fhir/datatypes.html)\n */\n\nconst fhirTypeToJsType: Record<string, string> = {\n base64Binary: 'string',\n boolean: 'boolean',\n canonical: 'string',\n code: 'string',\n date: 'string',\n dateTime: 'string',\n decimal: 'number',\n id: 'string',\n instant: 'string',\n integer: 'number',\n markdown: 'string',\n oid: 'string',\n positiveInt: 'number',\n string: 'string',\n time: 'string',\n unsignedInt: 'number',\n uri: 'string',\n url: 'string',\n uuid: 'string',\n xhtml: 'string',\n 'http://hl7.org/fhirpath/System.String': 'string',\n};\n\nconst baseResourceProperties = new Set<string>([\n // Resource\n 'resourceType',\n 'id',\n 'meta',\n 'implicitRules',\n 'language',\n\n // DomainResource\n 'text',\n 'contained',\n 'extension',\n 'modifierExtension',\n]);\n\n/**\n * Returns true if the given string is a valid FHIR resource type.\n *\n * ```ts\n * isResourceType('Patient'); // true\n * isResourceType('XYZ'); // false\n * ```\n *\n * Note that this depends on globalSchema, which is populated by the StructureDefinition loader.\n *\n * In a server context, you can load all schema definitions:\n *\n * ```ts\n * import { indexStructureDefinitionBundle } from '@medplum/core';\n * import { readJson } from '@medplum/definitions';\n * import { Bundle } from '@medplum/fhirtypes';\n *\n * indexStructureDefinitionBundle(readJson('fhir/r4/profiles-resources.json') as Bundle);\n * ```\n *\n * In a client context, you can load the schema definitions using MedplumClient:\n *\n * ```ts\n * import { MedplumClient } from '@medplum/core';\n *\n * const medplum = new MedplumClient();\n * await medplum.requestSchema('Patient');\n * ```\n * @param resourceType The candidate resource type string.\n * @returns True if the resource type is a valid FHIR resource type.\n */\nexport function isResourceType(resourceType: string): boolean {\n const typeSchema = globalSchema.types[resourceType];\n return (\n typeSchema &&\n typeSchema.structureDefinition.id === resourceType &&\n typeSchema.structureDefinition.kind === 'resource'\n );\n}\n\n/**\n * Validates that the given string is a valid FHIR resource type.\n * On success, silently returns void.\n * On failure, throws an OperationOutcomeError.\n *\n * ```ts\n * validateResourceType('Patient'); // nothing\n * validateResourceType('XYZ'); // throws OperationOutcomeError\n * ```\n *\n * Note that this depends on globalSchema, which is populated by the StructureDefinition loader.\n *\n * In a server context, you can load all schema definitions:\n *\n * ```ts\n * import { indexStructureDefinitionBundle } from '@medplum/core';\n * import { readJson } from '@medplum/definitions';\n * import { Bundle } from '@medplum/fhirtypes';\n *\n * indexStructureDefinitionBundle(readJson('fhir/r4/profiles-resources.json') as Bundle);\n * ```\n *\n * In a client context, you can load the schema definitions using MedplumClient:\n *\n * ```ts\n * import { MedplumClient } from '@medplum/core';\n *\n * const medplum = new MedplumClient();\n * await medplum.requestSchema('Patient');\n * ```\n * @param resourceType The candidate resource type string.\n */\nexport function validateResourceType(resourceType: string): void {\n if (!resourceType) {\n throw new OperationOutcomeError(validationError('Resource type is null'));\n }\n if (!isResourceType(resourceType)) {\n throw new OperationOutcomeError(validationError('Unknown resource type'));\n }\n}\n\n/**\n * Validates a candidate FHIR resource object.\n * On success, silently returns void.\n * On failure, throws an OperationOutcomeError with issues for each violation.\n *\n * ```ts\n * validateResource({ resourceType: 'Patient' }); // nothing\n * validateResource({ resourceType: 'XYZ' }); // throws OperationOutcomeError\n * ```\n *\n * Note that this depends on globalSchema, which is populated by the StructureDefinition loader.\n *\n * In a server context, you can load all schema definitions:\n *\n * ```ts\n * import { indexStructureDefinitionBundle } from '@medplum/core';\n * import { readJson } from '@medplum/definitions';\n * import { Bundle } from '@medplum/fhirtypes';\n *\n * indexStructureDefinitionBundle(readJson('fhir/r4/profiles-resources.json') as Bundle);\n * ```\n *\n * In a client context, you can load the schema definitions using MedplumClient:\n *\n * ```ts\n * import { MedplumClient } from '@medplum/core';\n *\n * const medplum = new MedplumClient();\n * await medplum.requestSchema('Patient');\n * ```\n * @param resource The candidate resource.\n */\nexport function validateResource<T extends Resource>(resource: T): void {\n new FhirSchemaValidator(resource).validate();\n}\n\nexport class FhirSchemaValidator<T extends Resource> {\n private readonly issues: OperationOutcomeIssue[];\n private readonly root: T;\n\n constructor(root: T) {\n this.issues = [];\n this.root = root;\n }\n\n validate(): void {\n const resource = this.root;\n if (!resource) {\n throw new OperationOutcomeError(validationError('Resource is null'));\n }\n\n const resourceType = resource.resourceType;\n if (!resourceType) {\n throw new OperationOutcomeError(validationError('Missing resource type'));\n }\n\n // Check for \"null\" once for the entire object hierarchy\n checkForNull(resource, '', this.issues);\n\n this.validateObject(toTypedValue(resource), resourceType);\n\n if (this.issues.length > 0) {\n throw new OperationOutcomeError({\n resourceType: 'OperationOutcome',\n issue: this.issues,\n });\n }\n }\n\n private validateObject(typedValue: TypedValue, path: string): void {\n const definition = globalSchema.types[typedValue.type];\n if (!definition) {\n throw new OperationOutcomeError(validationError('Unknown type: ' + typedValue.type));\n }\n\n const propertyDefinitions = definition.properties;\n this.checkProperties(path, propertyDefinitions, typedValue);\n this.checkAdditionalProperties(path, typedValue, propertyDefinitions);\n }\n\n private checkProperties(\n path: string,\n propertyDefinitions: Record<string, ElementDefinition>,\n typedValue: TypedValue\n ): void {\n for (const [key, elementDefinition] of Object.entries(propertyDefinitions)) {\n this.checkProperty(path + '.' + key, elementDefinition, typedValue);\n }\n }\n\n private checkProperty(path: string, elementDefinition: ElementDefinition, typedValue: TypedValue): void {\n const propertyName = path.split('.').pop() as string;\n const value = getTypedPropertyValue(typedValue, propertyName);\n\n if (isEmpty(value)) {\n if (elementDefinition.min !== undefined && elementDefinition.min > 0) {\n this.issues.push(createStructureIssue(path, 'Missing required property'));\n }\n return;\n }\n\n if (elementDefinition.max === '*') {\n if (!Array.isArray(value)) {\n this.issues.push(createStructureIssue(path, 'Expected array for property'));\n return;\n }\n for (const item of value) {\n this.checkPropertyValue(path, elementDefinition, item);\n }\n } else {\n if (Array.isArray(value)) {\n this.issues.push(createStructureIssue(path, 'Expected single value for property'));\n return;\n }\n this.checkPropertyValue(path, elementDefinition, value as TypedValue);\n }\n }\n\n private checkPropertyValue(path: string, elementDefinition: ElementDefinition, typedValue: TypedValue): void {\n if (typedValue.value === null) {\n // Null handled separately\n return;\n }\n\n if (isLowerCase(typedValue.type.charAt(0))) {\n this.validatePrimitiveType(elementDefinition, typedValue);\n } else {\n this.validateObject(typedValue, path);\n }\n }\n\n private validatePrimitiveType(elementDefinition: ElementDefinition, typedValue: TypedValue): void {\n const { type, value } = typedValue;\n\n if (value === null) {\n // Null handled separately, so this code should never be reached\n // Leaving this check in place for now, in case we change the null handling\n return;\n }\n\n // First, make sure the value is the correct JS type\n const expectedType = fhirTypeToJsType[typedValue.type];\n if (typeof value !== expectedType) {\n this.createIssue(elementDefinition, 'Invalid type for ' + type);\n return;\n }\n\n // Then, perform additional checks for specialty types\n if (expectedType === 'string') {\n this.validateString(elementDefinition, type as PropertyType, value as string);\n } else if (expectedType === 'number') {\n this.validateNumber(elementDefinition, type as PropertyType, value as number);\n }\n }\n\n private validateString(elementDefinition: ElementDefinition, type: PropertyType, value: string): void {\n if (!value.trim()) {\n this.createIssue(elementDefinition, 'Invalid empty string');\n return;\n }\n\n // Try to get the regex\n const valueDefinition = globalSchema.types[type]?.properties?.['value'];\n if (valueDefinition?.type) {\n const regex = getExtensionValue(valueDefinition.type[0], 'http://hl7.org/fhir/StructureDefinition/regex');\n if (regex) {\n if (!value.match(new RegExp(regex))) {\n this.createIssue(elementDefinition, 'Invalid ' + type + ' format');\n }\n }\n }\n }\n\n private validateNumber(elementDefinition: ElementDefinition, type: PropertyType, value: number): void {\n if (isNaN(value) || !isFinite(value)) {\n this.createIssue(elementDefinition, 'Invalid ' + type + ' value');\n return;\n }\n\n if (isIntegerType(type) && !Number.isInteger(value)) {\n this.createIssue(elementDefinition, 'Number is not an integer');\n }\n\n if (type === PropertyType.positiveInt && value <= 0) {\n this.createIssue(elementDefinition, 'Number is less than or equal to zero');\n }\n\n if (type === PropertyType.unsignedInt && value < 0) {\n this.createIssue(elementDefinition, 'Number is negative');\n }\n }\n\n private checkAdditionalProperties(\n path: string,\n typedValue: TypedValue,\n propertyDefinitions: Record<string, ElementDefinition>\n ): void {\n const object = typedValue.value as Record<string, unknown>;\n for (const key of Object.keys(object)) {\n this.checkAdditionalProperty(path, key, typedValue, propertyDefinitions);\n }\n }\n\n /**\n * Checks if the given property is allowed on the given object.\n * @param path The path of the current object.\n * @param key The key of a property to check.\n * @param typedValue The current object.\n * @param propertyDefinitions The property definitions of the current object.\n */\n private checkAdditionalProperty(\n path: string,\n key: string,\n typedValue: TypedValue,\n propertyDefinitions: Record<string, ElementDefinition>\n ): void {\n if (\n !baseResourceProperties.has(key) &&\n !(key in propertyDefinitions) &&\n !isChoiceOfType(key, typedValue, propertyDefinitions) &&\n !this.checkPrimitiveElement(path, key, typedValue)\n ) {\n const expression = `${path}.${key}`;\n this.issues.push(createStructureIssue(expression, `Invalid additional property \"${expression}\"`));\n }\n }\n\n /**\n * Checks the element for a primitive.\n *\n * FHIR elements with primitive data types are represented in two parts:\n * 1) A JSON property with the name of the element, which has a JSON type of number, boolean, or string\n * 2) a JSON property with _ prepended to the name of the element, which, if present, contains the value's id and/or extensions\n *\n * See: https://hl7.org/fhir/json.html#primitive\n * @param path The path to the property\n * @param key The key in the current typed value.\n * @param typedValue The current typed value.\n * @returns True if the primitive element is valid.\n */\n private checkPrimitiveElement(path: string, key: string, typedValue: TypedValue): boolean {\n // Primitive element starts with underscore\n if (!key.startsWith('_')) {\n return false;\n }\n\n // Validate the non-underscore property exists\n const primitiveKey = key.slice(1);\n if (!(primitiveKey in typedValue.value)) {\n return false;\n }\n\n // Then validate the element\n this.validateObject({ type: 'Element', value: typedValue.value[key] }, path);\n return true;\n }\n\n private createIssue(elementDefinition: ElementDefinition, message: string): void {\n this.issues.push(createStructureIssue(elementDefinition.path as string, message));\n }\n}\n\nfunction isIntegerType(propertyType: PropertyType): boolean {\n return (\n propertyType === PropertyType.integer ||\n propertyType === PropertyType.positiveInt ||\n propertyType === PropertyType.unsignedInt\n );\n}\n\nfunction isChoiceOfType(\n key: string,\n typedValue: TypedValue,\n propertyDefinitions: Record<string, ElementDefinition>\n): boolean {\n for (const propertyName of Object.keys(propertyDefinitions)) {\n if (!propertyName.endsWith('[x]')) {\n continue;\n }\n const basePropertyName = propertyName.replace('[x]', '');\n if (!key.startsWith(basePropertyName)) {\n continue;\n }\n let typedPropertyValue = getTypedPropertyValue(typedValue, propertyName);\n if (!typedPropertyValue) {\n continue;\n }\n if (Array.isArray(typedPropertyValue)) {\n // At present, there are no choice types that are arrays in the FHIR spec\n // Leaving this here to make TypeScript happy, and in case that changes\n typedPropertyValue = typedPropertyValue[0];\n }\n if (typedPropertyValue && key === basePropertyName + capitalize(typedPropertyValue.type)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Recursively checks for null values in an object.\n *\n * Note that \"null\" is a special value in JSON that is not allowed in FHIR.\n * @param value Input value of any type.\n * @param path Path string to the value for OperationOutcome.\n * @param issues Output list of issues.\n */\nexport function checkForNull(value: unknown, path: string, issues: OperationOutcomeIssue[]): void {\n if (value === null) {\n issues.push(createStructureIssue(path, 'Invalid null value'));\n } else if (Array.isArray(value)) {\n checkArrayForNull(value, path, issues);\n } else if (typeof value === 'object') {\n checkObjectForNull(value as Record<string, unknown>, path, issues);\n }\n}\n\nfunction checkArrayForNull(array: unknown[], path: string, issues: OperationOutcomeIssue[]): void {\n for (let i = 0; i < array.length; i++) {\n if (array[i] === undefined) {\n issues.push(createStructureIssue(`${path}[${i}]`, 'Invalid undefined value'));\n } else {\n checkForNull(array[i], `${path}[${i}]`, issues);\n }\n }\n}\n\nfunction checkObjectForNull(obj: Record<string, unknown>, path: string, issues: OperationOutcomeIssue[]): void {\n for (const [key, value] of Object.entries(obj)) {\n checkForNull(value, `${path}${path ? '.' : ''}${key}`, issues);\n }\n}\n\nexport function createStructureIssue(expression: string, details: string): OperationOutcomeIssue {\n return {\n severity: 'error',\n code: 'structure',\n details: {\n text: details,\n },\n expression: [expression],\n };\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import { globalSchema, getElementDefinition, buildTypeName, PropertyType } from '../types.mjs';
|
|
2
|
-
import { capitalize } from '../utils.mjs';
|
|
3
|
-
|
|
4
|
-
var SearchParameterType;
|
|
5
|
-
(function (SearchParameterType) {
|
|
6
|
-
SearchParameterType["BOOLEAN"] = "BOOLEAN";
|
|
7
|
-
SearchParameterType["NUMBER"] = "NUMBER";
|
|
8
|
-
SearchParameterType["QUANTITY"] = "QUANTITY";
|
|
9
|
-
SearchParameterType["TEXT"] = "TEXT";
|
|
10
|
-
SearchParameterType["REFERENCE"] = "REFERENCE";
|
|
11
|
-
SearchParameterType["CANONICAL"] = "CANONICAL";
|
|
12
|
-
SearchParameterType["DATE"] = "DATE";
|
|
13
|
-
SearchParameterType["DATETIME"] = "DATETIME";
|
|
14
|
-
SearchParameterType["PERIOD"] = "PERIOD";
|
|
15
|
-
SearchParameterType["UUID"] = "UUID";
|
|
16
|
-
})(SearchParameterType || (SearchParameterType = {}));
|
|
17
|
-
/**
|
|
18
|
-
* Returns the type details of a SearchParameter.
|
|
19
|
-
*
|
|
20
|
-
* The SearchParameter resource has a "type" parameter, but that is missing some critical information.
|
|
21
|
-
*
|
|
22
|
-
* For example:
|
|
23
|
-
* 1) The "date" type includes "date", "datetime", and "period".
|
|
24
|
-
* 2) The "token" type includes enums and booleans.
|
|
25
|
-
* 3) Arrays/multiple values are not reflected at all.
|
|
26
|
-
* @param resourceType The root resource type.
|
|
27
|
-
* @param searchParam The search parameter.
|
|
28
|
-
* @returns The search parameter type details.
|
|
29
|
-
*/
|
|
30
|
-
function getSearchParameterDetails(resourceType, searchParam) {
|
|
31
|
-
let result = globalSchema.types[resourceType]?.searchParamsDetails?.[searchParam.code];
|
|
32
|
-
if (!result) {
|
|
33
|
-
result = buildSearchParameterDetails(resourceType, searchParam);
|
|
34
|
-
}
|
|
35
|
-
return result;
|
|
36
|
-
}
|
|
37
|
-
function setSearchParameterDetails(resourceType, code, details) {
|
|
38
|
-
const typeSchema = globalSchema.types[resourceType];
|
|
39
|
-
if (!typeSchema.searchParamsDetails) {
|
|
40
|
-
typeSchema.searchParamsDetails = {};
|
|
41
|
-
}
|
|
42
|
-
typeSchema.searchParamsDetails[code] = details;
|
|
43
|
-
}
|
|
44
|
-
function buildSearchParameterDetails(resourceType, searchParam) {
|
|
45
|
-
const code = searchParam.code;
|
|
46
|
-
const columnName = convertCodeToColumnName(code);
|
|
47
|
-
const expression = getExpressionForResourceType(resourceType, searchParam.expression)?.split('.');
|
|
48
|
-
if (!expression) {
|
|
49
|
-
// This happens on compound types
|
|
50
|
-
// In the future, explore returning multiple column definitions
|
|
51
|
-
return { columnName, type: SearchParameterType.TEXT };
|
|
52
|
-
}
|
|
53
|
-
let baseType = resourceType;
|
|
54
|
-
let elementDefinition = undefined;
|
|
55
|
-
let propertyType = undefined;
|
|
56
|
-
let array = false;
|
|
57
|
-
for (let i = 1; i < expression.length; i++) {
|
|
58
|
-
let propertyName = expression[i];
|
|
59
|
-
let hasArrayIndex = false;
|
|
60
|
-
const arrayIndexMatch = /\[\d+\]$/.exec(propertyName);
|
|
61
|
-
if (arrayIndexMatch) {
|
|
62
|
-
propertyName = propertyName.substring(0, propertyName.length - arrayIndexMatch[0].length);
|
|
63
|
-
hasArrayIndex = true;
|
|
64
|
-
}
|
|
65
|
-
elementDefinition = getElementDefinition(baseType, propertyName);
|
|
66
|
-
if (!elementDefinition) {
|
|
67
|
-
throw new Error(`Element definition not found for ${resourceType} ${searchParam.code}`);
|
|
68
|
-
}
|
|
69
|
-
if (elementDefinition.max !== '0' && elementDefinition.max !== '1' && !hasArrayIndex) {
|
|
70
|
-
array = true;
|
|
71
|
-
}
|
|
72
|
-
// "code" is only missing when using "contentReference"
|
|
73
|
-
// "contentReference" is handled above in "getElementDefinition"
|
|
74
|
-
propertyType = elementDefinition.type?.[0].code;
|
|
75
|
-
if (i < expression.length - 1) {
|
|
76
|
-
if (isBackboneElement(propertyType)) {
|
|
77
|
-
baseType = buildTypeName(elementDefinition.path?.split('.'));
|
|
78
|
-
}
|
|
79
|
-
else {
|
|
80
|
-
baseType = propertyType;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
const type = getSearchParameterType(searchParam, propertyType);
|
|
85
|
-
const result = { columnName, type, elementDefinition, array };
|
|
86
|
-
setSearchParameterDetails(resourceType, code, result);
|
|
87
|
-
return result;
|
|
88
|
-
}
|
|
89
|
-
function isBackboneElement(propertyType) {
|
|
90
|
-
return propertyType === 'Element' || propertyType === 'BackboneElement';
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Converts a hyphen-delimited code to camelCase string.
|
|
94
|
-
* @param code The search parameter code.
|
|
95
|
-
* @returns The SQL column name.
|
|
96
|
-
*/
|
|
97
|
-
function convertCodeToColumnName(code) {
|
|
98
|
-
return code.split('-').reduce((result, word, index) => result + (index ? capitalize(word) : word), '');
|
|
99
|
-
}
|
|
100
|
-
function getSearchParameterType(searchParam, propertyType) {
|
|
101
|
-
let type = SearchParameterType.TEXT;
|
|
102
|
-
switch (searchParam.type) {
|
|
103
|
-
case 'date':
|
|
104
|
-
if (propertyType === PropertyType.date) {
|
|
105
|
-
type = SearchParameterType.DATE;
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
type = SearchParameterType.DATETIME;
|
|
109
|
-
}
|
|
110
|
-
break;
|
|
111
|
-
case 'number':
|
|
112
|
-
type = SearchParameterType.NUMBER;
|
|
113
|
-
break;
|
|
114
|
-
case 'quantity':
|
|
115
|
-
type = SearchParameterType.QUANTITY;
|
|
116
|
-
break;
|
|
117
|
-
case 'reference':
|
|
118
|
-
if (propertyType === PropertyType.canonical) {
|
|
119
|
-
type = SearchParameterType.CANONICAL;
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
type = SearchParameterType.REFERENCE;
|
|
123
|
-
}
|
|
124
|
-
break;
|
|
125
|
-
case 'token':
|
|
126
|
-
if (propertyType === 'boolean') {
|
|
127
|
-
type = SearchParameterType.BOOLEAN;
|
|
128
|
-
}
|
|
129
|
-
break;
|
|
130
|
-
}
|
|
131
|
-
return type;
|
|
132
|
-
}
|
|
133
|
-
function getExpressionForResourceType(resourceType, expression) {
|
|
134
|
-
const expressions = expression.split(' | ');
|
|
135
|
-
for (const e of expressions) {
|
|
136
|
-
if (isIgnoredExpression(e)) {
|
|
137
|
-
continue;
|
|
138
|
-
}
|
|
139
|
-
const simplified = simplifyExpression(e);
|
|
140
|
-
if (simplified.startsWith(resourceType + '.')) {
|
|
141
|
-
return simplified;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
return undefined;
|
|
145
|
-
}
|
|
146
|
-
function isIgnoredExpression(input) {
|
|
147
|
-
return input.includes(' as Period') || input.includes(' as SampledDate');
|
|
148
|
-
}
|
|
149
|
-
function simplifyExpression(input) {
|
|
150
|
-
let result = input.trim();
|
|
151
|
-
if (result.startsWith('(') && result.endsWith(')')) {
|
|
152
|
-
result = result.substring(1, result.length - 1);
|
|
153
|
-
}
|
|
154
|
-
const stopStrings = [' != ', ' as ', '.as(', '.exists(', '.resolve(', '.where('];
|
|
155
|
-
for (const stopString of stopStrings) {
|
|
156
|
-
if (result.includes(stopString)) {
|
|
157
|
-
result = result.substring(0, result.indexOf(stopString));
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
return result;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export { SearchParameterType, getExpressionForResourceType, getSearchParameterDetails };
|
|
164
|
-
//# sourceMappingURL=details.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"details.mjs","sources":["../../../src/search/details.ts"],"sourcesContent":["import { ElementDefinition, SearchParameter } from '@medplum/fhirtypes';\nimport { buildTypeName, getElementDefinition, globalSchema, PropertyType } from '../types';\nimport { capitalize } from '../utils';\n\nexport enum SearchParameterType {\n BOOLEAN = 'BOOLEAN',\n NUMBER = 'NUMBER',\n QUANTITY = 'QUANTITY',\n TEXT = 'TEXT',\n REFERENCE = 'REFERENCE',\n CANONICAL = 'CANONICAL',\n DATE = 'DATE',\n DATETIME = 'DATETIME',\n PERIOD = 'PERIOD',\n UUID = 'UUID',\n}\n\nexport interface SearchParameterDetails {\n readonly columnName: string;\n readonly type: SearchParameterType;\n readonly elementDefinition?: ElementDefinition;\n readonly array?: boolean;\n}\n\n/**\n * Returns the type details of a SearchParameter.\n *\n * The SearchParameter resource has a \"type\" parameter, but that is missing some critical information.\n *\n * For example:\n * 1) The \"date\" type includes \"date\", \"datetime\", and \"period\".\n * 2) The \"token\" type includes enums and booleans.\n * 3) Arrays/multiple values are not reflected at all.\n * @param resourceType The root resource type.\n * @param searchParam The search parameter.\n * @returns The search parameter type details.\n */\nexport function getSearchParameterDetails(resourceType: string, searchParam: SearchParameter): SearchParameterDetails {\n let result: SearchParameterDetails | undefined =\n globalSchema.types[resourceType]?.searchParamsDetails?.[searchParam.code as string];\n if (!result) {\n result = buildSearchParameterDetails(resourceType, searchParam);\n }\n return result;\n}\n\nfunction setSearchParameterDetails(resourceType: string, code: string, details: SearchParameterDetails): void {\n const typeSchema = globalSchema.types[resourceType];\n if (!typeSchema.searchParamsDetails) {\n typeSchema.searchParamsDetails = {};\n }\n typeSchema.searchParamsDetails[code] = details;\n}\n\nfunction buildSearchParameterDetails(resourceType: string, searchParam: SearchParameter): SearchParameterDetails {\n const code = searchParam.code as string;\n const columnName = convertCodeToColumnName(code);\n const expression = getExpressionForResourceType(resourceType, searchParam.expression as string)?.split('.');\n if (!expression) {\n // This happens on compound types\n // In the future, explore returning multiple column definitions\n return { columnName, type: SearchParameterType.TEXT };\n }\n\n let baseType = resourceType;\n let elementDefinition = undefined;\n let propertyType = undefined;\n let array = false;\n\n for (let i = 1; i < expression.length; i++) {\n let propertyName = expression[i];\n let hasArrayIndex = false;\n\n const arrayIndexMatch = /\\[\\d+\\]$/.exec(propertyName);\n if (arrayIndexMatch) {\n propertyName = propertyName.substring(0, propertyName.length - arrayIndexMatch[0].length);\n hasArrayIndex = true;\n }\n\n elementDefinition = getElementDefinition(baseType, propertyName);\n if (!elementDefinition) {\n throw new Error(`Element definition not found for ${resourceType} ${searchParam.code}`);\n }\n\n if (elementDefinition.max !== '0' && elementDefinition.max !== '1' && !hasArrayIndex) {\n array = true;\n }\n\n // \"code\" is only missing when using \"contentReference\"\n // \"contentReference\" is handled above in \"getElementDefinition\"\n propertyType = elementDefinition.type?.[0].code as string;\n\n if (i < expression.length - 1) {\n if (isBackboneElement(propertyType)) {\n baseType = buildTypeName(elementDefinition.path?.split('.') as string[]);\n } else {\n baseType = propertyType;\n }\n }\n }\n\n const type = getSearchParameterType(searchParam, propertyType as PropertyType);\n const result = { columnName, type, elementDefinition, array };\n setSearchParameterDetails(resourceType, code, result);\n return result;\n}\n\nfunction isBackboneElement(propertyType: string): boolean {\n return propertyType === 'Element' || propertyType === 'BackboneElement';\n}\n\n/**\n * Converts a hyphen-delimited code to camelCase string.\n * @param code The search parameter code.\n * @returns The SQL column name.\n */\nfunction convertCodeToColumnName(code: string): string {\n return code.split('-').reduce((result, word, index) => result + (index ? capitalize(word) : word), '');\n}\n\nfunction getSearchParameterType(searchParam: SearchParameter, propertyType: PropertyType): SearchParameterType {\n let type = SearchParameterType.TEXT;\n switch (searchParam.type) {\n case 'date':\n if (propertyType === PropertyType.date) {\n type = SearchParameterType.DATE;\n } else {\n type = SearchParameterType.DATETIME;\n }\n break;\n case 'number':\n type = SearchParameterType.NUMBER;\n break;\n case 'quantity':\n type = SearchParameterType.QUANTITY;\n break;\n case 'reference':\n if (propertyType === PropertyType.canonical) {\n type = SearchParameterType.CANONICAL;\n } else {\n type = SearchParameterType.REFERENCE;\n }\n break;\n case 'token':\n if (propertyType === 'boolean') {\n type = SearchParameterType.BOOLEAN;\n }\n break;\n }\n return type;\n}\n\nexport function getExpressionForResourceType(resourceType: string, expression: string): string | undefined {\n const expressions = expression.split(' | ');\n for (const e of expressions) {\n if (isIgnoredExpression(e)) {\n continue;\n }\n const simplified = simplifyExpression(e);\n if (simplified.startsWith(resourceType + '.')) {\n return simplified;\n }\n }\n return undefined;\n}\n\nfunction isIgnoredExpression(input: string): boolean {\n return input.includes(' as Period') || input.includes(' as SampledDate');\n}\n\nfunction simplifyExpression(input: string): string {\n let result = input.trim();\n\n if (result.startsWith('(') && result.endsWith(')')) {\n result = result.substring(1, result.length - 1);\n }\n\n const stopStrings = [' != ', ' as ', '.as(', '.exists(', '.resolve(', '.where('];\n for (const stopString of stopStrings) {\n if (result.includes(stopString)) {\n result = result.substring(0, result.indexOf(stopString));\n }\n }\n\n return result;\n}\n"],"names":[],"mappings":"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"}
|