diff --git a/build.gradle.kts b/build.gradle.kts index a53e6d86..c50440c4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -257,10 +257,10 @@ mavenPublishing { dependencies { annotationProcessor(libs.nullaway) + api(libs.jspecify) api(libs.protobuf.java) implementation(enforcedPlatform(libs.cel)) implementation(libs.cel.core) - implementation(libs.guava) buf("build.buf:buf:${libs.versions.buf.get()}:${osdetector.classifier}@exe") @@ -269,5 +269,5 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter") testRuntimeOnly("org.junit.platform:junit-platform-launcher") - errorprone(libs.errorprone) + errorprone(libs.errorprone.core) } diff --git a/conformance/build.gradle.kts b/conformance/build.gradle.kts index a69c45f3..64a3789b 100644 --- a/conformance/build.gradle.kts +++ b/conformance/build.gradle.kts @@ -10,6 +10,12 @@ plugins { alias(libs.plugins.osdetector) } +// Conformance tests aren't bound by lowest common library version. +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + val buf: Configuration by configurations.creating tasks.register("configureBuf") { @@ -116,7 +122,7 @@ configure { dependencies { implementation(project(":")) - implementation(libs.guava) + implementation(libs.errorprone.annotations) implementation(libs.protobuf.java) implementation(libs.assertj) @@ -127,5 +133,5 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter") testRuntimeOnly("org.junit.platform:junit-platform-launcher") - errorprone(libs.errorprone) + errorprone(libs.errorprone.core) } diff --git a/conformance/src/main/java/build/buf/protovalidate/conformance/Main.java b/conformance/src/main/java/build/buf/protovalidate/conformance/Main.java index a59e0de9..9c2cd915 100644 --- a/conformance/src/main/java/build/buf/protovalidate/conformance/Main.java +++ b/conformance/src/main/java/build/buf/protovalidate/conformance/Main.java @@ -24,7 +24,6 @@ import build.buf.validate.conformance.harness.TestConformanceRequest; import build.buf.validate.conformance.harness.TestConformanceResponse; import build.buf.validate.conformance.harness.TestResult; -import com.google.common.base.Splitter; import com.google.errorprone.annotations.FormatMethod; import com.google.protobuf.Any; import com.google.protobuf.ByteString; @@ -34,7 +33,6 @@ import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.TypeRegistry; import java.util.HashMap; -import java.util.List; import java.util.Map; public class Main { @@ -84,8 +82,11 @@ static TestConformanceResponse testConformance(TestConformanceRequest request) { static TestResult testCase( Validator validator, Map fileDescriptors, Any testCase) throws InvalidProtocolBufferException { - List urlParts = Splitter.on('/').limit(2).splitToList(testCase.getTypeUrl()); - String fullName = urlParts.get(urlParts.size() - 1); + String fullName = testCase.getTypeUrl(); + int slash = fullName.indexOf('/'); + if (slash != -1) { + fullName = fullName.substring(slash + 1); + } Descriptors.Descriptor descriptor = fileDescriptors.get(fullName); if (descriptor == null) { return unexpectedErrorResult("Unable to find descriptor: %s", fullName); diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 610b257a..21ac837c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,6 +2,7 @@ assertj = "3.27.3" buf = "1.52.1" cel = "0.5.1" +error-prone = "2.37.0" junit = "5.12.1" maven-publish = "0.31.0" # When updating, make sure to update versions in the following files to match and regenerate code with 'make generate'. @@ -16,8 +17,9 @@ assertj = { module = "org.assertj:assertj-core", version.ref = "assertj" } buf = { module = "build.buf:buf", version.ref = "buf" } cel = { module = "org.projectnessie.cel:cel-bom", version.ref = "cel" } cel-core = { module = "org.projectnessie.cel:cel-core" } -errorprone = { module = "com.google.errorprone:error_prone_core", version = "2.37.0" } -guava = { module = "com.google.guava:guava", version = "33.4.0-jre" } +errorprone-annotations = { module = "com.google.errorprone:error_prone_annotations", version.ref = "error-prone" } +errorprone-core = { module = "com.google.errorprone:error_prone_core", version.ref = "error-prone" } +jspecify = { module ="org.jspecify:jspecify", version = "1.0.0" } junit-bom = { module = "org.junit:junit-bom", version.ref = "junit" } maven-plugin = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "maven-publish" } nullaway = { module = "com.uber.nullaway:nullaway", version = "0.12.6" } diff --git a/src/main/java/build/buf/protovalidate/CelPrograms.java b/src/main/java/build/buf/protovalidate/CelPrograms.java index a3df4ed6..38bcc097 100644 --- a/src/main/java/build/buf/protovalidate/CelPrograms.java +++ b/src/main/java/build/buf/protovalidate/CelPrograms.java @@ -17,7 +17,7 @@ import build.buf.protovalidate.exceptions.ExecutionException; import java.util.ArrayList; import java.util.List; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** Evaluator that executes a {@link CompiledProgram}. */ class CelPrograms implements Evaluator { diff --git a/src/main/java/build/buf/protovalidate/CompiledProgram.java b/src/main/java/build/buf/protovalidate/CompiledProgram.java index fa1c68a8..d9343ccc 100644 --- a/src/main/java/build/buf/protovalidate/CompiledProgram.java +++ b/src/main/java/build/buf/protovalidate/CompiledProgram.java @@ -16,7 +16,7 @@ import build.buf.protovalidate.exceptions.ExecutionException; import build.buf.validate.FieldPath; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import org.projectnessie.cel.Program; import org.projectnessie.cel.common.types.Err; import org.projectnessie.cel.common.types.ref.Val; @@ -63,8 +63,7 @@ public CompiledProgram( * violations. * @throws ExecutionException If the evaluation of the CEL program fails with an error. */ - @Nullable - public ConstraintViolation.Builder eval(Value fieldValue, Variable bindings) + public ConstraintViolation.@Nullable Builder eval(Value fieldValue, Variable bindings) throws ExecutionException { Program.EvalResult evalResult = program.eval(bindings); Val val = evalResult.getVal(); diff --git a/src/main/java/build/buf/protovalidate/ConstraintCache.java b/src/main/java/build/buf/protovalidate/ConstraintCache.java index 708019f2..740b25a3 100644 --- a/src/main/java/build/buf/protovalidate/ConstraintCache.java +++ b/src/main/java/build/buf/protovalidate/ConstraintCache.java @@ -32,7 +32,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import org.projectnessie.cel.Ast; import org.projectnessie.cel.Env; import org.projectnessie.cel.EnvOption; @@ -199,7 +199,7 @@ public List compile( return celRules; } - private @Nullable build.buf.validate.PredefinedConstraints getFieldConstraints( + private build.buf.validate.@Nullable PredefinedConstraints getFieldConstraints( FieldDescriptor constraintFieldDesc) throws CompilationException { DescriptorProtos.FieldOptions options = constraintFieldDesc.getOptions(); // If the protovalidate field option is unknown, reparse options using our extension registry. diff --git a/src/main/java/build/buf/protovalidate/ConstraintViolation.java b/src/main/java/build/buf/protovalidate/ConstraintViolation.java index 4b46ddf4..c141d1ff 100644 --- a/src/main/java/build/buf/protovalidate/ConstraintViolation.java +++ b/src/main/java/build/buf/protovalidate/ConstraintViolation.java @@ -23,7 +23,7 @@ import java.util.Deque; import java.util.List; import java.util.Objects; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@link ConstraintViolation} contains all of the collected information about an individual diff --git a/src/main/java/build/buf/protovalidate/ConstraintViolationHelper.java b/src/main/java/build/buf/protovalidate/ConstraintViolationHelper.java index 2a9b4943..05e7ae28 100644 --- a/src/main/java/build/buf/protovalidate/ConstraintViolationHelper.java +++ b/src/main/java/build/buf/protovalidate/ConstraintViolationHelper.java @@ -18,7 +18,7 @@ import build.buf.validate.FieldPathElement; import java.util.ArrayList; import java.util.List; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; class ConstraintViolationHelper { private static final List EMPTY_PREFIX = new ArrayList<>(); @@ -46,8 +46,7 @@ class ConstraintViolationHelper { this.fieldPathElement = null; } - @Nullable - FieldPathElement getFieldPathElement() { + @Nullable FieldPathElement getFieldPathElement() { return fieldPathElement; } diff --git a/src/main/java/build/buf/protovalidate/CustomOverload.java b/src/main/java/build/buf/protovalidate/CustomOverload.java index fa244fbe..968ad5fa 100644 --- a/src/main/java/build/buf/protovalidate/CustomOverload.java +++ b/src/main/java/build/buf/protovalidate/CustomOverload.java @@ -14,7 +14,6 @@ package build.buf.protovalidate; -import com.google.common.primitives.Bytes; import java.util.HashSet; import java.util.Set; import java.util.regex.Pattern; @@ -205,12 +204,34 @@ private static Overload celContains() { if (lhsType == TypeEnum.Bytes) { byte[] receiver = (byte[]) lhs.value(); byte[] param = (byte[]) rhs.value(); - return Types.boolOf(Bytes.indexOf(receiver, param) != -1); + return Types.boolOf(bytesContains(receiver, param)); } return Err.noSuchOverload(lhs, OVERLOAD_CONTAINS, rhs); }); } + static boolean bytesContains(byte[] arr, byte[] subArr) { + if (subArr.length == 0) { + return true; + } + if (subArr.length > arr.length) { + return false; + } + for (int i = 0; i < arr.length - subArr.length + 1; i++) { + boolean found = true; + for (int j = 0; j < subArr.length; j++) { + if (arr[i + j] != subArr[j]) { + found = false; + break; + } + } + if (found) { + return true; + } + } + return false; + } + /** * Creates a custom binary function overload for the "isHostname" operation. * diff --git a/src/main/java/build/buf/protovalidate/DescriptorMappings.java b/src/main/java/build/buf/protovalidate/DescriptorMappings.java index d6f33497..20dd9490 100644 --- a/src/main/java/build/buf/protovalidate/DescriptorMappings.java +++ b/src/main/java/build/buf/protovalidate/DescriptorMappings.java @@ -21,7 +21,7 @@ import com.google.protobuf.Descriptors.OneofDescriptor; import java.util.HashMap; import java.util.Map; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import org.projectnessie.cel.checker.Decls; /** diff --git a/src/main/java/build/buf/protovalidate/EvaluatorBuilder.java b/src/main/java/build/buf/protovalidate/EvaluatorBuilder.java index 6b25545d..538bf2bd 100644 --- a/src/main/java/build/buf/protovalidate/EvaluatorBuilder.java +++ b/src/main/java/build/buf/protovalidate/EvaluatorBuilder.java @@ -22,7 +22,6 @@ import build.buf.validate.Ignore; import build.buf.validate.MessageConstraints; import build.buf.validate.OneofConstraints; -import com.google.common.collect.ImmutableMap; import com.google.protobuf.ByteString; import com.google.protobuf.Descriptors; import com.google.protobuf.Descriptors.Descriptor; @@ -35,8 +34,9 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import org.projectnessie.cel.Env; import org.projectnessie.cel.EnvOption; import org.projectnessie.cel.checker.Decls; @@ -47,7 +47,7 @@ class EvaluatorBuilder { FieldPathUtils.fieldPathElement( FieldConstraints.getDescriptor().findFieldByNumber(FieldConstraints.CEL_FIELD_NUMBER)); - private volatile ImmutableMap evaluatorCache = ImmutableMap.of(); + private volatile Map evaluatorCache = Collections.emptyMap(); private final Env env; private final boolean disableLazy; @@ -98,7 +98,7 @@ private Evaluator build(Descriptor desc) throws CompilationException { return eval; } // Rebuild cache with this descriptor (and any of its dependencies). - ImmutableMap updatedCache = + Map updatedCache = new DescriptorCacheBuilder(env, constraints, evaluatorCache).build(desc); evaluatorCache = updatedCache; eval = updatedCache.get(desc); @@ -117,9 +117,7 @@ private static class DescriptorCacheBuilder { private final HashMap cache; private DescriptorCacheBuilder( - Env env, - ConstraintCache constraintCache, - ImmutableMap previousCache) { + Env env, ConstraintCache constraintCache, Map previousCache) { this.env = Objects.requireNonNull(env, "env"); this.constraintCache = Objects.requireNonNull(constraintCache, "constraintCache"); this.cache = new HashMap<>(previousCache); @@ -130,13 +128,13 @@ private DescriptorCacheBuilder( * references). * * @param descriptor Descriptor used to build the cache. - * @return Immutable map of descriptors to evaluators. + * @return Unmodifiable map of descriptors to evaluators. * @throws CompilationException If an error occurs compiling a constraint on the cache. */ - public ImmutableMap build(Descriptor descriptor) + public Map build(Descriptor descriptor) throws CompilationException { createMessageEvaluator(descriptor); - return ImmutableMap.copyOf(cache); + return Collections.unmodifiableMap(cache); } private MessageEvaluator createMessageEvaluator(Descriptor desc) throws CompilationException { @@ -234,12 +232,10 @@ private FieldEvaluator buildField( return fieldEvaluator; } - @SuppressWarnings("deprecation") private boolean shouldSkip(FieldConstraints constraints) { return constraints.getIgnore() == Ignore.IGNORE_ALWAYS; } - @SuppressWarnings("deprecation") private static boolean shouldIgnoreEmpty(FieldConstraints constraints) { return constraints.getIgnore() == Ignore.IGNORE_IF_UNPOPULATED || constraints.getIgnore() == Ignore.IGNORE_IF_DEFAULT_VALUE; diff --git a/src/main/java/build/buf/protovalidate/FieldEvaluator.java b/src/main/java/build/buf/protovalidate/FieldEvaluator.java index c8069a67..3d4e23b7 100644 --- a/src/main/java/build/buf/protovalidate/FieldEvaluator.java +++ b/src/main/java/build/buf/protovalidate/FieldEvaluator.java @@ -22,7 +22,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** Performs validation on a single message field, defined by its descriptor. */ class FieldEvaluator implements Evaluator { diff --git a/src/main/java/build/buf/protovalidate/FieldPathUtils.java b/src/main/java/build/buf/protovalidate/FieldPathUtils.java index aca207be..b07664e8 100644 --- a/src/main/java/build/buf/protovalidate/FieldPathUtils.java +++ b/src/main/java/build/buf/protovalidate/FieldPathUtils.java @@ -18,7 +18,7 @@ import build.buf.validate.FieldPathElement; import com.google.protobuf.Descriptors; import java.util.List; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** Utility class for manipulating error paths in violations. */ final class FieldPathUtils { diff --git a/src/main/java/build/buf/protovalidate/Ipv6.java b/src/main/java/build/buf/protovalidate/Ipv6.java index 239cd894..7e036292 100644 --- a/src/main/java/build/buf/protovalidate/Ipv6.java +++ b/src/main/java/build/buf/protovalidate/Ipv6.java @@ -16,7 +16,7 @@ import java.util.ArrayList; import java.util.List; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Ipv6 is a class used to parse a given string to determine if it is an IPv6 address or address diff --git a/src/main/java/build/buf/protovalidate/MessageValue.java b/src/main/java/build/buf/protovalidate/MessageValue.java index 9ec965d7..de57f26d 100644 --- a/src/main/java/build/buf/protovalidate/MessageValue.java +++ b/src/main/java/build/buf/protovalidate/MessageValue.java @@ -19,7 +19,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** The {@link Value} type that contains a {@link com.google.protobuf.Message}. */ final class MessageValue implements Value { @@ -37,7 +37,7 @@ public MessageValue(Message value) { } @Override - public @Nullable Descriptors.FieldDescriptor fieldDescriptor() { + public Descriptors.@Nullable FieldDescriptor fieldDescriptor() { return null; } diff --git a/src/main/java/build/buf/protovalidate/NowVariable.java b/src/main/java/build/buf/protovalidate/NowVariable.java index ab021979..97953460 100644 --- a/src/main/java/build/buf/protovalidate/NowVariable.java +++ b/src/main/java/build/buf/protovalidate/NowVariable.java @@ -15,7 +15,7 @@ package build.buf.protovalidate; import java.time.Instant; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import org.projectnessie.cel.common.types.TimestampT; import org.projectnessie.cel.interpreter.Activation; import org.projectnessie.cel.interpreter.ResolvedValue; diff --git a/src/main/java/build/buf/protovalidate/ObjectValue.java b/src/main/java/build/buf/protovalidate/ObjectValue.java index 1c897220..dcd2fa9b 100644 --- a/src/main/java/build/buf/protovalidate/ObjectValue.java +++ b/src/main/java/build/buf/protovalidate/ObjectValue.java @@ -22,7 +22,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import org.projectnessie.cel.common.ULong; /** The {@link Value} type that contains a field descriptor and its value. */ diff --git a/src/main/java/build/buf/protovalidate/Value.java b/src/main/java/build/buf/protovalidate/Value.java index 5aa920f2..3d9ae6a7 100644 --- a/src/main/java/build/buf/protovalidate/Value.java +++ b/src/main/java/build/buf/protovalidate/Value.java @@ -18,7 +18,7 @@ import com.google.protobuf.Message; import java.util.List; import java.util.Map; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@link Value} is a wrapper around a protobuf value that provides helper methods for accessing the @@ -31,8 +31,7 @@ interface Value { * @return The underlying {@link Descriptors.FieldDescriptor}. null if the underlying value is not * a message field. */ - @Nullable - Descriptors.FieldDescriptor fieldDescriptor(); + Descriptors.@Nullable FieldDescriptor fieldDescriptor(); /** * Get the underlying value as a {@link Message} type. @@ -40,8 +39,7 @@ interface Value { * @return The underlying {@link Message} value. null if the underlying value is not a {@link * Message} type. */ - @Nullable - Message messageValue(); + @Nullable Message messageValue(); /** * Get the underlying value and cast it to the class type. diff --git a/src/main/java/build/buf/protovalidate/ValueEvaluator.java b/src/main/java/build/buf/protovalidate/ValueEvaluator.java index 8b0c0035..44af2d91 100644 --- a/src/main/java/build/buf/protovalidate/ValueEvaluator.java +++ b/src/main/java/build/buf/protovalidate/ValueEvaluator.java @@ -20,7 +20,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@link ValueEvaluator} performs validation on any concrete value contained within a singular @@ -28,7 +28,7 @@ */ class ValueEvaluator implements Evaluator { /** The {@link Descriptors.FieldDescriptor} targeted by this evaluator */ - @Nullable private final Descriptors.FieldDescriptor descriptor; + private final Descriptors.@Nullable FieldDescriptor descriptor; /** The nested rule path that this value evaluator is for */ @Nullable private final FieldPath nestedRule; @@ -46,12 +46,12 @@ class ValueEvaluator implements Evaluator { private boolean ignoreEmpty; /** Constructs a {@link ValueEvaluator}. */ - ValueEvaluator(@Nullable Descriptors.FieldDescriptor descriptor, @Nullable FieldPath nestedRule) { + ValueEvaluator(Descriptors.@Nullable FieldDescriptor descriptor, @Nullable FieldPath nestedRule) { this.descriptor = descriptor; this.nestedRule = nestedRule; } - public @Nullable Descriptors.FieldDescriptor getDescriptor() { + public Descriptors.@Nullable FieldDescriptor getDescriptor() { return descriptor; } diff --git a/src/main/java/build/buf/protovalidate/Variable.java b/src/main/java/build/buf/protovalidate/Variable.java index e026ef6e..7f9830e3 100644 --- a/src/main/java/build/buf/protovalidate/Variable.java +++ b/src/main/java/build/buf/protovalidate/Variable.java @@ -14,7 +14,7 @@ package build.buf.protovalidate; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import org.projectnessie.cel.interpreter.Activation; import org.projectnessie.cel.interpreter.ResolvedValue; diff --git a/src/main/java/build/buf/protovalidate/Violation.java b/src/main/java/build/buf/protovalidate/Violation.java index b510ec00..ab91edaa 100644 --- a/src/main/java/build/buf/protovalidate/Violation.java +++ b/src/main/java/build/buf/protovalidate/Violation.java @@ -15,7 +15,7 @@ package build.buf.protovalidate; import com.google.protobuf.Descriptors; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@link Violation} provides all of the collected information about an individual constraint @@ -29,8 +29,7 @@ interface FieldValue { * * @return The value of the protobuf field. */ - @Nullable - Object getValue(); + @Nullable Object getValue(); /** * Gets the field descriptor of the field this value is from. @@ -52,14 +51,12 @@ interface FieldValue { * * @return Value of the field associated with the violation, or null if there is none. */ - @Nullable - FieldValue getFieldValue(); + @Nullable FieldValue getFieldValue(); /** * Gets the value of the rule this violation pertains to, or null if there is none. * * @return Value of the rule associated with the violation, or null if there is none. */ - @Nullable - FieldValue getRuleValue(); + @Nullable FieldValue getRuleValue(); } diff --git a/src/test/java/build/buf/protovalidate/CustomOverloadTest.java b/src/test/java/build/buf/protovalidate/CustomOverloadTest.java index 24deb7ec..a4c7a4d1 100644 --- a/src/test/java/build/buf/protovalidate/CustomOverloadTest.java +++ b/src/test/java/build/buf/protovalidate/CustomOverloadTest.java @@ -17,10 +17,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import java.util.List; -import java.util.Map; +import java.util.Arrays; +import java.util.Collections; import org.junit.jupiter.api.Test; import org.projectnessie.cel.Ast; import org.projectnessie.cel.Env; @@ -36,28 +34,20 @@ public class CustomOverloadTest { @Test public void testIsInf() { - Map testCases = - ImmutableMap.builder() - .put("0.0.isInf()", false) - .put("(1.0/0.0).isInf()", true) - .put("(1.0/0.0).isInf(0)", true) - .put("(1.0/0.0).isInf(1)", true) - .put("(1.0/0.0).isInf(-1)", false) - .put("(-1.0/0.0).isInf()", true) - .put("(-1.0/0.0).isInf(0)", true) - .put("(-1.0/0.0).isInf(1)", false) - .put("(-1.0/0.0).isInf(-1)", true) - .build(); - for (Map.Entry testCase : testCases.entrySet()) { - Program.EvalResult result = eval(testCase.getKey()); - assertThat(result.getVal().booleanValue()).isEqualTo(testCase.getValue()); - } + assertThat(evalToBool("0.0.isInf()")).isFalse(); + assertThat(evalToBool("(1.0/0.0).isInf()")).isTrue(); + assertThat(evalToBool("(1.0/0.0).isInf(0)")).isTrue(); + assertThat(evalToBool("(1.0/0.0).isInf(1)")).isTrue(); + assertThat(evalToBool("(1.0/0.0).isInf(-1)")).isFalse(); + assertThat(evalToBool("(-1.0/0.0).isInf()")).isTrue(); + assertThat(evalToBool("(-1.0/0.0).isInf(0)")).isTrue(); + assertThat(evalToBool("(-1.0/0.0).isInf(1)")).isFalse(); + assertThat(evalToBool("(-1.0/0.0).isInf(-1)")).isTrue(); } @Test public void testIsInfUnsupported() { - List testCases = ImmutableList.of("'abc'.isInf()", "0.0.isInf('abc')"); - for (String testCase : testCases) { + for (String testCase : Arrays.asList("'abc'.isInf()", "0.0.isInf('abc')")) { Val val = eval(testCase).getVal(); assertThat(Err.isError(val)).isTrue(); assertThatThrownBy(() -> val.convertToNative(Exception.class)) @@ -67,22 +57,14 @@ public void testIsInfUnsupported() { @Test public void testIsNan() { - Map testCases = - ImmutableMap.builder() - .put("0.0.isNan()", false) - .put("(0.0/0.0).isNan()", true) - .put("(1.0/0.0).isNan()", false) - .build(); - for (Map.Entry testCase : testCases.entrySet()) { - Program.EvalResult result = eval(testCase.getKey()); - assertThat(result.getVal().booleanValue()).isEqualTo(testCase.getValue()); - } + assertThat(evalToBool("0.0.isNan()")).isFalse(); + assertThat(evalToBool("(0.0/0.0).isNan()")).isTrue(); + assertThat(evalToBool("(1.0/0.0).isNan()")).isFalse(); } @Test public void testIsNanUnsupported() { - List testCases = ImmutableList.of("'foo'.isNan()"); - for (String testCase : testCases) { + for (String testCase : Collections.singletonList("'foo'.isNan()")) { Val val = eval(testCase).getVal(); assertThat(Err.isError(val)).isTrue(); assertThatThrownBy(() -> val.convertToNative(Exception.class)) @@ -92,37 +74,29 @@ public void testIsNanUnsupported() { @Test public void testUnique() { - Map testCases = - ImmutableMap.builder() - .put("[].unique()", true) - .put("[true].unique()", true) - .put("[true, false].unique()", true) - .put("[true, true].unique()", false) - .put("[1, 2, 3].unique()", true) - .put("[1, 2, 1].unique()", false) - .put("[1u, 2u, 3u].unique()", true) - .put("[1u, 2u, 2u].unique()", false) - .put("[1.0, 2.0, 3.0].unique()", true) - .put("[3.0,2.0,3.0].unique()", false) - .put("['abc', 'def'].unique()", true) - .put("['abc', 'abc'].unique()", false) - .put("[b'abc', b'123'].unique()", true) - .put("[b'123', b'123'].unique()", false) - // Previously, the unique() method returned false here as both bytes were converted - // to UTF-8. Since both contain invalid UTF-8, this would lead to them treated as equal - // because they'd have the same substitution character. - .put("[b'\\xFF', b'\\xFE'].unique()", true) - .build(); - for (Map.Entry testCase : testCases.entrySet()) { - Program.EvalResult result = eval(testCase.getKey()); - assertThat(result.getVal().booleanValue()).isEqualTo(testCase.getValue()); - } + assertThat(evalToBool("[].unique()")).isTrue(); + assertThat(evalToBool("[true].unique()")).isTrue(); + assertThat(evalToBool("[true, false].unique()")).isTrue(); + assertThat(evalToBool("[true, true].unique()")).isFalse(); + assertThat(evalToBool("[1, 2, 3].unique()")).isTrue(); + assertThat(evalToBool("[1, 2, 1].unique()")).isFalse(); + assertThat(evalToBool("[1u, 2u, 3u].unique()")).isTrue(); + assertThat(evalToBool("[1u, 2u, 2u].unique()")).isFalse(); + assertThat(evalToBool("[1.0, 2.0, 3.0].unique()")).isTrue(); + assertThat(evalToBool("[3.0,2.0,3.0].unique()")).isFalse(); + assertThat(evalToBool("['abc', 'def'].unique()")).isTrue(); + assertThat(evalToBool("['abc', 'abc'].unique()")).isFalse(); + assertThat(evalToBool("[b'abc', b'123'].unique()")).isTrue(); + assertThat(evalToBool("[b'123', b'123'].unique()")).isFalse(); + // Previously, the unique() method returned false here as both bytes were converted + // to UTF-8. Since both contain invalid UTF-8, this would lead to them treated as equal + // because they'd have the same substitution character. + assertThat(evalToBool("[b'\\xFF', b'\\xFE'].unique()")).isTrue(); } @Test public void testUniqueUnsupported() { - List testCases = ImmutableList.of("1.unique()"); - for (String testCase : testCases) { + for (String testCase : Collections.singletonList("1.unique()")) { Program.EvalResult result = eval(testCase); Val val = result.getVal(); assertThat(Err.isError(val)).isTrue(); @@ -133,44 +107,40 @@ public void testUniqueUnsupported() { @Test public void testIsIpPrefix() { - Map testCases = - ImmutableMap.builder() - .put("'1.2.3.0/24'.isIpPrefix()", true) - .put("'1.2.3.4/24'.isIpPrefix()", true) - .put("'1.2.3.0/24'.isIpPrefix(true)", true) - .put("'1.2.3.4/24'.isIpPrefix(true)", false) - .put("'fd7a:115c:a1e0:ab12:4843:cd96:626b:4000/118'.isIpPrefix()", true) - .put("'fd7a:115c:a1e0:ab12:4843:cd96:626b:430b/118'.isIpPrefix()", true) - .put("'fd7a:115c:a1e0:ab12:4843:cd96:626b:4000/118'.isIpPrefix(true)", true) - .put("'fd7a:115c:a1e0:ab12:4843:cd96:626b:430b/118'.isIpPrefix(true)", false) - .put("'1.2.3.4'.isIpPrefix()", false) - .put("'fd7a:115c:a1e0:ab12:4843:cd96:626b:430b'.isIpPrefix()", false) - .put("'1.2.3.0/24'.isIpPrefix(4)", true) - .put("'1.2.3.4/24'.isIpPrefix(4)", true) - .put("'1.2.3.0/24'.isIpPrefix(4,true)", true) - .put("'1.2.3.4/24'.isIpPrefix(4,true)", false) - .put("'fd7a:115c:a1e0:ab12:4843:cd96:626b:4000/118'.isIpPrefix(4)", false) - .put("'fd7a:115c:a1e0:ab12:4843:cd96:626b:4000/118'.isIpPrefix(6)", true) - .put("'fd7a:115c:a1e0:ab12:4843:cd96:626b:430b/118'.isIpPrefix(6)", true) - .put("'fd7a:115c:a1e0:ab12:4843:cd96:626b:4000/118'.isIpPrefix(6,true)", true) - .put("'fd7a:115c:a1e0:ab12:4843:cd96:626b:430b/118'.isIpPrefix(6,true)", false) - .put("'1.2.3.0/24'.isIpPrefix(6)", false) - .build(); - for (Map.Entry testCase : testCases.entrySet()) { - Program.EvalResult result = eval(testCase.getKey()); - assertThat(result.getVal().booleanValue()).isEqualTo(testCase.getValue()); - } + assertThat(evalToBool("'1.2.3.0/24'.isIpPrefix()")).isTrue(); + assertThat(evalToBool("'1.2.3.4/24'.isIpPrefix()")).isTrue(); + assertThat(evalToBool("'1.2.3.0/24'.isIpPrefix(true)")).isTrue(); + assertThat(evalToBool("'1.2.3.4/24'.isIpPrefix(true)")).isFalse(); + assertThat(evalToBool("'fd7a:115c:a1e0:ab12:4843:cd96:626b:4000/118'.isIpPrefix()")).isTrue(); + assertThat(evalToBool("'fd7a:115c:a1e0:ab12:4843:cd96:626b:430b/118'.isIpPrefix()")).isTrue(); + assertThat(evalToBool("'fd7a:115c:a1e0:ab12:4843:cd96:626b:4000/118'.isIpPrefix(true)")) + .isTrue(); + assertThat(evalToBool("'fd7a:115c:a1e0:ab12:4843:cd96:626b:430b/118'.isIpPrefix(true)")) + .isFalse(); + assertThat(evalToBool("'1.2.3.4'.isIpPrefix()")).isFalse(); + assertThat(evalToBool("'fd7a:115c:a1e0:ab12:4843:cd96:626b:430b'.isIpPrefix()")).isFalse(); + assertThat(evalToBool("'1.2.3.0/24'.isIpPrefix(4)")).isTrue(); + assertThat(evalToBool("'1.2.3.4/24'.isIpPrefix(4)")).isTrue(); + assertThat(evalToBool("'1.2.3.0/24'.isIpPrefix(4,true)")).isTrue(); + assertThat(evalToBool("'1.2.3.4/24'.isIpPrefix(4,true)")).isFalse(); + assertThat(evalToBool("'fd7a:115c:a1e0:ab12:4843:cd96:626b:4000/118'.isIpPrefix(4)")).isFalse(); + assertThat(evalToBool("'fd7a:115c:a1e0:ab12:4843:cd96:626b:4000/118'.isIpPrefix(6)")).isTrue(); + assertThat(evalToBool("'fd7a:115c:a1e0:ab12:4843:cd96:626b:430b/118'.isIpPrefix(6)")).isTrue(); + assertThat(evalToBool("'fd7a:115c:a1e0:ab12:4843:cd96:626b:4000/118'.isIpPrefix(6,true)")) + .isTrue(); + assertThat(evalToBool("'fd7a:115c:a1e0:ab12:4843:cd96:626b:430b/118'.isIpPrefix(6,true)")) + .isFalse(); + assertThat(evalToBool("'1.2.3.0/24'.isIpPrefix(6)")).isFalse(); } @Test public void testIsIpPrefixUnsupported() { - List testCases = - ImmutableList.of( + for (String testCase : + Arrays.asList( "1.isIpPrefix()", "'1.2.3.0/24'.isIpPrefix('foo')", "'1.2.3.0/24'.isIpPrefix(4,'foo')", - "'1.2.3.0/24'.isIpPrefix('foo',true)"); - for (String testCase : testCases) { + "'1.2.3.0/24'.isIpPrefix('foo',true)")) { Program.EvalResult result = eval(testCase); Val val = result.getVal(); assertThat(Err.isError(val)).isTrue(); @@ -181,38 +151,32 @@ public void testIsIpPrefixUnsupported() { @Test public void testIsHostname() { - Map testCases = - ImmutableMap.builder() - .put("'example.com'.isHostname()", true) - .put("'example.123'.isHostname()", false) - .build(); - for (Map.Entry testCase : testCases.entrySet()) { - Program.EvalResult result = eval(testCase.getKey()); - assertThat(result.getVal().booleanValue()) - .as( - "expected %s=%s, got=%s", - testCase.getKey(), testCase.getValue(), !testCase.getValue()) - .isEqualTo(testCase.getValue()); - } + assertThat(evalToBool("'example.com'.isHostname()")).isTrue(); + assertThat(evalToBool("'example.123'.isHostname()")).isFalse(); } @Test public void testIsEmail() { - Map testCases = - ImmutableMap.builder() - .put("'foo@example.com'.isEmail()", true) - .put("''.isEmail()", false) - .put("' foo@example.com'.isEmail()", false) - .put("'foo@example.com '.isEmail()", false) - .build(); - for (Map.Entry testCase : testCases.entrySet()) { - Program.EvalResult result = eval(testCase.getKey()); - assertThat(result.getVal().booleanValue()) - .as( - "expected %s=%s, got=%s", - testCase.getKey(), testCase.getValue(), !testCase.getValue()) - .isEqualTo(testCase.getValue()); - } + assertThat(evalToBool("'foo@example.com'.isEmail()")).isTrue(); + assertThat(evalToBool("''.isEmail()")).isFalse(); + assertThat(evalToBool("' foo@example.com'.isEmail()")).isFalse(); + assertThat(evalToBool("'foo@example.com '.isEmail()")).isFalse(); + } + + @Test + public void testBytesContains() { + assertThat(evalToBool("bytes('12345').contains(bytes(''))")).isTrue(); + assertThat(evalToBool("bytes('12345').contains(bytes('1'))")).isTrue(); + assertThat(evalToBool("bytes('12345').contains(bytes('5'))")).isTrue(); + assertThat(evalToBool("bytes('12345').contains(bytes('123'))")).isTrue(); + assertThat(evalToBool("bytes('12345').contains(bytes('234'))")).isTrue(); + assertThat(evalToBool("bytes('12345').contains(bytes('345'))")).isTrue(); + assertThat(evalToBool("bytes('12345').contains(bytes('12345'))")).isTrue(); + + assertThat(evalToBool("bytes('12345').contains(bytes('6'))")).isFalse(); + assertThat(evalToBool("bytes('12345').contains(bytes('13'))")).isFalse(); + assertThat(evalToBool("bytes('12345').contains(bytes('35'))")).isFalse(); + assertThat(evalToBool("bytes('12345').contains(bytes('123456'))")).isFalse(); } private Program.EvalResult eval(String source) { @@ -225,4 +189,9 @@ private Program.EvalResult eval(String source, Object vars) { Ast ast = parsed.getAst(); return env.program(ast).eval(vars); } + + private boolean evalToBool(String source) { + Program.EvalResult result = eval(source); + return result.getVal().booleanValue(); + } }