Common errors
CEL expressions can fail at runtime if they encounter operations that have no valid result, such as dividing by zero or accessing a list element that doesn't exist. In Protovalidate, a runtime error in a CEL expression causes the validation rule to fail with an error rather than returning a clean pass or fail result. This page covers the most common CEL runtime errors and the guard patterns you can use to avoid them.
Division by zero
Section titled “Division by zero”Integer division by zero produces a runtime error in CEL. Unlike floating-point division, which returns infinity, integer division has no valid result for a zero divisor.
this.total / this.countIf count is 0, this expression fails at runtime. The fix is to guard the division with a short-circuit check. Because && in CEL is short-circuit evaluated, the right side is only evaluated when the left side is true:
this.count != 0u && this.total / this.count >= 10uIn a Protovalidate rule, this pattern looks like:
import "buf/validate/validate.proto";
message Stats { // Total number of events observed. uint64 total = 1; // Number of periods over which events were counted. uint64 count = 2; option (buf.validate.message).cel = { id: "stats.average_minimum" message: "average must be at least 10" // Guard against division by zero using short-circuit evaluation. expression: "this.count != 0u && this.total / this.count >= 10u" };}Index out of bounds
Section titled “Index out of bounds”Accessing a list element by index fails at runtime if the index is beyond the list's size. This includes accessing index 0 on an empty list.
this.tags[0] == "primary"If tags is empty, this expression fails. Guard it by checking the size first:
size(this.tags) > 0 && this.tags[0] == "primary"Missing map keys
Section titled “Missing map keys”Accessing a map key that doesn't exist produces a runtime error. This applies to Protobuf map fields as well as any CEL map value.
this.labels["env"] == "production"If the labels map has no "env" key, this expression fails. Guard it with the in operator, which checks for key existence without accessing the value:
"env" in this.labels && this.labels["env"] == "production"In a Protovalidate rule:
import "buf/validate/validate.proto";
message Deployment { // Key-value labels describing the deployment. map<string, string> labels = 1 [(buf.validate.field).cel = { id: "deployment.labels.env" message: "deployments must be labeled with an env of 'production' or 'staging'" // Guard with `in` before accessing the key. expression: "'env' in this && (this['env'] == 'production' || this['env'] == 'staging')" }];}Null wrapper values
Section titled “Null wrapper values”Protobuf wrapper types like google.protobuf.StringValue, google.protobuf.Int32Value, and others evaluate to null in CEL when the field is unset. Using a null value in an operation that expects a concrete type produces a runtime error.
this.display_name.size() > 0If display_name is an unset google.protobuf.StringValue, it evaluates to null and calling size() on it fails. Guard it with the has() macro, which checks whether a field is set without evaluating its value:
has(this.display_name) && this.display_name.size() > 0In a Protovalidate rule:
import "buf/validate/validate.proto";import "google/protobuf/wrappers.proto";
message Profile { // Optional display name for the user. google.protobuf.StringValue display_name = 1; option (buf.validate.message).cel = { id: "profile.display_name_length" message: "display name, if provided, must be at least 3 characters" // Guard against null wrapper with has(). expression: "!has(this.display_name) || this.display_name.size() >= 3" };}Notice the inverted pattern here: !has(this.display_name) || ... means "either the field isn't set (which is fine) or, if it is set, it must pass this check." This is a common way to express optional-but-validated fields.
The ternary fallback pattern
Section titled “The ternary fallback pattern”When your expression needs to produce a value rather than just return true or false, the ternary operator provides a way to supply a safe default. This is especially useful when the expression is part of a larger computation.
has(this.nickname) ? this.nickname : this.full_nameThis pairs well with the division-by-zero guard when you need the result of the division itself:
this.count != 0u ? this.total / this.count : 0uLearn more
Section titled “Learn more”- Common errors on celbyexample.com covers additional CEL error scenarios and patterns.
- Logic and conditions explains short-circuit evaluation in detail.
- The
has()macro on celbyexample.com provides more examples of field presence checks. - Custom CEL rules shows how to write CEL-based validation rules in Protovalidate.