-
Notifications
You must be signed in to change notification settings - Fork 9
Description
Description
I was attempting to write a message-level validator that confirms fields across two maps were globally unique. My first attempt was to write an expression that turned the fields of interest into a big list and then call unique() on that list. This gives an error about calling unique() on something that is not a list type.
Steps to Reproduce
Write a message validator that concatenates two lists. This can be from either repeated or maps containers. This example is used below in the expected behavior.
syntax = "proto2";
package playground;
import "buf/validate/validate.proto";
message Foo {
optional string name = 1;
}
message FooContainer {
option (buf.validate.message).cel = {
id: "globally_unique_names"
message: "all names in bar and baz must be globally unique"
expression: "(this.bar.map(k1, this.bar[k1].name) + this.baz.map(k2, this.baz[k2].name)).unique()"
};
map<string, Foo> bar = 1;
map<string, Foo> baz = 2;
}
Expected Behavior
The playground shows that this is valid CEL. Here's an example of the validation correctly showing that all fields are globally unique (ref).
Here's the playground showing the validation failure when fields are not globally unique (ref).
The reproduction that shows this for maps, but it works for repeated fields as well. (ref).
Actual Behavior
A CELEvalError is raised.
site-packages/protovalidate/internal/extra_func.py in cel_unique(val)
332 if not isinstance(val, celtypes.ListType):
333 msg = "invalid argument, expected list"
--> 334 raise celpy.CELEvalError(msg)
335 return celtypes.BoolType(len(val) == len(set(val)))
336
CELEvalError: invalid argument, expected list
Screenshots/Logs
Debug logs are very verbose, so those are shared below.
Environment
- Operating System: Linux
- Version: Ubuntu 24.04
- Compiler/Toolchain: GCC 13.3.0
- Protobuf Compiler & Version: protobuf 5.29.3
- Protovalidate Version: 0.11.0
Possible Solution
¯\_(ツ)_/¯
I do have a workaround, though.
Additional Context
I was able to write a more complicated validator that ended up using size() instead. Here's a playground example of this filter (ref).
It is slightly less readable and likely more computationally complex due to the extra map call for every element in one of the containers. Our validation is done only at application startup and the input messages are small, so this has not been an issue.
This suggests to me that the translation performed by map is correctly creating a list but that the type checking code in protovalidate is not condition appropriately.