Skip to content

Conversation

@bufdev
Copy link
Member

@bufdev bufdev commented Nov 8, 2025

This has been a constant annoyance of mine whenever using Protovalidate that I'd like to clean up in every language. It should be easy in other languages, this is just for Go.

Whenever I define a CEL rule, I am constantly annoyed that I have to define an id and a message when the expression contains all the context I need. I want to be able to do this:

message Invoice {
  option (buf.validate.message).cel = {expression: "this.sub_total.currency_code == this.taxes.currency_code"};

  money.v1.Money sub_total = 5 [(buf.validate.field).required = true];
  money.v1.Money taxes = 6 [(buf.validate.field).required = true];
}

Instead, I'm all but forced to do this:

message Invoice {
  option (buf.validate.message).cel = {
    id: "same_currency_code"
    message: "sub_total and taxes cannot have different currency codes"
    expression: "this.sub_total.currency_code == this.taxes.currency_code"
  };

  money.v1.Money sub_total = 5 [(buf.validate.field).required = true];
  money.v1.Money taxes = 6 [(buf.validate.field).required = true];
}

I don't understand why I have to come up with a unique ID, and why I have to pay the mental overhead of coming up with a human-readable message when all the context I need is right in the expression.

Right now, if I don't set a message or ID, I'll get a Violation, but the String of the violation is []. This PR fixes that to default to returning the expression, and the error message is now exactly what I want:

"this.sub_total.currency_code == this.taxes.currency_code" returned false

Corollary, but both out of scope in this PR and not perfectly unfixable without a breaking change (although we can make improvements):

It's super annoying to me that (buf.validate.field).cel isn't CEL. It's a message that then has a field expression that is CEL. All I actually want to do is this:

message Invoice {
  option (buf.validate.message).cel = "this.sub_total.currency_code == this.taxes.currency_code";
}

As a next-best, I'd want to do this:

message Invoice {
  option (buf.validate.message).cel.expression = "this.sub_total.currency_code == this.taxes.currency_code";
}

But this is a Protobuf compile error because (buf.validate.message).cel is a repeated message.

In hindsight, I wish we did:

message MessageRules {
  repeated string cel = 1;
  repeated Rule rule = 2;
}

And then I could do the full version with (buf.validate.message).rule and the simple version with (buf.validate.message).cel, which also matches what I'd expect (rule is a Rule, cel is a CEL expression). But we can't do that anymore.

Perhaps we could do:

message MessageRules {
  // Or perhaps leave it non-deprecated so that we don't freak people out.
  repeated Rule cel = 3 [deprecated = true];
  repeated string cel_expr = 5;
  repeated Rule rule = 6;
}

Then I could do:

message Invoice {
  option (buf.validate.message).cel_expr = "this.sub_total.currency_code == this.taxes.currency_code";
}

But that is for a future PR. For now, I just want this to work without id or message.

@github-actions
Copy link

github-actions bot commented Nov 8, 2025

The latest Buf updates on your PR. Results from workflow Buf / validate-protos (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed⏩ skippedNov 25, 2025, 6:54 PM

@bufdev bufdev merged commit 28aa77b into main Nov 25, 2025
7 checks passed
@bufdev bufdev deleted the expression-only branch November 25, 2025 19:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants