@peerbits/fhir-validator 1.0.0 → 1.1.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.
@@ -0,0 +1,109 @@
1
+ import { validateCodeableConcept } from "../coding.js";
2
+ import { validateReference } from "../reference-rules.js";
3
+ const VALID_COVERAGE_STATUSES = ["active", "cancelled", "draft", "entered-in-error"];
4
+ /**
5
+ * Validates base structural rules for FHIR R4 Coverage resource.
6
+ */
7
+ export function validateCoverage(resource, path = "Coverage") {
8
+ const issues = [];
9
+ if (resource.resourceType !== "Coverage") {
10
+ issues.push({
11
+ severity: "error",
12
+ path: `${path}.resourceType`,
13
+ code: "invalid-resource-type",
14
+ message: `Expected resourceType 'Coverage', found '${String(resource.resourceType)}'.`,
15
+ });
16
+ }
17
+ // status (Required)
18
+ if (resource.status === undefined || resource.status === null || resource.status === "") {
19
+ issues.push({
20
+ severity: "error",
21
+ path: `${path}.status`,
22
+ code: "required",
23
+ message: `Missing required 'status' in '${path}'.`,
24
+ });
25
+ }
26
+ else if (typeof resource.status !== "string" || !VALID_COVERAGE_STATUSES.includes(resource.status)) {
27
+ issues.push({
28
+ severity: "error",
29
+ path: `${path}.status`,
30
+ code: "invalid-value",
31
+ message: `Invalid Coverage status '${String(resource.status)}'. Allowed values: ${VALID_COVERAGE_STATUSES.join(", ")}.`,
32
+ });
33
+ }
34
+ // beneficiary (Required Reference: Patient)
35
+ if (resource.beneficiary === undefined || resource.beneficiary === null) {
36
+ issues.push({
37
+ severity: "error",
38
+ path: `${path}.beneficiary`,
39
+ code: "required",
40
+ message: `Missing required 'beneficiary' in '${path}'.`,
41
+ });
42
+ }
43
+ else {
44
+ issues.push(...validateReference(resource.beneficiary, `${path}.beneficiary`, ["Patient"]));
45
+ }
46
+ // payor (Required Reference[]: Organization | Patient | RelatedPerson, min 1)
47
+ if (resource.payor === undefined || resource.payor === null) {
48
+ issues.push({
49
+ severity: "error",
50
+ path: `${path}.payor`,
51
+ code: "required",
52
+ message: `Missing required 'payor' in '${path}'.`,
53
+ });
54
+ }
55
+ else if (!Array.isArray(resource.payor)) {
56
+ issues.push({
57
+ severity: "error",
58
+ path: `${path}.payor`,
59
+ code: "invalid-type",
60
+ message: `Expected '${path}.payor' to be an array, got ${typeof resource.payor}.`,
61
+ });
62
+ }
63
+ else if (resource.payor.length === 0) {
64
+ issues.push({
65
+ severity: "error",
66
+ path: `${path}.payor`,
67
+ code: "cardinality",
68
+ message: `Expected at least 1 payor in '${path}.payor', found 0.`,
69
+ });
70
+ }
71
+ else {
72
+ resource.payor.forEach((p, idx) => {
73
+ issues.push(...validateReference(p, `${path}.payor[${idx}]`, ["Organization", "Patient", "RelatedPerson"]));
74
+ });
75
+ }
76
+ // subscriber (Reference: Patient | RelatedPerson)
77
+ if (resource.subscriber !== undefined) {
78
+ issues.push(...validateReference(resource.subscriber, `${path}.subscriber`, ["Patient", "RelatedPerson"]));
79
+ }
80
+ // subscriberId (string)
81
+ if (resource.subscriberId !== undefined && typeof resource.subscriberId !== "string") {
82
+ issues.push({
83
+ severity: "error",
84
+ path: `${path}.subscriberId`,
85
+ code: "invalid-type",
86
+ message: `Expected '${path}.subscriberId' to be a string.`,
87
+ });
88
+ }
89
+ // type (CodeableConcept)
90
+ if (resource.type !== undefined) {
91
+ issues.push(...validateCodeableConcept(resource.type, `${path}.type`));
92
+ }
93
+ // relationship (CodeableConcept)
94
+ if (resource.relationship !== undefined) {
95
+ issues.push(...validateCodeableConcept(resource.relationship, `${path}.relationship`));
96
+ }
97
+ // period (Period)
98
+ if (resource.period !== undefined) {
99
+ if (typeof resource.period !== "object" || resource.period === null || Array.isArray(resource.period)) {
100
+ issues.push({
101
+ severity: "error",
102
+ path: `${path}.period`,
103
+ code: "invalid-structure",
104
+ message: `Expected '${path}.period' to be an object.`,
105
+ });
106
+ }
107
+ }
108
+ return issues;
109
+ }
@@ -0,0 +1,5 @@
1
+ import { ValidationIssue } from "../types.js";
2
+ /**
3
+ * Validates base structural rules for FHIR R4 Encounter resource.
4
+ */
5
+ export declare function validateEncounter(resource: Record<string, unknown>, path?: string): ValidationIssue[];
@@ -0,0 +1,137 @@
1
+ import { validateCodeableConcept, validateCoding } from "../coding.js";
2
+ import { validateReference } from "../reference-rules.js";
3
+ const VALID_ENCOUNTER_STATUSES = [
4
+ "planned",
5
+ "arrived",
6
+ "triaged",
7
+ "in-progress",
8
+ "onleave",
9
+ "finished",
10
+ "cancelled",
11
+ "entered-in-error",
12
+ "unknown",
13
+ ];
14
+ /**
15
+ * Validates base structural rules for FHIR R4 Encounter resource.
16
+ */
17
+ export function validateEncounter(resource, path = "Encounter") {
18
+ const issues = [];
19
+ if (resource.resourceType !== "Encounter") {
20
+ issues.push({
21
+ severity: "error",
22
+ path: `${path}.resourceType`,
23
+ code: "invalid-resource-type",
24
+ message: `Expected resourceType 'Encounter', found '${String(resource.resourceType)}'.`,
25
+ });
26
+ }
27
+ // status (Required)
28
+ if (resource.status === undefined || resource.status === null || resource.status === "") {
29
+ issues.push({
30
+ severity: "error",
31
+ path: `${path}.status`,
32
+ code: "required",
33
+ message: `Missing required 'status' in '${path}'.`,
34
+ });
35
+ }
36
+ else if (typeof resource.status !== "string" || !VALID_ENCOUNTER_STATUSES.includes(resource.status)) {
37
+ issues.push({
38
+ severity: "error",
39
+ path: `${path}.status`,
40
+ code: "invalid-value",
41
+ message: `Invalid Encounter status '${String(resource.status)}'. Allowed values: ${VALID_ENCOUNTER_STATUSES.join(", ")}.`,
42
+ });
43
+ }
44
+ // class (Required Coding / Coding-like object)
45
+ if (resource.class === undefined || resource.class === null) {
46
+ issues.push({
47
+ severity: "error",
48
+ path: `${path}.class`,
49
+ code: "required",
50
+ message: `Missing required 'class' in '${path}'.`,
51
+ });
52
+ }
53
+ else {
54
+ // In FHIR R4, Encounter.class is a Coding
55
+ issues.push(...validateCoding(resource.class, `${path}.class`));
56
+ }
57
+ // subject (Reference: Patient | Group)
58
+ if (resource.subject !== undefined) {
59
+ issues.push(...validateReference(resource.subject, `${path}.subject`, ["Patient", "Group"]));
60
+ }
61
+ // period (Period)
62
+ if (resource.period !== undefined) {
63
+ if (typeof resource.period !== "object" || resource.period === null || Array.isArray(resource.period)) {
64
+ issues.push({
65
+ severity: "error",
66
+ path: `${path}.period`,
67
+ code: "invalid-structure",
68
+ message: `Expected '${path}.period' to be an object.`,
69
+ });
70
+ }
71
+ else {
72
+ const p = resource.period;
73
+ if (p.start !== undefined && typeof p.start !== "string") {
74
+ issues.push({
75
+ severity: "error",
76
+ path: `${path}.period.start`,
77
+ code: "invalid-type",
78
+ message: `Expected '${path}.period.start' to be a string.`,
79
+ });
80
+ }
81
+ if (p.end !== undefined && typeof p.end !== "string") {
82
+ issues.push({
83
+ severity: "error",
84
+ path: `${path}.period.end`,
85
+ code: "invalid-type",
86
+ message: `Expected '${path}.period.end' to be a string.`,
87
+ });
88
+ }
89
+ }
90
+ }
91
+ // participant
92
+ if (resource.participant !== undefined) {
93
+ if (!Array.isArray(resource.participant)) {
94
+ issues.push({
95
+ severity: "error",
96
+ path: `${path}.participant`,
97
+ code: "invalid-type",
98
+ message: `Expected '${path}.participant' to be an array.`,
99
+ });
100
+ }
101
+ else {
102
+ resource.participant.forEach((part, idx) => {
103
+ const partPath = `${path}.participant[${idx}]`;
104
+ if (typeof part !== "object" || part === null || Array.isArray(part)) {
105
+ issues.push({
106
+ severity: "error",
107
+ path: partPath,
108
+ code: "invalid-structure",
109
+ message: `Expected '${partPath}' to be an object.`,
110
+ });
111
+ }
112
+ else {
113
+ const p = part;
114
+ if (p.individual !== undefined) {
115
+ issues.push(...validateReference(p.individual, `${partPath}.individual`, ["Practitioner", "PractitionerRole", "RelatedPerson"]));
116
+ }
117
+ if (p.type !== undefined && Array.isArray(p.type)) {
118
+ p.type.forEach((t, tIdx) => {
119
+ issues.push(...validateCodeableConcept(t, `${partPath}.type[${tIdx}]`));
120
+ });
121
+ }
122
+ }
123
+ });
124
+ }
125
+ }
126
+ // serviceProvider (Reference: Organization)
127
+ if (resource.serviceProvider !== undefined) {
128
+ issues.push(...validateReference(resource.serviceProvider, `${path}.serviceProvider`, ["Organization"]));
129
+ }
130
+ // type (CodeableConcept[])
131
+ if (resource.type !== undefined && Array.isArray(resource.type)) {
132
+ resource.type.forEach((t, idx) => {
133
+ issues.push(...validateCodeableConcept(t, `${path}.type[${idx}]`));
134
+ });
135
+ }
136
+ return issues;
137
+ }
@@ -0,0 +1,5 @@
1
+ import { ValidationIssue } from "../types.js";
2
+ /**
3
+ * Validates base structural rules for FHIR R4 Observation resource.
4
+ */
5
+ export declare function validateObservation(resource: Record<string, unknown>, path?: string): ValidationIssue[];
@@ -0,0 +1,240 @@
1
+ import { validateCodeableConcept } from "../coding.js";
2
+ import { validateReference } from "../reference-rules.js";
3
+ const VALID_OBSERVATION_STATUSES = [
4
+ "registered",
5
+ "preliminary",
6
+ "final",
7
+ "amended",
8
+ "corrected",
9
+ "cancelled",
10
+ "entered-in-error",
11
+ "unknown",
12
+ ];
13
+ /**
14
+ * Validates base structural rules for FHIR R4 Observation resource.
15
+ */
16
+ export function validateObservation(resource, path = "Observation") {
17
+ const issues = [];
18
+ const effectiveFields = ["effectiveDateTime", "effectivePeriod", "effectiveTiming", "effectiveInstant"].filter((field) => resource[field] !== undefined);
19
+ if (effectiveFields.length > 1)
20
+ issues.push({ severity: "error", path: `${path}.effective[x]`, code: "cardinality", message: "Observation may contain only one effective[x] value." });
21
+ const valueFields = ["valueQuantity", "valueCodeableConcept", "valueString", "valueBoolean", "valueInteger", "valueRange", "valueRatio", "valueSampledData", "valueTime", "valueDateTime", "valuePeriod"].filter((field) => resource[field] !== undefined);
22
+ if (valueFields.length > 1)
23
+ issues.push({ severity: "error", path: `${path}.value[x]`, code: "cardinality", message: "Observation may contain only one value[x] value." });
24
+ if (resource.resourceType !== "Observation") {
25
+ issues.push({
26
+ severity: "error",
27
+ path: `${path}.resourceType`,
28
+ code: "invalid-resource-type",
29
+ message: `Expected resourceType 'Observation', found '${String(resource.resourceType)}'.`,
30
+ });
31
+ }
32
+ // status (Required)
33
+ if (resource.status === undefined || resource.status === null || resource.status === "") {
34
+ issues.push({
35
+ severity: "error",
36
+ path: `${path}.status`,
37
+ code: "required",
38
+ message: `Missing required 'status' in '${path}'.`,
39
+ });
40
+ }
41
+ else if (typeof resource.status !== "string" || !VALID_OBSERVATION_STATUSES.includes(resource.status)) {
42
+ issues.push({
43
+ severity: "error",
44
+ path: `${path}.status`,
45
+ code: "invalid-value",
46
+ message: `Invalid Observation status '${String(resource.status)}'. Allowed values: ${VALID_OBSERVATION_STATUSES.join(", ")}.`,
47
+ });
48
+ }
49
+ // code (Required CodeableConcept)
50
+ if (resource.code === undefined || resource.code === null) {
51
+ issues.push({
52
+ severity: "error",
53
+ path: `${path}.code`,
54
+ code: "required",
55
+ message: `Missing required 'code' in '${path}'.`,
56
+ });
57
+ }
58
+ else {
59
+ issues.push(...validateCodeableConcept(resource.code, `${path}.code`, { requireCoding: true }));
60
+ }
61
+ // category (CodeableConcept[])
62
+ if (resource.category !== undefined) {
63
+ if (!Array.isArray(resource.category)) {
64
+ issues.push({
65
+ severity: "error",
66
+ path: `${path}.category`,
67
+ code: "invalid-type",
68
+ message: `Expected '${path}.category' to be an array, got ${typeof resource.category}.`,
69
+ });
70
+ }
71
+ else {
72
+ resource.category.forEach((cat, idx) => {
73
+ issues.push(...validateCodeableConcept(cat, `${path}.category[${idx}]`));
74
+ });
75
+ }
76
+ }
77
+ // subject (Reference: Patient | Group | Device | Location)
78
+ if (resource.subject !== undefined) {
79
+ issues.push(...validateReference(resource.subject, `${path}.subject`, ["Patient", "Group", "Device", "Location"]));
80
+ }
81
+ // encounter (Reference: Encounter)
82
+ if (resource.encounter !== undefined) {
83
+ issues.push(...validateReference(resource.encounter, `${path}.encounter`, ["Encounter"]));
84
+ }
85
+ // performer (Reference[])
86
+ if (resource.performer !== undefined) {
87
+ if (!Array.isArray(resource.performer)) {
88
+ issues.push({
89
+ severity: "error",
90
+ path: `${path}.performer`,
91
+ code: "invalid-type",
92
+ message: `Expected '${path}.performer' to be an array, got ${typeof resource.performer}.`,
93
+ });
94
+ }
95
+ else {
96
+ resource.performer.forEach((perf, idx) => {
97
+ issues.push(...validateReference(perf, `${path}.performer[${idx}]`, ["Practitioner", "PractitionerRole", "Organization", "CareTeam", "Patient", "RelatedPerson"]));
98
+ });
99
+ }
100
+ }
101
+ // valueQuantity
102
+ if (resource.valueQuantity !== undefined) {
103
+ if (typeof resource.valueQuantity !== "object" || resource.valueQuantity === null || Array.isArray(resource.valueQuantity)) {
104
+ issues.push({
105
+ severity: "error",
106
+ path: `${path}.valueQuantity`,
107
+ code: "invalid-structure",
108
+ message: `Expected '${path}.valueQuantity' to be an object.`,
109
+ });
110
+ }
111
+ else {
112
+ const q = resource.valueQuantity;
113
+ if (q.value !== undefined && (typeof q.value !== "number" || !Number.isFinite(q.value))) {
114
+ issues.push({
115
+ severity: "error",
116
+ path: `${path}.valueQuantity.value`,
117
+ code: "invalid-type",
118
+ message: `Expected '${path}.valueQuantity.value' to be a number, got ${typeof q.value}.`,
119
+ });
120
+ }
121
+ if (q.unit !== undefined && typeof q.unit !== "string") {
122
+ issues.push({
123
+ severity: "error",
124
+ path: `${path}.valueQuantity.unit`,
125
+ code: "invalid-type",
126
+ message: `Expected '${path}.valueQuantity.unit' to be a string.`,
127
+ });
128
+ }
129
+ if (q.system !== undefined && typeof q.system !== "string") {
130
+ issues.push({
131
+ severity: "error",
132
+ path: `${path}.valueQuantity.system`,
133
+ code: "invalid-type",
134
+ message: `Expected '${path}.valueQuantity.system' to be a string.`,
135
+ });
136
+ }
137
+ if (q.code !== undefined && typeof q.code !== "string") {
138
+ issues.push({
139
+ severity: "error",
140
+ path: `${path}.valueQuantity.code`,
141
+ code: "invalid-type",
142
+ message: `Expected '${path}.valueQuantity.code' to be a string.`,
143
+ });
144
+ }
145
+ if (q.system !== undefined && q.system === "http://unitsofmeasure.org" && (typeof q.code !== "string" || !q.code.trim())) {
146
+ issues.push({ severity: "error", path: `${path}.valueQuantity.code`, code: "required", message: "UCUM Quantity requires a non-empty code." });
147
+ }
148
+ }
149
+ }
150
+ if (resource.effectiveDateTime !== undefined && (typeof resource.effectiveDateTime !== "string" || Number.isNaN(Date.parse(resource.effectiveDateTime)))) {
151
+ issues.push({ severity: "error", path: `${path}.effectiveDateTime`, code: "invalid-value", message: "effectiveDateTime must be a valid date-time string." });
152
+ }
153
+ if (resource.effectivePeriod !== undefined) {
154
+ if (!resource.effectivePeriod || typeof resource.effectivePeriod !== "object" || Array.isArray(resource.effectivePeriod)) {
155
+ issues.push({ severity: "error", path: `${path}.effectivePeriod`, code: "invalid-structure", message: "effectivePeriod must be an object." });
156
+ }
157
+ else {
158
+ const period = resource.effectivePeriod;
159
+ const start = typeof period.start === "string" ? Date.parse(period.start) : NaN;
160
+ const end = typeof period.end === "string" ? Date.parse(period.end) : NaN;
161
+ if (Number.isNaN(start) || Number.isNaN(end) || start > end)
162
+ issues.push({ severity: "error", path: `${path}.effectivePeriod`, code: "invalid-value", message: "effectivePeriod requires valid start and end values in chronological order." });
163
+ }
164
+ }
165
+ // valueCodeableConcept
166
+ if (resource.valueCodeableConcept !== undefined) {
167
+ issues.push(...validateCodeableConcept(resource.valueCodeableConcept, `${path}.valueCodeableConcept`));
168
+ }
169
+ // component (Array of components)
170
+ if (resource.component !== undefined) {
171
+ if (!Array.isArray(resource.component)) {
172
+ issues.push({
173
+ severity: "error",
174
+ path: `${path}.component`,
175
+ code: "invalid-type",
176
+ message: `Expected '${path}.component' to be an array, got ${typeof resource.component}.`,
177
+ });
178
+ }
179
+ else {
180
+ resource.component.forEach((comp, idx) => {
181
+ const compPath = `${path}.component[${idx}]`;
182
+ if (typeof comp !== "object" || comp === null || Array.isArray(comp)) {
183
+ issues.push({
184
+ severity: "error",
185
+ path: compPath,
186
+ code: "invalid-structure",
187
+ message: `Expected '${compPath}' to be an object.`,
188
+ });
189
+ }
190
+ else {
191
+ const c = comp;
192
+ if (c.code === undefined || c.code === null) {
193
+ issues.push({
194
+ severity: "error",
195
+ path: `${compPath}.code`,
196
+ code: "required",
197
+ message: `Missing required 'code' in '${compPath}'.`,
198
+ });
199
+ }
200
+ else {
201
+ issues.push(...validateCodeableConcept(c.code, `${compPath}.code`, { requireCoding: true }));
202
+ }
203
+ if (c.valueQuantity !== undefined) {
204
+ const q = c.valueQuantity;
205
+ if (!q || Array.isArray(q)) {
206
+ issues.push({ severity: "error", path: `${compPath}.valueQuantity`, code: "invalid-structure", message: `Expected '${compPath}.valueQuantity' to be an object.` });
207
+ }
208
+ else if (typeof q.value !== "number" || !Number.isFinite(q.value)) {
209
+ issues.push({
210
+ severity: "error",
211
+ path: `${compPath}.valueQuantity.value`,
212
+ code: "invalid-type",
213
+ message: `Expected '${compPath}.valueQuantity.value' to be a number.`,
214
+ });
215
+ }
216
+ }
217
+ if (c.valueCodeableConcept !== undefined) {
218
+ issues.push(...validateCodeableConcept(c.valueCodeableConcept, `${compPath}.valueCodeableConcept`));
219
+ }
220
+ }
221
+ });
222
+ }
223
+ }
224
+ // hasMember / derivedFrom
225
+ if (resource.hasMember !== undefined) {
226
+ if (Array.isArray(resource.hasMember)) {
227
+ resource.hasMember.forEach((hm, idx) => {
228
+ issues.push(...validateReference(hm, `${path}.hasMember[${idx}]`, ["Observation", "QuestionnaireResponse", "MolecularSequence"]));
229
+ });
230
+ }
231
+ }
232
+ if (resource.derivedFrom !== undefined) {
233
+ if (Array.isArray(resource.derivedFrom)) {
234
+ resource.derivedFrom.forEach((df, idx) => {
235
+ issues.push(...validateReference(df, `${path}.derivedFrom[${idx}]`, ["DocumentReference", "ImagingStudy", "Media", "QuestionnaireResponse", "Observation", "MolecularSequence"]));
236
+ });
237
+ }
238
+ }
239
+ return issues;
240
+ }
@@ -0,0 +1,5 @@
1
+ import { ValidationIssue } from "../types.js";
2
+ /**
3
+ * Validates base structural rules for FHIR R4 Patient resource.
4
+ */
5
+ export declare function validatePatient(resource: Record<string, unknown>, path?: string): ValidationIssue[];
@@ -0,0 +1,163 @@
1
+ import { validateCodeableConcept } from "../coding.js";
2
+ import { validateReference } from "../reference-rules.js";
3
+ const VALID_GENDERS = ["male", "female", "other", "unknown"];
4
+ const DATE_REGEX = /^\d{4}(-\d{2}(-\d{2})?)?$/;
5
+ /**
6
+ * Validates base structural rules for FHIR R4 Patient resource.
7
+ */
8
+ export function validatePatient(resource, path = "Patient") {
9
+ const issues = [];
10
+ if (resource.resourceType !== "Patient") {
11
+ issues.push({
12
+ severity: "error",
13
+ path: `${path}.resourceType`,
14
+ code: "invalid-resource-type",
15
+ message: `Expected resourceType 'Patient', found '${String(resource.resourceType)}'.`,
16
+ });
17
+ }
18
+ // active
19
+ if (resource.active !== undefined && typeof resource.active !== "boolean") {
20
+ issues.push({
21
+ severity: "error",
22
+ path: `${path}.active`,
23
+ code: "invalid-type",
24
+ message: `Expected '${path}.active' to be a boolean, got ${typeof resource.active}.`,
25
+ });
26
+ }
27
+ // gender
28
+ if (resource.gender !== undefined) {
29
+ if (typeof resource.gender !== "string" || !VALID_GENDERS.includes(resource.gender)) {
30
+ issues.push({
31
+ severity: "error",
32
+ path: `${path}.gender`,
33
+ code: "invalid-value",
34
+ message: `Invalid gender '${String(resource.gender)}'. Allowed values: ${VALID_GENDERS.join(", ")}.`,
35
+ });
36
+ }
37
+ }
38
+ // birthDate
39
+ if (resource.birthDate !== undefined) {
40
+ if (typeof resource.birthDate !== "string" || !DATE_REGEX.test(resource.birthDate)) {
41
+ issues.push({
42
+ severity: "error",
43
+ path: `${path}.birthDate`,
44
+ code: "invalid-format",
45
+ message: `Expected '${path}.birthDate' to match YYYY, YYYY-MM, or YYYY-MM-DD format.`,
46
+ });
47
+ }
48
+ }
49
+ // name (HumanName[])
50
+ if (resource.name !== undefined) {
51
+ if (!Array.isArray(resource.name)) {
52
+ issues.push({
53
+ severity: "error",
54
+ path: `${path}.name`,
55
+ code: "invalid-type",
56
+ message: `Expected '${path}.name' to be an array, got ${typeof resource.name}.`,
57
+ });
58
+ }
59
+ else {
60
+ resource.name.forEach((n, idx) => {
61
+ const namePath = `${path}.name[${idx}]`;
62
+ if (typeof n !== "object" || n === null) {
63
+ issues.push({
64
+ severity: "error",
65
+ path: namePath,
66
+ code: "invalid-structure",
67
+ message: `Expected '${namePath}' to be a HumanName object.`,
68
+ });
69
+ }
70
+ else {
71
+ const hn = n;
72
+ if (hn.family !== undefined && typeof hn.family !== "string") {
73
+ issues.push({
74
+ severity: "error",
75
+ path: `${namePath}.family`,
76
+ code: "invalid-type",
77
+ message: `Expected '${namePath}.family' to be a string.`,
78
+ });
79
+ }
80
+ if (hn.given !== undefined && (!Array.isArray(hn.given) || hn.given.some((g) => typeof g !== "string"))) {
81
+ issues.push({
82
+ severity: "error",
83
+ path: `${namePath}.given`,
84
+ code: "invalid-type",
85
+ message: `Expected '${namePath}.given' to be an array of strings.`,
86
+ });
87
+ }
88
+ }
89
+ });
90
+ }
91
+ }
92
+ // identifier (Identifier[])
93
+ if (resource.identifier !== undefined) {
94
+ if (!Array.isArray(resource.identifier)) {
95
+ issues.push({
96
+ severity: "error",
97
+ path: `${path}.identifier`,
98
+ code: "invalid-type",
99
+ message: `Expected '${path}.identifier' to be an array, got ${typeof resource.identifier}.`,
100
+ });
101
+ }
102
+ else {
103
+ resource.identifier.forEach((ident, idx) => {
104
+ const idPath = `${path}.identifier[${idx}]`;
105
+ if (typeof ident !== "object" || ident === null) {
106
+ issues.push({
107
+ severity: "error",
108
+ path: idPath,
109
+ code: "invalid-structure",
110
+ message: `Expected '${idPath}' to be an Identifier object.`,
111
+ });
112
+ }
113
+ else {
114
+ const idObj = ident;
115
+ if (idObj.system !== undefined && typeof idObj.system !== "string") {
116
+ issues.push({
117
+ severity: "error",
118
+ path: `${idPath}.system`,
119
+ code: "invalid-type",
120
+ message: `Expected '${idPath}.system' to be a string.`,
121
+ });
122
+ }
123
+ if (idObj.value !== undefined && typeof idObj.value !== "string") {
124
+ issues.push({
125
+ severity: "error",
126
+ path: `${idPath}.value`,
127
+ code: "invalid-type",
128
+ message: `Expected '${idPath}.value' to be a string.`,
129
+ });
130
+ }
131
+ if (idObj.type !== undefined) {
132
+ issues.push(...validateCodeableConcept(idObj.type, `${idPath}.type`));
133
+ }
134
+ }
135
+ });
136
+ }
137
+ }
138
+ // generalPractitioner (Reference[])
139
+ if (resource.generalPractitioner !== undefined) {
140
+ if (!Array.isArray(resource.generalPractitioner)) {
141
+ issues.push({
142
+ severity: "error",
143
+ path: `${path}.generalPractitioner`,
144
+ code: "invalid-type",
145
+ message: `Expected '${path}.generalPractitioner' to be an array.`,
146
+ });
147
+ }
148
+ else {
149
+ resource.generalPractitioner.forEach((gp, idx) => {
150
+ issues.push(...validateReference(gp, `${path}.generalPractitioner[${idx}]`, ["Organization", "Practitioner", "PractitionerRole"]));
151
+ });
152
+ }
153
+ }
154
+ // managingOrganization (Reference)
155
+ if (resource.managingOrganization !== undefined) {
156
+ issues.push(...validateReference(resource.managingOrganization, `${path}.managingOrganization`, ["Organization"]));
157
+ }
158
+ // maritalStatus (CodeableConcept)
159
+ if (resource.maritalStatus !== undefined) {
160
+ issues.push(...validateCodeableConcept(resource.maritalStatus, `${path}.maritalStatus`));
161
+ }
162
+ return issues;
163
+ }