diff --git a/src/main/java/build/buf/protovalidate/CustomDeclarations.java b/src/main/java/build/buf/protovalidate/CustomDeclarations.java index 095478bd..d46d07a7 100644 --- a/src/main/java/build/buf/protovalidate/CustomDeclarations.java +++ b/src/main/java/build/buf/protovalidate/CustomDeclarations.java @@ -160,39 +160,12 @@ static List create() { // Add 'format' function declaration List formatOverloads = new ArrayList<>(); - for (com.google.api.expr.v1alpha1.Type type : - Arrays.asList( - Decls.String, - Decls.Int, - Decls.Uint, - Decls.Double, - Decls.Bytes, - Decls.Bool, - Decls.Duration, - Decls.Timestamp)) { - formatOverloads.add( - Decls.newInstanceOverload( - String.format("format_%s", Types.formatCheckedType(type).toLowerCase(Locale.US)), - Arrays.asList(Decls.String, Decls.newListType(type)), - Decls.String)); - formatOverloads.add( - Decls.newInstanceOverload( - String.format("format_list_%s", Types.formatCheckedType(type).toLowerCase(Locale.US)), - Arrays.asList(Decls.String, Decls.newListType(Decls.newListType(type))), - Decls.String)); - formatOverloads.add( - Decls.newInstanceOverload( - String.format( - "format_bytes_%s", Types.formatCheckedType(type).toLowerCase(Locale.US)), - Arrays.asList(Decls.Bytes, Decls.newListType(type)), - Decls.Bytes)); - formatOverloads.add( - Decls.newInstanceOverload( - String.format( - "format_bytes_list_%s", Types.formatCheckedType(type).toLowerCase(Locale.US)), - Arrays.asList(Decls.Bytes, Decls.newListType(Decls.newListType(type))), - Decls.Bytes)); - } + formatOverloads.add( + Decls.newInstanceOverload( + "format_list_dyn", + Arrays.asList(Decls.String, Decls.newListType(Decls.Dyn)), + Decls.String)); + decls.add(Decls.newFunction("format", formatOverloads)); return Collections.unmodifiableList(decls); diff --git a/src/main/java/build/buf/protovalidate/Format.java b/src/main/java/build/buf/protovalidate/Format.java index 38a7008a..9b1a8211 100644 --- a/src/main/java/build/buf/protovalidate/Format.java +++ b/src/main/java/build/buf/protovalidate/Format.java @@ -22,8 +22,13 @@ import java.nio.charset.StandardCharsets; import java.text.DecimalFormat; import java.time.Instant; +import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Optional; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.stream.Collectors; import org.projectnessie.cel.common.types.Err.ErrException; import org.projectnessie.cel.common.types.IntT; import org.projectnessie.cel.common.types.IteratorT; @@ -132,10 +137,11 @@ static String format(String fmtString, ListT list) { private static String formatString(Val val) { TypeEnum type = val.type().typeEnum(); switch (type) { - case Bool: - return Boolean.toString(val.booleanValue()); + case Type: case String: return val.value().toString(); + case Bool: + return Boolean.toString(val.booleanValue()); case Int: case Uint: Optional str = validateNumber(val); @@ -195,17 +201,23 @@ private static String formatMap(MapT val) { StringBuilder builder = new StringBuilder(); builder.append('{'); + SortedMap sorted = new TreeMap<>(); + IteratorT iter = val.iterator(); while (iter.hasNext().booleanValue()) { Val key = iter.next(); String mapKey = formatString(key); String mapVal = formatString(val.find(key)); - builder.append(mapKey).append(": ").append(mapVal); - if (iter.hasNext().booleanValue()) { - builder.append(", "); - } + sorted.put(mapKey, mapVal); } - builder.append('}'); + + String result = + sorted.entrySet().stream() + .map(entry -> entry.getKey() + ": " + entry.getValue()) + .collect(Collectors.joining(", ")); + + builder.append(result).append('}'); + return builder.toString(); } @@ -259,7 +271,7 @@ private static String formatHex(Val val) { } else { throw new ErrException( "error during formatting: only integers, byte buffers, and strings can be formatted as hex, was given " - + typeToString(val)); + + val.type()); } } @@ -280,7 +292,7 @@ private static String formatDecimal(Val val) { } else { throw new ErrException( "error during formatting: decimal clause can only be used on integers, was given " - + typeToString(val)); + + val.type()); } } @@ -291,7 +303,7 @@ private static String formatOctal(Val val) { } else { throw new ErrException( "error during formatting: octal clause can only be used on integers, was given " - + typeToString(val)); + + val.type()); } } @@ -304,7 +316,7 @@ private static String formatBinary(Val val) { } else { throw new ErrException( "error during formatting: only integers and bools can be formatted as binary, was given " - + typeToString(val)); + + val.type()); } } @@ -320,7 +332,7 @@ private static String formatExponential(Val val, int precision) { } else { throw new ErrException( "error during formatting: scientific clause can only be used on doubles, was given " - + typeToString(val)); + + val.type()); } } @@ -344,7 +356,7 @@ private static String formatFloat(Val val, int precision) { } else { throw new ErrException( "error during formatting: fixed-point clause can only be used on doubles, was given " - + typeToString(val)); + + val.type()); } } @@ -356,9 +368,4 @@ private static Optional validateNumber(Val val) { } return Optional.empty(); } - - private static String typeToString(Val val) { - TypeEnum type = val.type().typeEnum(); - return type.getName(); - } } diff --git a/src/test/java/build/buf/protovalidate/FormatTest.java b/src/test/java/build/buf/protovalidate/FormatTest.java index 88f51b39..c13bb515 100644 --- a/src/test/java/build/buf/protovalidate/FormatTest.java +++ b/src/test/java/build/buf/protovalidate/FormatTest.java @@ -29,7 +29,6 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -60,24 +59,6 @@ class FormatTest { private static List formatTests; private static List formatErrorTests; - private static List SKIPPED_TESTS = - Arrays.asList( - // Success Tests - // found no matching overload for 'format' applied to 'string.(list(type(string)))' - "type() support for string", - // found no matching overload for 'format' applied to 'string.(list(map(string, dyn)))' - "map support for string", - // found no matching overload for 'format' applied to 'string.(list(map(dyn, dyn)))' - "map support (all key types)", - // found no matching overload for 'format' applied to 'string.(list(map(dyn, dyn)))' - "dyntype support for maps", - // Error Tests - // found no matching overload for 'format' applied to - // 'string.(list(cel.expr.conformance.proto3.TestAllTypes))' - "object not allowed", - // found no matching overload for 'format' applied to 'string.(list(map(int, dyn)))' - "object inside map"); - @BeforeAll private static void setUp() throws Exception { // The test data from the cel-spec conformance tests @@ -158,11 +139,9 @@ private static Program.EvalResult evaluate(SimpleTest test) { } private static Stream getTestStream(List tests) { - List args = new ArrayList(); + List args = new ArrayList<>(); for (SimpleTest test : tests) { - if (!SKIPPED_TESTS.contains(test.getName())) { - args.add(Arguments.arguments(Named.named(test.getName(), test))); - } + args.add(Arguments.arguments(Named.named(test.getName(), test))); } return args.stream(); @@ -178,7 +157,7 @@ private static Stream getFormatErrorTests() { // Builds the variable definitions to be used during evaluation private static Map buildVariables(Map bindings) { - Map vars = new HashMap(); + Map vars = new HashMap<>(); for (Map.Entry entry : bindings.entrySet()) { ExprValue exprValue = entry.getValue(); if (exprValue.hasValue()) { @@ -208,8 +187,7 @@ private static String getExpectedResult(SimpleTest test) { // Builds the declarations for a given test private static List buildDecls(SimpleTest test) { - List decls = - new ArrayList(); + List decls = new ArrayList<>(); for (Decl decl : test.getTypeEnvList()) { if (decl.hasIdent()) { Decl.IdentDecl ident = decl.getIdent(); diff --git a/src/test/resources/testdata/string_ext_supplemental.textproto b/src/test/resources/testdata/string_ext_supplemental.textproto index 6f4402fc..dde7b83b 100644 --- a/src/test/resources/testdata/string_ext_supplemental.textproto +++ b/src/test/resources/testdata/string_ext_supplemental.textproto @@ -11,14 +11,14 @@ section: { name: "format" test: { name: "bytes support for string with invalid utf-8 encoding" - expr: '"%s".format([b"\xF0abc\x8C\xF0xyz"])' + expr: '"%s".format([b"\\xF0abc\\x8C\\xF0xyz"])' value: { string_value: '\ufffdabc\ufffdxyz', } } test: { name: "bytes support for string with only invalid utf-8 sequences" - expr: '"%s".format([b"\xF0\x8C\xF0"])' + expr: '"%s".format([b"\\xF0\\x8C\\xF0"])' value: { string_value: '\ufffd', }