diff --git a/raml-parser-2/src/main/java/org/raml/v2/internal/impl/v10/type/TypeToRuleVisitor.java b/raml-parser-2/src/main/java/org/raml/v2/internal/impl/v10/type/TypeToRuleVisitor.java index 27b44a6d..77728616 100644 --- a/raml-parser-2/src/main/java/org/raml/v2/internal/impl/v10/type/TypeToRuleVisitor.java +++ b/raml-parser-2/src/main/java/org/raml/v2/internal/impl/v10/type/TypeToRuleVisitor.java @@ -48,6 +48,11 @@ public class TypeToRuleVisitor implements TypeVisitor { + private static final String CAST_STRINGS_AS_NUMBERS_PROP = "org.raml.cast_strings_as_number"; + public static boolean CAST_STRINGS_AS_NUMBERS = Boolean.parseBoolean(System.getProperty(CAST_STRINGS_AS_NUMBERS_PROP, "false")); + private static final String NILLABLE_STRINGS_PROP = "org.raml.nillable_strings"; + public static boolean NILLABLE_STRINGS = Boolean.parseBoolean(System.getProperty(NILLABLE_STRINGS_PROP, "false")); + private ResourceLoader resourceLoader; private final boolean useDiscriminatorsToCalculateTypes; private boolean strictMode = false; @@ -84,11 +89,11 @@ public Rule generateRule(ResolvedType items) @Override public Rule visitString(StringResolvedType stringTypeNode) { - final AllOfRule typeRule = new AllOfRule(new StringTypeRule()); + final AllOfRule typeRule = new AllOfRule(new StringTypeRule(NILLABLE_STRINGS)); registerRule(stringTypeNode, typeRule); if (isNotEmpty(stringTypeNode.getPattern())) { - typeRule.and(new RegexValueRule(Pattern.compile(stringTypeNode.getPattern()))); + typeRule.and(new RegexValueRule(Pattern.compile(stringTypeNode.getPattern()), NILLABLE_STRINGS)); } if (stringTypeNode.getEnums() != null && !stringTypeNode.getEnums().isEmpty()) @@ -99,13 +104,13 @@ public Rule visitString(StringResolvedType stringTypeNode) if (stringTypeNode.getMaxLength() != null) { Integer maxLength = stringTypeNode.getMaxLength(); - typeRule.and(new MaxLengthRule(maxLength)); + typeRule.and(new MaxLengthRule(maxLength, NILLABLE_STRINGS)); } if (stringTypeNode.getMinLength() != null) { Integer maxLength = stringTypeNode.getMinLength(); - typeRule.and(new MinLengthRule(maxLength)); + typeRule.and(new MinLengthRule(maxLength, NILLABLE_STRINGS)); } return typeRule; } @@ -307,13 +312,13 @@ public Rule visitBoolean(BooleanResolvedType booleanTypeDefinition) @Override public Rule visitInteger(IntegerResolvedType integerTypeDefinition) { - return visitNumber(integerTypeDefinition, new IntegerTypeRule()); + return visitNumber(integerTypeDefinition, new IntegerTypeRule(CAST_STRINGS_AS_NUMBERS)); } @Override public Rule visitNumber(NumberResolvedType numberTypeDefinition) { - return visitNumber(numberTypeDefinition, new NumberTypeRule()); + return visitNumber(numberTypeDefinition, new NumberTypeRule(CAST_STRINGS_AS_NUMBERS)); } private Rule visitNumber(NumberResolvedType numericTypeNode, Rule numericTypeRule) @@ -343,7 +348,7 @@ else if (numericTypeNode.getMaximum() != null) } if (numericTypeNode.getMultiple() != null) { - typeRule.and(new DivisorValueRule(numericTypeNode.getMultiple())); + typeRule.and(new DivisorValueRule(numericTypeNode.getMultiple(), CAST_STRINGS_AS_NUMBERS)); } if (numericTypeNode.getFormat() != null) { diff --git a/yagi/src/main/java/org/raml/yagi/framework/grammar/rule/DivisorValueRule.java b/yagi/src/main/java/org/raml/yagi/framework/grammar/rule/DivisorValueRule.java index abe6decf..e3b6c5b7 100644 --- a/yagi/src/main/java/org/raml/yagi/framework/grammar/rule/DivisorValueRule.java +++ b/yagi/src/main/java/org/raml/yagi/framework/grammar/rule/DivisorValueRule.java @@ -15,10 +15,7 @@ */ package org.raml.yagi.framework.grammar.rule; -import org.raml.yagi.framework.nodes.FloatingNode; -import org.raml.yagi.framework.nodes.IntegerNode; -import org.raml.yagi.framework.nodes.Node; -import org.raml.yagi.framework.nodes.SimpleTypeNode; +import org.raml.yagi.framework.nodes.*; import org.raml.yagi.framework.suggester.ParsingContext; import org.raml.yagi.framework.suggester.Suggestion; @@ -32,11 +29,20 @@ public class DivisorValueRule extends Rule { + private final boolean castStringsAsNumbers; private Number divisorValue; public DivisorValueRule(Number divisorValue) { this.divisorValue = divisorValue; + this.castStringsAsNumbers = false; + } + + public DivisorValueRule(Number multiple, boolean castStringsAsNumbers) + { + + this.divisorValue = multiple; + this.castStringsAsNumbers = castStringsAsNumbers; } @Nonnull @@ -51,6 +57,18 @@ public boolean matches(@Nonnull Node node) { final BigDecimal divisor = new BigDecimal(divisorValue.toString()); BigDecimal value = null; + if (node instanceof StringNode && castStringsAsNumbers) + { + String intString = ((StringNode) node).getValue(); + try + { + value = new BigDecimal(intString); + } + catch (NumberFormatException e) + { + return false; + } + } if (node instanceof IntegerNode) { value = new BigDecimal(((IntegerNode) node).getValue()); diff --git a/yagi/src/main/java/org/raml/yagi/framework/grammar/rule/IntegerTypeRule.java b/yagi/src/main/java/org/raml/yagi/framework/grammar/rule/IntegerTypeRule.java index 0f73088d..1d750cad 100644 --- a/yagi/src/main/java/org/raml/yagi/framework/grammar/rule/IntegerTypeRule.java +++ b/yagi/src/main/java/org/raml/yagi/framework/grammar/rule/IntegerTypeRule.java @@ -16,11 +16,7 @@ package org.raml.yagi.framework.grammar.rule; import com.google.common.collect.Range; -import org.raml.yagi.framework.nodes.FloatingNode; -import org.raml.yagi.framework.nodes.IntegerNode; -import org.raml.yagi.framework.nodes.Node; -import org.raml.yagi.framework.nodes.NodeType; -import org.raml.yagi.framework.nodes.jackson.JFloatingNode; +import org.raml.yagi.framework.nodes.*; import org.raml.yagi.framework.suggester.ParsingContext; import org.raml.yagi.framework.suggester.Suggestion; @@ -33,12 +29,14 @@ public class IntegerTypeRule extends AbstractTypeRule { + private final boolean castStringsAsNumbers; @Nullable private Range range; public IntegerTypeRule(@Nullable Range range) { this.range = range; + this.castStringsAsNumbers = false; } public IntegerTypeRule() @@ -46,6 +44,12 @@ public IntegerTypeRule() this(null); } + public IntegerTypeRule(boolean castStringsAsNumbers) + { + + this.castStringsAsNumbers = castStringsAsNumbers; + } + @Nonnull @Override public List getSuggestions(Node node, ParsingContext context) @@ -57,6 +61,20 @@ public List getSuggestions(Node node, ParsingContext context) @Override public boolean matches(@Nonnull Node node) { + if (node instanceof StringNode && castStringsAsNumbers) + { + String intString = ((StringNode) node).getValue(); + try + { + long longValue = Long.parseLong(intString); + return isInRange(longValue); + } + catch (NumberFormatException e) + { + return false; + } + } + if (node instanceof IntegerNode) { return isInRange(((IntegerNode) node).getValue()); @@ -68,7 +86,6 @@ else if (node instanceof FloatingNode) long value = ((FloatingNode) node).getValue().longValue(); if (((FloatingNode) node).getValue().compareTo(new BigDecimal(value)) != 0) { - return false; } diff --git a/yagi/src/main/java/org/raml/yagi/framework/grammar/rule/MaxLengthRule.java b/yagi/src/main/java/org/raml/yagi/framework/grammar/rule/MaxLengthRule.java index 563a2ad8..c0f8c424 100644 --- a/yagi/src/main/java/org/raml/yagi/framework/grammar/rule/MaxLengthRule.java +++ b/yagi/src/main/java/org/raml/yagi/framework/grammar/rule/MaxLengthRule.java @@ -17,6 +17,7 @@ import org.raml.yagi.framework.nodes.Node; +import org.raml.yagi.framework.nodes.NullNode; import org.raml.yagi.framework.nodes.SimpleTypeNode; import org.raml.yagi.framework.suggester.ParsingContext; import org.raml.yagi.framework.suggester.Suggestion; @@ -27,11 +28,20 @@ public class MaxLengthRule extends Rule { - private int maxLength; + private final int maxLength; + private final boolean nillableStrings; public MaxLengthRule(int maxLength) { this.maxLength = maxLength; + this.nillableStrings = false; + } + + public MaxLengthRule(int maxLength, boolean nillableStrings) + { + + this.maxLength = maxLength; + this.nillableStrings = nillableStrings; } @Nonnull @@ -44,6 +54,11 @@ public List getSuggestions(Node node, ParsingContext context) @Override public boolean matches(@Nonnull Node node) { + if (node instanceof NullNode && nillableStrings) + { + return true; + } + if (node instanceof SimpleTypeNode) { return ((SimpleTypeNode) node).getLiteralValue().length() <= maxLength; @@ -54,10 +69,17 @@ public boolean matches(@Nonnull Node node) @Override public Node apply(@Nonnull Node node) { + if (!matches(node)) { return ErrorNodeFactory.createInvalidMaxLength(maxLength, node); } + + if (node instanceof NullNode && nillableStrings) + { + return createNodeUsingFactory(node, ""); + } + return createNodeUsingFactory(node, ((SimpleTypeNode) node).getLiteralValue()); } diff --git a/yagi/src/main/java/org/raml/yagi/framework/grammar/rule/MinLengthRule.java b/yagi/src/main/java/org/raml/yagi/framework/grammar/rule/MinLengthRule.java index bd46770e..e0af9a57 100644 --- a/yagi/src/main/java/org/raml/yagi/framework/grammar/rule/MinLengthRule.java +++ b/yagi/src/main/java/org/raml/yagi/framework/grammar/rule/MinLengthRule.java @@ -17,6 +17,7 @@ import org.raml.yagi.framework.nodes.Node; +import org.raml.yagi.framework.nodes.NullNode; import org.raml.yagi.framework.nodes.SimpleTypeNode; import org.raml.yagi.framework.suggester.ParsingContext; import org.raml.yagi.framework.suggester.Suggestion; @@ -27,11 +28,20 @@ public class MinLengthRule extends Rule { - private int minLength; + private final int minLength; + private final boolean nillableStrings; public MinLengthRule(int minLength) { this.minLength = minLength; + this.nillableStrings = false; + } + + public MinLengthRule(int minLength, boolean nillableStrings) + { + + this.minLength = minLength; + this.nillableStrings = nillableStrings; } @Nonnull @@ -44,6 +54,12 @@ public List getSuggestions(Node node, ParsingContext context) @Override public boolean matches(@Nonnull Node node) { + if (node instanceof NullNode && nillableStrings) + { + + return minLength == 0; + } + if (node instanceof SimpleTypeNode) { return ((SimpleTypeNode) node).getLiteralValue().length() >= minLength; @@ -58,6 +74,12 @@ public Node apply(@Nonnull Node node) { return ErrorNodeFactory.createInvalidMinLength(minLength, node); } + + if (node instanceof NullNode && nillableStrings) + { + return createNodeUsingFactory(node, ""); + } + return createNodeUsingFactory(node, ((SimpleTypeNode) node).getLiteralValue()); } diff --git a/yagi/src/main/java/org/raml/yagi/framework/grammar/rule/NumberTypeRule.java b/yagi/src/main/java/org/raml/yagi/framework/grammar/rule/NumberTypeRule.java index 96acb882..ce609522 100644 --- a/yagi/src/main/java/org/raml/yagi/framework/grammar/rule/NumberTypeRule.java +++ b/yagi/src/main/java/org/raml/yagi/framework/grammar/rule/NumberTypeRule.java @@ -31,12 +31,14 @@ public class NumberTypeRule extends AbstractTypeRule { + private final boolean castStringsAsNumbers; @Nullable private Range range; public NumberTypeRule(@Nullable Range range) { this.range = range; + this.castStringsAsNumbers = false; } public NumberTypeRule() @@ -44,6 +46,12 @@ public NumberTypeRule() this(null); } + public NumberTypeRule(boolean castStringsAsNumbers) + { + + this.castStringsAsNumbers = castStringsAsNumbers; + } + @Nonnull @Override public List getSuggestions(Node node, ParsingContext context) @@ -55,6 +63,19 @@ public List getSuggestions(Node node, ParsingContext context) @Override public boolean matches(@Nonnull Node node) { + if (node instanceof StringNode && castStringsAsNumbers) + { + String intString = ((StringNode) node).getValue(); + try + { + double doubleValue = Double.parseDouble(intString); + return range == null || range.contains(doubleValue); + } + catch (NumberFormatException e) + { + return false; + } + } if (node instanceof FloatingNode) { return range == null || range.contains(((FloatingNode) node).getValue().doubleValue()); diff --git a/yagi/src/main/java/org/raml/yagi/framework/grammar/rule/RegexValueRule.java b/yagi/src/main/java/org/raml/yagi/framework/grammar/rule/RegexValueRule.java index faf117ea..0b957e24 100644 --- a/yagi/src/main/java/org/raml/yagi/framework/grammar/rule/RegexValueRule.java +++ b/yagi/src/main/java/org/raml/yagi/framework/grammar/rule/RegexValueRule.java @@ -18,6 +18,7 @@ import org.apache.commons.lang.StringUtils; import org.raml.yagi.framework.nodes.Node; +import org.raml.yagi.framework.nodes.NullNode; import org.raml.yagi.framework.nodes.SimpleTypeNode; import org.raml.yagi.framework.suggester.DefaultSuggestion; import org.raml.yagi.framework.suggester.ParsingContext; @@ -33,6 +34,7 @@ public class RegexValueRule extends Rule { + private final boolean nillableString; private Pattern value; private String description; private List suggestions = new ArrayList<>(); @@ -42,6 +44,13 @@ public class RegexValueRule extends Rule public RegexValueRule(Pattern value) { this.value = value; + this.nillableString = false; + } + + public RegexValueRule(Pattern value, boolean nillableString) + { + this.value = value; + this.nillableString = nillableString; } @Nonnull @@ -72,6 +81,12 @@ public List getSuggestions(Node node, ParsingContext context) @Override public boolean matches(@Nonnull Node node) { + if (node instanceof NullNode && nillableString) + { + + return fullMatch ? value.matcher("").matches() : value.matcher("").find(); + } + return node instanceof SimpleTypeNode && (fullMatch ? getMatcher((SimpleTypeNode) node).matches() : getMatcher((SimpleTypeNode) node).find()); } @@ -107,6 +122,13 @@ public RegexValueRule fullMatch(boolean fullMatch) @Override public Node apply(@Nonnull Node node) { + + if (node instanceof NullNode && nillableString) + { + + return node; + } + if (!matches(node)) { return ErrorNodeFactory.createInvalidValue(node, String.valueOf(value)); diff --git a/yagi/src/main/java/org/raml/yagi/framework/grammar/rule/StringTypeRule.java b/yagi/src/main/java/org/raml/yagi/framework/grammar/rule/StringTypeRule.java index c25b8e36..cd963f73 100644 --- a/yagi/src/main/java/org/raml/yagi/framework/grammar/rule/StringTypeRule.java +++ b/yagi/src/main/java/org/raml/yagi/framework/grammar/rule/StringTypeRule.java @@ -17,6 +17,7 @@ import org.raml.yagi.framework.nodes.Node; import org.raml.yagi.framework.nodes.NodeType; +import org.raml.yagi.framework.nodes.NullNode; import org.raml.yagi.framework.nodes.StringNode; import org.raml.yagi.framework.suggester.ParsingContext; import org.raml.yagi.framework.suggester.Suggestion; @@ -27,6 +28,18 @@ public class StringTypeRule extends AbstractTypeRule { + private final boolean nillableStrings; + + public StringTypeRule() + { + nillableStrings = false; + } + + public StringTypeRule(boolean nillableStrings) + { + this.nillableStrings = nillableStrings; + } + @Nonnull @Override public List getSuggestions(Node node, ParsingContext context) @@ -37,7 +50,7 @@ public List getSuggestions(Node node, ParsingContext context) @Override public boolean matches(@Nonnull Node node) { - return node instanceof StringNode; + return node instanceof StringNode || (node instanceof NullNode && nillableStrings); } @Override diff --git a/yagi/src/main/java/org/raml/yagi/framework/util/DateUtils.java b/yagi/src/main/java/org/raml/yagi/framework/util/DateUtils.java index 4c2b3e83..19857c6e 100644 --- a/yagi/src/main/java/org/raml/yagi/framework/util/DateUtils.java +++ b/yagi/src/main/java/org/raml/yagi/framework/util/DateUtils.java @@ -19,9 +19,6 @@ import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.joda.time.format.DateTimeFormatterBuilder; -import org.joda.time.format.ISODateTimeFormat; - -import static org.joda.time.format.ISODateTimeFormat.*; public class DateUtils { @@ -31,29 +28,36 @@ public class DateUtils private static final String STRICT_DATES_RFC3339 = "org.raml.dates_rfc3339_validation"; private static final String STRICT_DATES_RFC2616 = "org.raml.dates_rfc2616_validation"; + private static final String FALLBACK_DATETIME = "org.raml.fallback_datetime_to_datetime-only"; + public static boolean FOUR_YEARS_VALIDATION = Boolean.parseBoolean(System.getProperty( DATE_ONLY_FOUR_DIGITS_YEAR_LENGTH_VALIDATION, System.getProperty(DATE_ONLY_FOUR_DIGITS_YEAR_LENGTH_VALIDATION_ALTERNATE, "true"))); public static boolean STRICT_DATES_VALIDATION_3339 = Boolean.parseBoolean(System.getProperty(STRICT_DATES_RFC3339, "true")); public static boolean STRICT_DATES_VALIDATION_2616 = Boolean.parseBoolean(System.getProperty(STRICT_DATES_RFC2616, "true")); - private DateUtils(boolean strictYear, boolean strictDates3339, boolean strictDates2616) + public static boolean FALLBACK_DATETIME_TO_DATETIME_ONLY = Boolean.parseBoolean(System.getProperty(FALLBACK_DATETIME, "false")); + + private final boolean fallbackDatetime; + + private DateUtils(boolean strictYear, boolean strictDates3339, boolean strictDates2616, boolean fallbackDatetime) { + this.fallbackDatetime = fallbackDatetime; setFormatters(strictYear, strictDates3339, strictDates2616); } public static DateUtils createStrictDateUtils() { - return new DateUtils(true, true, true); + return new DateUtils(true, true, true, false); } public static DateUtils createNonStrictDateUtils() { - return new DateUtils(false, false, false); + return new DateUtils(false, false, false, true); } public static DateUtils createFromProperties() { - return new DateUtils(FOUR_YEARS_VALIDATION, STRICT_DATES_VALIDATION_3339, STRICT_DATES_VALIDATION_2616); + return new DateUtils(FOUR_YEARS_VALIDATION, STRICT_DATES_VALIDATION_3339, STRICT_DATES_VALIDATION_2616, FALLBACK_DATETIME_TO_DATETIME_ONLY); } public void setFormatters(boolean strictYear, boolean strictDates3339, boolean strictDates2616) @@ -153,16 +157,11 @@ public boolean isValidDate(String date, DateType format, String rfc) timeOnlyFormatter.parseLocalTime(date); break; case datetime_only: - try - { - dateTimeOnlyFormatterNoMillis.parseLocalDateTime(date); - } - catch (Exception e) - { - dateTimeOnlyFormatterMillis.parseLocalDateTime(date); - } + checkDatetimeOnly(date); break; case datetime: + // Mon., 20 Jan. 2020 19:21:21 EST + // Mon, 20 Jan 2020 19:23:30 EST if ("rfc2616".equals(rfc)) { rfc2616Formatter.parseLocalDateTime(date); @@ -174,9 +173,24 @@ public boolean isValidDate(String date, DateType format, String rfc) { rfc3339FormatterMillis.parseLocalDateTime(date); } - catch (Exception e) + catch (IllegalArgumentException e) { - rfc3339FormatterNoMillis.parseLocalDateTime(date); + try + { + rfc3339FormatterNoMillis.parseLocalDateTime(date); + } + catch (IllegalArgumentException e2) + { + + if (fallbackDatetime) + { + checkDatetimeOnly(date); + } + else + { + throw e2; + } + } } break; } @@ -191,6 +205,18 @@ public boolean isValidDate(String date, DateType format, String rfc) } } + private void checkDatetimeOnly(String date) + { + try + { + dateTimeOnlyFormatterNoMillis.parseLocalDateTime(date); + } + catch (IllegalArgumentException e) + { + dateTimeOnlyFormatterMillis.parseLocalDateTime(date); + } + } + private DateTimeFormatterBuilder yearFormat(boolean strictYear, boolean strictDates) {