Skip to content

Commit

Permalink
Require a valid header segment when writing delimiter
Browse files Browse the repository at this point in the history
Fixes #275

Signed-off-by: Michael Edgar <michael@xlate.io>
  • Loading branch information
MikeEdgar committed Jun 11, 2022
1 parent 3ca78c1 commit c14848a
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 17 deletions.
35 changes: 30 additions & 5 deletions src/main/java/io/xlate/edi/internal/stream/StaEDIStreamWriter.java
Expand Up @@ -307,6 +307,10 @@ private Optional<Validator> validator() {
}

private void write(int output) throws EDIStreamException {
write(output, false);
}

private void write(int output, boolean isPrettyPrint) throws EDIStreamException {
CharacterClass clazz;

clazz = characters.getClass(output);
Expand All @@ -322,7 +326,7 @@ private void write(int output) throws EDIStreamException {
case HEADER_EDIFACT_U: // U(NA) or U(NB)
case HEADER_TRADACOMS_S: // S(TX)
unconfirmedBuffer.clear();
writeHeader((char) output);
writeHeader((char) output, isPrettyPrint);
break;
case HEADER_X12_S:
case HEADER_EDIFACT_N:
Expand All @@ -332,7 +336,7 @@ private void write(int output) throws EDIStreamException {
case HEADER_ELEMENT_END:
case HEADER_COMPONENT_END:
case HEADER_SEGMENT_END:
writeHeader((char) output);
writeHeader((char) output, isPrettyPrint);
break;
case INVALID:
throw new EDIException(String.format("Invalid state: %s; output 0x%04X", state, output));
Expand All @@ -342,8 +346,8 @@ private void write(int output) throws EDIStreamException {
}
}

void writeHeader(char output) throws EDIStreamException {
if (!dialect.appendHeader(characters, output)) {
void writeHeader(char output, boolean isPrettyPrint) throws EDIStreamException {
if (!isPrettyPrint && !dialect.appendHeader(characters, output)) {
throw new EDIStreamException(String.format("Unexpected header character: 0x%04X [%s]", (int) output, output), location);
}

Expand Down Expand Up @@ -381,6 +385,9 @@ void writeHeader(char output) throws EDIStreamException {
while (unconfirmedBuffer.hasRemaining()) {
writeOutput(unconfirmedBuffer.get());
}
} else if (dialect.isRejected()) {
state = State.INITIAL;
throw new EDIStreamException(dialect.getRejectionMessage(), location);
}
}

Expand Down Expand Up @@ -499,7 +506,9 @@ void writeSegmentTerminator() throws EDIStreamException {
write(this.segmentTerminator);

if (prettyPrint) {
writeString(prettyPrintString);
for (int i = 0, m = prettyPrintString.length(); i < m; i++) {
write(prettyPrintString.charAt(i), true);
}
}
}

Expand All @@ -522,6 +531,22 @@ public EDIStreamWriter writeEndSegment() throws EDIStreamException {

writeSegmentTerminator();

switch (state) {
case SEGMENT_END:
case HEADER_SEGMENT_END:
case INITIAL: // Ending final segment of the interchange
break;
default:
if (state.isHeaderState()) {
Optional<String> invalidHeaderReason = dialect.assertValidHeaderEnd();

if (invalidHeaderReason.isPresent()) {
throw new EDIStreamException(invalidHeaderReason.get());
}
}
break;
}

level = LEVEL_INTERCHANGE;
location.clearSegmentLocations();
transactionSchemaAllowed = false;
Expand Down
Expand Up @@ -15,6 +15,8 @@
******************************************************************************/
package io.xlate.edi.internal.stream.tokenization;

import java.util.Optional;

import io.xlate.edi.stream.Location;

public abstract class Dialect {
Expand Down Expand Up @@ -117,6 +119,12 @@ public boolean isServiceAdviceSegment(CharSequence segmentTag) {

public abstract boolean appendHeader(CharacterSet characters, char value);

/**
* Check if the current state of the dialect is at a valid end of segment position.
* If false, the reason will be given to the provided reasonHandler.
*/
public abstract Optional<String> assertValidHeaderEnd();

/**
* Notify the dialect of element data and its location in the stream. Does
* not support binary elements.
Expand Down
Expand Up @@ -15,6 +15,8 @@
******************************************************************************/
package io.xlate.edi.internal.stream.tokenization;

import java.util.Optional;

import io.xlate.edi.stream.EDIStreamConstants.Standards;
import io.xlate.edi.stream.Location;

Expand Down Expand Up @@ -180,6 +182,26 @@ public boolean appendHeader(CharacterSet characters, char value) {
return proceed;
}

@Override
public Optional<String> assertValidHeaderEnd() {
if (isRejected()) {
return Optional.of(getRejectionMessage());
} else if (!isConfirmed()) {
if (UNB.equals(headerTag)) {
return Optional.of("EDIFACT UNB segment incomplete");
} else {
int length = header.length();

if (length < EDIFACT_UNA_LENGTH) {
return Optional.of("EDIFACT UNA segment incomplete");
} else if (length > EDIFACT_UNA_LENGTH) {
return Optional.of("EDIFACT UNB segment incomplete following UNA service advice segment");
}
}
}
return Optional.empty();
}

boolean processInterchangeHeader(CharacterSet characters, char value) {
if (index == 0) {
characters.setClass(componentDelimiter, CharacterClass.COMPONENT_DELIMITER);
Expand Down Expand Up @@ -228,7 +250,7 @@ boolean processServiceStringAdvice(CharacterSet characters, char value) {
break;
}

if (index > EDIFACT_UNA_LENGTH) {
if (index >= EDIFACT_UNA_LENGTH) {
if (characters.isIgnored(value)) {
header.deleteCharAt(index--);
} else if (isIndexBeyondUNBFirstElement()) {
Expand Down
Expand Up @@ -357,4 +357,8 @@ public State transition(int dialect, CharacterClass clazz) {
return TRANSITIONS[dialect][code][clazz.code];
}

public boolean isHeaderState() {
return Category.HEADER == code;
}

}
Expand Up @@ -15,6 +15,8 @@
******************************************************************************/
package io.xlate.edi.internal.stream.tokenization;

import java.util.Optional;

import io.xlate.edi.stream.EDIStreamConstants.Standards;
import io.xlate.edi.stream.Location;

Expand Down Expand Up @@ -128,6 +130,16 @@ public boolean appendHeader(CharacterSet characters, char value) {
return proceed;
}

@Override
public Optional<String> assertValidHeaderEnd() {
if (isRejected()) {
return Optional.of(getRejectionMessage());
} else if (!isConfirmed()) {
return Optional.of("TRADACOMS STX segment incomplete");
}
return Optional.empty();
}

boolean processInterchangeHeader(CharacterSet characters, char value) {
if (segmentDelimiter == value) {
initialize(characters);
Expand Down
Expand Up @@ -15,6 +15,8 @@
******************************************************************************/
package io.xlate.edi.internal.stream.tokenization;

import java.util.Optional;

import io.xlate.edi.stream.EDIStreamConstants.Standards;
import io.xlate.edi.stream.Location;

Expand Down Expand Up @@ -158,6 +160,16 @@ public boolean appendHeader(CharacterSet characters, char value) {
return proceed;
}

@Override
public Optional<String> assertValidHeaderEnd() {
if (isRejected()) {
return Optional.of(getRejectionMessage());
} else if (index < X12_SEGMENT_OFFSET) {
return Optional.of("Invalid X12 ISA segment: too short or elements missing");
}
return Optional.empty();
}

@Override
public void clearTransactionVersion() {
agencyCode = "";
Expand Down
Expand Up @@ -219,7 +219,7 @@ void testWriteEndSegment() throws EDIStreamException {
writer.startInterchange();
writer.writeStartSegment("ISA");
writer.writeStartElement().writeElementData("E1").endElement();
writer.writeEndSegment();
assertThrows(EDIStreamException.class, () -> writer.writeEndSegment());
writer.flush();
unconfirmedBufferEquals("ISA*E1~", writer);
}
Expand Down Expand Up @@ -349,8 +349,8 @@ void testComponent() throws IllegalStateException, EDIStreamException {
writer.writeStartElement()
.startComponent()
.endComponent()
.startComponent()
.writeEndSegment();
.startComponent();
assertThrows(EDIStreamException.class, () -> writer.writeEndSegment());
writer.flush();
unconfirmedBufferEquals("ISA*:~", writer);
}
Expand All @@ -369,8 +369,8 @@ void testComponent_EmptyTruncated() throws IllegalStateException, EDIStreamExcep
.startComponent()
.endComponent()
.endElement()
.writeEmptyElement()
.writeEndSegment();
.writeEmptyElement();
assertThrows(EDIStreamException.class, () -> writer.writeEndSegment());
writer.flush();

unconfirmedBufferEquals("ISA~", writer);
Expand Down Expand Up @@ -418,7 +418,7 @@ void testWriteEmptyElement() throws IllegalStateException, EDIStreamException {
writer.writeEmptyElement();
writer.writeEmptyElement();
writer.writeEmptyElement();
writer.writeEndSegment();
assertThrows(EDIStreamException.class, () -> writer.writeEndSegment());
writer.flush();
unconfirmedBufferEquals("ISA****~", writer);
}
Expand All @@ -435,7 +435,7 @@ void testWriteEmptyComponent() throws IllegalStateException, EDIStreamException
writer.writeEmptyComponent();
writer.writeEmptyComponent();
writer.writeEmptyComponent();
writer.writeEndSegment();
assertThrows(EDIStreamException.class, () -> writer.writeEndSegment());
writer.flush();
unconfirmedBufferEquals("ISA*:::~", writer);
}
Expand All @@ -454,7 +454,7 @@ void testWriteEmptyElements_EmptyTruncated() throws IllegalStateException, EDISt
writer.writeEmptyComponent();
writer.writeEmptyComponent();
writer.endElement();
writer.writeEndSegment();
assertThrows(EDIStreamException.class, () -> writer.writeEndSegment());
writer.flush();
unconfirmedBufferEquals("ISA~", writer);
}
Expand Down Expand Up @@ -513,7 +513,7 @@ void testWriteTruncatedElements() throws IllegalStateException, EDIStreamExcepti
writer.endElement();
writer.writeEmptyElement();

writer.writeEndSegment();
assertThrows(EDIStreamException.class, () -> writer.writeEndSegment());
writer.flush();

unconfirmedBufferEquals("ISA**:LAST*:::LAST*:SECOND::LAST***LAST~", writer);
Expand All @@ -528,7 +528,7 @@ void testWriteElementDataCharSequence() throws EDIStreamException {
writer.writeStartSegment("ISA");
writer.writeStartElement();
writer.writeElementData("TEST-ELEMENT");
writer.writeEndSegment();
assertThrows(EDIStreamException.class, () -> writer.writeEndSegment());
writer.flush();
unconfirmedBufferEquals("ISA*TEST-ELEMENT~", writer);
}
Expand All @@ -554,7 +554,7 @@ void testWriteElementDataCharArray() throws EDIStreamException {
writer.writeStartSegment("ISA");
writer.writeStartElement();
writer.writeElementData(new char[] { 'C', 'H', 'A', 'R', 'S' }, 0, 5);
writer.writeEndSegment();
assertThrows(EDIStreamException.class, () -> writer.writeEndSegment());
writer.flush();
unconfirmedBufferEquals("ISA*CHARS~", writer);
}
Expand Down

0 comments on commit c14848a

Please sign in to comment.