Skip to content

Commit

Permalink
[Validation] introduce property to control whether or not to validate…
Browse files Browse the repository at this point in the history
… empty tags on decoders, and treat them as missing tags if encountered when validation is off.
  • Loading branch information
writeoncereadmany committed Nov 27, 2023
1 parent 6ea914c commit 7f7e3c2
Show file tree
Hide file tree
Showing 14 changed files with 178 additions and 95 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2015-2023 Real Logic Limited.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package uk.co.real_logic.artio.builder;

public final class RejectEmptyTag
{
private static final String CODEC_DISABLE_REJECT_EMPTY_TAG_PROP =
"fix.codecs.disable_reject_empty_tag";
private static final boolean CODEC_DISABLE_REJECT_EMPTY_TAG_ENABLED =
Boolean.getBoolean(CODEC_DISABLE_REJECT_EMPTY_TAG_PROP);
public static final boolean CODEC_REJECT_EMPTY_TAG_ENABLED =
!CODEC_DISABLE_REJECT_EMPTY_TAG_ENABLED;
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@

import org.agrona.generation.OutputManager;
import org.agrona.generation.PackageOutputManager;


import uk.co.real_logic.artio.builder.RejectEmptyTag;
import uk.co.real_logic.artio.builder.RejectUnknownEnumValue;
import uk.co.real_logic.artio.builder.RejectUnknownField;
import uk.co.real_logic.artio.builder.Validation;
Expand Down Expand Up @@ -163,6 +166,7 @@ private static void generateDictionary(
parentPackage,
encoderOutput,
Validation.class,
RejectEmptyTag.class,
RejectUnknownField.class,
RejectUnknownEnumValue.class,
codecRejectUnknownEnumValueEnabled,
Expand All @@ -176,6 +180,7 @@ private static void generateDictionary(
encoderPackage,
decoderOutput,
Validation.class,
RejectEmptyTag.class,
RejectUnknownField.class,
RejectUnknownEnumValue.class,
false,
Expand All @@ -198,6 +203,7 @@ private static void generateDictionary(
parentPackage,
encoderPackage, flyweightDecoderOutput,
Validation.class,
RejectEmptyTag.class,
RejectUnknownField.class,
RejectUnknownEnumValue.class,
true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,15 +128,17 @@ static String decoderClassName(final String name)
final String encoderPackage,
final OutputManager outputManager,
final Class<?> validationClass,
final Class<?> rejectEmptyTagClass,
final Class<?> rejectUnknownFieldClass,
final Class<?> rejectUnknownEnumValueClass,
final boolean flyweightsEnabled,
final boolean wrapEmptyBuffer,
final String codecRejectUnknownEnumValueEnabled,
final boolean fixTagsInJavadoc)
{
super(dictionary, thisPackage, commonPackage, outputManager, validationClass, rejectUnknownFieldClass,
rejectUnknownEnumValueClass, flyweightsEnabled, codecRejectUnknownEnumValueEnabled, fixTagsInJavadoc);
super(dictionary, thisPackage, commonPackage, outputManager, validationClass, rejectEmptyTagClass,
rejectUnknownFieldClass, rejectUnknownEnumValueClass, flyweightsEnabled, codecRejectUnknownEnumValueEnabled,
fixTagsInJavadoc);
this.initialBufferSize = initialBufferSize;
this.encoderPackage = encoderPackage;
this.wrapEmptyBuffer = wrapEmptyBuffer;
Expand Down Expand Up @@ -1676,30 +1678,31 @@ private String decodeMethod(final List<Entry> entries, final Aggregate aggregate
.collect(joining("\n", "", "\n"));

final String suffix =
" default:\n" +
" if (!" + CODEC_REJECT_UNKNOWN_FIELD_ENABLED + ")\n" +
" {\n" +
" default:\n" +
" if (!" + CODEC_REJECT_UNKNOWN_FIELD_ENABLED + ")\n" +
" {\n" +
(isGroup ?
" seenFields.remove(tag);\n" :
" alreadyVisitedFields.remove(tag);\n") +
" }\n" +
" seenFields.remove(tag);\n" :
" alreadyVisitedFields.remove(tag);\n") +
" }\n" +
(isGroup ? "" :
" else\n" +
" {\n" +
" if (!" + unknownFieldPredicate(type) + ")\n" +
" else\n" +
" {\n" +
" unknownFields.add(tag);\n" +
" }\n" +
" }\n") +
" if (!" + unknownFieldPredicate(type) + ")\n" +
" {\n" +
" unknownFields.add(tag);\n" +
" }\n" +
" }\n") +

// Skip the thing if it's a completely unknown field and you aren't validating messages
" if (" + CODEC_REJECT_UNKNOWN_FIELD_ENABLED +
" if (" + CODEC_REJECT_UNKNOWN_FIELD_ENABLED +
" || " + unknownFieldPredicate(type) + ")\n" +
" {\n" +
decodeTrailerOrReturn(hasCommonCompounds, 5) +
" }\n" +
" {\n" +
decodeTrailerOrReturn(hasCommonCompounds, 6) +
" }\n" +
"\n" +
" }\n\n" +
" }\n\n" +
" }\n" +
" if (position < (endOfField + 1))\n" +
" {\n" +
" position = endOfField + 1;\n" +
Expand All @@ -1718,61 +1721,63 @@ private String generateDecodePrefix(
final String endGroupCheck)
{
return " public int decode(final AsciiBuffer buffer, final int offset, final int length)\n" +
" {\n" +
" // Decode " + aggregate.name() + "\n" +
" int seenFieldCount = 0;\n" +
" if (" + CODEC_VALIDATION_ENABLED + ")\n" +
" {\n" +
" missingRequiredFields.copy(" + REQUIRED_FIELDS + ");\n" +
(isGroup ? "" : " alreadyVisitedFields.clear();\n") +
" }\n" +
" this.buffer = buffer;\n" +
" final int end = offset + length;\n" +
" int position = offset;\n" +
(hasCommonCompounds ? " position += header.decode(buffer, position, length);\n" : "") +
(isGroup ? " seenFields.clear();\n" : "") +
" int tag;\n\n" +
" while (position < end)\n" +
" {\n" +
" final int equalsPosition = buffer.scan(position, end, '=');\n" +
" if (equalsPosition == AsciiBuffer.UNKNOWN_INDEX)\n" +
" {\n" +
" return position;\n" +
" }\n" +
" tag = buffer.getInt(position, equalsPosition);\n" +
endGroupCheck +
" final int valueOffset = equalsPosition + 1;\n" +
" int endOfField = buffer.scan(valueOffset, end, START_OF_HEADER);\n" +
" if (endOfField == AsciiBuffer.UNKNOWN_INDEX)\n" +
" {\n" +
" rejectReason = " + VALUE_IS_INCORRECT + ";\n" +
" break;\n" +
" }\n" +
" final int valueLength = endOfField - valueOffset;\n" +
" if (" + CODEC_VALIDATION_ENABLED + ")\n" +
" {\n" +
" if (tag <= 0)\n" +
" {\n" +
" invalidTagId = tag;\n" +
" rejectReason = " + INVALID_TAG_NUMBER + ";\n" +
" }\n" +
" else if (valueLength == 0)\n" +
" {\n" +
" invalidTagId = tag;\n" +
" rejectReason = " + TAG_SPECIFIED_WITHOUT_A_VALUE + ";\n" +
" }\n" +
headerValidation(isHeader) +
(isGroup ? "" :
" {\n" +
" // Decode " + aggregate.name() + "\n" +
" int seenFieldCount = 0;\n" +
" if (" + CODEC_VALIDATION_ENABLED + ")\n" +
" {\n" +
" missingRequiredFields.copy(" + REQUIRED_FIELDS + ");\n" +
(isGroup ? "" : " alreadyVisitedFields.clear();\n") +
" }\n" +
" this.buffer = buffer;\n" +
" final int end = offset + length;\n" +
" int position = offset;\n" +
(hasCommonCompounds ? " position += header.decode(buffer, position, length);\n" : "") +
(isGroup ? " seenFields.clear();\n" : "") +
" int tag;\n\n" +
" while (position < end)\n" +
" {\n" +
" final int equalsPosition = buffer.scan(position, end, '=');\n" +
" if (equalsPosition == AsciiBuffer.UNKNOWN_INDEX)\n" +
" {\n" +
" return position;\n" +
" }\n" +
" tag = buffer.getInt(position, equalsPosition);\n" +
endGroupCheck +
" final int valueOffset = equalsPosition + 1;\n" +
" int endOfField = buffer.scan(valueOffset, end, START_OF_HEADER);\n" +
" if (endOfField == AsciiBuffer.UNKNOWN_INDEX)\n" +
" {\n" +
" rejectReason = " + VALUE_IS_INCORRECT + ";\n" +
" break;\n" +
" }\n" +
" final int valueLength = endOfField - valueOffset;\n" +
" if (" + CODEC_VALIDATION_ENABLED + ")\n" +
" {\n" +
" if (tag <= 0)\n" +
" {\n" +
" invalidTagId = tag;\n" +
" rejectReason = " + INVALID_TAG_NUMBER + ";\n" +
" }\n" +
" else if (" + CODEC_REJECT_EMPTY_TAG_ENABLED + " && valueLength == 0)\n" +
" {\n" +
" invalidTagId = tag;\n" +
" rejectReason = " + TAG_SPECIFIED_WITHOUT_A_VALUE + ";\n" +
" }\n" +
headerValidation(isHeader) +
(isGroup ? "" :
" if (!alreadyVisitedFields.add(tag))\n" +
" {\n" +
" invalidTagId = tag;\n" +
" rejectReason = " + TAG_APPEARS_MORE_THAN_ONCE + ";\n" +
" }\n") +
" missingRequiredFields.remove(tag);\n" +
" seenFieldCount++;\n" +
" }\n\n" +
" switch (tag)\n" +
" {\n";
" missingRequiredFields.remove(tag);\n" +
" seenFieldCount++;\n" +
" }\n\n" +
" if (valueLength > 0)\n" +
" {\n" +
" switch (tag)\n" +
" {\n";
}

private String decodeTrailerOrReturn(final boolean hasCommonCompounds, final int indent)
Expand Down Expand Up @@ -1948,13 +1953,13 @@ private String decodeField(final Entry entry, final String suffix)
final String fieldName = formatPropertyName(name);

return String.format(
" case Constants.%s:\n" +
" case Constants.%s:\n" +
"%s" +
"%s" +
"%s" +
"%s" +
"%s" +
" break;\n",
" break;\n",
constantName(name),
optionalAssign(entry),
fieldDecodeMethod(field, fieldName),
Expand All @@ -1966,25 +1971,25 @@ private String decodeField(final Entry entry, final String suffix)
private String storeLengthForVariableLengthFields(final Type type, final String fieldName)
{
return type.hasLengthField(flyweightsEnabled) ?
String.format(" %sLength = valueLength;\n", fieldName) :
String.format(" %sLength = valueLength;\n", fieldName) :
"";
}

private String storeOffsetForVariableLengthFields(final Type type, final String fieldName)
{
return type.hasOffsetField(flyweightsEnabled) ?
String.format(" %sOffset = valueOffset;\n", fieldName) :
String.format(" %sOffset = valueOffset;\n", fieldName) :
"";
}

private String optionalAssign(final Entry entry)
{
return entry.required() ? "" : String.format(" has%s = true;\n", entry.name());
return entry.required() ? "" : String.format(" has%s = true;\n", entry.name());
}

private String fieldDecodeMethod(final Field field, final String fieldName)
{
final String prefix = String.format(" %s = ", fieldName);
final String prefix = String.format(" %s = ", fieldName);
final String decodeMethod;
switch (field.type())
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,13 +162,15 @@ static String encoderClassName(final String name)
final String builderCommonPackage,
final OutputManager outputManager,
final Class<?> validationClass,
final Class<?> rejectEmptyTagClass,
final Class<?> rejectUnknownFieldClass,
final Class<?> rejectUnknownEnumValueClass,
final String codecRejectUnknownEnumValueEnabled,
final boolean fixTagsInJavadoc)
{
super(dictionary, builderPackage, builderCommonPackage, outputManager, validationClass, rejectUnknownFieldClass,
rejectUnknownEnumValueClass, false, codecRejectUnknownEnumValueEnabled, fixTagsInJavadoc);
super(dictionary, builderPackage, builderCommonPackage, outputManager, validationClass, rejectEmptyTagClass,
rejectUnknownFieldClass, rejectUnknownEnumValueClass, false, codecRejectUnknownEnumValueEnabled,
fixTagsInJavadoc);

final Component header = dictionary.header();
validateHasField(header, BEGIN_STRING);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public abstract class Generator
public static final String BODY_LENGTH = "BodyLength";

public static final String CODEC_VALIDATION_ENABLED = "CODEC_VALIDATION_ENABLED";
public static final String CODEC_REJECT_EMPTY_TAG_ENABLED = "CODEC_REJECT_EMPTY_TAG_ENABLED";
public static final String CODEC_REJECT_UNKNOWN_FIELD_ENABLED = "CODEC_REJECT_UNKNOWN_FIELD_ENABLED";
public static final String RUNTIME_REJECT_UNKNOWN_ENUM_VALUE_PROPERTY = "CODEC_REJECT_UNKNOWN_ENUM_VALUE_ENABLED";
public static final Pattern NEWLINE = Pattern.compile("^", MULTILINE);
Expand Down Expand Up @@ -92,6 +93,7 @@ protected String commonCompoundImports(final String form, final boolean headerWr
private final String commonPackage;
protected final OutputManager outputManager;
protected final Class<?> validationClass;
protected final Class<?> rejectEmptyTagClass;
protected final Class<?> rejectUnknownFieldClass;
private final Class<?> rejectUnknownEnumValueClass;
protected final boolean flyweightsEnabled;
Expand All @@ -107,6 +109,7 @@ protected Generator(
final String commonPackage,
final OutputManager outputManager,
final Class<?> validationClass,
final Class<?> rejectEmptyTagClass,
final Class<?> rejectUnknownFieldClass,
final Class<?> rejectUnknownEnumValueClass,
final boolean flyweightsEnabled,
Expand All @@ -118,6 +121,7 @@ protected Generator(
this.commonPackage = commonPackage;
this.outputManager = outputManager;
this.validationClass = validationClass;
this.rejectEmptyTagClass = rejectEmptyTagClass;
this.rejectUnknownFieldClass = rejectUnknownFieldClass;
this.rejectUnknownEnumValueClass = rejectUnknownEnumValueClass;
this.flyweightsEnabled = flyweightsEnabled;
Expand Down Expand Up @@ -179,6 +183,7 @@ protected void generateImports(

out .append(importStaticFor(StandardCharsets.class, "US_ASCII"))
.append(importStaticFor(validationClass, CODEC_VALIDATION_ENABLED))
.append(importStaticFor(rejectEmptyTagClass, CODEC_REJECT_EMPTY_TAG_ENABLED))
.append(importStaticFor(rejectUnknownFieldClass, CODEC_REJECT_UNKNOWN_FIELD_ENABLED))
.append(importStaticFor(rejectUnknownEnumValueClass, RUNTIME_REJECT_UNKNOWN_ENUM_VALUE_PROPERTY));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,10 @@ public final class ExampleDictionary
public static final String EG_NO_OPTIONAL_FIELDS_MESSAGE =
"8=FIX.4.4\0019=0049\00135=Z\0011001=USD\0011002=N\0011003=US\00110=209\001";

public static final String EG_OPTIONAL_FIELDS_EMPTY_MESSAGE =
"8=FIX.4.4\0019=0049\00135=Z\0011001=USD\0011002=N\0011003=US" +
"\0011004=\0011005=\0011006=\0011007=\00110=209\001";

public static final String EG_HIGH_NUMBER_FIELD_MESSAGE =
"8=FIX.4.4\0019=0049\00135=Z\0019001=1\0011001=USD\0011002=N\0011003=US\00110=209\001";

Expand Down
Loading

0 comments on commit 7f7e3c2

Please sign in to comment.