Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 6 additions & 33 deletions src/main/java/build/buf/protovalidate/CustomDeclarations.java
Original file line number Diff line number Diff line change
Expand Up @@ -160,39 +160,12 @@ static List<Decl> create() {

// Add 'format' function declaration
List<Decl.FunctionDecl.Overload> 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);
Expand Down
43 changes: 25 additions & 18 deletions src/main/java/build/buf/protovalidate/Format.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String> str = validateNumber(val);
Expand Down Expand Up @@ -195,17 +201,23 @@ private static String formatMap(MapT val) {
StringBuilder builder = new StringBuilder();
builder.append('{');

SortedMap<String, String> 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();
}

Expand Down Expand Up @@ -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());
}
}

Expand All @@ -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());
}
}

Expand All @@ -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());
}
}

Expand All @@ -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());
}
}

Expand All @@ -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());
}
}

Expand All @@ -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());
}
}

Expand All @@ -356,9 +368,4 @@ private static Optional<String> validateNumber(Val val) {
}
return Optional.empty();
}

private static String typeToString(Val val) {
TypeEnum type = val.type().typeEnum();
return type.getName();
}
}
30 changes: 4 additions & 26 deletions src/test/java/build/buf/protovalidate/FormatTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -60,24 +59,6 @@ class FormatTest {
private static List<SimpleTest> formatTests;
private static List<SimpleTest> formatErrorTests;

private static List<String> 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
Expand Down Expand Up @@ -158,11 +139,9 @@ private static Program.EvalResult evaluate(SimpleTest test) {
}

private static Stream<Arguments> getTestStream(List<SimpleTest> tests) {
List<Arguments> args = new ArrayList<Arguments>();
List<Arguments> 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();
Expand All @@ -178,7 +157,7 @@ private static Stream<Arguments> getFormatErrorTests() {

// Builds the variable definitions to be used during evaluation
private static Map<String, Object> buildVariables(Map<String, ExprValue> bindings) {
Map<String, Object> vars = new HashMap<String, Object>();
Map<String, Object> vars = new HashMap<>();
for (Map.Entry<String, ExprValue> entry : bindings.entrySet()) {
ExprValue exprValue = entry.getValue();
if (exprValue.hasValue()) {
Expand Down Expand Up @@ -208,8 +187,7 @@ private static String getExpectedResult(SimpleTest test) {

// Builds the declarations for a given test
private static List<com.google.api.expr.v1alpha1.Decl> buildDecls(SimpleTest test) {
List<com.google.api.expr.v1alpha1.Decl> decls =
new ArrayList<com.google.api.expr.v1alpha1.Decl>();
List<com.google.api.expr.v1alpha1.Decl> decls = new ArrayList<>();
for (Decl decl : test.getTypeEnvList()) {
if (decl.hasIdent()) {
Decl.IdentDecl ident = decl.getIdent();
Expand Down
4 changes: 2 additions & 2 deletions src/test/resources/testdata/string_ext_supplemental.textproto
Original file line number Diff line number Diff line change
Expand Up @@ -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"])'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drive-by change. I realized in Python that loading this file with actual invalid utf-8 bytes will prevent it from being loaded. So, just escaping the \x so that it loads as a normal string. Updated the PR to the cel-spec also.

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',
}
Expand Down