fhirsmith 0.12.0 → 0.13.0
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/CHANGELOG.md +305 -0
- package/README.md +1 -1
- package/config-template.json +13 -0
- package/library/languages.js +191 -34
- package/library/path-safety.js +51 -0
- package/library/request-token.js +56 -0
- package/package.json +2 -2
- package/packages/packages.js +17 -118
- package/publisher/publisher.js +60 -30
- package/publisher/validation.js +15 -3
- package/server.js +12 -3
- package/translations/Messages.properties +1 -0
- package/tx/README.md +21 -0
- package/tx/cs/cs-api.js +30 -1
- package/tx/cs/cs-cs.js +1 -1
- package/tx/cs/cs-icd11.js +805 -0
- package/tx/cs/cs-lang.js +331 -85
- package/tx/cs/cs-loinc.js +45 -4
- package/tx/cs/cs-mimetypes.js +492 -21
- package/tx/cs/cs-snomed.js +38 -4
- package/tx/cs/cs-ucum.js +33 -3
- package/tx/cs/icd11-expressions.js +243 -0
- package/tx/importers/export-icd11-codesystem.js +823 -0
- package/tx/importers/icd11-schema.md +285 -0
- package/tx/importers/import-icd11.js +861 -0
- package/tx/importers/import-loinc.module.js +14 -7
- package/tx/importers/import-sct.module.js +10 -1
- package/tx/library/codesystem.js +21 -27
- package/tx/library/nesting-limit.js +79 -0
- package/tx/library/operation-outcome.js +97 -3
- package/tx/library/valueset.js +16 -20
- package/tx/library.js +23 -1
- package/tx/ocl/cache/cache-utils.cjs +5 -3
- package/tx/ocl/cs-ocl.cjs +73 -17
- package/tx/ocl/jobs/background-queue.cjs +6 -3
- package/tx/ocl/mappers/concept-mapper.cjs +9 -1
- package/tx/ocl/shared/patches.cjs +48 -1
- package/tx/ocl/vs-ocl.cjs +15 -13
- package/tx/sct/expressions.js +194 -17
- package/tx/tx.fhir.org.yml +2 -1
- package/tx/tx.js +15 -42
- package/tx/workers/batch-validate.js +6 -16
- package/tx/workers/batch.js +16 -28
- package/tx/workers/cache-control.js +6 -12
- package/tx/workers/closure.js +3 -8
- package/tx/workers/compare.js +6 -34
- package/tx/workers/expand.js +36 -45
- package/tx/workers/lookup.js +21 -39
- package/tx/workers/read.js +6 -40
- package/tx/workers/search.js +2 -8
- package/tx/workers/subsumes.js +58 -41
- package/tx/workers/translate.js +11 -17
- package/tx/workers/validate.js +76 -77
- package/tx/workers/worker.js +70 -1
- package/tx/xml/xml-base.js +243 -76
- package/tx/xversion/xv-valueset.js +39 -1
package/tx/workers/compare.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
const { TerminologyWorker } = require('./worker');
|
|
12
12
|
const {TxParameters} = require("../params");
|
|
13
13
|
const {Extensions} = require("../library/extensions");
|
|
14
|
-
const {Issue, OperationOutcome} = require(
|
|
14
|
+
const { Issue, OperationOutcome, buildOperationOutcome, outcomeFromError } = require('../library/operation-outcome');
|
|
15
15
|
const ValueSet = require("../library/valueset");
|
|
16
16
|
const {ValueSetExpander} = require("./expand");
|
|
17
17
|
const {SearchFilterText} = require("../library/designations");
|
|
@@ -59,18 +59,7 @@ class CompareWorker extends TerminologyWorker {
|
|
|
59
59
|
oo.addIssue(error);
|
|
60
60
|
return res.status(error.statusCode || 500).json(oo.jsonObj);
|
|
61
61
|
} else {
|
|
62
|
-
|
|
63
|
-
return res.status(statusCode).json({
|
|
64
|
-
resourceType: 'OperationOutcome',
|
|
65
|
-
issue: [{
|
|
66
|
-
severity: 'error',
|
|
67
|
-
code: issueCode,
|
|
68
|
-
details: {
|
|
69
|
-
text: error.message
|
|
70
|
-
},
|
|
71
|
-
diagnostics: error.message
|
|
72
|
-
}]
|
|
73
|
-
});
|
|
62
|
+
return res.status(statusCode).json(outcomeFromError(error));
|
|
74
63
|
}
|
|
75
64
|
}
|
|
76
65
|
}
|
|
@@ -89,18 +78,7 @@ class CompareWorker extends TerminologyWorker {
|
|
|
89
78
|
debugLog(error);
|
|
90
79
|
req.logInfo = this.usedSources.join("|")+" - error"+(error.msgId ? " "+error.msgId : "");
|
|
91
80
|
const statusCode = error.statusCode || 500;
|
|
92
|
-
|
|
93
|
-
return res.status(statusCode).json({
|
|
94
|
-
resourceType: 'OperationOutcome',
|
|
95
|
-
issue: [{
|
|
96
|
-
severity: 'error',
|
|
97
|
-
code: issueCode,
|
|
98
|
-
details: {
|
|
99
|
-
text : error.message
|
|
100
|
-
},
|
|
101
|
-
diagnostics: error.message
|
|
102
|
-
}]
|
|
103
|
-
});
|
|
81
|
+
return res.status(statusCode).json(outcomeFromError(error));
|
|
104
82
|
}
|
|
105
83
|
}
|
|
106
84
|
|
|
@@ -158,15 +136,9 @@ class CompareWorker extends TerminologyWorker {
|
|
|
158
136
|
* @param {string} message - Diagnostic message
|
|
159
137
|
* @returns {Object} OperationOutcome resource
|
|
160
138
|
*/
|
|
161
|
-
operationOutcome(severity, code, message) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
issue: [{
|
|
165
|
-
severity,
|
|
166
|
-
code,
|
|
167
|
-
diagnostics: message
|
|
168
|
-
}]
|
|
169
|
-
};
|
|
139
|
+
operationOutcome(severity, code, message, txIssueType = null) {
|
|
140
|
+
// the shared builder, so that every outcome has details.text and a tx-issue-type coding
|
|
141
|
+
return buildOperationOutcome(severity, code, message, txIssueType);
|
|
170
142
|
}
|
|
171
143
|
|
|
172
144
|
async readValueSet(res, prefix, params) {
|
package/tx/workers/expand.js
CHANGED
|
@@ -14,7 +14,7 @@ const {Designations, SearchFilterText} = require("../library/designations");
|
|
|
14
14
|
const {Extensions} = require("../library/extensions");
|
|
15
15
|
const {getValuePrimitive, getValueName, validateParameter} = require("../../library/utilities");
|
|
16
16
|
const {div} = require("../../library/html");
|
|
17
|
-
const {Issue, OperationOutcome} = require(
|
|
17
|
+
const { Issue, OperationOutcome, buildOperationOutcome, outcomeFromError } = require('../library/operation-outcome');
|
|
18
18
|
const crypto = require('crypto');
|
|
19
19
|
const ValueSet = require("../library/valueset");
|
|
20
20
|
const {VersionUtilities} = require("../../library/version-utilities");
|
|
@@ -447,8 +447,21 @@ class ValueSetExpander {
|
|
|
447
447
|
}
|
|
448
448
|
}
|
|
449
449
|
|
|
450
|
+
// '*' asks for every property the code system defines for the concept (R6's
|
|
451
|
+
// ValueSet.compose.property allows it, and a request can name it too). It is handled
|
|
452
|
+
// here rather than by expanding it into a list of names, because which properties
|
|
453
|
+
// exist is a property of the concept, not of the request
|
|
454
|
+
if (this.params.properties.includes('*') && csProps != null && cs != null) {
|
|
455
|
+
for (const cp of csProps) {
|
|
456
|
+
const vn = getValueName(cp);
|
|
457
|
+
this.defineProperty(expansion, n, this.getPropUrl(cs, cp.code, cp), cp.code, vn, cp[vn], true);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
450
461
|
for (const pn of this.params.properties) {
|
|
451
|
-
if (pn === '
|
|
462
|
+
if (pn === '*') {
|
|
463
|
+
continue; // done above
|
|
464
|
+
} else if (pn === 'definition') {
|
|
452
465
|
if (definition) {
|
|
453
466
|
this.defineProperty(expansion, n, 'http://hl7.org/fhir/concept-properties#definition', pn, "valueString", definition);
|
|
454
467
|
}
|
|
@@ -1094,7 +1107,7 @@ class ValueSetExpander {
|
|
|
1094
1107
|
this.worker.opContext.log('prep filters');
|
|
1095
1108
|
const prep = await cs.getPrepContext(true);
|
|
1096
1109
|
if (!filter.isNull) {
|
|
1097
|
-
await cs.searchFilter(
|
|
1110
|
+
await cs.searchFilter(prep, filter, true);
|
|
1098
1111
|
}
|
|
1099
1112
|
|
|
1100
1113
|
if (cs.specialEnumeration()) {
|
|
@@ -1699,16 +1712,23 @@ class ValueSetExpander {
|
|
|
1699
1712
|
if (!expansion.property) {
|
|
1700
1713
|
expansion.property = [];
|
|
1701
1714
|
}
|
|
1702
|
-
|
|
1715
|
+
// match on the uri only when there is one. A code system may declare several properties with
|
|
1716
|
+
// no uri at all, and those are told apart by their code - matching on an undefined uri would
|
|
1717
|
+
// collapse all of them into whichever one was declared first
|
|
1718
|
+
let pd = url
|
|
1719
|
+
? expansion.property.find(t1 => t1.uri == url || t1.code == code)
|
|
1720
|
+
: expansion.property.find(t1 => t1.code == code);
|
|
1703
1721
|
if (!pd) {
|
|
1704
1722
|
pd = {};
|
|
1705
1723
|
expansion.property.push(pd);
|
|
1706
|
-
pd.uri = url;
|
|
1707
1724
|
pd.code = code;
|
|
1708
|
-
|
|
1725
|
+
if (url) {
|
|
1726
|
+
pd.uri = url;
|
|
1727
|
+
}
|
|
1728
|
+
} else if (url && !pd.uri) {
|
|
1709
1729
|
pd.uri = url;
|
|
1710
1730
|
}
|
|
1711
|
-
if (pd.uri != url) {
|
|
1731
|
+
if (url && pd.uri != url) {
|
|
1712
1732
|
throw new Error('URL mismatch on expansion: ' + pd.uri + ' vs ' + url + ' for code ' + code);
|
|
1713
1733
|
}
|
|
1714
1734
|
return pd.code;
|
|
@@ -1738,7 +1758,7 @@ class ValueSetExpander {
|
|
|
1738
1758
|
}
|
|
1739
1759
|
}
|
|
1740
1760
|
for (const definition of definitions) {
|
|
1741
|
-
if (
|
|
1761
|
+
if (used.has(definition.code)) {
|
|
1742
1762
|
this.declareProperty(expansion, definition.uri, definition.code);
|
|
1743
1763
|
}
|
|
1744
1764
|
}
|
|
@@ -1754,10 +1774,9 @@ class ValueSetExpander {
|
|
|
1754
1774
|
if (value === undefined || value == null) {
|
|
1755
1775
|
return;
|
|
1756
1776
|
}
|
|
1757
|
-
//
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
}
|
|
1777
|
+
// every property that appears in contains.property has to be declared in expansion.property,
|
|
1778
|
+
// whether or not the code system gave it a uri
|
|
1779
|
+
code = this.declareProperty(expansion, url, code);
|
|
1761
1780
|
|
|
1762
1781
|
if (!contains.property) {
|
|
1763
1782
|
contains.property = [];
|
|
@@ -1950,18 +1969,7 @@ class ExpandWorker extends TerminologyWorker {
|
|
|
1950
1969
|
oo.addIssue(error);
|
|
1951
1970
|
return res.status(error.statusCode || 500).json(oo.jsonObj);
|
|
1952
1971
|
} else {
|
|
1953
|
-
|
|
1954
|
-
return res.status(statusCode).json({
|
|
1955
|
-
resourceType: 'OperationOutcome',
|
|
1956
|
-
issue: [{
|
|
1957
|
-
severity: 'error',
|
|
1958
|
-
code: issueCode,
|
|
1959
|
-
details: {
|
|
1960
|
-
text: error.message
|
|
1961
|
-
},
|
|
1962
|
-
diagnostics: error.message
|
|
1963
|
-
}]
|
|
1964
|
-
});
|
|
1972
|
+
return res.status(statusCode).json(outcomeFromError(error));
|
|
1965
1973
|
}
|
|
1966
1974
|
}
|
|
1967
1975
|
}
|
|
@@ -1980,18 +1988,7 @@ class ExpandWorker extends TerminologyWorker {
|
|
|
1980
1988
|
debugLog(error);
|
|
1981
1989
|
req.logInfo = this.usedSources.join("|")+" - error"+(error.msgId ? " "+error.msgId : "");
|
|
1982
1990
|
const statusCode = error.statusCode || 500;
|
|
1983
|
-
|
|
1984
|
-
return res.status(statusCode).json({
|
|
1985
|
-
resourceType: 'OperationOutcome',
|
|
1986
|
-
issue: [{
|
|
1987
|
-
severity: 'error',
|
|
1988
|
-
code: issueCode,
|
|
1989
|
-
details: {
|
|
1990
|
-
text : error.message
|
|
1991
|
-
},
|
|
1992
|
-
diagnostics: error.message
|
|
1993
|
-
}]
|
|
1994
|
-
});
|
|
1991
|
+
return res.status(statusCode).json(outcomeFromError(error));
|
|
1995
1992
|
}
|
|
1996
1993
|
}
|
|
1997
1994
|
|
|
@@ -2253,15 +2250,9 @@ class ExpandWorker extends TerminologyWorker {
|
|
|
2253
2250
|
* @param {string} message - Diagnostic message
|
|
2254
2251
|
* @returns {Object} OperationOutcome resource
|
|
2255
2252
|
*/
|
|
2256
|
-
operationOutcome(severity, code, message) {
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
issue: [{
|
|
2260
|
-
severity,
|
|
2261
|
-
code,
|
|
2262
|
-
diagnostics: message
|
|
2263
|
-
}]
|
|
2264
|
-
};
|
|
2253
|
+
operationOutcome(severity, code, message, txIssueType = null) {
|
|
2254
|
+
// the shared builder, so that every outcome has details.text and a tx-issue-type coding
|
|
2255
|
+
return buildOperationOutcome(severity, code, message, txIssueType);
|
|
2265
2256
|
}
|
|
2266
2257
|
|
|
2267
2258
|
}
|
package/tx/workers/lookup.js
CHANGED
|
@@ -12,7 +12,7 @@ const { FhirCodeSystemProvider } = require('../cs/cs-cs');
|
|
|
12
12
|
const { Designations} = require("../library/designations");
|
|
13
13
|
const {TxParameters} = require("../params");
|
|
14
14
|
const {Parameters} = require("../library/parameters");
|
|
15
|
-
const {Issue, OperationOutcome} = require(
|
|
15
|
+
const { Issue, OperationOutcome, buildOperationOutcome, outcomeFromError } = require('../library/operation-outcome');
|
|
16
16
|
const {debugLog} = require("../operation-context");
|
|
17
17
|
const {checkAddedParameters} = require("../library/parameter-check");
|
|
18
18
|
|
|
@@ -49,16 +49,7 @@ class LookupWorker extends TerminologyWorker {
|
|
|
49
49
|
this.log.error(error);
|
|
50
50
|
debugLog(error);
|
|
51
51
|
req.logInfo = this.usedSources.join("|")+" - error"+(error.msgId ? " "+error.msgId : "");
|
|
52
|
-
|
|
53
|
-
const issueCode = error.issueCode || 'exception';
|
|
54
|
-
return res.status(statusCode).json({
|
|
55
|
-
resourceType: 'OperationOutcome',
|
|
56
|
-
issue: [{
|
|
57
|
-
severity: 'error',
|
|
58
|
-
code: issueCode,
|
|
59
|
-
diagnostics: error.message
|
|
60
|
-
}]
|
|
61
|
-
});
|
|
52
|
+
return res.status(error.statusCode || 500).json(outcomeFromError(error));
|
|
62
53
|
}
|
|
63
54
|
}
|
|
64
55
|
|
|
@@ -76,15 +67,7 @@ class LookupWorker extends TerminologyWorker {
|
|
|
76
67
|
this.log.error(error);
|
|
77
68
|
debugLog(error);
|
|
78
69
|
req.logInfo = this.usedSources.join("|")+" - error"+(error.msgId ? " "+error.msgId : "");
|
|
79
|
-
|
|
80
|
-
return res.status(400).json({
|
|
81
|
-
resourceType: 'OperationOutcome',
|
|
82
|
-
issue: [{
|
|
83
|
-
severity: 'error',
|
|
84
|
-
code: issueCode,
|
|
85
|
-
diagnostics: error.message
|
|
86
|
-
}]
|
|
87
|
-
});
|
|
70
|
+
return res.status(400).json(outcomeFromError(error));
|
|
88
71
|
}
|
|
89
72
|
}
|
|
90
73
|
|
|
@@ -140,8 +123,11 @@ class LookupWorker extends TerminologyWorker {
|
|
|
140
123
|
}
|
|
141
124
|
|
|
142
125
|
if (!csProvider) {
|
|
143
|
-
|
|
144
|
-
|
|
126
|
+
// params is a Map-like: params.system is always undefined, so the message read
|
|
127
|
+
// "CodeSystem not found: undefined" for every unknown system or version.
|
|
128
|
+
const coding = params.has('coding') ? params.get('coding') : null;
|
|
129
|
+
const systemUrl = params.has('system') ? params.get('system') : coding?.system;
|
|
130
|
+
const versionStr = params.has('version') ? params.get('version') : coding?.version;
|
|
145
131
|
const msg = versionStr
|
|
146
132
|
? `CodeSystem not found: ${systemUrl} version ${versionStr}`
|
|
147
133
|
: `CodeSystem not found: ${systemUrl}`;
|
|
@@ -169,8 +155,7 @@ class LookupWorker extends TerminologyWorker {
|
|
|
169
155
|
oo.addIssue(error);
|
|
170
156
|
return res.status(error.statusCode || 500).json(oo.jsonObj);
|
|
171
157
|
} else {
|
|
172
|
-
return res.status(error.statusCode || 500).json(
|
|
173
|
-
'error', error.issueCode || 'exception', error.message));
|
|
158
|
+
return res.status(error.statusCode || 500).json(outcomeFromError(error));
|
|
174
159
|
}
|
|
175
160
|
}
|
|
176
161
|
}
|
|
@@ -233,8 +218,7 @@ class LookupWorker extends TerminologyWorker {
|
|
|
233
218
|
oo.addIssue(error);
|
|
234
219
|
return res.status(error.statusCode || 500).json(oo.jsonObj);
|
|
235
220
|
} else {
|
|
236
|
-
return res.status(error.statusCode || 500).json(
|
|
237
|
-
'error', error.issueCode || 'exception', error.message));
|
|
221
|
+
return res.status(error.statusCode || 500).json(outcomeFromError(error));
|
|
238
222
|
}
|
|
239
223
|
}
|
|
240
224
|
}
|
|
@@ -271,7 +255,9 @@ class LookupWorker extends TerminologyWorker {
|
|
|
271
255
|
if (locateResult?.message) {
|
|
272
256
|
message += ' ('+locateResult.message+')';
|
|
273
257
|
}
|
|
274
|
-
|
|
258
|
+
// invalid-code, not not-found: the code system was found, the code in it was not.
|
|
259
|
+
// not-found is for a code system or value set that could not be resolved at all.
|
|
260
|
+
throw new Issue('error', 'not-found', null, null, message, 'invalid-code', 404);
|
|
275
261
|
}
|
|
276
262
|
|
|
277
263
|
const ctxt = locateResult.context;
|
|
@@ -441,21 +427,17 @@ class LookupWorker extends TerminologyWorker {
|
|
|
441
427
|
}
|
|
442
428
|
|
|
443
429
|
/**
|
|
444
|
-
* Build an OperationOutcome
|
|
430
|
+
* Build an OperationOutcome. Delegates to the shared builder so that every outcome this
|
|
431
|
+
* server emits has details.text and a tx-issue-type coding: diagnostics is stripped by
|
|
432
|
+
* the test harness, so nothing a caller needs may live there.
|
|
445
433
|
* @param {string} severity - error, warning, information
|
|
446
|
-
* @param {string} code -
|
|
447
|
-
* @param {string} message -
|
|
434
|
+
* @param {string} code - FHIR issue type
|
|
435
|
+
* @param {string} message - the human readable account of the problem
|
|
436
|
+
* @param {string} [txIssueType] - tx-issue-type; defaulted from code when not given
|
|
448
437
|
* @returns {Object} OperationOutcome resource
|
|
449
438
|
*/
|
|
450
|
-
operationOutcome(severity, code, message) {
|
|
451
|
-
return
|
|
452
|
-
resourceType: 'OperationOutcome',
|
|
453
|
-
issue: [{
|
|
454
|
-
severity,
|
|
455
|
-
code,
|
|
456
|
-
details: {text : message}
|
|
457
|
-
}]
|
|
458
|
-
};
|
|
439
|
+
operationOutcome(severity, code, message, txIssueType = null) {
|
|
440
|
+
return buildOperationOutcome(severity, code, message, txIssueType);
|
|
459
441
|
}
|
|
460
442
|
}
|
|
461
443
|
|
package/tx/workers/read.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
const { TerminologyWorker } = require('./worker');
|
|
8
8
|
const {debugLog} = require("../operation-context");
|
|
9
9
|
|
|
10
|
+
const { buildOperationOutcome } = require('../library/operation-outcome');
|
|
10
11
|
class ReadWorker extends TerminologyWorker {
|
|
11
12
|
/**
|
|
12
13
|
* @param {OperationContext} opContext - Operation context
|
|
@@ -50,27 +51,13 @@ class ReadWorker extends TerminologyWorker {
|
|
|
50
51
|
return await this.handleConceptMap(req, res, id);
|
|
51
52
|
|
|
52
53
|
default:
|
|
53
|
-
return res.status(404).json({
|
|
54
|
-
resourceType: 'OperationOutcome',
|
|
55
|
-
issue: [{
|
|
56
|
-
severity: 'error',
|
|
57
|
-
code: 'not-found',
|
|
58
|
-
diagnostics: `Unknown resource type: ${resourceType}`
|
|
59
|
-
}]
|
|
60
|
-
});
|
|
54
|
+
return res.status(404).json(buildOperationOutcome('error', 'not-found', `Unknown resource type: ${resourceType}`));
|
|
61
55
|
}
|
|
62
56
|
} catch (error) {
|
|
63
57
|
this.log.error(error);
|
|
64
58
|
debugLog(error);
|
|
65
59
|
req.logInfo = this.usedSources.join("|")+" - error"+(error.msgId ? " "+error.msgId : "");
|
|
66
|
-
return res.status(500).json(
|
|
67
|
-
resourceType: 'OperationOutcome',
|
|
68
|
-
issue: [{
|
|
69
|
-
severity: 'error',
|
|
70
|
-
code: 'exception',
|
|
71
|
-
diagnostics: error.message
|
|
72
|
-
}]
|
|
73
|
-
});
|
|
60
|
+
return res.status(500).json(buildOperationOutcome('error', 'exception', error.message));
|
|
74
61
|
}
|
|
75
62
|
}
|
|
76
63
|
|
|
@@ -127,14 +114,7 @@ class ReadWorker extends TerminologyWorker {
|
|
|
127
114
|
}
|
|
128
115
|
}
|
|
129
116
|
|
|
130
|
-
return res.status(404).json({
|
|
131
|
-
resourceType: 'OperationOutcome',
|
|
132
|
-
issue: [{
|
|
133
|
-
severity: 'error',
|
|
134
|
-
code: 'not-found',
|
|
135
|
-
diagnostics: `CodeSystem/${id} not found`
|
|
136
|
-
}]
|
|
137
|
-
});
|
|
117
|
+
return res.status(404).json(buildOperationOutcome('error', 'not-found', `CodeSystem/${id} not found`));
|
|
138
118
|
}
|
|
139
119
|
|
|
140
120
|
/**
|
|
@@ -151,14 +131,7 @@ class ReadWorker extends TerminologyWorker {
|
|
|
151
131
|
}
|
|
152
132
|
}
|
|
153
133
|
|
|
154
|
-
return res.status(404).json({
|
|
155
|
-
resourceType: 'OperationOutcome',
|
|
156
|
-
issue: [{
|
|
157
|
-
severity: 'error',
|
|
158
|
-
code: 'not-found',
|
|
159
|
-
diagnostics: `ValueSet/${id} not found`
|
|
160
|
-
}]
|
|
161
|
-
});
|
|
134
|
+
return res.status(404).json(buildOperationOutcome('error', 'not-found', `ValueSet/${id} not found`));
|
|
162
135
|
}
|
|
163
136
|
/**
|
|
164
137
|
* Handle ConceptMap read
|
|
@@ -174,14 +147,7 @@ class ReadWorker extends TerminologyWorker {
|
|
|
174
147
|
}
|
|
175
148
|
}
|
|
176
149
|
|
|
177
|
-
return res.status(404).json({
|
|
178
|
-
resourceType: 'OperationOutcome',
|
|
179
|
-
issue: [{
|
|
180
|
-
severity: 'error',
|
|
181
|
-
code: 'not-found',
|
|
182
|
-
diagnostics: `ConceptMap/${id} not found`
|
|
183
|
-
}]
|
|
184
|
-
});
|
|
150
|
+
return res.status(404).json(buildOperationOutcome('error', 'not-found', `ConceptMap/${id} not found`));
|
|
185
151
|
}
|
|
186
152
|
}
|
|
187
153
|
|
package/tx/workers/search.js
CHANGED
|
@@ -10,6 +10,7 @@ const {Utilities} = require("../../library/utilities");
|
|
|
10
10
|
const {debugLog} = require("../operation-context");
|
|
11
11
|
const {acceptsHtml} = require("../tx-html");
|
|
12
12
|
|
|
13
|
+
const { buildOperationOutcome } = require('../library/operation-outcome');
|
|
13
14
|
class SearchWorker extends TerminologyWorker {
|
|
14
15
|
/**
|
|
15
16
|
* @param {OperationContext} opContext - Operation context
|
|
@@ -127,14 +128,7 @@ class SearchWorker extends TerminologyWorker {
|
|
|
127
128
|
this.log.error(error);
|
|
128
129
|
debugLog(error);
|
|
129
130
|
req.logInfo = "error "+(error.msgId || error.className);
|
|
130
|
-
return res.status(500).json(
|
|
131
|
-
resourceType: 'OperationOutcome',
|
|
132
|
-
issue: [{
|
|
133
|
-
severity: 'error',
|
|
134
|
-
code: 'exception',
|
|
135
|
-
diagnostics: error.message
|
|
136
|
-
}]
|
|
137
|
-
});
|
|
131
|
+
return res.status(500).json(buildOperationOutcome('error', 'exception', error.message));
|
|
138
132
|
}
|
|
139
133
|
}
|
|
140
134
|
|
package/tx/workers/subsumes.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// POST /CodeSystem/{id}/$subsumes
|
|
8
8
|
//
|
|
9
9
|
|
|
10
|
-
const { TerminologyWorker } = require('./worker');
|
|
10
|
+
const { TerminologyWorker, Unknown_Code_in_VersionSCT, SCTVersion } = require('./worker');
|
|
11
11
|
const { FhirCodeSystemProvider } = require('../cs/cs-cs');
|
|
12
12
|
const {TxParameters} = require("../params");
|
|
13
13
|
const {Parameters} = require("../library/parameters");
|
|
@@ -51,8 +51,7 @@ class SubsumesWorker extends TerminologyWorker {
|
|
|
51
51
|
oo.addIssue(error);
|
|
52
52
|
return res.status(error.statusCode || 500).json(oo.jsonObj);
|
|
53
53
|
} else {
|
|
54
|
-
return res.status(error.statusCode || 500).json(this.
|
|
55
|
-
'error', error.issueCode || 'exception', error.message));
|
|
54
|
+
return res.status(error.statusCode || 500).json(this.unexpectedErrorOutcome(error));
|
|
56
55
|
}
|
|
57
56
|
}
|
|
58
57
|
}
|
|
@@ -74,8 +73,7 @@ class SubsumesWorker extends TerminologyWorker {
|
|
|
74
73
|
oo.addIssue(error);
|
|
75
74
|
return res.status(error.statusCode || 500).json(oo.jsonObj);
|
|
76
75
|
} else {
|
|
77
|
-
return res.status(error.statusCode || 500).json(this.
|
|
78
|
-
'error', error.issueCode || 'exception', error.message));
|
|
76
|
+
return res.status(error.statusCode || 500).json(this.unexpectedErrorOutcome(error));
|
|
79
77
|
}
|
|
80
78
|
}
|
|
81
79
|
}
|
|
@@ -87,13 +85,14 @@ class SubsumesWorker extends TerminologyWorker {
|
|
|
87
85
|
async handleTypeLevelSubsumes(req, res) {
|
|
88
86
|
this.deadCheck('subsumes-type-level');
|
|
89
87
|
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
//
|
|
88
|
+
// Parse the parameters first, then hand them to setupAdditionalResources. It used to be
|
|
89
|
+
// handed req.body, which meant a GET (no body) never reached the cache-id handling: when a
|
|
90
|
+
// client front-loads a suite's resources into a server-side cache the request carries no
|
|
91
|
+
// tx-resource at all, only the X-Cache-Id header, and every code system supplied that way
|
|
92
|
+
// was invisible. On a GET parseParameters turns the query string into the same Parameters
|
|
93
|
+
// shape, and the cache-id comes off the operation context
|
|
96
94
|
const params = new Parameters(this.parseParameters(req));
|
|
95
|
+
this.setupAdditionalResources(params.jsonObj);
|
|
97
96
|
const txp = new TxParameters(this.opContext.i18n.languageDefinitions, this.opContext.i18n);
|
|
98
97
|
txp.readParams(params.jsonObj);
|
|
99
98
|
|
|
@@ -101,7 +100,9 @@ class SubsumesWorker extends TerminologyWorker {
|
|
|
101
100
|
let codingA, codingB;
|
|
102
101
|
let csProvider;
|
|
103
102
|
|
|
103
|
+
let names = ['codeA', 'codeB'];
|
|
104
104
|
if (params.has('codingA') && params.has('codingB')) {
|
|
105
|
+
names = ['codingA', 'codingB'];
|
|
105
106
|
// Using codingA and codingB (only from Parameters resource)
|
|
106
107
|
codingA = params.get('codingA');
|
|
107
108
|
codingB = params.get('codingB');
|
|
@@ -138,7 +139,7 @@ class SubsumesWorker extends TerminologyWorker {
|
|
|
138
139
|
}
|
|
139
140
|
|
|
140
141
|
// Perform the subsumes check
|
|
141
|
-
const result = await this.doSubsumes(csProvider, codingA, codingB);
|
|
142
|
+
const result = await this.doSubsumes(csProvider, codingA, codingB, txp, names);
|
|
142
143
|
req.logInfo = this.usedSources.join("|")+txp.logInfo();
|
|
143
144
|
return res.status(200).json(result);
|
|
144
145
|
}
|
|
@@ -159,13 +160,9 @@ class SubsumesWorker extends TerminologyWorker {
|
|
|
159
160
|
throw new Issue('error', 'not found', null, null, `CodeSystem/${id} not found`, null, 404);
|
|
160
161
|
}
|
|
161
162
|
|
|
162
|
-
//
|
|
163
|
-
if (req.body && req.body.resourceType === 'Parameters') {
|
|
164
|
-
this.setupAdditionalResources(req.body);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Parse parameters from request
|
|
163
|
+
// See handleTypeLevelSubsumes: parse first, so a GET reaches the cache-id handling too
|
|
168
164
|
const params = new Parameters(this.parseParameters(req));
|
|
165
|
+
this.setupAdditionalResources(params.jsonObj);
|
|
169
166
|
const txp = new TxParameters(this.opContext.i18n.languageDefinitions, this.opContext.i18n);
|
|
170
167
|
txp.readParams(params.jsonObj);
|
|
171
168
|
|
|
@@ -178,7 +175,9 @@ class SubsumesWorker extends TerminologyWorker {
|
|
|
178
175
|
// Get the codings
|
|
179
176
|
let codingA, codingB;
|
|
180
177
|
|
|
178
|
+
let names = ['codeA', 'codeB'];
|
|
181
179
|
if (params.has('codingA') && params.has('codingB')) {
|
|
180
|
+
names = ['codingA', 'codingB'];
|
|
182
181
|
codingA = params.get('codingA');
|
|
183
182
|
codingB = params.get('codingB');
|
|
184
183
|
} else if (params.has('codeA') && params.has('codeB')) {
|
|
@@ -198,7 +197,7 @@ class SubsumesWorker extends TerminologyWorker {
|
|
|
198
197
|
}
|
|
199
198
|
|
|
200
199
|
// Perform the subsumes check
|
|
201
|
-
const result = await this.doSubsumes(csProvider, codingA, codingB);
|
|
200
|
+
const result = await this.doSubsumes(csProvider, codingA, codingB, txp, names);
|
|
202
201
|
req.logInfo = this.usedSources.join("|")+txp.logInfo();
|
|
203
202
|
return res.json(result);
|
|
204
203
|
}
|
|
@@ -263,9 +262,11 @@ class SubsumesWorker extends TerminologyWorker {
|
|
|
263
262
|
* @param {CodeSystemProvider} csProvider - CodeSystem provider
|
|
264
263
|
* @param {Object} codingA - First coding
|
|
265
264
|
* @param {Object} codingB - Second coding
|
|
265
|
+
* @param {TxParameters} txp - parsed parameters (for languages)
|
|
266
|
+
* @param {Array<string>} names - the parameters the two codes came from, for `expression`
|
|
266
267
|
* @returns {Object} Parameters resource with subsumes result
|
|
267
268
|
*/
|
|
268
|
-
async doSubsumes(csProvider, codingA, codingB) {
|
|
269
|
+
async doSubsumes(csProvider, codingA, codingB, txp, names = ['codeA', 'codeB']) {
|
|
269
270
|
this.deadCheck('doSubsumes');
|
|
270
271
|
|
|
271
272
|
const csSystem = csProvider.system();
|
|
@@ -287,18 +288,12 @@ class SubsumesWorker extends TerminologyWorker {
|
|
|
287
288
|
// Validate both codes exist
|
|
288
289
|
const locateA = await csProvider.locate(codingA.code);
|
|
289
290
|
if (!locateA || !locateA.context) {
|
|
290
|
-
|
|
291
|
-
error.statusCode = 404;
|
|
292
|
-
error.issueCode = 'not-found';
|
|
293
|
-
throw error;
|
|
291
|
+
throw this.unknownCodeIssue(csProvider, codingA.code, names[0], locateA ? locateA.message : null, txp);
|
|
294
292
|
}
|
|
295
293
|
|
|
296
294
|
const locateB = await csProvider.locate(codingB.code);
|
|
297
295
|
if (!locateB || !locateB.context) {
|
|
298
|
-
|
|
299
|
-
error.statusCode = 404;
|
|
300
|
-
error.issueCode = 'not-found';
|
|
301
|
-
throw error;
|
|
296
|
+
throw this.unknownCodeIssue(csProvider, codingB.code, names[1], locateB ? locateB.message : null, txp);
|
|
302
297
|
}
|
|
303
298
|
|
|
304
299
|
let equal = false;
|
|
@@ -324,21 +319,43 @@ class SubsumesWorker extends TerminologyWorker {
|
|
|
324
319
|
}
|
|
325
320
|
|
|
326
321
|
/**
|
|
327
|
-
* Build
|
|
328
|
-
*
|
|
329
|
-
* @param {
|
|
330
|
-
* @param {string}
|
|
322
|
+
* Build the Issue for a code that isn't in the code system. Uses the same message ids
|
|
323
|
+
* and tx-issue-type detail code as $validate-code, so clients get consistent errors
|
|
324
|
+
* @param {CodeSystemProvider} csProvider - CodeSystem provider
|
|
325
|
+
* @param {string} code - the code that wasn't found
|
|
326
|
+
* @param {string} path - the parameter the code came from ('codeA' / 'codeB')
|
|
327
|
+
* @param {string} message - any explanation the provider gave for not finding it
|
|
328
|
+
* @param {TxParameters} txp - parsed parameters (for languages)
|
|
329
|
+
* @returns {Issue}
|
|
330
|
+
*/
|
|
331
|
+
unknownCodeIssue(csProvider, code, path, message, txp) {
|
|
332
|
+
const system = csProvider.system();
|
|
333
|
+
const version = csProvider.version();
|
|
334
|
+
const msgId = Unknown_Code_in_VersionSCT(system, version);
|
|
335
|
+
const langs = txp ? txp.HTTPLanguages : null;
|
|
336
|
+
const msg = this.i18n.translate(msgId, langs, [code, system, version, SCTVersion(system, version)]);
|
|
337
|
+
const issue = new Issue('error', 'code-invalid', path, msgId, msg, 'invalid-code', 404);
|
|
338
|
+
if (message) {
|
|
339
|
+
issue.withDiagnostics(message);
|
|
340
|
+
}
|
|
341
|
+
return issue;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Wrap an error we did not expect as an OperationOutcome. The message goes in details.text,
|
|
346
|
+
* not diagnostics: TxTester strips diagnostics from every issue it compares, so anything
|
|
347
|
+
* reported that way is invisible in a failing test - all you see is 'exception'
|
|
348
|
+
* @param {Error} error - the error that escaped
|
|
331
349
|
* @returns {Object} OperationOutcome resource
|
|
332
350
|
*/
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
};
|
|
351
|
+
unexpectedErrorOutcome(error) {
|
|
352
|
+
// txIssueType, when the thrower set one, becomes the details.coding - that is where
|
|
353
|
+
// a client looks for the machine-readable reason (e.g. cannot-determine).
|
|
354
|
+
const issue = new Issue('error', error.issueCode || 'exception', null, null,
|
|
355
|
+
error.message || String(error), error.txIssueType || null, error.statusCode || 500);
|
|
356
|
+
const oo = new OperationOutcome();
|
|
357
|
+
oo.addIssue(issue);
|
|
358
|
+
return oo.jsonObj;
|
|
342
359
|
}
|
|
343
360
|
}
|
|
344
361
|
|