Skip to content

Commit

Permalink
Add more examples (#93)
Browse files Browse the repository at this point in the history
`option_*` cover most features from [the standard
options](https://buf.build/bufbuild/protovalidate/docs/main:buf.validate).

`cel_*` cover most features from [CEL's feature
list](https://github.com/google/cel-spec/blob/master/doc/langdef.md#list-of-standard-definitions).
Exception:
- From timestamp's `getFooFromBar` family, including `getDate`,
`getDayOfMonth`, `getDayOfYear`, `getFullYear`, `getHours`,
`getMilliseconds`, `getMinutes`, `getMonth` and `getSeconds`, only
`getDayOrWeek` is covered, but there is a comment mentioning all the
rest and a link to this feature list.

`cel_*` also cover [custom CEL functions and
overloads](https://github.com/bufbuild/protovalidate/blob/main/docs/cel.md#custom-variables-functions-and-overloads)
  • Loading branch information
oliversun9 authored Sep 19, 2023
1 parent 6760bb5 commit 4655215
Show file tree
Hide file tree
Showing 62 changed files with 1,991 additions and 62 deletions.
70 changes: 61 additions & 9 deletions examples/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -18,39 +18,91 @@ load("@rules_buf//buf:defs.bzl", "buf_lint_test")
proto_library(
name = "examples_proto",
srcs = [
"cel_assert_value_is_in_a_list.proto",
"cel_bytes_concatentation.proto",
"cel_bytes_contains.proto",
"cel_bytes_starts_with_ends_with.proto",
"cel_conditional_operator.proto",
"cel_duration_arithmetic.proto",
"cel_duration_from_string.proto",
"cel_enum_comparision.proto",
"cel_field_access.proto",
"cel_field_mask.proto",
"cel_field_presence.proto",
"cel_field_presence_nested.proto",
"cel_field_selection.proto",
"cel_map_key.proto",
"cel_infinity.proto",
"cel_list_concatenation.proto",
"cel_map_all.proto",
"cel_map_exists.proto",
"cel_map_exists_one.proto",
"cel_map_size.proto",
"cel_number_arithmetic.proto",
"cel_repeated_field_all.proto",
"cel_repeated_field_exists_one.proto",
"cel_repeated_field_filter_and_size.proto",
"cel_repeated_field_filter_and_count.proto",
"cel_repeated_field_unique.proto",
"cel_string_concatentation.proto",
"cel_string_contains.proto",
"cel_string_is_email.proto",
"cel_string_is_hostname.proto",
"cel_string_is_ip.proto",
"cel_string_is_uri.proto",
"cel_string_match_pattern.proto",
"cel_string_starts_with_ends_with.proto",
"cel_timestamp_comparsion.proto",
"cel_timestamp_get_attribute.proto",
"cel_timestamp_plus_duration.proto",
"cel_timestamp_subtraction.proto",
"cel_timestamp_to_day_of_week.proto",
"cel_type_conversion.proto",
"cel_value.proto",
"cel_wrapper_type.proto",
"option_any_type_allow_list.proto",
"option_any_type_ban_list.proto",
"option_bool.proto",
"option_bytes.proto",
"option_bytes_ban_values.proto",
"option_bytes_contains.proto",
"option_bytes_equal.proto",
"option_bytes_len.proto",
"option_bytes_pattern.proto",
"option_bytes_prefix_suffix.proto",
"option_duration_allow_values.proto",
"option_duration_disallow_values.proto",
"option_duration_equal.proto",
"option_duration_range.proto",
"option_enum_allow_values.proto",
"option_enum_disallow_values.proto",
"option_field_ignore_empty.proto",
"option_field_presence.proto",
"option_field_skip_validation.proto",
"option_map.proto",
"option_number.proto",
"option_message_disable_validation.proto",
"option_number_allow_values.proto",
"option_number_disallow_values.proto",
"option_number_equal.proto",
"option_number_finite_and_infinite.proto",
"option_number_range.proto",
"option_oneof.proto",
"option_repeated.proto",
"option_require_field_presence.proto",
"option_string.proto",
"option_wkt_any.proto",
"option_wkt_duration.proto",
"option_string_allow_values.proto",
"option_string_ban_values.proto",
"option_string_contains.proto",
"option_string_equal.proto",
"option_string_is_http_header.proto",
"option_string_len.proto",
"option_string_match_pattern.proto",
"option_string_prefix_suffix.proto",
"option_timestamp_range.proto",
"option_timestamp_relative_to_now.proto",
],
strip_import_prefix = "/examples",
visibility = ["//visibility:public"],
deps = [
"//proto/protovalidate/buf/validate:validate_proto",
"@com_google_protobuf//:any_proto",
"@com_google_protobuf//:duration_proto",
"@com_google_protobuf//:field_mask_proto",
"@com_google_protobuf//:struct_proto",
"@com_google_protobuf//:timestamp_proto",
"@com_google_protobuf//:wrappers_proto",
"@go_googleapis//google/type:postaladdress_proto",
Expand Down
42 changes: 42 additions & 0 deletions examples/cel_assert_value_is_in_a_list.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright 2023 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.

syntax = "proto3";

import "buf/validate/validate.proto";

service DivisionService {
// IsDivisible solves whether one number is divisible by another.
rpc IsDivisible(IsDivisibleRequest) returns (IsDivisibleResponse);
}

message IsDivisibleRequest {
// dividend is the dividend in the division.
int32 dividend = 1;
// divisor is the divisor in the division.
int32 divisor = 2 [(buf.validate.field).cel = {
id: "divisor_must_be_a_small_prime",
message: "the divisor must be one of 2, 3 and 5",
// `in` evaluates list membership. The expression evaluates to
// true when the value is in the list.
// Validates that divisor must be one of 2, 3 and 5.
expression: "this in [2, 3, 5]",
// Similarly, you can assert that it's not in a list with `!(this in [2, 3, 5])`.
}];
}

message IsDivisibleResponse {
// is_divisible is the result.
bool is_divisible = 1;
}
30 changes: 30 additions & 0 deletions examples/cel_bytes_concatentation.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2023 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.

syntax = "proto3";

import "buf/validate/validate.proto";

message CompactDocument {
option (buf.validate.message).cel = {
id: "header_footer_size_limit",
message: "header and footer should be less than 500 bytes in total",
// the `+` operator concatenates two `bytes` together and `size` returns
// the length of a `bytes`.
// This validates that the combined size of header and footer should be < 500.
expression: "size(this.header + this.footer) < 500"
};
bytes header = 1;
bytes footer = 2;
}
32 changes: 32 additions & 0 deletions examples/cel_bytes_contains.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright 2023 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.

syntax = "proto3";

import "buf/validate/validate.proto";

// Application is an application.
message Application {
// binary is the application's binary
bytes binary = 1 [(buf.validate.field).cel = {
id: "without_malicious_code",
message: "binary should not contain malicious code",
// `contains` returns if the receiver `bytes` contains the argument `bytes`.
// This validates that the application binary should not contain bytes for
// `'malicious code'`.
// Note that when checking whether bytes for a string is contained, `bytes()`
// must be called to convert the string into bytes.
expression: "!this.contains(bytes('malicious code'))"
}];
}
37 changes: 37 additions & 0 deletions examples/cel_bytes_starts_with_ends_with.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2023 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.

syntax = "proto3";

import "buf/validate/validate.proto";

message ScriptFile {
bytes content = 1 [(buf.validate.field).cel = {
id: "script_start_with_shabang_end_with_line_feed",
expression:
// this is a ternary expression in the form of a ? b : c, if the the
// script file doesn't start with '#!', the expression evaluates to the
// first error message, which fails the validation. Otherwise it
// evaluates to the next expression.
"!this.startsWith(bytes('#!')) ? 'must start with #!'"
// If this file does not end with the new line character, it evaluates
// to the second error message.
// Note: the `'\x0A'` is byte string for the new line character, but
// we have two backslashes below. This is because we need to escape the
// backslash.
": !this.endsWith(bytes('\\x0A')) ? 'must end with a new line'"
// If the file passes the two checks above, there is no error.
": ''"
}];
}
37 changes: 37 additions & 0 deletions examples/cel_duration_arithmetic.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2023 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.

syntax = "proto3";

import "buf/validate/validate.proto";
import "google/protobuf/duration.proto";

message IndirectFlight {
option (buf.validate.message).cel = {
id: "totol_lenght_limit",
message: "the entire trip should be less than 48 hours",
expression:
// This validates that the total duration of an indirect flight should be
// less than 48 hours.
// Adding two duration yields a duration.
// Note that if the CEL expression is too long, you can break it down into
// multiple lines like so:
"this.first_flight_duration + this.second_flight_duration"
// Durations can be compared with `<`, `<=`, `>=`, `>`, `==` and `!=`.
"+ this.layover_duration < duration('48h')"
};
google.protobuf.Duration first_flight_duration = 1;
google.protobuf.Duration layover_duration = 2;
google.protobuf.Duration second_flight_duration = 3;
}
54 changes: 54 additions & 0 deletions examples/cel_field_mask.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2023 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.

syntax = "proto3";

import "buf/validate/validate.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/field_mask.proto";
import "google/protobuf/timestamp.proto";

service SongService {
rpc UpdateSong(UpdateSongRequest) returns (UpdateSongResponse);
}

message UpdateSongRequest {
// song is the updated song.
Song song = 1;
// field_mask masks the fields to update.
google.protobuf.FieldMask field_mask = 2 [(buf.validate.field).cel = {
id: "valid_field_mask",
message: "a field mask path must be one of name, duration and artist.name",
// `some_list.all()` validates that a predicate is true for all elements
// from that list.
// In this case, `this.paths` is the field paths from.
expression: "this.paths.all(path, path in ['name', 'duration', 'artist.name'])",
}];
}

message Song {
string name = 1;
fixed64 id = 2;
google.protobuf.Timestamp create_time = 3;
google.protobuf.Duration duration = 4;
Artist artist = 5;
}

message Artist {
string name = 1;
// other fields:
// ...
}

message UpdateSongResponse {}
45 changes: 45 additions & 0 deletions examples/cel_field_presence_nested.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright 2023 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.

syntax = "proto3";

import "buf/validate/validate.proto";

service ContactService {
// CreateContact creates a contact.
rpc CreateContact(CreateContactRequest) returns (CreateContactResponse);
}

message CreateContactRequest {
option (buf.validate.message).cel = {
id: "create_contact_with_first_name",
message: "the contact must have first name",
// This expression validates that all of `this.contact`, `this.contact.full_name`
// and `this.contact.full_name.first_name` are set.
expression: "has(this.contact.full_name.first_name)"
};
// contact is the contact to create.
Contact contact = 1;
}

message Contact {
FullName full_name = 1;
}

message FullName {
string first_name = 1;
string last_name = 2;
}

message CreateContactResponse {}
Loading

0 comments on commit 4655215

Please sign in to comment.