Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions examples/typescript-r4/extension-profile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,12 @@ test("create() with no required params", () => {
const profile = nationalityProfile.create();
expect(profile.toResource().url).toBe("http://hl7.org/fhir/StructureDefinition/patient-nationality");
});

test("is() matches extensions by url", () => {
const ext = birthPlaceProfile.createResource({ valueAddress: { city: "Bonn" } });
expect(birthPlaceProfile.is(ext)).toBe(true);
expect(birthTimeProfile.is(ext)).toBe(false);
expect(birthPlaceProfile.is({ url: "http://example.com/other" })).toBe(false);
expect(birthPlaceProfile.is(null)).toBe(false);
expect(birthPlaceProfile.is("not an object")).toBe(false);
});
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ export class birthPlaceProfile {
return profile;
}

static is (resource: unknown) : resource is Extension {
if (typeof resource !== "object" || resource === null) return false;
return (resource as { url?: string }).url === birthPlaceProfile.canonicalUrl;
}

static apply (resource: Extension) : birthPlaceProfile {
resource.url = birthPlaceProfile.canonicalUrl;
Object.assign(resource, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ export class birthTimeProfile {
return profile;
}

static is (resource: unknown) : resource is Extension {
if (typeof resource !== "object" || resource === null) return false;
return (resource as { url?: string }).url === birthTimeProfile.canonicalUrl;
}

static apply (resource: Extension) : birthTimeProfile {
resource.url = birthTimeProfile.canonicalUrl;
Object.assign(resource, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ export class nationalityProfile {
return profile;
}

static is (resource: unknown) : resource is Extension {
if (typeof resource !== "object" || resource === null) return false;
return (resource as { url?: string }).url === nationalityProfile.canonicalUrl;
}

static apply (resource: Extension) : nationalityProfile {
resource.url = nationalityProfile.canonicalUrl;
Object.assign(resource, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ export class own_prefixProfile {
return profile;
}

static is (resource: unknown) : resource is Extension {
if (typeof resource !== "object" || resource === null) return false;
return (resource as { url?: string }).url === own_prefixProfile.canonicalUrl;
}

static apply (resource: Extension) : own_prefixProfile {
resource.url = own_prefixProfile.canonicalUrl;
Object.assign(resource, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ export class observation_bodyweightProfile {
return profile;
}

static is (resource: unknown) : resource is Observation {
if (typeof resource !== "object" || resource === null) return false;
const r = resource as { resourceType?: string; meta?: { profile?: string[] } };
if (r.resourceType !== "Observation") return false;
return (r.meta?.profile ?? []).includes(observation_bodyweightProfile.canonicalUrl);
}

static apply (resource: Observation) : observation_bodyweightProfile {
ensureProfile(resource, observation_bodyweightProfile.canonicalUrl);
Object.assign(resource, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ export class observation_bpProfile {
return profile;
}

static is (resource: unknown) : resource is Observation {
if (typeof resource !== "object" || resource === null) return false;
const r = resource as { resourceType?: string; meta?: { profile?: string[] } };
if (r.resourceType !== "Observation") return false;
return (r.meta?.profile ?? []).includes(observation_bpProfile.canonicalUrl);
}

static apply (resource: Observation) : observation_bpProfile {
ensureProfile(resource, observation_bpProfile.canonicalUrl);
Object.assign(resource, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ export class observation_vitalsignsProfile {
return profile;
}

static is (resource: unknown) : resource is Observation {
if (typeof resource !== "object" || resource === null) return false;
const r = resource as { resourceType?: string; meta?: { profile?: string[] } };
if (r.resourceType !== "Observation") return false;
return (r.meta?.profile ?? []).includes(observation_vitalsignsProfile.canonicalUrl);
}

static apply (resource: Observation) : observation_vitalsignsProfile {
ensureProfile(resource, observation_vitalsignsProfile.canonicalUrl);
resource.category = ensureSliceDefaults(
Expand Down
67 changes: 67 additions & 0 deletions examples/typescript-r4/profile-bodyweight.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,73 @@ describe("demo: read a bodyweight observation from JSON", () => {
});
});

describe("demo: filter a mixed collection with is()", () => {
test("is() narrows unknown values to profile matches", () => {
// A mixed bag: a matching bodyweight, a non-matching Observation, a non-Observation, and junk
const bodyweight: Observation = {
resourceType: "Observation",
meta: { profile: ["http://hl7.org/fhir/StructureDefinition/bodyweight"] },
status: "final",
code: { coding: [{ code: "29463-7", system: "http://loinc.org" }] },
subject: { reference: "Patient/pt-1" },
effectiveDateTime: "2024-06-15",
valueQuantity: { value: 75, unit: "kg" },
};
const otherObs: Observation = {
resourceType: "Observation",
status: "final",
code: { coding: [{ code: "8867-4", system: "http://loinc.org" }] },
};
const resources: unknown[] = [bodyweight, otherObs, { resourceType: "Patient" }, null, "not a resource"];

// is() is a cheap type guard — no validation, no instance constructed
const matches = resources.filter(bodyweightProfile.is);

expect(matches).toEqual([bodyweight]);
});
});

describe("is() type guard", () => {
test("returns true for Observation with matching meta.profile", () => {
const obs: Observation = {
resourceType: "Observation",
meta: { profile: ["http://hl7.org/fhir/StructureDefinition/bodyweight"] },
status: "final",
code: { coding: [{ code: "29463-7", system: "http://loinc.org" }] },
};
expect(bodyweightProfile.is(obs)).toBe(true);
});

test("returns false when meta.profile does not include canonical url", () => {
const obs: Observation = {
resourceType: "Observation",
status: "final",
code: { coding: [{ code: "29463-7", system: "http://loinc.org" }] },
};
expect(bodyweightProfile.is(obs)).toBe(false);
});

test("returns false for wrong resourceType", () => {
expect(bodyweightProfile.is({ resourceType: "Patient" })).toBe(false);
});

test("returns false for non-object inputs", () => {
expect(bodyweightProfile.is(null)).toBe(false);
expect(bodyweightProfile.is(undefined)).toBe(false);
expect(bodyweightProfile.is("string")).toBe(false);
expect(bodyweightProfile.is(42)).toBe(false);
});

test("does not validate — only checks identity", () => {
// A resource that claims the profile but is otherwise invalid still passes is()
const bogus = {
resourceType: "Observation",
meta: { profile: ["http://hl7.org/fhir/StructureDefinition/bodyweight"] },
};
expect(bodyweightProfile.is(bogus)).toBe(true);
});
});

describe("factory method equivalence", () => {
test("create(), createResource(), and apply() produce equal resources", () => {
const args = { status: "final" as const, subject: { reference: "Patient/pt-1" as const } };
Expand Down
14 changes: 14 additions & 0 deletions src/api/writer-generator/typescript/profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,20 @@ const generateFactoryMethods = (
w.lineSM("return profile");
});
w.line();
const canEmitIs = (hasMeta && isResourceIdentifier(flatProfile.base)) || flatProfile.base.name === "Extension";
if (canEmitIs) {
w.curlyBlock(["static", "is", "(resource: unknown)", `: resource is ${tsBaseResourceName}`], () => {
w.line(`if (typeof resource !== "object" || resource === null) return false;`);
if (hasMeta && isResourceIdentifier(flatProfile.base)) {
w.line(`const r = resource as { resourceType?: string; meta?: { profile?: string[] } };`);
w.line(`if (r.resourceType !== ${JSON.stringify(flatProfile.base.name)}) return false;`);
w.lineSM(`return (r.meta?.profile ?? []).includes(${profileClassName}.canonicalUrl)`);
} else {
w.lineSM(`return (resource as { url?: string }).url === ${profileClassName}.canonicalUrl`);
}
});
w.line();
}
w.curlyBlock(["static", "apply", `(resource: ${tsBaseResourceName})`, `: ${profileClassName}`], () => {
if (hasMeta) {
w.lineSM(`ensureProfile(resource, ${profileClassName}.canonicalUrl)`);
Expand Down
40 changes: 40 additions & 0 deletions test/api/write-generator/__snapshots__/typescript.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,13 @@ export class observation_bodyweightProfile {
return profile;
}

static is (resource: unknown) : resource is Observation {
if (typeof resource !== "object" || resource === null) return false;
const r = resource as { resourceType?: string; meta?: { profile?: string[] } };
if (r.resourceType !== "Observation") return false;
return (r.meta?.profile ?? []).includes(observation_bodyweightProfile.canonicalUrl);
}

static apply (resource: Observation) : observation_bodyweightProfile {
ensureProfile(resource, observation_bodyweightProfile.canonicalUrl);
Object.assign(resource, {
Expand Down Expand Up @@ -607,6 +614,13 @@ export class observation_bpProfile {
return profile;
}

static is (resource: unknown) : resource is Observation {
if (typeof resource !== "object" || resource === null) return false;
const r = resource as { resourceType?: string; meta?: { profile?: string[] } };
if (r.resourceType !== "Observation") return false;
return (r.meta?.profile ?? []).includes(observation_bpProfile.canonicalUrl);
}

static apply (resource: Observation) : observation_bpProfile {
ensureProfile(resource, observation_bpProfile.canonicalUrl);
Object.assign(resource, {
Expand Down Expand Up @@ -897,6 +911,13 @@ export class USCorePatientProfile {
return profile;
}

static is (resource: unknown) : resource is Patient {
if (typeof resource !== "object" || resource === null) return false;
const r = resource as { resourceType?: string; meta?: { profile?: string[] } };
if (r.resourceType !== "Patient") return false;
return (r.meta?.profile ?? []).includes(USCorePatientProfile.canonicalUrl);
}

static apply (resource: Patient) : USCorePatientProfile {
ensureProfile(resource, USCorePatientProfile.canonicalUrl);
return new USCorePatientProfile(resource);
Expand Down Expand Up @@ -1168,6 +1189,13 @@ export class USCoreBloodPressureProfile {
return profile;
}

static is (resource: unknown) : resource is Observation {
if (typeof resource !== "object" || resource === null) return false;
const r = resource as { resourceType?: string; meta?: { profile?: string[] } };
if (r.resourceType !== "Observation") return false;
return (r.meta?.profile ?? []).includes(USCoreBloodPressureProfile.canonicalUrl);
}

static apply (resource: Observation) : USCoreBloodPressureProfile {
ensureProfile(resource, USCoreBloodPressureProfile.canonicalUrl);
Object.assign(resource, {
Expand Down Expand Up @@ -1550,6 +1578,13 @@ export class USCoreBodyWeightProfile {
return profile;
}

static is (resource: unknown) : resource is Observation {
if (typeof resource !== "object" || resource === null) return false;
const r = resource as { resourceType?: string; meta?: { profile?: string[] } };
if (r.resourceType !== "Observation") return false;
return (r.meta?.profile ?? []).includes(USCoreBodyWeightProfile.canonicalUrl);
}

static apply (resource: Observation) : USCoreBodyWeightProfile {
ensureProfile(resource, USCoreBodyWeightProfile.canonicalUrl);
Object.assign(resource, {
Expand Down Expand Up @@ -1881,6 +1916,11 @@ export class USCoreRaceExtensionProfile {
return profile;
}

static is (resource: unknown) : resource is Extension {
if (typeof resource !== "object" || resource === null) return false;
return (resource as { url?: string }).url === USCoreRaceExtensionProfile.canonicalUrl;
}

static apply (resource: Extension) : USCoreRaceExtensionProfile {
resource.url = USCoreRaceExtensionProfile.canonicalUrl;
Object.assign(resource, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ export class ExampleTypedBundleProfile {
return profile;
}

static is (resource: unknown) : resource is Bundle {
if (typeof resource !== "object" || resource === null) return false;
const r = resource as { resourceType?: string; meta?: { profile?: string[] } };
if (r.resourceType !== "Bundle") return false;
return (r.meta?.profile ?? []).includes(ExampleTypedBundleProfile.canonicalUrl);
}

static apply (resource: Bundle) : ExampleTypedBundleProfile {
ensureProfile(resource, ExampleTypedBundleProfile.canonicalUrl);
return new ExampleTypedBundleProfile(resource);
Expand Down
Loading