blob: fb21dff63b9a8baedbcb77cd2969afcce571527f [file] [log] [blame]
Abhay Kumara2ae5992025-11-10 14:02:24 +00001// Copyright 2020-2024 Buf Technologies, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package protoutil
16
17import (
18 "fmt"
19
20 "google.golang.org/protobuf/reflect/protoreflect"
21 "google.golang.org/protobuf/types/descriptorpb"
22 "google.golang.org/protobuf/types/dynamicpb"
23
24 "github.com/bufbuild/protocompile/internal/editions"
25)
26
27// GetFeatureDefault gets the default value for the given feature and the given
28// edition. The given feature must represent a field of the google.protobuf.FeatureSet
29// message and must not be an extension.
30//
31// If the given field is from a dynamically built descriptor (i.e. it's containing
32// message descriptor is different from the linked-in descriptor for
33// [*descriptorpb.FeatureSet]), the returned value may be a dynamic value. In such
34// cases, the value may not be directly usable using [protoreflect.Message.Set] with
35// an instance of [*descriptorpb.FeatureSet] and must instead be used with a
36// [*dynamicpb.Message].
37//
38// To get the default value of a custom feature, use [GetCustomFeatureDefault]
39// instead.
40func GetFeatureDefault(edition descriptorpb.Edition, feature protoreflect.FieldDescriptor) (protoreflect.Value, error) {
41 if feature.ContainingMessage().FullName() != editions.FeatureSetDescriptor.FullName() {
42 return protoreflect.Value{}, fmt.Errorf("feature %s is a field of %s but should be a field of %s",
43 feature.Name(), feature.ContainingMessage().FullName(), editions.FeatureSetDescriptor.FullName())
44 }
45 var msgType protoreflect.MessageType
46 if feature.ContainingMessage() == editions.FeatureSetDescriptor {
47 msgType = editions.FeatureSetType
48 } else {
49 msgType = dynamicpb.NewMessageType(feature.ContainingMessage())
50 }
51 return editions.GetFeatureDefault(edition, msgType, feature)
52}
53
54// GetCustomFeatureDefault gets the default value for the given custom feature
55// and given edition. A custom feature is a field whose containing message is the
56// type of an extension field of google.protobuf.FeatureSet. The given extension
57// describes that extension field and message type. The given feature must be a
58// field of that extension's message type.
59func GetCustomFeatureDefault(edition descriptorpb.Edition, extension protoreflect.ExtensionType, feature protoreflect.FieldDescriptor) (protoreflect.Value, error) {
60 extDesc := extension.TypeDescriptor()
61 if extDesc.ContainingMessage().FullName() != editions.FeatureSetDescriptor.FullName() {
62 return protoreflect.Value{}, fmt.Errorf("extension %s does not extend %s", extDesc.FullName(), editions.FeatureSetDescriptor.FullName())
63 }
64 if extDesc.Message() == nil {
65 return protoreflect.Value{}, fmt.Errorf("extensions of %s should be messages; %s is instead %s",
66 editions.FeatureSetDescriptor.FullName(), extDesc.FullName(), extDesc.Kind().String())
67 }
68 if feature.IsExtension() {
69 return protoreflect.Value{}, fmt.Errorf("feature %s is an extension, but feature extension %s may not itself have extensions",
70 feature.FullName(), extDesc.FullName())
71 }
72 if feature.ContainingMessage().FullName() != extDesc.Message().FullName() {
73 return protoreflect.Value{}, fmt.Errorf("feature %s is a field of %s but should be a field of %s",
74 feature.Name(), feature.ContainingMessage().FullName(), extDesc.Message().FullName())
75 }
76 if feature.ContainingMessage() != extDesc.Message() {
77 return protoreflect.Value{}, fmt.Errorf("feature %s has a different message descriptor from the given extension type for %s",
78 feature.Name(), extDesc.Message().FullName())
79 }
80 return editions.GetFeatureDefault(edition, extension.Zero().Message().Type(), feature)
81}
82
83// ResolveFeature resolves a feature for the given descriptor.
84//
85// If the given element is in a proto2 or proto3 syntax file, this skips
86// resolution and just returns the relevant default (since such files are not
87// allowed to override features). If neither the given element nor any of its
88// ancestors override the given feature, the relevant default is returned.
89//
90// This has the same caveat as GetFeatureDefault if the given feature is from a
91// dynamically built descriptor.
92func ResolveFeature(element protoreflect.Descriptor, feature protoreflect.FieldDescriptor) (protoreflect.Value, error) {
93 edition := editions.GetEdition(element)
94 defaultVal, err := GetFeatureDefault(edition, feature)
95 if err != nil {
96 return protoreflect.Value{}, err
97 }
98 return resolveFeature(edition, defaultVal, element, feature)
99}
100
101// ResolveCustomFeature resolves a custom feature for the given extension and
102// field descriptor.
103//
104// The given extension must be an extension of google.protobuf.FeatureSet that
105// represents a non-repeated message value. The given feature is a field in
106// that extension's message type.
107//
108// If the given element is in a proto2 or proto3 syntax file, this skips
109// resolution and just returns the relevant default (since such files are not
110// allowed to override features). If neither the given element nor any of its
111// ancestors override the given feature, the relevant default is returned.
112func ResolveCustomFeature(element protoreflect.Descriptor, extension protoreflect.ExtensionType, feature protoreflect.FieldDescriptor) (protoreflect.Value, error) {
113 edition := editions.GetEdition(element)
114 defaultVal, err := GetCustomFeatureDefault(edition, extension, feature)
115 if err != nil {
116 return protoreflect.Value{}, err
117 }
118 return resolveFeature(edition, defaultVal, element, extension.TypeDescriptor(), feature)
119}
120
121func resolveFeature(
122 edition descriptorpb.Edition,
123 defaultVal protoreflect.Value,
124 element protoreflect.Descriptor,
125 fields ...protoreflect.FieldDescriptor,
126) (protoreflect.Value, error) {
127 if edition == descriptorpb.Edition_EDITION_PROTO2 || edition == descriptorpb.Edition_EDITION_PROTO3 {
128 // these syntax levels can't specify features, so we can short-circuit the search
129 // through the descriptor hierarchy for feature overrides
130 return defaultVal, nil
131 }
132 val, err := editions.ResolveFeature(element, fields...)
133 if err != nil {
134 return protoreflect.Value{}, err
135 }
136 if val.IsValid() {
137 return val, nil
138 }
139 return defaultVal, nil
140}