diff --git a/src/main/java/io/xlate/edi/internal/stream/StaEDIStreamWriter.java b/src/main/java/io/xlate/edi/internal/stream/StaEDIStreamWriter.java index 7b19e597..b4a0a01f 100644 --- a/src/main/java/io/xlate/edi/internal/stream/StaEDIStreamWriter.java +++ b/src/main/java/io/xlate/edi/internal/stream/StaEDIStreamWriter.java @@ -897,7 +897,7 @@ public boolean elementData(CharSequence text, boolean fromStream) { dialect.elementData(elementHolder, location); validator().ifPresent(validator -> { - if (!validator.validateElement(dialect, location, elementHolder, null)) { + if (!validator.validateElement(dialect, location, elementHolder, derivedComposite(validator), null)) { reportElementErrors(validator, elementHolder); } }); @@ -960,6 +960,14 @@ public void segmentError(CharSequence token, EDIReference typeReference, EDIStre } } + private boolean derivedComposite(Validator validator) { + if (location.getComponentPosition() > -1) { + return false; + } + + return validator.isComposite(dialect, location); + } + private void validate(Consumer command) { validator().ifPresent(validator -> { errors.clear(); @@ -1005,7 +1013,7 @@ CharSequence validateElement(Runnable setupCommand, CharSequence data, Validator errors.clear(); setupCommand.run(); - if (!validator.validateElement(dialect, location, data, this.formattedElement)) { + if (!validator.validateElement(dialect, location, data, derivedComposite(validator), this.formattedElement)) { reportElementErrors(validator, elementData); } diff --git a/src/main/java/io/xlate/edi/internal/stream/tokenization/ProxyEventHandler.java b/src/main/java/io/xlate/edi/internal/stream/tokenization/ProxyEventHandler.java index fbf52c4d..55141910 100644 --- a/src/main/java/io/xlate/edi/internal/stream/tokenization/ProxyEventHandler.java +++ b/src/main/java/io/xlate/edi/internal/stream/tokenization/ProxyEventHandler.java @@ -400,7 +400,7 @@ public boolean elementData(CharSequence text, boolean fromStream) { location.incrementComponentPosition(); } - valid = validator.validateElement(dialect, location, text, null); + valid = validator.validateElement(dialect, location, text, derivedComposite, null); typeReference = validator.getElementReference(); enqueueElementOccurrenceErrors(text, validator, valid); } else { diff --git a/src/main/java/io/xlate/edi/internal/stream/validation/Validator.java b/src/main/java/io/xlate/edi/internal/stream/validation/Validator.java index 74fdf288..6728c272 100644 --- a/src/main/java/io/xlate/edi/internal/stream/validation/Validator.java +++ b/src/main/java/io/xlate/edi/internal/stream/validation/Validator.java @@ -1098,7 +1098,7 @@ public boolean isComposite(Dialect dialect, StaEDIStreamLocation position) { return false; } - public boolean validateElement(Dialect dialect, StaEDIStreamLocation position, CharSequence value, StringBuilder formattedValue) { + public boolean validateElement(Dialect dialect, StaEDIStreamLocation position, CharSequence value, boolean derivedComposite, StringBuilder formattedValue) { if (!segmentExpected) { return true; } @@ -1143,7 +1143,7 @@ public boolean validateElement(Dialect dialect, StaEDIStreamLocation position, C } if (componentIndex > -1) { - validateComponentElement(dialect, componentIndex, valueReceived); + validateComponentElement(dialect, componentIndex, valueReceived, derivedComposite); } else { // Validated in validCompositeOccurrences for received composites validateImplUnusedElementBlank(this.element, this.implElement, valueReceived); @@ -1162,7 +1162,7 @@ public boolean validateElement(Dialect dialect, StaEDIStreamLocation position, C return elementErrors.isEmpty(); } - void validateComponentElement(Dialect dialect, int componentIndex, boolean valueReceived) { + void validateComponentElement(Dialect dialect, int componentIndex, boolean valueReceived, boolean derivedComposite) { if (!element.isNodeType(EDIType.Type.COMPOSITE)) { /* * This element has components but is not defined as a composite @@ -1177,7 +1177,7 @@ void validateComponentElement(Dialect dialect, int componentIndex, boolean value String version = dialect.getTransactionVersionString(); if (componentIndex < element.getChildren(version).size()) { - if (valueReceived || componentIndex != 0 /* Derived component */) { + if (valueReceived || componentIndex != 0 /* Derived component */ || !derivedComposite) { this.element = this.element.getChild(version, componentIndex); if (isImplElementSelected()) { diff --git a/src/test/java/io/xlate/edi/internal/stream/StaEDIStreamReaderTest.java b/src/test/java/io/xlate/edi/internal/stream/StaEDIStreamReaderTest.java index d99ee7e3..70fd00d0 100644 --- a/src/test/java/io/xlate/edi/internal/stream/StaEDIStreamReaderTest.java +++ b/src/test/java/io/xlate/edi/internal/stream/StaEDIStreamReaderTest.java @@ -45,6 +45,7 @@ import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; +import java.util.logging.Logger; import java.util.stream.Collectors; import javax.xml.parsers.DocumentBuilder; @@ -80,6 +81,7 @@ @SuppressWarnings({ "resource", "unused" }) class StaEDIStreamReaderTest implements ConstantsTest { + private static final Logger LOG = Logger.getLogger(StaEDIStreamReaderTest.class.getName()); private Set possible = new HashSet<>(); public StaEDIStreamReaderTest() { @@ -1907,6 +1909,7 @@ void testValidatorLookAhead_Issue122() throws Exception { case SEGMENT_ERROR: case ELEMENT_OCCURRENCE_ERROR: case ELEMENT_DATA_ERROR: + LOG.warning(String.format("Unexpected error: %s, %s at %s", reader.getEventType(), reader.getErrorType(), reader.getLocation())); unexpected.add(reader.getErrorType()); break; default: @@ -1919,7 +1922,57 @@ void testValidatorLookAhead_Issue122() throws Exception { reader.close(); } - assertEquals(0, unexpected.size()); + assertEquals(0, unexpected.size(), () -> unexpected.toString()); + } + + /** + * Original issue: https://github.com/xlate/staedi/issues/508 + */ + @Test + void testMissingComponentTriggersError() throws Exception { + EDIInputFactory factory = EDIInputFactory.newFactory(); + Schema transSchema = SchemaFactory.newFactory().createSchema(getClass().getResourceAsStream("/EDIFACT/issue122/BAPLIE-d95b.xml")); + EDIStreamReader reader = factory.createEDIStreamReader(new ByteArrayInputStream(("" + + "UNB+UNOA:2+SENDER+RECIPIENT+090304:1230+UNIQUEID1234+++++SHIPPINGLINE'\n" + + "UNH+SENDER123+BAPLIE:D:95B:UN:SMDG20'\n" + + "BGM++M1+9'\n" + + "DTM+137:0903031447:201'\n" + + "TDT+20+VOYAGENO123+++CARRIERID:172:20+++DLHV:103:ZZZ'\n" + + "LOC+161+::92'\n" // LOC02-1 required and missing + + "DTM+132'\n" + + "UNT+7+SENDER123'\n" + + "UNZ+1+UNIQUEID1234'").getBytes())); + List errorLocations = new ArrayList<>(); + List errors = new ArrayList<>(); + + try { + while (reader.hasNext()) { + switch (reader.next()) { + case START_TRANSACTION: + reader.setTransactionSchema(transSchema); + break; + case SEGMENT_ERROR: + case ELEMENT_OCCURRENCE_ERROR: + case ELEMENT_DATA_ERROR: + errors.add(reader.getErrorType()); + errorLocations.add(reader.getLocation().copy()); + break; + default: + break; + } + } + } finally { + reader.close(); + } + + assertEquals(1, errors.size(), () -> errors + "; " + errorLocations.toString()); + assertEquals(EDIStreamValidationError.REQUIRED_DATA_ELEMENT_MISSING, errors.get(0)); + + assertEquals(1, errorLocations.size()); + assertEquals("LOC", errorLocations.get(0).getSegmentTag()); + assertEquals(6, errorLocations.get(0).getSegmentPosition()); + assertEquals(2, errorLocations.get(0).getElementPosition()); + assertEquals(1, errorLocations.get(0).getComponentPosition()); } @Test diff --git a/src/test/resources/EDIFACT/issue122/BAPLIE-d95b.xml b/src/test/resources/EDIFACT/issue122/BAPLIE-d95b.xml index da65503b..0185c87a 100644 --- a/src/test/resources/EDIFACT/issue122/BAPLIE-d95b.xml +++ b/src/test/resources/EDIFACT/issue122/BAPLIE-d95b.xml @@ -2237,7 +2237,7 @@ - + @@ -2343,7 +2343,7 @@ - +