Skip to content

Commit

Permalink
Merge pull request #2275 from metriport/develop
Browse files Browse the repository at this point in the history
RELEASE 2024_06_14 IHE Zod Schemas
  • Loading branch information
jonahkaye committed Jun 14, 2024
2 parents b83d9b0 + 5f7dadf commit 454377a
Show file tree
Hide file tree
Showing 17 changed files with 721 additions and 321 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ export const expectedMultiNameAddressResponse: OutboundPatientDiscoveryRespSucce
given: ["nwhinone", "bartholomew"],
family: "nwhinzzztestpatient",
},
{
given: ["NWHINONE"],
family: "NWHINZZZTESTPATIENT",
},
],
address: [
...(expectedXcpdResponse.patientResource?.address ?? []),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,6 @@ describe("processDQResponse", () => {
},
});
expect(response.operationOutcome).toBeTruthy();
expect(response.operationOutcome?.issue[0]?.severity).toEqual("information");
expect(response.operationOutcome?.issue[0]?.code).toEqual("schema-error");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ describe("processDRResponse for MTOM with/without attachments and for different

testFiles.forEach(({ name, mimeType, fileExtension }) => {
it(`[mtom with attachments]: should process the ${fileExtension} DR response correctly`, async () => {
const xmlTemplatePath = path.join(__dirname, "./xmls/dr-no-mime-type.xml");
const xmlTemplatePath = path.join(__dirname, "./xmls/dr-insert-b64.xml");
const xmlTemplate = fs.readFileSync(xmlTemplatePath, "utf8");

const fileContent = fs.readFileSync(path.join(__dirname, `./files/${name}`));
Expand Down Expand Up @@ -173,7 +173,7 @@ describe("processDRResponse", () => {
},
});

expect(response?.operationOutcome?.issue[0]?.code).toBe("soap:Sender");
expect(response?.operationOutcome?.issue[0]?.code).toEqual("schema-error");
});

it("should process the registry error DR response correctly", async () => {
Expand Down Expand Up @@ -211,6 +211,6 @@ describe("processDRResponse", () => {
outboundRequest: outboundDrRequest,
},
});
expect(response.operationOutcome?.issue[0]?.severity).toEqual("information");
expect(response.operationOutcome?.issue[0]?.code).toEqual("schema-error");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<HomeCommunityId>urn:oid:urn:oid:2.16.840.1.113883.3.9621</HomeCommunityId>
<RepositoryUniqueId>urn:oid:2.16.840.1.113883.3.9621</RepositoryUniqueId>
<DocumentUniqueId>987654321</DocumentUniqueId>
<mimeType>undefined</mimeType>
<Document></Document>
</DocumentResponse>
</ihe:RetrieveDocumentSetResponse>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@
<given>bartholomew</given>
<family>nwhinzzztestpatient</family>
</name>
<name>
<family partType="FAM">NWHINZZZTESTPATIENT</family>
<given partType="GIV">NWHINONE</given>
</name>
<administrativeGenderCode code="M"/>
<birthTime value="19810101"/>
<addr>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { z } from "zod";

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const emptyToUndefined = (v: any) =>
typeof v === "string" && (v.length < 1 || v == undefined) ? undefined : v;

export const schemaOrEmpty = <T extends z.ZodTypeAny>(schema: T) =>
z.union([schema, z.literal("")]).transform(emptyToUndefined);
export const schemaOrArray = <T extends z.ZodTypeAny>(schema: T) =>
z.union([schema, z.array(schema)]);

export const schemaOrArrayOrEmpty = <T extends z.ZodTypeAny>(schema: T) =>
z.union([schema, z.array(schema), z.literal("")]).transform(emptyToUndefined);

export const textSchema = z.union([
z.string(),
z.object({
_text: z.string(),
}),
]);
export type TextOrTextObject = z.infer<typeof textSchema>;

export const stringOrNumberSchema = z.union([z.string(), z.number()]);
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ import {
DocumentReference,
XCAGateway,
} from "@metriport/ihe-gateway-sdk";
import { handleRegistryErrorResponse, handleHttpErrorResponse, handleEmptyResponse } from "./error";
import {
handleRegistryErrorResponse,
handleHttpErrorResponse,
handleEmptyResponse,
handleSchemaErrorResponse,
} from "./error";
import { DQSamlClientResponse } from "../send/dq-requests";
import { stripUrnPrefix } from "../../../../../../util/urn";
import {
Expand All @@ -17,46 +22,19 @@ import {
} from "../../../../shared";
import { successStatus, partialSuccessStatus } from "./constants";
import { capture } from "../../../../../../util/notifications";
import { errorToString, toArray } from "@metriport/shared";
import { iti38Schema, Slot, ExternalIdentifier, Classification, ExtrinsicObject } from "./schema";
import { out } from "../../../../../../util/log";

dayjs.extend(utc);

const { log } = out("DQ Processing");

type Identifier = {
_identificationScheme: string;
_value: string;
};

type Classification = {
_classificationScheme: string;
Name: {
LocalizedString: {
_charset: string;
_value: string;
};
};
};

type Slot = {
_name: string;
ValueList: {
Value: string | string[];
};
};

function getResponseHomeCommunityId(
//eslint-disable-next-line @typescript-eslint/no-explicit-any
extrinsicObject: any
): string {
function getResponseHomeCommunityId(extrinsicObject: ExtrinsicObject): string {
return stripUrnPrefix(extrinsicObject?._home);
}

function getHomeCommunityIdForDr(
request: OutboundDocumentQueryReq,
//eslint-disable-next-line @typescript-eslint/no-explicit-any
extrinsicObject: any
): string {
function getHomeCommunityIdForDr(extrinsicObject: ExtrinsicObject): string {
return getResponseHomeCommunityId(extrinsicObject);
}

Expand All @@ -69,11 +47,13 @@ function getCreationTime(time: string | undefined): string | undefined {
}
}

function parseDocumentReference(
//eslint-disable-next-line @typescript-eslint/no-explicit-any
extrinsicObject: any,
outboundRequest: OutboundDocumentQueryReq
): DocumentReference | undefined {
function parseDocumentReference({
extrinsicObject,
outboundRequest,
}: {
extrinsicObject: ExtrinsicObject;
outboundRequest: OutboundDocumentQueryReq;
}): DocumentReference | undefined {
const slots = Array.isArray(extrinsicObject?.Slot)
? extrinsicObject?.Slot
: [extrinsicObject?.Slot];
Expand All @@ -86,16 +66,12 @@ function parseDocumentReference(

const findSlotValue = (name: string): string | undefined => {
const slot = slots.find((slot: Slot) => slot._name === name);
return slot
? Array.isArray(slot.ValueList.Value)
? slot.ValueList.Value.join(", ")
: slot.ValueList.Value
: undefined;
return slot ? String(slot.ValueList.Value) : undefined;
};

const findExternalIdentifierValue = (scheme: string): string | undefined => {
const identifier = externalIdentifiers?.find(
(identifier: Identifier) => identifier._identificationScheme === scheme
(identifier: ExternalIdentifier) => identifier._identificationScheme === scheme
);
return identifier ? identifier._value : undefined;
};
Expand All @@ -109,17 +85,11 @@ function parseDocumentReference(
);
if (!classification) return undefined;

const slotArray = Array.isArray(classification.Slot)
? classification.Slot
: [classification.Slot];
const slotArray = toArray(classification.Slot);
const classificationSlots = slotArray.flatMap((slot: Slot) => slot ?? []);

const slot = classificationSlots.find((s: Slot) => s._name === slotName);
return slot
? Array.isArray(slot.ValueList.Value)
? slot.ValueList.Value.join(", ")
: slot.ValueList.Value
: undefined;
return slot ? String(slot.ValueList.Value) : undefined;
};

const findClassificationName = (scheme: string): string | undefined => {
Expand Down Expand Up @@ -149,7 +119,7 @@ function parseDocumentReference(
const creationTime = String(findSlotValue("creationTime"));

const documentReference: DocumentReference = {
homeCommunityId: getHomeCommunityIdForDr(outboundRequest, extrinsicObject),
homeCommunityId: getHomeCommunityIdForDr(extrinsicObject),
repositoryUniqueId,
docUniqueId: stripUrnPrefix(docUniqueId),
contentType: extrinsicObject?._mimeType,
Expand All @@ -167,16 +137,13 @@ function handleSuccessResponse({
outboundRequest,
gateway,
}: {
//eslint-disable-next-line @typescript-eslint/no-explicit-any
extrinsicObjects: any;
extrinsicObjects: ExtrinsicObject[];
outboundRequest: OutboundDocumentQueryReq;
gateway: XCAGateway;
}): OutboundDocumentQueryResp {
const documentReferences = Array.isArray(extrinsicObjects)
? extrinsicObjects.flatMap(
extrinsicObject => parseDocumentReference(extrinsicObject, outboundRequest) ?? []
)
: [parseDocumentReference(extrinsicObjects, outboundRequest) ?? []].flat();
const documentReferences = extrinsicObjects.flatMap(
extrinsicObject => parseDocumentReference({ extrinsicObject, outboundRequest }) ?? []
);

const response: OutboundDocumentQueryResp = {
id: outboundRequest.id,
Expand Down Expand Up @@ -209,29 +176,40 @@ export function processDQResponse({
parseAttributeValue: false,
removeNSPrefix: true,
});

const jsonObj = parser.parse(response);
const status = jsonObj?.Envelope?.Body?.AdhocQueryResponse?._status?.split(":").pop();
const extrinsicObjects =
jsonObj?.Envelope?.Body?.AdhocQueryResponse?.RegistryObjectList?.ExtrinsicObject;
const registryErrorList = jsonObj?.Envelope?.Body?.AdhocQueryResponse?.RegistryErrorList;

if ((status === successStatus || status === partialSuccessStatus) && extrinsicObjects) {
return handleSuccessResponse({
extrinsicObjects,
outboundRequest,
gateway,
});
} else if (registryErrorList) {
return handleRegistryErrorResponse({
registryErrorList,
outboundRequest,
gateway,
});
} else {
return handleEmptyResponse({

try {
const iti38Response = iti38Schema.parse(jsonObj);

const status = iti38Response.Envelope.Body.AdhocQueryResponse._status.split(":").pop();
const registryObjectList = iti38Response.Envelope.Body.AdhocQueryResponse.RegistryObjectList;
const extrinsicObjects = registryObjectList ? registryObjectList.ExtrinsicObject : undefined;
const registryErrorList = iti38Response.Envelope.Body.AdhocQueryResponse?.RegistryErrorList;

if ((status === successStatus || status === partialSuccessStatus) && extrinsicObjects) {
return handleSuccessResponse({
extrinsicObjects: toArray(extrinsicObjects),
outboundRequest,
gateway,
});
} else if (registryErrorList) {
return handleRegistryErrorResponse({
registryErrorList,
outboundRequest,
gateway,
});
} else {
return handleEmptyResponse({
outboundRequest,
gateway,
});
}
} catch (error) {
log("Error processing DQ response", error);
return handleSchemaErrorResponse({
outboundRequest,
gateway,
text: errorToString(error),
});
}
}
Loading

0 comments on commit 454377a

Please sign in to comment.