Skip to content

Commit 8533e9e

Browse files
smaye81timostammpkwarrendependabot[bot]
authored
Upgrade validation logic (#258)
This includes upgrades to validation logic for: - URIs, URI references - IP addresses and prefixes (v4 and v6) - Hostname, Port - Email Address The validation here adheres to the defined RFC standards for each entity and passes conformance for all new conformance tests defined in: - bufbuild/protovalidate#320 - bufbuild/protovalidate#341 --------- Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: Timo Stamm <[email protected]> Co-authored-by: Philip K. Warren <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1 parent 43fb8f6 commit 8533e9e

27 files changed

+1872
-594
lines changed

build.gradle.kts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -257,12 +257,10 @@ mavenPublishing {
257257

258258
dependencies {
259259
annotationProcessor(libs.nullaway)
260+
api(libs.jspecify)
260261
api(libs.protobuf.java)
261262
implementation(enforcedPlatform(libs.cel))
262263
implementation(libs.cel.core)
263-
implementation(libs.guava)
264-
implementation(libs.ipaddress)
265-
implementation(libs.jakarta.mail.api)
266264

267265
buf("build.buf:buf:${libs.versions.buf.get()}:${osdetector.classifier}@exe")
268266

@@ -271,5 +269,5 @@ dependencies {
271269
testImplementation("org.junit.jupiter:junit-jupiter")
272270
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
273271

274-
errorprone(libs.errorprone)
272+
errorprone(libs.errorprone.core)
275273
}

conformance/build.gradle.kts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ plugins {
1010
alias(libs.plugins.osdetector)
1111
}
1212

13+
// Conformance tests aren't bound by lowest common library version.
14+
java {
15+
sourceCompatibility = JavaVersion.VERSION_21
16+
targetCompatibility = JavaVersion.VERSION_21
17+
}
18+
1319
val buf: Configuration by configurations.creating
1420

1521
tasks.register("configureBuf") {
@@ -116,7 +122,7 @@ configure<SpotlessExtension> {
116122

117123
dependencies {
118124
implementation(project(":"))
119-
implementation(libs.guava)
125+
implementation(libs.errorprone.annotations)
120126
implementation(libs.protobuf.java)
121127

122128
implementation(libs.assertj)
@@ -127,5 +133,5 @@ dependencies {
127133
testImplementation("org.junit.jupiter:junit-jupiter")
128134
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
129135

130-
errorprone(libs.errorprone)
136+
errorprone(libs.errorprone.core)
131137
}

conformance/expected-failures.yaml

Lines changed: 0 additions & 218 deletions
Original file line numberDiff line numberDiff line change
@@ -107,224 +107,6 @@ custom_constraints:
107107
#ERROR: <input>:1:1: expression of type 'int' cannot be range of a comprehension (must be list, map, or dynamic)
108108
# | this.all(e, e == 1)
109109
# | ^
110-
library/is_email:
111-
- invalid/non_ascii
112-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsEmail]:{val:"µ@example.com"}
113-
# want: validation error (1 violation)
114-
# 1. constraint_id: "library.is_email"
115-
# got: valid
116-
- invalid/quoted-string/a
117-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsEmail]:{val:"\"foo bar\"@example.com"}
118-
# want: validation error (1 violation)
119-
# 1. constraint_id: "library.is_email"
120-
# got: valid
121-
- invalid/quoted-string/b
122-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsEmail]:{val:"\"foo..bar\"@example.com"}
123-
# want: validation error (1 violation)
124-
# 1. constraint_id: "library.is_email"
125-
# got: valid
126-
- valid/empty_atext
127-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsEmail]:{val:"[email protected]"}
128-
# want: valid
129-
# got: validation error (1 violation)
130-
# 1. constraint_id: "library.is_email"
131-
# message: ""
132-
- valid/exhaust_atext
133-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsEmail]:{val:"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&'*+-/=?^_`{|}[email protected]"}
134-
# want: valid
135-
# got: validation error (1 violation)
136-
# 1. constraint_id: "library.is_email"
137-
# message: ""
138-
- valid/label_all_digits
139-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsEmail]:{val:"[email protected]"}
140-
# want: valid
141-
# got: validation error (1 violation)
142-
# 1. constraint_id: "library.is_email"
143-
# message: ""
144-
- valid/multiple_empty_atext
145-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsEmail]:{val:"[email protected]"}
146-
# want: valid
147-
# got: validation error (1 violation)
148-
# 1. constraint_id: "library.is_email"
149-
# message: ""
150-
library/is_host_and_port:
151-
- port_required/false/invalid/ipv6_zone-id_too_short
152-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsHostAndPort]:{val:"[::1%]"}
153-
# want: validation error (1 violation)
154-
# 1. constraint_id: "library.is_host_and_port"
155-
# got: valid
156-
- port_required/false/invalid/port_number_sign
157-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsHostAndPort]:{val:"example.com:+0"}
158-
# want: validation error (1 violation)
159-
# 1. constraint_id: "library.is_host_and_port"
160-
# got: valid
161-
- port_required/false/valid/ipv6_embedded_ipv4
162-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsHostAndPort]:{val:"[0:0:0:0:0:ffff:192.1.56.10]"}
163-
# want: valid
164-
# got: validation error (1 violation)
165-
# 1. constraint_id: "library.is_host_and_port"
166-
# message: ""
167-
- port_required/false/valid/ipv6_with_zone-id
168-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsHostAndPort]:{val:"[::1%foo]"}
169-
# want: valid
170-
# got: validation error (1 violation)
171-
# 1. constraint_id: "library.is_host_and_port"
172-
# message: ""
173-
- port_required/false/valid/ipv6_zone-id_any_non_null_character
174-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsHostAndPort]:{val:"[::1%% :x\x1f]"}
175-
# want: valid
176-
# got: validation error (1 violation)
177-
# 1. constraint_id: "library.is_host_and_port"
178-
# message: ""
179-
- port_required/true/invalid/port_number_sign
180-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsHostAndPort]:{val:"example.com:+0" port_required:true}
181-
# want: validation error (1 violation)
182-
# 1. constraint_id: "library.is_host_and_port"
183-
# got: valid
184-
library/is_ip:
185-
- version/omitted/invalid/ipv6_zone-id
186-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsIp]:{val:"::1%"}
187-
# want: validation error (1 violation)
188-
# 1. constraint_id: "library.is_ip"
189-
# got: valid
190-
- version/omitted/valid/ipv6_zone-id
191-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsIp]:{val:"::1%foo"}
192-
# want: valid
193-
# got: validation error (1 violation)
194-
# 1. constraint_id: "library.is_ip"
195-
# message: ""
196-
- version/omitted/valid/ipv6_zone-id_any_non_null_character
197-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsIp]:{val:"::1%% :x\x1f"}
198-
# want: valid
199-
# got: validation error (1 violation)
200-
# 1. constraint_id: "library.is_ip"
201-
# message: ""
202-
library/is_ip_prefix:
203-
- version/omitted/strict/omitted/invalid/ipv4_bad_leading_zero_in_prefix-length
204-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsIpPrefix]:{val:"192.168.1.0/024"}
205-
# want: validation error (1 violation)
206-
# 1. constraint_id: "library.is_ip_prefix"
207-
# got: valid
208-
- version/omitted/strict/omitted/invalid/ipv4_prefix_leading_space
209-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsIpPrefix]:{val:" 127.0.0.1/16"}
210-
# want: validation error (1 violation)
211-
# 1. constraint_id: "library.is_ip_prefix"
212-
# got: valid
213-
- version/omitted/strict/omitted/invalid/ipv4_prefix_trailing_space
214-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsIpPrefix]:{val:"127.0.0.1/16 "}
215-
# want: validation error (1 violation)
216-
# 1. constraint_id: "library.is_ip_prefix"
217-
# got: valid
218-
- version/omitted/strict/omitted/invalid/ipv6_bad_leading_zero_in_prefix-length
219-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsIpPrefix]:{val:"2001:0DB8:ABCD:0012:FFFF:FFFF:FFFF:FFFF/024"}
220-
# want: validation error (1 violation)
221-
# 1. constraint_id: "library.is_ip_prefix"
222-
# got: valid
223-
- version/omitted/strict/omitted/invalid/ipv6_prefix_leading_space
224-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsIpPrefix]:{val:" ::1/64"}
225-
# want: validation error (1 violation)
226-
# 1. constraint_id: "library.is_ip_prefix"
227-
# got: valid
228-
- version/omitted/strict/omitted/invalid/ipv6_prefix_trailing_space
229-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsIpPrefix]:{val:"::1/64 "}
230-
# want: validation error (1 violation)
231-
# 1. constraint_id: "library.is_ip_prefix"
232-
# got: valid
233-
- version/omitted/strict/omitted/invalid/ipv6_zone-id/a
234-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsIpPrefix]:{val:"::1%en1/64"}
235-
# want: validation error (1 violation)
236-
# 1. constraint_id: "library.is_ip_prefix"
237-
# got: valid
238-
library/is_uri:
239-
- invalid/host/c
240-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://foo@你好.com"}
241-
# want: validation error (1 violation)
242-
# 1. constraint_id: "library.is_uri"
243-
# got: valid
244-
- invalid/host_ipv6/a
245-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://2001:0db8:85a3:0000:0000:8a2e:0370:7334"}
246-
# want: validation error (1 violation)
247-
# 1. constraint_id: "library.is_uri"
248-
# got: valid
249-
- invalid/host_ipv6_zone-id_empty
250-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://[::1%25]"}
251-
# want: validation error (1 violation)
252-
# 1. constraint_id: "library.is_uri"
253-
# got: valid
254-
- invalid/host_ipv6_zone-id_unquoted
255-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://[::1%eth0]"}
256-
# want: validation error (1 violation)
257-
# 1. constraint_id: "library.is_uri"
258-
# got: valid
259-
- invalid/host_reg-name_pct-encoded_invalid_utf8
260-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://foo%c3x%96"}
261-
# want: validation error (1 violation)
262-
# 1. constraint_id: "library.is_uri"
263-
# got: valid
264-
- invalid/port/a
265-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com:8a"}
266-
# want: validation error (1 violation)
267-
# 1. constraint_id: "library.is_uri"
268-
# got: valid
269-
- invalid/port/b
270-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com:x"}
271-
# want: validation error (1 violation)
272-
# 1. constraint_id: "library.is_uri"
273-
# got: valid
274-
- invalid/userinfo_reserved_at
275-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://@@example.com"}
276-
# want: validation error (1 violation)
277-
# 1. constraint_id: "library.is_uri"
278-
# got: valid
279-
- valid/host_ipfuture_exhaust
280-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://[vF.-!$&'()*+,;=._~0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ]"}
281-
# want: valid
282-
# got: validation error (1 violation)
283-
# 1. constraint_id: "library.is_uri"
284-
# message: ""
285-
- valid/host_ipfuture_long
286-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://[v1234AF.x]"}
287-
# want: valid
288-
# got: validation error (1 violation)
289-
# 1. constraint_id: "library.is_uri"
290-
# message: ""
291-
- valid/host_ipfuture_short
292-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://[v1.x]"}
293-
# want: valid
294-
# got: validation error (1 violation)
295-
# 1. constraint_id: "library.is_uri"
296-
# message: ""
297-
- valid/host_ipv6_zone-id_pct-encoded_ascii
298-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://[::1%25foo%61%20%23]"}
299-
# want: valid
300-
# got: validation error (1 violation)
301-
# 1. constraint_id: "library.is_uri"
302-
# message: ""
303-
- valid/host_ipv6_zone-id_pct-encoded_utf8
304-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://[::1%25foo%c3%96]"}
305-
# want: valid
306-
# got: validation error (1 violation)
307-
# 1. constraint_id: "library.is_uri"
308-
# message: ""
309-
- valid/path-empty
310-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:"}
311-
# want: valid
312-
# got: validation error (1 violation)
313-
# 1. constraint_id: "library.is_uri"
314-
# message: ""
315-
library/is_uri_ref:
316-
- valid/empty_string
317-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{}
318-
# want: valid
319-
# got: validation error (1 violation)
320-
# 1. constraint_id: "library.is_uri_ref"
321-
# message: ""
322-
- valid/path-empty
323-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{}
324-
# want: valid
325-
# got: validation error (1 violation)
326-
# 1. constraint_id: "library.is_uri_ref"
327-
# message: ""
328110
standard_constraints/ignore:
329111
- proto/2023/map/ignore_always/invalid/populated
330112
# input: [type.googleapis.com/buf.validate.conformance.cases.EditionsMapIgnoreAlways]:{val:{key:1 value:1}}
6 KB
Binary file not shown.

conformance/src/main/java/build/buf/protovalidate/conformance/Main.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import build.buf.validate.conformance.harness.TestConformanceRequest;
2525
import build.buf.validate.conformance.harness.TestConformanceResponse;
2626
import build.buf.validate.conformance.harness.TestResult;
27-
import com.google.common.base.Splitter;
2827
import com.google.errorprone.annotations.FormatMethod;
2928
import com.google.protobuf.Any;
3029
import com.google.protobuf.ByteString;
@@ -34,7 +33,6 @@
3433
import com.google.protobuf.InvalidProtocolBufferException;
3534
import com.google.protobuf.TypeRegistry;
3635
import java.util.HashMap;
37-
import java.util.List;
3836
import java.util.Map;
3937

4038
public class Main {
@@ -84,8 +82,11 @@ static TestConformanceResponse testConformance(TestConformanceRequest request) {
8482
static TestResult testCase(
8583
Validator validator, Map<String, Descriptors.Descriptor> fileDescriptors, Any testCase)
8684
throws InvalidProtocolBufferException {
87-
List<String> urlParts = Splitter.on('/').limit(2).splitToList(testCase.getTypeUrl());
88-
String fullName = urlParts.get(urlParts.size() - 1);
85+
String fullName = testCase.getTypeUrl();
86+
int slash = fullName.indexOf('/');
87+
if (slash != -1) {
88+
fullName = fullName.substring(slash + 1);
89+
}
8990
Descriptors.Descriptor descriptor = fileDescriptors.get(fullName);
9091
if (descriptor == null) {
9192
return unexpectedErrorResult("Unable to find descriptor: %s", fullName);

gradle/libs.versions.toml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
assertj = "3.27.3"
33
buf = "1.52.1"
44
cel = "0.5.1"
5-
ipaddress = "5.5.1"
5+
error-prone = "2.38.0"
66
junit = "5.12.2"
77
maven-publish = "0.31.0"
88
# When updating, make sure to update versions in the following files to match and regenerate code with 'make generate'.
@@ -17,10 +17,9 @@ assertj = { module = "org.assertj:assertj-core", version.ref = "assertj" }
1717
buf = { module = "build.buf:buf", version.ref = "buf" }
1818
cel = { module = "org.projectnessie.cel:cel-bom", version.ref = "cel" }
1919
cel-core = { module = "org.projectnessie.cel:cel-core" }
20-
errorprone = { module = "com.google.errorprone:error_prone_core", version = "2.38.0" }
21-
guava = { module = "com.google.guava:guava", version = "33.4.0-jre" }
22-
ipaddress = { module = "com.github.seancfoley:ipaddress", version.ref = "ipaddress" }
23-
jakarta-mail-api = { module = "jakarta.mail:jakarta.mail-api", version = "2.1.3" }
20+
errorprone-annotations = { module = "com.google.errorprone:error_prone_annotations", version.ref = "error-prone" }
21+
errorprone-core = { module = "com.google.errorprone:error_prone_core", version.ref = "error-prone" }
22+
jspecify = { module ="org.jspecify:jspecify", version = "1.0.0" }
2423
junit-bom = { module = "org.junit:junit-bom", version.ref = "junit" }
2524
maven-plugin = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "maven-publish" }
2625
nullaway = { module = "com.uber.nullaway:nullaway", version = "0.12.6" }

src/main/java/build/buf/protovalidate/CelPrograms.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import build.buf.protovalidate.exceptions.ExecutionException;
1818
import java.util.ArrayList;
1919
import java.util.List;
20-
import javax.annotation.Nullable;
20+
import org.jspecify.annotations.Nullable;
2121

2222
/** Evaluator that executes a {@link CompiledProgram}. */
2323
class CelPrograms implements Evaluator {

src/main/java/build/buf/protovalidate/CompiledProgram.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
import build.buf.protovalidate.exceptions.ExecutionException;
1818
import build.buf.validate.FieldPath;
19-
import javax.annotation.Nullable;
19+
import org.jspecify.annotations.Nullable;
2020
import org.projectnessie.cel.Program;
2121
import org.projectnessie.cel.common.types.Err;
2222
import org.projectnessie.cel.common.types.ref.Val;
@@ -63,8 +63,7 @@ public CompiledProgram(
6363
* violations.
6464
* @throws ExecutionException If the evaluation of the CEL program fails with an error.
6565
*/
66-
@Nullable
67-
public ConstraintViolation.Builder eval(Value fieldValue, Variable bindings)
66+
public ConstraintViolation.@Nullable Builder eval(Value fieldValue, Variable bindings)
6867
throws ExecutionException {
6968
Program.EvalResult evalResult = program.eval(bindings);
7069
Val val = evalResult.getVal();

src/main/java/build/buf/protovalidate/ConstraintCache.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
import java.util.List;
3333
import java.util.Map;
3434
import java.util.concurrent.ConcurrentHashMap;
35-
import javax.annotation.Nullable;
35+
import org.jspecify.annotations.Nullable;
3636
import org.projectnessie.cel.Ast;
3737
import org.projectnessie.cel.Env;
3838
import org.projectnessie.cel.EnvOption;
@@ -199,7 +199,7 @@ public List<CompiledProgram> compile(
199199
return celRules;
200200
}
201201

202-
private @Nullable build.buf.validate.PredefinedConstraints getFieldConstraints(
202+
private build.buf.validate.@Nullable PredefinedConstraints getFieldConstraints(
203203
FieldDescriptor constraintFieldDesc) throws CompilationException {
204204
DescriptorProtos.FieldOptions options = constraintFieldDesc.getOptions();
205205
// If the protovalidate field option is unknown, reparse options using our extension registry.

src/main/java/build/buf/protovalidate/ConstraintViolation.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import java.util.Deque;
2424
import java.util.List;
2525
import java.util.Objects;
26-
import javax.annotation.Nullable;
26+
import org.jspecify.annotations.Nullable;
2727

2828
/**
2929
* {@link ConstraintViolation} contains all of the collected information about an individual

0 commit comments

Comments
 (0)