Repository: xlate/staedi (java)

Issue:
MinLength not triggering validation failure
When using composites in EDIFACT, the MinLength value does not seem to work

Schema extract: 

	<segmentType name="LOC">
		<sequence>
			<element type="E3227" minOccurs="0" maxOccurs="1"/>
			<composite type="C517" minOccurs="1" maxOccurs="1"/>
			<composite type="C519" minOccurs="0" maxOccurs="1"/>
			<composite type="C553" minOccurs="0"/>
			<element type="E5479" minOccurs="0"/>
		</sequence>
	</segmentType>

	<elementType name="E3225" base="string" minLength="5" maxLength="17"/>

	<compositeType name="C517">
		<sequence>
			<element type="E3225" minOccurs="1" maxOccurs="1"/>
			<element type="E1131" minOccurs="0"/>
			<element type="E3055" minOccurs="0" maxOccurs="1"/>
			<element type="E3224" minOccurs="0"/>
		</sequence>
	</compositeType>

So you can see the LOC segment contains a C517 composite, which begins with an E3225 element, with a minOccurs of 1

I would expect to see a failure if the E3225 element is missing but the E3055 element is populated as follows:

`LOC+172+::92'`

as opposed to the valid format: 

`LOC+172+VWXYZ::92'`

Pull request description:
Fix validation of empty first component elements
Fixes #508 

Patch:
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 7b19e59..b4a0a01 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 class StaEDIStreamWriter implements EDIStreamWriter, ElementDataHandler,
         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 class StaEDIStreamWriter implements EDIStreamWriter, ElementDataHandler,
         }
     }
 
+    private boolean derivedComposite(Validator validator) {
+        if (location.getComponentPosition() > -1) {
+            return false;
+        }
+
+        return validator.isComposite(dialect, location);
+    }
+
     private void validate(Consumer<Validator> command) {
         validator().ifPresent(validator -> {
             errors.clear();
@@ -1005,7 +1013,7 @@ public class StaEDIStreamWriter implements EDIStreamWriter, ElementDataHandler,
         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 fbf52c4..5514191 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 class ProxyEventHandler implements EventHandler {
                 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 74fdf28..6728c27 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 class Validator {
         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 class Validator {
         }
 
         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 class Validator {
         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 @@ public class Validator {
             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()) {


Test patch:
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 d99ee7e..70fd00d 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.Map;
 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 @@ import io.xlate.edi.stream.Location;
 @SuppressWarnings({ "resource", "unused" })
 class StaEDIStreamReaderTest implements ConstantsTest {
 
+    private static final Logger LOG = Logger.getLogger(StaEDIStreamReaderTest.class.getName());
     private Set<EDIStreamEvent> possible = new HashSet<>();
 
     public StaEDIStreamReaderTest() {
@@ -1907,6 +1909,7 @@ class StaEDIStreamReaderTest implements ConstantsTest {
                 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 @@ class StaEDIStreamReaderTest implements ConstantsTest {
             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<Location> errorLocations = new ArrayList<>();
+        List<EDIStreamValidationError> 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 da65503..0185c87 100644
--- a/src/test/resources/EDIFACT/issue122/BAPLIE-d95b.xml
+++ b/src/test/resources/EDIFACT/issue122/BAPLIE-d95b.xml
@@ -2237,7 +2237,7 @@
   </compositeType>
   <compositeType name="CEC517">
     <sequence>
-      <element type="DE3225"/>
+      <element type="DE3225" minOccurs="1"/>
       <element type="DE1131"/>
       <element type="DE3055"/>
       <element type="DE3224"/>
@@ -2343,7 +2343,7 @@
   <segmentType name="LOC">
     <sequence>
       <element type="DE3227" minOccurs="1"/>
-      <composite type="CEC517"/>
+      <composite type="CEC517" minOccurs="1"/>
       <composite type="CEC519"/>
       <composite type="CEC553"/>
       <element type="DE5479"/>