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 all 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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ GOLANGCI_LINT_VERSION ?= v1.59.1
# Set to use a different version of protovalidate-conformance.
# Should be kept in sync with the version referenced in proto/buf.lock and
# 'buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go' in go.mod.
CONFORMANCE_VERSION ?= v0.6.3
CONFORMANCE_VERSION ?= v0.7.1

.PHONY: help
help: ## Describe useful make targets
Expand Down
12 changes: 6 additions & 6 deletions buf.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
version: v2
deps:
- name: buf.build/bufbuild/protovalidate
commit: b983156c5e994cc9892e0ce3e64e17e0
digest: b5:a02a4a5a0a9306cf1391d17e8811bbd6a0dbd0e0e29ddfc34b9d103311164974472d43627c3d63e6a8abaffd6c7a301c4f7965bbab2dc56f7f7812429a84b812
commit: a6c49f84cc0f4e038680d390392e2ab0
digest: b5:e968392e88ff7915adcbd1635d670b45bff8836ec2415d81fc559ca5470a695dbdc30030bad8bc5764647c731079e9e7bba0023ea25c4e4a1672a7d2561d4a19
- name: buf.build/bufbuild/protovalidate-testing
commit: 09d8555b907f4bfa9e784469b66e2bea
digest: b5:de77b29bc5e88cfa2b0b1fa7b0e12f33d77aefca2821f465c5a54b77696e20919ffb7981cc90ceff41d0a3aab761457030056460bf20129b2dacae5cdcbe21b7
commit: 7c0e0340f14c4d6580c200dcaa9b00d0
digest: b5:f0dacc3a02e3fcd277c3516cd0089e69478374b5ad15fe1032853789668d1d660cac4c6e1776799a66b251b9583ddc320e9db91acf6e8681e31d76ccc1f22ea3
- name: buf.build/envoyproxy/protoc-gen-validate
commit: 71881f09a0c5420a9545a07987a86728
digest: b5:e8a034a81f1d6218f712879269bedac768c9e39452d7a92f83d70923fcd6aa9eb02c81ff6ff337cd0dd9b9fe719d6c4459e655f662ae7112aaa1c2110992afd0
commit: daf171c6cdb54629b5f51e345a79e4dd
digest: b5:c745e1521879f43740230b1df673d0729f55704efefdcfc489d4a0a2d40c92a26cacfeab62813403040a8b180142d53b398c7ca784a065e43823605ee49681de
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
}
Loading
Loading