Skip to content

Commit 25b0351

Browse files
author
Tobias Machein
committed
968: add Validation Support for Resources
968: extract validator to own module 968: use validator in rest-api and admin-api 968: remove double checking of profiles from db 968: add validation feature toggle 968: fix paranthesis 968: transform db profile to input stream instead of string 968: add system tests for validator feature toggle 968: add validation for Bundle requests 968: add integration tests 968: rename enable validation env var, docs
1 parent 74bb7b4 commit 25b0351

File tree

27 files changed

+1177
-188
lines changed

27 files changed

+1177
-188
lines changed
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
#!/bin/bash -e
2+
3+
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
4+
. "$SCRIPT_DIR/util.sh"
5+
6+
patient-valid-no-profile() {
7+
cat <<END
8+
{
9+
"resourceType": "Patient"
10+
}
11+
END
12+
}
13+
14+
patient-invalid-with-profile() {
15+
cat <<END
16+
{
17+
"resourceType": "Patient",
18+
"meta": {
19+
"profile": "http://example.org/url-114730"
20+
}
21+
}
22+
END
23+
}
24+
25+
patient-valid-with-profile() {
26+
cat <<END
27+
{
28+
"resourceType": "Patient",
29+
"active": true,
30+
"meta": {
31+
"profile": "http://example.org/url-114730"
32+
}
33+
}
34+
END
35+
}
36+
37+
patient-valid-with-profile-as-bundle() {
38+
cat <<END
39+
{
40+
"resourceType": "Patient",
41+
"active": true,
42+
"meta": {
43+
"profile": "http://example.org/url-114730"
44+
}
45+
}
46+
END
47+
}
48+
49+
bundle-valid-patient-with-profile() {
50+
cat <<END
51+
{
52+
"resourceType": "Bundle",
53+
"type": "transaction",
54+
"entry": [
55+
{
56+
"resource": $(patient-valid-with-profile),
57+
"request": {
58+
"method": "POST",
59+
"url": "/Patient"
60+
}
61+
}
62+
]
63+
}
64+
END
65+
}
66+
67+
structure-definition-patient() {
68+
cat <<END
69+
{
70+
"resourceType": "StructureDefinition",
71+
"name": "Patient-profile-1",
72+
"status": "active",
73+
"kind": "resource",
74+
"abstract": false,
75+
"url": "http://example.org/url-114730",
76+
"type": "Patient",
77+
"baseDefinition": "http://hl7.org/fhir/StructureDefinition/Patient",
78+
"derivation": "constraint",
79+
"differential": {
80+
"element": [
81+
{
82+
"id": "patient-active-rule",
83+
"path": "Patient.active",
84+
"mustSupport": true,
85+
"min": 1
86+
}
87+
]
88+
}
89+
}
90+
END
91+
}
92+
93+
BASE="http://localhost:8080/fhir"
94+
95+
echo "1: testing before Patient profile created"
96+
97+
echo "testing valid patient without profile"
98+
RESULT_NO_PROFILE=$(patient-valid-no-profile | curl -s -H 'Accept: application/fhir+json' -H "Content-Type: application/fhir+json" -d @- "$BASE/Patient")
99+
test "resource type" "$(echo "$RESULT_NO_PROFILE" | jq -r .resourceType)" "Patient"
100+
101+
echo "testing invalid patient with profile"
102+
RESULT_INVALID_WITH_PROFILE=$(patient-invalid-with-profile | curl -s -H 'Accept: application/fhir+json' -H "Content-Type: application/fhir+json" -d @- "$BASE/Patient")
103+
test "resource type" "$(echo "$RESULT_INVALID_WITH_PROFILE" | jq -r .resourceType)" "OperationOutcome"
104+
test "issue diagnostics" "$(echo "$RESULT_INVALID_WITH_PROFILE" | jq -r .issue[0].diagnostics)" "Profile reference 'http://example.org/url-114730' has not been checked because it could not be found"
105+
106+
echo "testing valid patient with profile"
107+
RESULT_VALID_WITH_PROFILE=$(patient-valid-with-profile | curl -s -H 'Accept: application/fhir+json' -H "Content-Type: application/fhir+json" -d @- "$BASE/Patient")
108+
test "resource type" "$(echo "$RESULT_VALID_WITH_PROFILE" | jq -r .resourceType)" "OperationOutcome"
109+
test "issue diagnostics" "$(echo "$RESULT_VALID_WITH_PROFILE" | jq -r .issue[0].diagnostics)" "Profile reference 'http://example.org/url-114730' has not been checked because it could not be found"
110+
111+
echo "testing valid patient with profile as bundle"
112+
RESULT_BUNDLE_VALID_WITH_PROFILE=$(bundle-valid-patient-with-profile | curl -s -H 'Accept: application/fhir+json' -H "Content-Type: application/fhir+json" -d @- "$BASE")
113+
test "resource type" "$(echo "$RESULT_VALID_WITH_PROFILE" | jq -r .resourceType)" "OperationOutcome"
114+
test "issue diagnostics" "$(echo "$RESULT_VALID_WITH_PROFILE" | jq -r .issue[0].diagnostics)" "Profile reference 'http://example.org/url-114730' has not been checked because it could not be found"
115+
116+
echo "2: creating the patient profile"
117+
RESULT_STRUCTURE_DEFINITION=$(structure-definition-patient | curl -s -H 'Accept: application/fhir+json' -H "Content-Type: application/fhir+json" -d @- "$BASE/StructureDefinition")
118+
test "resource type" "$(echo "$RESULT_STRUCTURE_DEFINITION" | jq -r .resourceType)" "StructureDefinition"
119+
STRUCTURE_DEFINITION_ID=$(echo "$RESULT_STRUCTURE_DEFINITION" | jq -r .id)
120+
121+
sleep 1
122+
echo "3: testing after Patient profile created"
123+
echo "testing valid patient without profile"
124+
RESULT_NO_PROFILE=$(patient-valid-no-profile | curl -s -H 'Accept: application/fhir+json' -H "Content-Type: application/fhir+json" -d @- "$BASE/Patient")
125+
test "resource type" "$(echo "$RESULT_NO_PROFILE" | jq -r .resourceType)" "Patient"
126+
127+
echo "testing invalid patient with profile"
128+
RESULT_INVALID_WITH_PROFILE=$(patient-invalid-with-profile | curl -s -H 'Accept: application/fhir+json' -H "Content-Type: application/fhir+json" -d @- "$BASE/Patient")
129+
test "resource type" "$(echo "$RESULT_INVALID_WITH_PROFILE" | jq -r .resourceType)" "OperationOutcome"
130+
test "issue diagnostics" "$(echo "$RESULT_INVALID_WITH_PROFILE" | jq -r .issue[0].diagnostics)" "Patient.active: minimum required = 1, but only found 0 (from http://example.org/url-114730)"
131+
132+
echo "testing valid patient with profile"
133+
RESULT_VALID_WITH_PROFILE=$(patient-valid-with-profile | curl -s -H 'Accept: application/fhir+json' -H "Content-Type: application/fhir+json" -d @- "$BASE/Patient")
134+
test "resource type" "$(echo "$RESULT_VALID_WITH_PROFILE" | jq -r .resourceType)" "Patient"
135+
136+
echo "testing valid patient with profile as bundle"
137+
RESULT_BUNDLE_VALID_WITH_PROFILE=$(bundle-valid-patient-with-profile | curl -s -H 'Accept: application/fhir+json' -H "Content-Type: application/fhir+json" -d @- "$BASE")
138+
echo "$RESULT_BUNDLE_VALID_WITH_PROFILE"
139+
test "resource type" "$(echo "$RESULT_BUNDLE_VALID_WITH_PROFILE" | jq -r .resourceType)" "Bundle"
140+
test "response status" "$(echo "$RESULT_BUNDLE_VALID_WITH_PROFILE" | jq -r .entry[0].response.status)" "201"
141+
142+
sleep 1
143+
echo "4: deleting Patient profile and testing invalid again"
144+
RESULT_STRUCTURE_DEFINITION_DELETE=$(structure-definition-patient | curl -s -X DELETE -H 'Accept: application/fhir+json' -H "Content-Type: application/fhir+json" -d @- "$BASE/StructureDefinition/$STRUCTURE_DEFINITION_ID")
145+
test "delete response is empty" "$RESULT_STRUCTURE_DEFINITION_DELETE" ""
146+
147+
echo "testing invalid patient with profile"
148+
RESULT_INVALID_WITH_PROFILE=$(patient-invalid-with-profile | curl -s -H 'Accept: application/fhir+json' -H "Content-Type: application/fhir+json" -d @- "$BASE/Patient")
149+
test "resource type" "$(echo "$RESULT_INVALID_WITH_PROFILE" | jq -r .resourceType)" "OperationOutcome"
150+
test "issue diagnostics" "$(echo "$RESULT_INVALID_WITH_PROFILE" | jq -r .issue[0].diagnostics)" "Profile reference 'http://example.org/url-114730' has not been checked because it could not be found"

.github/workflows/build.yml

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1725,7 +1725,7 @@ jobs:
17251725
- name: Expand Most KDS Terminology Resources
17261726
run: .github/value-set-expand/expand-most.sh
17271727

1728-
integration-test-validation:
1728+
integration-test-terminology-service-validation:
17291729
needs: build
17301730
runs-on: ubuntu-24.04
17311731

@@ -1800,6 +1800,52 @@ jobs:
18001800
- name: SNOMED CT Validate Code
18011801
run: .github/scripts/snomed-ct-validate-code.sh
18021802

1803+
1804+
integration-test-validation:
1805+
needs: build
1806+
runs-on: ubuntu-24.04
1807+
1808+
steps:
1809+
- name: Setup Java
1810+
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5
1811+
with:
1812+
distribution: 'temurin'
1813+
java-version: '21'
1814+
1815+
- name: Check out Git repository
1816+
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
1817+
1818+
- name: Setup Node
1819+
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
1820+
with:
1821+
node-version-file: .nvmrc
1822+
1823+
- name: Install Blazectl
1824+
env:
1825+
GH_TOKEN: ${{ github.token }}
1826+
run: .github/scripts/install-blazectl.sh
1827+
1828+
- name: Download Blaze Image
1829+
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
1830+
with:
1831+
name: blaze-image
1832+
path: /tmp
1833+
1834+
- name: Load Blaze Image
1835+
run: docker load --input /tmp/blaze.tar
1836+
1837+
- name: Run Blaze
1838+
run: docker run --name blaze -d -e JAVA_TOOL_OPTIONS=-Xmx8g -e ENABLE_VALIDATION_ON_INGEST=true -p 8080:8080 --read-only --tmpfs /tmp:exec -v blaze-data:/app/data blaze:latest
1839+
1840+
- name: Wait for Blaze
1841+
run: .github/scripts/wait-for-url.sh http://localhost:8080/health
1842+
1843+
- name: Docker Logs
1844+
run: docker logs blaze
1845+
1846+
- name: Run Validation tests
1847+
run: .github/scripts/validation-patient.sh
1848+
18031849
terminology-tests:
18041850
needs: build
18051851
runs-on: ubuntu-24.04
@@ -2682,6 +2728,7 @@ jobs:
26822728
- integration-test-kds
26832729
- integration-test-patient-purge
26842730
- integration-test-value-set-expand
2731+
- integration-test-terminology-service-validation
26852732
- integration-test-validation
26862733
- terminology-tests
26872734
- not-enforcing-referential-integrity-test

docs/.vitepress/config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,12 @@ export default defineConfig({
160160
{ text: 'Validation', link: '/terminology-service/validation' }
161161
]
162162
},
163+
{
164+
text: 'Validation',
165+
items: [
166+
{ text: 'Overview', link: '/validation' }
167+
]
168+
},
163169
{
164170
text: 'Usage',
165171
items: [

docs/deployment/environment-variables.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -464,9 +464,13 @@ Enable SNOMED CT for the Terminology Service by using the value `true`.
464464

465465
Path of an official SNOMED CT release.
466466

467-
[1]: <https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/doc-files/net-properties.html#Proxies>
468-
[2]: <https://github.com/facebook/rocksdb/wiki/Setup-Options-and-Basic-Tuning#block-cache-size>
469-
[3]: <https://github.com/facebook/rocksdb/wiki/Thread-Pool>
470-
[4]: <https://openid.net/connect/>
471-
[5]: <../authentication.md>
472-
[6]: <http://tx.fhir.org/r4>
467+
#### `ENABLE_VALIDATION_ON_INGEST` <Badge type="warning" text="Since unreleased"/>
468+
469+
Enable [Validation](../validation.md).
470+
471+
[1]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/doc-files/net-properties.html#Proxies
472+
[2]: https://github.com/facebook/rocksdb/wiki/Setup-Options-and-Basic-Tuning#block-cache-size
473+
[3]: https://github.com/facebook/rocksdb/wiki/Thread-Pool
474+
[4]: https://openid.net/connect/
475+
[5]: ../authentication.md
476+
[6]: http://tx.fhir.org/r4

docs/validation.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Validation
2+
3+
Blaze supports validation based on profiles (`StructureDefinition`) upon resource ingest (create and update).
4+
To enable validation, set the environment variable `ENABLE_VALIDATION_ON_INGEST` to true.
5+
6+
The profile, which a resource is validated against, has to exist on the server and needs to be named in `meta.profile` of the resource.
7+
Validation also works if they are inserted as part of a [transaction/batch](./api/interaction/transaction.md) request.
8+
9+
Example:
10+
11+
```json
12+
{
13+
"resourceType": "Patient",
14+
...
15+
"meta": {
16+
"profile": "http://example.org/url-114730"
17+
}
18+
}
19+
```
20+
21+
**Note:** Validation skips empty resources in a `Bundle`, as they are not a valid use case.
22+
23+
## Caching
24+
25+
Validation profiles are cached upon initialization of the `validator`. If a new profiles is created or an existing profile is modified or deleted this cache gets invalidated and rebuilt automatically.
26+
27+
## Performance
28+
29+
Validation of resources comes at a performance cost, roughly doubling the time of the create or update request.

modules/admin-api/deps.edn

Lines changed: 3 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -34,59 +34,13 @@
3434
blaze/spec
3535
{:local/root "../spec"}
3636

37+
blaze/validator
38+
{:local/root "../validator"}
39+
3740
fi.metosin/reitit-openapi
3841
{:mvn/version "0.9.1"
3942
:exclusions [javax.xml.bind/jaxb-api]}
4043

41-
ca.uhn.hapi.fhir/hapi-fhir-validation
42-
{:mvn/version "8.4.0"
43-
:exclusions
44-
[com.nimbusds/nimbus-jose-jwt
45-
commons-beanutils/commons-beanutils
46-
info.cqframework/cql
47-
info.cqframework/qdm
48-
info.cqframework/quick
49-
info.cqframework/cql-to-elm
50-
info.cqframework/elm
51-
info.cqframework/model
52-
io.opentelemetry.instrumentation/opentelemetry-instrumentation-annotations
53-
net.sf.saxon/Saxon-HE
54-
net.sourceforge.plantuml/plantuml-mit
55-
org.ogce/xpp3
56-
ognl/ognl
57-
org.attoparser/attoparser
58-
org.unbescape/unbescape
59-
org.xerial/sqlite-jdbc
60-
org.apache.commons/commons-collections4
61-
org.apache.httpcomponents/httpclient
62-
com.google.errorprone/error_prone_annotations
63-
org.apache.santuario/xmlsec
64-
org.commonmark/commonmark
65-
org.commonmark/commonmark-ext-gfm-tables]}
66-
67-
ca.uhn.hapi.fhir/hapi-fhir-structures-r4
68-
{:mvn/version "8.4.0"
69-
:exclusions
70-
[com.google.code.findbugs/jsr305
71-
commons-net/commons-net
72-
io.opentelemetry.instrumentation/opentelemetry-instrumentation-annotations
73-
;; Remove after https://github.com/hapifhir/hapi-fhir/issues/7005 is fixed
74-
org.apache.jena/jena-shex
75-
net.sf.saxon/Saxon-HE]}
76-
77-
ca.uhn.hapi.fhir/hapi-fhir-validation-resources-r4
78-
{:mvn/version "8.4.0"
79-
:exclusions
80-
[com.google.code.findbugs/jsr305
81-
io.opentelemetry/opentelemetry-api
82-
io.opentelemetry.instrumentation/opentelemetry-instrumentation-annotations
83-
org.slf4j/jcl-over-slf4j]}
84-
85-
ca.uhn.hapi.fhir/hapi-fhir-caching-caffeine
86-
{:mvn/version "8.4.0"
87-
:exclusions
88-
[io.opentelemetry.instrumentation/opentelemetry-instrumentation-annotations]}
89-
9044
com.fasterxml.jackson.datatype/jackson-datatype-jsr310
9145
{:mvn/version "2.20.0"}
9246

0 commit comments

Comments
 (0)