diff --git a/rs-e2e/src/main/java/gov/hhs/cdc/trustedintermediary/rse2e/hl7/HL7Message.java b/rs-e2e/src/main/java/gov/hhs/cdc/trustedintermediary/rse2e/hl7/HL7Message.java index 6edee471a..0d690efc1 100644 --- a/rs-e2e/src/main/java/gov/hhs/cdc/trustedintermediary/rse2e/hl7/HL7Message.java +++ b/rs-e2e/src/main/java/gov/hhs/cdc/trustedintermediary/rse2e/hl7/HL7Message.java @@ -20,6 +20,10 @@ public class HL7Message implements HealthData { this.encodingCharacters = encodingCharacters; } + public List getSegments() { + return segments; + } + public List getSegments(String name) { return segments.stream().filter(segment -> segment.name().equals(name)).toList(); } @@ -59,8 +63,8 @@ public String getValue(String hl7Path) throws HL7MessageException { public String getValue(String segmentName, int... indices) throws HL7MessageException { List fields = getSegment(segmentName).fields(); - char[] levelDelimiters = this.getOrderedLevelDelimiters(); - return HL7Parser.parseAndGetValue(fields, levelDelimiters, indices); + char[] segmentDelimiters = this.getOrderedSegmentDelimiters(); + return HL7Parser.parseAndGetValue(fields, segmentDelimiters, indices); } public char getEncodingCharacter(String type) { @@ -71,7 +75,7 @@ public char getEscapeCharacter() { return getEncodingCharacter(HL7Parser.ESCAPE_CHARACTER_NAME); } - public char[] getOrderedLevelDelimiters() { + public char[] getOrderedSegmentDelimiters() { return new char[] { getEncodingCharacter(HL7Parser.FIELD_DELIMITER_NAME), getEncodingCharacter(HL7Parser.COMPONENT_DELIMITER_NAME), diff --git a/rs-e2e/src/main/java/gov/hhs/cdc/trustedintermediary/rse2e/hl7/HL7Parser.java b/rs-e2e/src/main/java/gov/hhs/cdc/trustedintermediary/rse2e/hl7/HL7Parser.java index e3d2efbbf..f1b323ac0 100644 --- a/rs-e2e/src/main/java/gov/hhs/cdc/trustedintermediary/rse2e/hl7/HL7Parser.java +++ b/rs-e2e/src/main/java/gov/hhs/cdc/trustedintermediary/rse2e/hl7/HL7Parser.java @@ -62,9 +62,9 @@ public static String parseAndGetValue(List fields, char[] delimiters, in if (i >= delimiters.length) { return null; } - char levelDelimiter = delimiters[i]; + char segmentDelimiter = delimiters[i]; int index = indices[i] - 1; - String[] parts = value.split(Pattern.quote(String.valueOf(levelDelimiter))); + String[] parts = value.split(Pattern.quote(String.valueOf(segmentDelimiter))); if (index < 0 || index >= parts.length) { return null; } @@ -81,7 +81,7 @@ public static Map getEncodingCharacterMap(String encodingChar return Map.of( FIELD_DELIMITER_NAME, HL7Parser.DEFAULT_FIELD_DELIMITER, - COMPONENT_DELIMITER_NAME, HL7Parser.DEFAULT_COMPONENT_DELIMITER, + COMPONENT_DELIMITER_NAME, encodingCharacters[0], REPETITION_DELIMITER_NAME, encodingCharacters.length > 1 ? encodingCharacters[1] diff --git a/rs-e2e/src/main/java/gov/hhs/cdc/trustedintermediary/rse2e/hl7/HL7Segment.java b/rs-e2e/src/main/java/gov/hhs/cdc/trustedintermediary/rse2e/hl7/HL7Segment.java index b3e2dfb7a..4abd09ef0 100644 --- a/rs-e2e/src/main/java/gov/hhs/cdc/trustedintermediary/rse2e/hl7/HL7Segment.java +++ b/rs-e2e/src/main/java/gov/hhs/cdc/trustedintermediary/rse2e/hl7/HL7Segment.java @@ -2,9 +2,4 @@ import java.util.List; -public record HL7Segment(String name, List fields) { - - public int getIndex() { - return Integer.parseInt(fields.get(0)); - } -} +public record HL7Segment(String name, List fields) {} diff --git a/rs-e2e/src/test/groovy/gov/hhs/cdc/trustedintermediary/rse2e/hl7/HL7MessageTest.groovy b/rs-e2e/src/test/groovy/gov/hhs/cdc/trustedintermediary/rse2e/hl7/HL7MessageTest.groovy index 7a70a4bfa..eb7a66bd3 100644 --- a/rs-e2e/src/test/groovy/gov/hhs/cdc/trustedintermediary/rse2e/hl7/HL7MessageTest.groovy +++ b/rs-e2e/src/test/groovy/gov/hhs/cdc/trustedintermediary/rse2e/hl7/HL7MessageTest.groovy @@ -42,6 +42,11 @@ OBX|2|NM|||3122||||||F|||202402221854-0500||""" actualSegment.fields().get(0) == "2" } + def "getSegments returns all segments if no called with no arguments"() { + expect: + message.getSegments().size() == 7 + } + def "getSegmentCount returns the expected number of segments"() { expect: message.getSegmentCount("MSH") == 1 @@ -101,6 +106,6 @@ OBX|2|NM|||3122||||||F|||202402221854-0500||""" def "getEscapeCharacter should return the escape character"() { expect: - message.getEscapeCharacter() == '\\' + message.getEscapeCharacter() == '\\' as char } } diff --git a/rs-e2e/src/test/groovy/gov/hhs/cdc/trustedintermediary/rse2e/hl7/HL7ParserTest.groovy b/rs-e2e/src/test/groovy/gov/hhs/cdc/trustedintermediary/rse2e/hl7/HL7ParserTest.groovy index e88ece145..e45db2438 100644 --- a/rs-e2e/src/test/groovy/gov/hhs/cdc/trustedintermediary/rse2e/hl7/HL7ParserTest.groovy +++ b/rs-e2e/src/test/groovy/gov/hhs/cdc/trustedintermediary/rse2e/hl7/HL7ParserTest.groovy @@ -5,32 +5,124 @@ import spock.lang.Specification class HL7ParserTest extends Specification { - def "getValue returns the expected value given the segment name and indices"() { + def "parse should handle basic HL7 message"() { given: - def content = """MSH|^~\\&|Sender Application^sender.test.com^DNS|Sender Facility^0.0.0.0.0.0.0.0^ISO|Receiver Application^0.0.0.0.0.0.0.0^ISO|Receiver Facility^automated-staging-test-receiver-id^DNS|20230101010000-0000||ORM^O01^ORM_O01|001|N|2.5.1|||||||||| -PID|1||1300974^^^Baptist East^MR||ONE^TESTCASE||202402210152-0500|F^Female^HL70001||2106-3^White^HL70005|1234 GPCS WAY^^MONTGOMERY^Alabama^36117^USA^home^^Montgomery|||||||2227600015||||N^Not Hispanic or Latino^HL70189|||1|||||||||||||||LRI_NG_FRN_PROFILE^^2.16.840.1.113883.9.195.3.4^ISO~LRI_NDBS_COMPONENT^^2.16.840.1.113883.9.195.3.6^ISO~LAB_PRN_Component^^2.16.840.1.113883.9.81^ISO~LAB_TO_COMPONENT^^2.16.840.1.113883.9.22^ISO| -NK1|1|ONE^MOMFIRST|MTH^Mother^HL70063||^^^^^804^5693861||||||||||||||||||||||||||||123456789^^^Medicaid&2.16.840.1.113883.4.446&ISO^MD||||000-00-0000^^^ssn&2.16.840.1.113883.4.1&ISO^SS -ORC|NW|4560411583^ORDERID||||||||||12345^^^^^^^^NPI&2.16.840.1.113883.4.6&ISO^L||||||||| -OBR|1|4560411583^ORDERID||54089-8^Newborn screening panel AHIC^LN|||202402221854-0500|||||||||12345^^^^^^^^NPI&2.16.840.1.113883.4.6&ISO^L|||||||| -OBX|1|ST|57723-9^Unique bar code number of Current sample^LN||123456||||||F|||202402221854-0500 -""" + def content = """MSH|^~\\&|sending_app|sending_facility +PID|||12345||Doe^John||19800101|M""" when: - HL7Message message = HL7Parser.parse(content) + def message = HL7Parser.parse(content) + def segments = message.getSegments() then: - message.getValue("PID", 3) == "1300974^^^Baptist East^MR" - message.getValue("PID", 3, 0) == null - message.getValue("PID", 3, 1) == "1300974" - message.getValue("PID", 3, 2) == "" - message.getValue("PID", 3, 4) == "Baptist East" - message.getValue("PID", 3, 5) == "MR" - message.getValue("PID", 3, 6) == null - message.getValue("PID", 40, 4, 1) == "ISO" - message.getValue("PID", 40, 4, 2) == "LRI_NDBS_COMPONENT" - message.getValue("PID", 40, 4, 3) == null - message.getValue("NK1", 33, 4, 1, 1) == "Medicaid" - message.getValue("NK1", 33, 4, 1, 1, 1) == null + segments.size() == 2 + segments[0].name() == "MSH" + segments[0].fields().size() == 4 + segments[0].fields().get(0) == "|" + segments[0].fields().get(1) == "^~\\&" + segments[1].name() == "PID" + segments[1].fields().size() == 8 + segments[1].fields().get(2) == "12345" + segments[1].fields().get(4) == "Doe^John" + } + + def "parse should handle empty lines in message"() { + given: + def content = """MSH|^~\\&|sending_app + +PID|||12345""" + + when: + def result = HL7Parser.parse(content) + + then: + result.getSegments().size() == 2 + } + + def "parse should preserve empty fields"() { + given: + def content = "MSH|^~\\&|sending_app||sending_facility" + + when: + def result = HL7Parser.parse(content) + + then: + result.getSegments().get(0).fields().get(3) == "" + } + + def "parseAndGetValue should handle different field levels"() { + given: + def fields = [ + "value1", + "component1^component2", + "rep1~rep2^", + "comp1^~sub1&sub2" + ] + def delimiters = ['|', '^', '~', '&'] as char[] + + when: + def result = HL7Parser.parseAndGetValue(fields, delimiters, indices as int[]) + + then: + result == expectedValue + + where: + scenario | indices | expectedValue + "simple field" | [1] | "value1" + "component" | [2, 2] | "component2" + "repetition" | [3, 1, 2] | "rep2" + "subcomponent" | [4, 2, 2, 2] | "sub2" + "invalid index" | [5] | null + } + + def "parseAndGetValue should handle null inputs"() { + when: + def result = HL7Parser.parseAndGetValue(null, [] as char[], 1) + + then: + result == null + } + + def "getEncodingCharacterMap should use defaults when no encoding characters provided"() { + when: + def encodingChars = HL7Parser.getEncodingCharacterMap(null) + + then: + encodingChars["field"] == '|' as char + encodingChars["component"] == '^' as char + encodingChars["repetition"] == '~' as char + encodingChars["escape"] == '\\' as char + encodingChars["subcomponent"] == '&' as char + } + + def "getEncodingCharacterMap should use default character when one is missing"() { + given: + def customEncodingChars = "_" + + when: + def encodingChars = HL7Parser.getEncodingCharacterMap(customEncodingChars) + + then: + encodingChars["field"] == '|' as char + encodingChars["component"] == '_' as char + encodingChars["repetition"] == '~' as char + encodingChars["escape"] == '\\' as char + encodingChars["subcomponent"] == '&' as char + } + + def "getEncodingCharacterMap should use custom encoding characters when provided"() { + given: + def customEncodingChars = "@#+_" + + when: + def result = HL7Parser.getEncodingCharacterMap(customEncodingChars) + + then: + result["field"] == '|' as char + result["component"] == '@' as char + result["repetition"] == '#' as char + result["escape"] == '+' as char + result["subcomponent"] == '_' as char } def "parseAndGetValue returns null if a null list of fields is given"() {