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
33 changes: 15 additions & 18 deletions src/main/java/build/buf/protovalidate/ValidatorImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import dev.cel.bundle.Cel;
import dev.cel.bundle.CelFactory;
import dev.cel.common.CelOptions;
import dev.cel.extensions.CelExtensions;
import java.util.ArrayList;
import java.util.List;

Expand All @@ -35,29 +36,13 @@ final class ValidatorImpl implements Validator {
private final boolean failFast;

ValidatorImpl(Config config) {
ValidateLibrary validateLibrary = new ValidateLibrary();
Cel cel =
CelFactory.standardCelBuilder()
.addCompilerLibraries(validateLibrary)
.addRuntimeLibraries(validateLibrary)
.setOptions(
CelOptions.DEFAULT.toBuilder().evaluateCanonicalTypesToNativeValues(true).build())
.build();
this.evaluatorBuilder = new EvaluatorBuilder(cel, config);
this.evaluatorBuilder = new EvaluatorBuilder(newCel(), config);
this.failFast = config.isFailFast();
}

ValidatorImpl(Config config, List<Descriptor> descriptors, boolean disableLazy)
throws CompilationException {
ValidateLibrary validateLibrary = new ValidateLibrary();
Cel cel =
CelFactory.standardCelBuilder()
.addCompilerLibraries(validateLibrary)
.addRuntimeLibraries(validateLibrary)
.setOptions(
CelOptions.DEFAULT.toBuilder().evaluateCanonicalTypesToNativeValues(true).build())
.build();
this.evaluatorBuilder = new EvaluatorBuilder(cel, config, descriptors, disableLazy);
this.evaluatorBuilder = new EvaluatorBuilder(newCel(), config, descriptors, disableLazy);
this.failFast = config.isFailFast();
}

Expand All @@ -78,4 +63,16 @@ public ValidationResult validate(Message msg) throws ValidationException {
}
return new ValidationResult(violations);
}

private static Cel newCel() {
ValidateLibrary validateLibrary = new ValidateLibrary();
// NOTE: CelExtensions.strings() does not implement string.reverse() or strings.quote() which
// are available in protovalidate-go.
return CelFactory.standardCelBuilder()
.addCompilerLibraries(validateLibrary, CelExtensions.strings())
.addRuntimeLibraries(validateLibrary, CelExtensions.strings())
.setOptions(
CelOptions.DEFAULT.toBuilder().evaluateCanonicalTypesToNativeValues(true).build())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2023-2025 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package build.buf.protovalidate;

import static org.assertj.core.api.Assertions.assertThat;

import build.buf.protovalidate.exceptions.ValidationException;
import build.buf.validate.Violation;
import com.example.noimports.validationtest.ExampleStringExtensions;
import org.junit.jupiter.api.Test;

public class ValidatorStringExtensionsTest {
@Test
public void testStringExtensionsPathMatchesParent() throws ValidationException {
// Test for https://github.com/bufbuild/protovalidate-java/issues/438.
Validator validator = ValidatorFactory.newBuilder().build();

ExampleStringExtensions validMsg =
ExampleStringExtensions.newBuilder().setParent("foo/bar").setPath("foo/bar/baz").build();
ValidationResult result = validator.validate(validMsg);
assertThat(result.isSuccess()).isTrue();

ExampleStringExtensions invalidMsg =
ExampleStringExtensions.newBuilder().setParent("foo/bar").setPath("foo/other/baz").build();
result = validator.validate(invalidMsg);
assertThat(result.isSuccess()).isFalse();
Violation expectedViolation =
Violation.newBuilder()
.setRuleId("path_matches_parent")
.setMessage("path must be a child of parent")
.build();
assertThat(result.toProto().getViolationsList()).containsExactly(expectedViolation);
}
}
13 changes: 13 additions & 0 deletions src/test/resources/proto/validationtest/validationtest.proto
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,19 @@ message ExampleImportMessageInMap {
map<int64, ExampleImportedMessage> imported_submessage = 1;
}

message ExampleStringExtensions {
string parent = 1;
string path = 2;

option (buf.validate.message).cel = {
id: "path_matches_parent"
message: "path must be a child of parent"
expression:
"this.path.lastIndexOf('/') >= 0 && "
"this.path.substring(0, this.path.lastIndexOf('/')) == this.parent"
};
}

message ExampleImportMessageInMapFieldRule {
ExampleImportMessageInMap message_with_import = 1 [
(buf.validate.field).cel = {
Expand Down
Loading