Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Always treat groups as message fields #132

Merged
merged 3 commits into from
Jul 19, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
3 changes: 2 additions & 1 deletion celext/lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ func RequiredCELEnvOptions(fieldDesc protoreflect.FieldDescriptor) []cel.EnvOpti
RequiredCELEnvOptions(fieldDesc.MapValue())...,
)
}
if fieldDesc.Kind() == protoreflect.MessageKind {
if fieldDesc.Kind() == protoreflect.MessageKind ||
fieldDesc.Kind() == protoreflect.GroupKind {
return []cel.EnvOption{
cel.Types(dynamicpb.NewMessage(fieldDesc.Message())),
}
Expand Down
3 changes: 2 additions & 1 deletion celext/lookups.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ func ProtoFieldToCELType(fieldDesc protoreflect.FieldDescriptor, generic, forIte
}
}

if fieldDesc.Kind() == protoreflect.MessageKind {
if fieldDesc.Kind() == protoreflect.MessageKind ||
fieldDesc.Kind() == protoreflect.GroupKind {
Comment on lines +45 to +46
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this fixes behavior with edition 2023 message fields with features.message_encoding = DELIMITED, this also fixes the same issue with proto2 groups.

Proto2 groups are really only syntactic sugar. When compiled to descriptors, the keyword synthesizes a message for the group, and adds a field with TYPE_GROUP that references the message. When serialized, the field is delimited by start end end tags instead of a length-delimited tag.

The edition message_encoding feature is intended to reproduce the wire representation, but in descriptors, it's represented as a regular message field with TYPE_MESSAGE, and the feature DELIMITED in an option.

protoreflect however aims to have a single representation, and translates the DELIMITED option back to field kind group. So as long as we treat TYPE_GROUP the same as TYPE_MESSAGE, we should be good with both proto2 groups and edition DELIMITED (assuming we don't do any serialization).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I had the same feeling, this should definitely fix proto2 groups. I assume we don't have any tests for proto2 groups currently, but we could always add some if desired.

Either way, I definitely have an appreciation for how clever the 2023 edition features are. It seems like if you were using official protobuf reflection libraries and supporting all of the proto2 and proto3 features it "just works", so fixing editions support is really just fixing support for those features.

switch fqn := fieldDesc.Message().FullName(); fqn {
case "google.protobuf.Any":
return cel.AnyType
Expand Down
3 changes: 2 additions & 1 deletion internal/constraints/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,8 @@ func (c *Cache) getExpectedConstraintDescriptor(
return mapFieldConstraintsDesc, true
case targetFieldDesc.IsList() && !forItems:
return repeatedFieldConstraintsDesc, true
case targetFieldDesc.Kind() == protoreflect.MessageKind:
case targetFieldDesc.Kind() == protoreflect.MessageKind,
targetFieldDesc.Kind() == protoreflect.GroupKind:
expected, ok = expectedWKTConstraints[targetFieldDesc.Message().FullName()]
return expected, ok
default:
Expand Down
19 changes: 15 additions & 4 deletions internal/evaluator/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ func (bldr *Builder) processEmbeddedMessage(
valEval *value,
cache MessageCache,
) error {
if fdesc.Kind() != protoreflect.MessageKind ||
if !isMessageField(fdesc) ||
bldr.shouldSkip(rules) ||
fdesc.IsMap() ||
(fdesc.IsList() && !forItems) {
Expand All @@ -326,7 +326,7 @@ func (bldr *Builder) processWrapperConstraints(
valEval *value,
cache MessageCache,
) error {
if fdesc.Kind() != protoreflect.MessageKind ||
if !isMessageField(fdesc) ||
bldr.shouldSkip(rules) ||
fdesc.IsMap() ||
(fdesc.IsList() && !forItems) {
Expand Down Expand Up @@ -374,7 +374,7 @@ func (bldr *Builder) processAnyConstraints(
_ MessageCache,
) error {
if (fdesc.IsList() && !forItems) ||
fdesc.Kind() != protoreflect.MessageKind ||
!isMessageField(fdesc) ||
fdesc.Message().FullName() != "google.protobuf.Any" {
return nil
}
Expand Down Expand Up @@ -488,7 +488,7 @@ func (bldr *Builder) zeroValue(fdesc protoreflect.FieldDescriptor, forItems bool
case forItems && fdesc.IsList():
msg := dynamicpb.NewMessage(fdesc.ContainingMessage())
return msg.Get(fdesc).List().NewElement()
case fdesc.Kind() == protoreflect.MessageKind &&
case isMessageField(fdesc) &&
fdesc.Cardinality() != protoreflect.Repeated:
msg := dynamicpb.NewMessage(fdesc.Message())
return protoreflect.ValueOfMessage(msg)
Expand All @@ -509,3 +509,14 @@ func (c MessageCache) SyncTo(other MessageCache) {
other[k] = v
}
}

// isMessageField returns true if the field descriptor fdesc describes a field
// containing a submessage. Although they are represented differently on the
// wire, group fields are treated like message fields in protoreflect and have
// similar properties. In the 2023 edition of protobuf, message fields with the
// delimited encoding feature will be detected as groups, but should otherwise
// be treated the same.
func isMessageField(fdesc protoreflect.FieldDescriptor) bool {
return fdesc.Kind() == protoreflect.MessageKind ||
fdesc.Kind() == protoreflect.GroupKind
}
3 changes: 2 additions & 1 deletion legacy/translate.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ func translateRule(
case pgvDesc.IsList():
// only repeated fields are either scalar types or WKTs
typConstraints.Set(pvDesc, value)
case pgvDesc.Kind() == protoreflect.MessageKind &&
case (pgvDesc.Kind() == protoreflect.MessageKind ||
pgvDesc.Kind() == protoreflect.GroupKind) &&
pgvDesc.Message().FullName() == "validate.FieldRules":
// recurse to translate RepeatedRules.items & MapRules.{keys,values}
cons := translateRules(value.Message())
Expand Down