diff options
| author | mo khan <mo@mokhan.ca> | 2025-05-20 14:28:06 -0600 |
|---|---|---|
| committer | mo khan <mo@mokhan.ca> | 2025-05-23 14:49:19 -0600 |
| commit | 4beee46dc6c7642316e118a4d3aa51e4b407256e (patch) | |
| tree | 039bdf57b99061844aeb0fe55ad0bc1c864166af /vendor/github.com/bufbuild/protocompile/internal | |
| parent | 0ba49bfbde242920d8675a193d7af89420456fc0 (diff) | |
feat: add external authorization service (authzd) with JWT authentication
- Add new authzd gRPC service implementing Envoy's external authorization API
- Integrate JWT authentication filter in Envoy configuration with claim extraction
- Update middleware to support both cookie-based and header-based user authentication
- Add comprehensive test coverage for authorization service and server
- Configure proper service orchestration with authzd, sparkled, and Envoy
- Update build system and Docker configuration for multi-service deployment
- Add grpcurl tool for gRPC service debugging and testing
This enables fine-grained authorization control through Envoy's ext_authz filter
while maintaining backward compatibility with existing cookie-based authentication.
Diffstat (limited to 'vendor/github.com/bufbuild/protocompile/internal')
12 files changed, 1388 insertions, 0 deletions
diff --git a/vendor/github.com/bufbuild/protocompile/internal/editions/editions.go b/vendor/github.com/bufbuild/protocompile/internal/editions/editions.go new file mode 100644 index 0000000..ee054fa --- /dev/null +++ b/vendor/github.com/bufbuild/protocompile/internal/editions/editions.go @@ -0,0 +1,420 @@ +// Copyright 2020-2024 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package editions contains helpers related to resolving features for +// Protobuf editions. These are lower-level helpers. Higher-level helpers +// (which use this package under the hood) can be found in the exported +// protoutil package. +package editions + +import ( + "fmt" + "strings" + "sync" + + "google.golang.org/protobuf/encoding/prototext" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/reflect/protoregistry" + "google.golang.org/protobuf/types/descriptorpb" + "google.golang.org/protobuf/types/dynamicpb" +) + +const ( + // MinSupportedEdition is the earliest edition supported by this module. + // It should be 2023 (the first edition) for the indefinite future. + MinSupportedEdition = descriptorpb.Edition_EDITION_2023 + + // MaxSupportedEdition is the most recent edition supported by this module. + MaxSupportedEdition = descriptorpb.Edition_EDITION_2023 +) + +var ( + // SupportedEditions is the exhaustive set of editions that protocompile + // can support. We don't allow it to compile future/unknown editions, to + // make sure we don't generate incorrect descriptors, in the event that + // a future edition introduces a change or new feature that requires + // new logic in the compiler. + SupportedEditions = computeSupportedEditions(MinSupportedEdition, MaxSupportedEdition) + + // FeatureSetDescriptor is the message descriptor for the compiled-in + // version (in the descriptorpb package) of the google.protobuf.FeatureSet + // message type. + FeatureSetDescriptor = (*descriptorpb.FeatureSet)(nil).ProtoReflect().Descriptor() + // FeatureSetType is the message type for the compiled-in version (in + // the descriptorpb package) of google.protobuf.FeatureSet. + FeatureSetType = (*descriptorpb.FeatureSet)(nil).ProtoReflect().Type() + + editionDefaults map[descriptorpb.Edition]*descriptorpb.FeatureSet + editionDefaultsInit sync.Once +) + +// HasFeatures is implemented by all options messages and provides a +// nil-receiver-safe way of accessing the features explicitly configured +// in those options. +type HasFeatures interface { + GetFeatures() *descriptorpb.FeatureSet +} + +var _ HasFeatures = (*descriptorpb.FileOptions)(nil) +var _ HasFeatures = (*descriptorpb.MessageOptions)(nil) +var _ HasFeatures = (*descriptorpb.FieldOptions)(nil) +var _ HasFeatures = (*descriptorpb.OneofOptions)(nil) +var _ HasFeatures = (*descriptorpb.ExtensionRangeOptions)(nil) +var _ HasFeatures = (*descriptorpb.EnumOptions)(nil) +var _ HasFeatures = (*descriptorpb.EnumValueOptions)(nil) +var _ HasFeatures = (*descriptorpb.ServiceOptions)(nil) +var _ HasFeatures = (*descriptorpb.MethodOptions)(nil) + +// ResolveFeature resolves a feature for the given descriptor. This simple +// helper examines the given element and its ancestors, searching for an +// override. If there is no overridden value, it returns a zero value. +func ResolveFeature( + element protoreflect.Descriptor, + fields ...protoreflect.FieldDescriptor, +) (protoreflect.Value, error) { + for { + var features *descriptorpb.FeatureSet + if withFeatures, ok := element.Options().(HasFeatures); ok { + // It should not really be possible for 'ok' to ever be false... + features = withFeatures.GetFeatures() + } + + // TODO: adaptFeatureSet is only looking at the first field. But if we needed to + // support an extension field inside a custom feature, we'd really need + // to check all fields. That gets particularly complicated if the traversal + // path of fields includes list and map values. Luckily, features are not + // supposed to be repeated and not supposed to themselves have extensions. + // So this should be fine, at least for now. + msgRef, err := adaptFeatureSet(features, fields[0]) + if err != nil { + return protoreflect.Value{}, err + } + // Navigate the fields to find the value + var val protoreflect.Value + for i, field := range fields { + if i > 0 { + msgRef = val.Message() + } + if !msgRef.Has(field) { + val = protoreflect.Value{} + break + } + val = msgRef.Get(field) + } + if val.IsValid() { + // All fields were set! + return val, nil + } + + parent := element.Parent() + if parent == nil { + // We've reached the end of the inheritance chain. + return protoreflect.Value{}, nil + } + element = parent + } +} + +// HasEdition should be implemented by values that implement +// [protoreflect.FileDescriptor], to provide access to the file's +// edition when its syntax is [protoreflect.Editions]. +type HasEdition interface { + // Edition returns the numeric value of a google.protobuf.Edition enum + // value that corresponds to the edition of this file. If the file does + // not use editions, it should return the enum value that corresponds + // to the syntax level, EDITION_PROTO2 or EDITION_PROTO3. + Edition() int32 +} + +// GetEdition returns the edition for a given element. It returns +// EDITION_PROTO2 or EDITION_PROTO3 if the element is in a file that +// uses proto2 or proto3 syntax, respectively. It returns EDITION_UNKNOWN +// if the syntax of the given element is not recognized or if the edition +// cannot be ascertained from the element's [protoreflect.FileDescriptor]. +func GetEdition(d protoreflect.Descriptor) descriptorpb.Edition { + switch d.ParentFile().Syntax() { + case protoreflect.Proto2: + return descriptorpb.Edition_EDITION_PROTO2 + case protoreflect.Proto3: + return descriptorpb.Edition_EDITION_PROTO3 + case protoreflect.Editions: + withEdition, ok := d.ParentFile().(HasEdition) + if !ok { + // The parent file should always be a *result, so we should + // never be able to actually get in here. If we somehow did + // have another implementation of protoreflect.FileDescriptor, + // it doesn't provide a way to get the edition, other than the + // potentially expensive step of generating a FileDescriptorProto + // and then querying for the edition from that. :/ + return descriptorpb.Edition_EDITION_UNKNOWN + } + return descriptorpb.Edition(withEdition.Edition()) + default: + return descriptorpb.Edition_EDITION_UNKNOWN + } +} + +// GetEditionDefaults returns the default feature values for the given edition. +// It returns nil if the given edition is not known. +// +// This only populates known features, those that are fields of [*descriptorpb.FeatureSet]. +// It does not populate any extension fields. +// +// The returned value must not be mutated as it references shared package state. +func GetEditionDefaults(edition descriptorpb.Edition) *descriptorpb.FeatureSet { + editionDefaultsInit.Do(func() { + editionDefaults = make(map[descriptorpb.Edition]*descriptorpb.FeatureSet, len(descriptorpb.Edition_name)) + // Compute default for all known editions in descriptorpb. + for editionInt := range descriptorpb.Edition_name { + edition := descriptorpb.Edition(editionInt) + defaults := &descriptorpb.FeatureSet{} + defaultsRef := defaults.ProtoReflect() + fields := defaultsRef.Descriptor().Fields() + // Note: we are not computing defaults for extensions. Those are not needed + // by anything in the compiler, so we can get away with just computing + // defaults for these static, non-extension fields. + for i, length := 0, fields.Len(); i < length; i++ { + field := fields.Get(i) + val, err := GetFeatureDefault(edition, FeatureSetType, field) + if err != nil { + // should we fail somehow?? + continue + } + defaultsRef.Set(field, val) + } + editionDefaults[edition] = defaults + } + }) + return editionDefaults[edition] +} + +// GetFeatureDefault computes the default value for a feature. The given container +// is the message type that contains the field. This should usually be the descriptor +// for google.protobuf.FeatureSet, but can be a different message for computing the +// default value of custom features. +// +// Note that this always re-computes the default. For known fields of FeatureSet, +// it is more efficient to query from the statically computed default messages, +// like so: +// +// editions.GetEditionDefaults(edition).ProtoReflect().Get(feature) +func GetFeatureDefault(edition descriptorpb.Edition, container protoreflect.MessageType, feature protoreflect.FieldDescriptor) (protoreflect.Value, error) { + opts, ok := feature.Options().(*descriptorpb.FieldOptions) + if !ok { + // this is most likely impossible except for contrived use cases... + return protoreflect.Value{}, fmt.Errorf("options is %T instead of *descriptorpb.FieldOptions", feature.Options()) + } + maxEdition := descriptorpb.Edition(-1) + var maxVal string + for _, def := range opts.EditionDefaults { + if def.GetEdition() <= edition && def.GetEdition() > maxEdition { + maxEdition = def.GetEdition() + maxVal = def.GetValue() + } + } + if maxEdition == -1 { + // no matching default found + return protoreflect.Value{}, fmt.Errorf("no relevant default for edition %s", edition) + } + // We use a typed nil so that it won't fall back to the global registry. Features + // should not use extensions or google.protobuf.Any, so a nil *Types is fine. + unmarshaler := prototext.UnmarshalOptions{Resolver: (*protoregistry.Types)(nil)} + // The string value is in the text format: either a field value literal or a + // message literal. (Repeated and map features aren't supported, so there's no + // array or map literal syntax to worry about.) + if feature.Kind() == protoreflect.MessageKind || feature.Kind() == protoreflect.GroupKind { + fldVal := container.Zero().NewField(feature) + err := unmarshaler.Unmarshal([]byte(maxVal), fldVal.Message().Interface()) + if err != nil { + return protoreflect.Value{}, err + } + return fldVal, nil + } + // The value is the textformat for the field. But prototext doesn't provide a way + // to unmarshal a single field value. To work around, we unmarshal into an enclosing + // message, which means we must prefix the value with the field name. + if feature.IsExtension() { + maxVal = fmt.Sprintf("[%s]: %s", feature.FullName(), maxVal) + } else { + maxVal = fmt.Sprintf("%s: %s", feature.Name(), maxVal) + } + empty := container.New() + err := unmarshaler.Unmarshal([]byte(maxVal), empty.Interface()) + if err != nil { + return protoreflect.Value{}, err + } + return empty.Get(feature), nil +} + +func adaptFeatureSet(msg *descriptorpb.FeatureSet, field protoreflect.FieldDescriptor) (protoreflect.Message, error) { + msgRef := msg.ProtoReflect() + var actualField protoreflect.FieldDescriptor + switch { + case field.IsExtension(): + // Extensions can be used directly with the feature set, even if + // field.ContainingMessage() != FeatureSetDescriptor. But only if + // the value is either not a message or is a message with the + // right descriptor, i.e. val.Descriptor() == field.Message(). + if actualField = actualDescriptor(msgRef, field); actualField == nil || actualField == field { + if msgRef.Has(field) || len(msgRef.GetUnknown()) == 0 { + return msgRef, nil + } + // The field is not present, but the message has unrecognized values. So + // let's try to parse the unrecognized bytes, just in case they contain + // this extension. + temp := &descriptorpb.FeatureSet{} + unmarshaler := proto.UnmarshalOptions{ + AllowPartial: true, + Resolver: resolverForExtension{field}, + } + if err := unmarshaler.Unmarshal(msgRef.GetUnknown(), temp); err != nil { + return nil, fmt.Errorf("failed to parse unrecognized fields of FeatureSet: %w", err) + } + return temp.ProtoReflect(), nil + } + case field.ContainingMessage() == FeatureSetDescriptor: + // Known field, not dynamically generated. Can directly use with the feature set. + return msgRef, nil + default: + actualField = FeatureSetDescriptor.Fields().ByNumber(field.Number()) + } + + // If we get here, we have a dynamic field descriptor or an extension + // descriptor whose message type does not match the descriptor of the + // stored value. We need to copy its value into a dynamic message, + // which requires marshalling/unmarshalling. + // We only need to copy over the unrecognized bytes (if any) + // and the same field (if present). + data := msgRef.GetUnknown() + if actualField != nil && msgRef.Has(actualField) { + subset := &descriptorpb.FeatureSet{} + subset.ProtoReflect().Set(actualField, msgRef.Get(actualField)) + var err error + data, err = proto.MarshalOptions{AllowPartial: true}.MarshalAppend(data, subset) + if err != nil { + return nil, fmt.Errorf("failed to marshal FeatureSet field %s to bytes: %w", field.Name(), err) + } + } + if len(data) == 0 { + // No relevant data to copy over, so we can just return + // a zero value message + return dynamicpb.NewMessageType(field.ContainingMessage()).Zero(), nil + } + + other := dynamicpb.NewMessage(field.ContainingMessage()) + // We don't need to use a resolver for this step because we know that + // field is not an extension. And features are not allowed to themselves + // have extensions. + if err := (proto.UnmarshalOptions{AllowPartial: true}).Unmarshal(data, other); err != nil { + return nil, fmt.Errorf("failed to marshal FeatureSet field %s to bytes: %w", field.Name(), err) + } + return other, nil +} + +type resolverForExtension struct { + ext protoreflect.ExtensionDescriptor +} + +func (r resolverForExtension) FindMessageByName(_ protoreflect.FullName) (protoreflect.MessageType, error) { + return nil, protoregistry.NotFound +} + +func (r resolverForExtension) FindMessageByURL(_ string) (protoreflect.MessageType, error) { + return nil, protoregistry.NotFound +} + +func (r resolverForExtension) FindExtensionByName(field protoreflect.FullName) (protoreflect.ExtensionType, error) { + if field == r.ext.FullName() { + return asExtensionType(r.ext), nil + } + return nil, protoregistry.NotFound +} + +func (r resolverForExtension) FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionType, error) { + if message == r.ext.ContainingMessage().FullName() && field == r.ext.Number() { + return asExtensionType(r.ext), nil + } + return nil, protoregistry.NotFound +} + +func asExtensionType(ext protoreflect.ExtensionDescriptor) protoreflect.ExtensionType { + if xtd, ok := ext.(protoreflect.ExtensionTypeDescriptor); ok { + return xtd.Type() + } + return dynamicpb.NewExtensionType(ext) +} + +func computeSupportedEditions(minEdition, maxEdition descriptorpb.Edition) map[string]descriptorpb.Edition { + supportedEditions := map[string]descriptorpb.Edition{} + for editionNum := range descriptorpb.Edition_name { + edition := descriptorpb.Edition(editionNum) + if edition >= minEdition && edition <= maxEdition { + name := strings.TrimPrefix(edition.String(), "EDITION_") + supportedEditions[name] = edition + } + } + return supportedEditions +} + +// actualDescriptor returns the actual field descriptor referenced by msg that +// corresponds to the given ext (i.e. same number). It returns nil if msg has +// no reference, if the actual descriptor is the same as ext, or if ext is +// otherwise safe to use as is. +func actualDescriptor(msg protoreflect.Message, ext protoreflect.ExtensionDescriptor) protoreflect.FieldDescriptor { + if !msg.Has(ext) || ext.Message() == nil { + // nothing to match; safe as is + return nil + } + val := msg.Get(ext) + switch { + case ext.IsMap(): // should not actually be possible + expectedDescriptor := ext.MapValue().Message() + if expectedDescriptor == nil { + return nil // nothing to match + } + // We know msg.Has(field) is true, from above, so there's at least one entry. + var matches bool + val.Map().Range(func(_ protoreflect.MapKey, val protoreflect.Value) bool { + matches = val.Message().Descriptor() == expectedDescriptor + return false + }) + if matches { + return nil + } + case ext.IsList(): + // We know msg.Has(field) is true, from above, so there's at least one entry. + if val.List().Get(0).Message().Descriptor() == ext.Message() { + return nil + } + case !ext.IsMap(): + if val.Message().Descriptor() == ext.Message() { + return nil + } + } + // The underlying message descriptors do not match. So we need to return + // the actual field descriptor. Sadly, protoreflect.Message provides no way + // to query the field descriptor in a message by number. For non-extensions, + // one can query the associated message descriptor. But for extensions, we + // have to do the slow thing, and range through all fields looking for it. + var actualField protoreflect.FieldDescriptor + msg.Range(func(fd protoreflect.FieldDescriptor, _ protoreflect.Value) bool { + if fd.Number() == ext.Number() { + actualField = fd + return false + } + return true + }) + return actualField +} diff --git a/vendor/github.com/bufbuild/protocompile/internal/featuresext/cpp_features.protoset b/vendor/github.com/bufbuild/protocompile/internal/featuresext/cpp_features.protoset Binary files differnew file mode 100644 index 0000000..106ad8e --- /dev/null +++ b/vendor/github.com/bufbuild/protocompile/internal/featuresext/cpp_features.protoset diff --git a/vendor/github.com/bufbuild/protocompile/internal/featuresext/featuresext.go b/vendor/github.com/bufbuild/protocompile/internal/featuresext/featuresext.go new file mode 100644 index 0000000..892524e --- /dev/null +++ b/vendor/github.com/bufbuild/protocompile/internal/featuresext/featuresext.go @@ -0,0 +1,84 @@ +// Copyright 2020-2024 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package featuresext provides file descriptors for the +// "google/protobuf/cpp_features.proto" and "google/protobuf/java_features.proto" +// standard import files. Unlike the other standard/well-known +// imports, these files have no standard Go package in their +// runtime with generated code. So in order to make them available +// as "standard imports" to compiler users, we must embed these +// descriptors into a Go package. +package featuresext + +import ( + _ "embed" + "fmt" + "sync" + + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protodesc" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/reflect/protoregistry" + "google.golang.org/protobuf/types/descriptorpb" +) + +var ( + //go:embed cpp_features.protoset + cppFeatures []byte + + //go:embed java_features.protoset + javaFeatures []byte + + initOnce sync.Once + initCppFeatures protoreflect.FileDescriptor + initCppErr error + initJavaFeatures protoreflect.FileDescriptor + initJavaErr error +) + +func initDescriptors() { + initOnce.Do(func() { + initCppFeatures, initCppErr = buildDescriptor("google/protobuf/cpp_features.proto", cppFeatures) + initJavaFeatures, initJavaErr = buildDescriptor("google/protobuf/java_features.proto", javaFeatures) + }) +} + +func CppFeaturesDescriptor() (protoreflect.FileDescriptor, error) { + initDescriptors() + return initCppFeatures, initCppErr +} + +func JavaFeaturesDescriptor() (protoreflect.FileDescriptor, error) { + initDescriptors() + return initJavaFeatures, initJavaErr +} + +func buildDescriptor(name string, data []byte) (protoreflect.FileDescriptor, error) { + var files descriptorpb.FileDescriptorSet + err := proto.Unmarshal(data, &files) + if err != nil { + return nil, fmt.Errorf("failed to load descriptor for %q: %w", name, err) + } + if len(files.File) != 1 { + return nil, fmt.Errorf("failed to load descriptor for %q: expected embedded descriptor set to contain exactly one file but it instead has %d", name, len(files.File)) + } + if files.File[0].GetName() != name { + return nil, fmt.Errorf("failed to load descriptor for %q: embedded descriptor contains wrong file %q", name, files.File[0].GetName()) + } + descriptor, err := protodesc.NewFile(files.File[0], protoregistry.GlobalFiles) + if err != nil { + return nil, fmt.Errorf("failed to load descriptor for %q: %w", name, err) + } + return descriptor, nil +} diff --git a/vendor/github.com/bufbuild/protocompile/internal/featuresext/java_features.protoset b/vendor/github.com/bufbuild/protocompile/internal/featuresext/java_features.protoset Binary files differnew file mode 100644 index 0000000..60de3eb --- /dev/null +++ b/vendor/github.com/bufbuild/protocompile/internal/featuresext/java_features.protoset diff --git a/vendor/github.com/bufbuild/protocompile/internal/message_context.go b/vendor/github.com/bufbuild/protocompile/internal/message_context.go new file mode 100644 index 0000000..52acbdf --- /dev/null +++ b/vendor/github.com/bufbuild/protocompile/internal/message_context.go @@ -0,0 +1,98 @@ +// Copyright 2020-2024 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + "bytes" + "fmt" + + "google.golang.org/protobuf/types/descriptorpb" + + "github.com/bufbuild/protocompile/ast" +) + +// ParsedFile wraps an optional AST and required FileDescriptorProto. +// This is used so types like parser.Result can be passed to this internal package avoiding circular imports. +// Additionally, it makes it less likely that users might specify one or the other. +type ParsedFile interface { + // AST returns the parsed abstract syntax tree. This returns nil if the + // Result was created without an AST. + AST() *ast.FileNode + // FileDescriptorProto returns the file descriptor proto. + FileDescriptorProto() *descriptorpb.FileDescriptorProto +} + +// MessageContext provides information about the location in a descriptor +// hierarchy, for adding context to warnings and error messages. +type MessageContext struct { + // The relevant file + File ParsedFile + + // The type and fully-qualified name of the element within the file. + ElementType string + ElementName string + + // If the element being processed is an option (or *in* an option) + // on the named element above, this will be non-nil. + Option *descriptorpb.UninterpretedOption + // If the element being processed is inside a message literal in an + // option value, this will be non-empty and represent a traversal + // to the element in question. + OptAggPath string +} + +func (c *MessageContext) String() string { + var ctx bytes.Buffer + if c.ElementType != "file" { + _, _ = fmt.Fprintf(&ctx, "%s %s: ", c.ElementType, c.ElementName) + } + if c.Option != nil && c.Option.Name != nil { + ctx.WriteString("option ") + writeOptionName(&ctx, c.Option.Name) + if c.File.AST() == nil { + // if we have no source position info, try to provide as much context + // as possible (if nodes != nil, we don't need this because any errors + // will actually have file and line numbers) + if c.OptAggPath != "" { + _, _ = fmt.Fprintf(&ctx, " at %s", c.OptAggPath) + } + } + ctx.WriteString(": ") + } + return ctx.String() +} + +func writeOptionName(buf *bytes.Buffer, parts []*descriptorpb.UninterpretedOption_NamePart) { + first := true + for _, p := range parts { + if first { + first = false + } else { + buf.WriteByte('.') + } + nm := p.GetNamePart() + if nm[0] == '.' { + // skip leading dot + nm = nm[1:] + } + if p.GetIsExtension() { + buf.WriteByte('(') + buf.WriteString(nm) + buf.WriteByte(')') + } else { + buf.WriteString(nm) + } + } +} diff --git a/vendor/github.com/bufbuild/protocompile/internal/messageset/messageset.go b/vendor/github.com/bufbuild/protocompile/internal/messageset/messageset.go new file mode 100644 index 0000000..850a0c6 --- /dev/null +++ b/vendor/github.com/bufbuild/protocompile/internal/messageset/messageset.go @@ -0,0 +1,62 @@ +// Copyright 2020-2024 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package messageset + +import ( + "math" + "sync" + + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protodesc" + "google.golang.org/protobuf/types/descriptorpb" +) + +var ( + messageSetSupport bool + messageSetSupportInit sync.Once +) + +// CanSupportMessageSets returns true if the protobuf-go runtime supports +// serializing messages with the message set wire format. +func CanSupportMessageSets() bool { + messageSetSupportInit.Do(func() { + // We check using the protodesc package, instead of just relying + // on protolegacy build tag, in case someone links in a fork of + // the protobuf-go runtime that supports legacy proto1 features + // or in case the protobuf-go runtime adds another mechanism to + // enable or disable it (such as environment variable). + _, err := protodesc.NewFile(&descriptorpb.FileDescriptorProto{ + Name: proto.String("test.proto"), + MessageType: []*descriptorpb.DescriptorProto{ + { + Name: proto.String("MessageSet"), + Options: &descriptorpb.MessageOptions{ + MessageSetWireFormat: proto.Bool(true), + }, + ExtensionRange: []*descriptorpb.DescriptorProto_ExtensionRange{ + { + Start: proto.Int32(1), + End: proto.Int32(math.MaxInt32), + }, + }, + }, + }, + }, nil) + // When message sets are not supported, the above returns an error: + // message "MessageSet" is a MessageSet, which is a legacy proto1 feature that is no longer supported + messageSetSupport = err == nil + }) + return messageSetSupport +} diff --git a/vendor/github.com/bufbuild/protocompile/internal/norace.go b/vendor/github.com/bufbuild/protocompile/internal/norace.go new file mode 100644 index 0000000..2acf4e4 --- /dev/null +++ b/vendor/github.com/bufbuild/protocompile/internal/norace.go @@ -0,0 +1,19 @@ +// Copyright 2020-2024 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build !race + +package internal + +const IsRace = false diff --git a/vendor/github.com/bufbuild/protocompile/internal/options.go b/vendor/github.com/bufbuild/protocompile/internal/options.go new file mode 100644 index 0000000..4eaa0f6 --- /dev/null +++ b/vendor/github.com/bufbuild/protocompile/internal/options.go @@ -0,0 +1,71 @@ +// Copyright 2020-2024 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + "google.golang.org/protobuf/types/descriptorpb" + + "github.com/bufbuild/protocompile/ast" +) + +type hasOptionNode interface { + OptionNode(part *descriptorpb.UninterpretedOption) ast.OptionDeclNode + FileNode() ast.FileDeclNode // needed in order to query for NodeInfo +} + +type errorHandler func(span ast.SourceSpan, format string, args ...interface{}) error + +func FindFirstOption(res hasOptionNode, handler errorHandler, scope string, opts []*descriptorpb.UninterpretedOption, name string) (int, error) { + return findOption(res, handler, scope, opts, name, false, true) +} + +func FindOption(res hasOptionNode, handler errorHandler, scope string, opts []*descriptorpb.UninterpretedOption, name string) (int, error) { + return findOption(res, handler, scope, opts, name, true, false) +} + +func findOption(res hasOptionNode, handler errorHandler, scope string, opts []*descriptorpb.UninterpretedOption, name string, exact, first bool) (int, error) { + found := -1 + for i, opt := range opts { + if exact && len(opt.Name) != 1 { + continue + } + if opt.Name[0].GetIsExtension() || opt.Name[0].GetNamePart() != name { + continue + } + if first { + return i, nil + } + if found >= 0 { + optNode := res.OptionNode(opt) + fn := res.FileNode() + node := optNode.GetName() + nodeInfo := fn.NodeInfo(node) + return -1, handler(nodeInfo, "%s: option %s cannot be defined more than once", scope, name) + } + found = i + } + return found, nil +} + +func RemoveOption(uo []*descriptorpb.UninterpretedOption, indexToRemove int) []*descriptorpb.UninterpretedOption { + switch { + case indexToRemove == 0: + return uo[1:] + case indexToRemove == len(uo)-1: + return uo[:len(uo)-1] + default: + return append(uo[:indexToRemove], uo[indexToRemove+1:]...) + } +} diff --git a/vendor/github.com/bufbuild/protocompile/internal/race.go b/vendor/github.com/bufbuild/protocompile/internal/race.go new file mode 100644 index 0000000..e70e414 --- /dev/null +++ b/vendor/github.com/bufbuild/protocompile/internal/race.go @@ -0,0 +1,19 @@ +// Copyright 2020-2024 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build race + +package internal + +const IsRace = true diff --git a/vendor/github.com/bufbuild/protocompile/internal/tags.go b/vendor/github.com/bufbuild/protocompile/internal/tags.go new file mode 100644 index 0000000..179728f --- /dev/null +++ b/vendor/github.com/bufbuild/protocompile/internal/tags.go @@ -0,0 +1,336 @@ +// Copyright 2020-2024 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import "math" + +const ( + // MaxNormalTag is the maximum allowed tag number for a field in a normal message. + MaxNormalTag = 536870911 // 2^29 - 1 + + // MaxMessageSetTag is the maximum allowed tag number of a field in a message that + // uses the message set wire format. + MaxMessageSetTag = math.MaxInt32 - 1 + + // MaxTag is the maximum allowed tag number. (It is the same as MaxMessageSetTag + // since that is the absolute highest allowed.) + MaxTag = MaxMessageSetTag + + // SpecialReservedStart is the first tag in a range that is reserved and not + // allowed for use in message definitions. + SpecialReservedStart = 19000 + // SpecialReservedEnd is the last tag in a range that is reserved and not + // allowed for use in message definitions. + SpecialReservedEnd = 19999 + + // NB: It would be nice to use constants from generated code instead of + // hard-coding these here. But code-gen does not emit these as constants + // anywhere. The only places they appear in generated code are struct tags + // on fields of the generated descriptor protos. + + // FilePackageTag is the tag number of the package element in a file + // descriptor proto. + FilePackageTag = 2 + // FileDependencyTag is the tag number of the dependencies element in a + // file descriptor proto. + FileDependencyTag = 3 + // FileMessagesTag is the tag number of the messages element in a file + // descriptor proto. + FileMessagesTag = 4 + // FileEnumsTag is the tag number of the enums element in a file descriptor + // proto. + FileEnumsTag = 5 + // FileServicesTag is the tag number of the services element in a file + // descriptor proto. + FileServicesTag = 6 + // FileExtensionsTag is the tag number of the extensions element in a file + // descriptor proto. + FileExtensionsTag = 7 + // FileOptionsTag is the tag number of the options element in a file + // descriptor proto. + FileOptionsTag = 8 + // FileOptionsJavaStringCheckUTF8Tag is the tag number of the java_string_check_utf8 + // field in the FileOptions proto. + FileOptionsJavaStringCheckUTF8Tag = 27 + // FileOptionsFeaturesTag is the tag number of the features field in the + // FileOptions proto. + FileOptionsFeaturesTag = 50 + // FileSourceCodeInfoTag is the tag number of the source code info element + // in a file descriptor proto. + FileSourceCodeInfoTag = 9 + // FilePublicDependencyTag is the tag number of the public dependency element + // in a file descriptor proto. + FilePublicDependencyTag = 10 + // FileWeakDependencyTag is the tag number of the weak dependency element + // in a file descriptor proto. + FileWeakDependencyTag = 11 + // FileSyntaxTag is the tag number of the syntax element in a file + // descriptor proto. + FileSyntaxTag = 12 + // FileEditionTag is the tag number of the edition element in a file + // descriptor proto. + FileEditionTag = 14 + // MessageNameTag is the tag number of the name element in a message + // descriptor proto. + MessageNameTag = 1 + // MessageFieldsTag is the tag number of the fields element in a message + // descriptor proto. + MessageFieldsTag = 2 + // MessageNestedMessagesTag is the tag number of the nested messages + // element in a message descriptor proto. + MessageNestedMessagesTag = 3 + // MessageEnumsTag is the tag number of the enums element in a message + // descriptor proto. + MessageEnumsTag = 4 + // MessageExtensionRangesTag is the tag number of the extension ranges + // element in a message descriptor proto. + MessageExtensionRangesTag = 5 + // MessageExtensionsTag is the tag number of the extensions element in a + // message descriptor proto. + MessageExtensionsTag = 6 + // MessageOptionsTag is the tag number of the options element in a message + // descriptor proto. + MessageOptionsTag = 7 + // MessageOptionsFeaturesTag is the tag number of the features field in the + // MessageOptions proto. + MessageOptionsFeaturesTag = 12 + // MessageOneofsTag is the tag number of the one-ofs element in a message + // descriptor proto. + MessageOneofsTag = 8 + // MessageReservedRangesTag is the tag number of the reserved ranges element + // in a message descriptor proto. + MessageReservedRangesTag = 9 + // MessageReservedNamesTag is the tag number of the reserved names element + // in a message descriptor proto. + MessageReservedNamesTag = 10 + // ExtensionRangeStartTag is the tag number of the start index in an + // extension range proto. + ExtensionRangeStartTag = 1 + // ExtensionRangeEndTag is the tag number of the end index in an + // extension range proto. + ExtensionRangeEndTag = 2 + // ExtensionRangeOptionsTag is the tag number of the options element in an + // extension range proto. + ExtensionRangeOptionsTag = 3 + // ExtensionRangeOptionsDeclarationTag is the tag number of the declaration + // field in the ExtensionRangeOptions proto. + ExtensionRangeOptionsDeclarationTag = 2 + // ExtensionRangeOptionsVerificationTag is the tag number of the verification + // field in the ExtensionRangeOptions proto. + ExtensionRangeOptionsVerificationTag = 3 + // ExtensionRangeOptionsDeclarationNumberTag is the tag number of the number + // field in the ExtensionRangeOptions.Declaration proto. + ExtensionRangeOptionsDeclarationNumberTag = 1 + // ExtensionRangeOptionsDeclarationFullNameTag is the tag number of the full_name + // field in the ExtensionRangeOptions.Declaration proto. + ExtensionRangeOptionsDeclarationFullNameTag = 2 + // ExtensionRangeOptionsDeclarationTypeTag is the tag number of the type + // field in the ExtensionRangeOptions.Declaration proto. + ExtensionRangeOptionsDeclarationTypeTag = 3 + // ExtensionRangeOptionsDeclarationReservedTag is the tag number of the reserved + // field in the ExtensionRangeOptions.Declaration proto. + ExtensionRangeOptionsDeclarationReservedTag = 5 + // ExtensionRangeOptionsDeclarationRepeatedTag is the tag number of the repeated + // field in the ExtensionRangeOptions.Declaration proto. + ExtensionRangeOptionsDeclarationRepeatedTag = 6 + // ExtensionRangeOptionsFeaturesTag is the tag number of the features field in the + // ExtensionRangeOptions proto. + ExtensionRangeOptionsFeaturesTag = 50 + // ReservedRangeStartTag is the tag number of the start index in a reserved + // range proto. This field number is the same for both "flavors" of reserved + // ranges: DescriptorProto.ReservedRange and EnumDescriptorProto.EnumReservedRange. + ReservedRangeStartTag = 1 + // ReservedRangeEndTag is the tag number of the end index in a reserved + // range proto. This field number is the same for both "flavors" of reserved + // ranges: DescriptorProto.ReservedRange and EnumDescriptorProto.EnumReservedRange. + ReservedRangeEndTag = 2 + // FieldNameTag is the tag number of the name element in a field descriptor + // proto. + FieldNameTag = 1 + // FieldExtendeeTag is the tag number of the extendee element in a field + // descriptor proto. + FieldExtendeeTag = 2 + // FieldNumberTag is the tag number of the number element in a field + // descriptor proto. + FieldNumberTag = 3 + // FieldLabelTag is the tag number of the label element in a field + // descriptor proto. + FieldLabelTag = 4 + // FieldTypeTag is the tag number of the type element in a field descriptor + // proto. + FieldTypeTag = 5 + // FieldTypeNameTag is the tag number of the type name element in a field + // descriptor proto. + FieldTypeNameTag = 6 + // FieldDefaultTag is the tag number of the default value element in a + // field descriptor proto. + FieldDefaultTag = 7 + // FieldOptionsTag is the tag number of the options element in a field + // descriptor proto. + FieldOptionsTag = 8 + // FieldOptionsCTypeTag is the number of the ctype field in the + // FieldOptions proto. + FieldOptionsCTypeTag = 1 + // FieldOptionsPackedTag is the number of the packed field in the + // FieldOptions proto. + FieldOptionsPackedTag = 2 + // FieldOptionsLazyTag is the number of the lazy field in the + // FieldOptions proto. + FieldOptionsLazyTag = 5 + // FieldOptionsJSTypeTag is the number of the jstype field in the + // FieldOptions proto. + FieldOptionsJSTypeTag = 6 + // FieldOptionsUnverifiedLazyTag is the number of the unverified_lazy + // field in the FieldOptions proto. + FieldOptionsUnverifiedLazyTag = 15 + // FieldOptionsFeaturesTag is the tag number of the features field in the + // FieldOptions proto. + FieldOptionsFeaturesTag = 21 + // FieldOneofIndexTag is the tag number of the oneof index element in a + // field descriptor proto. + FieldOneofIndexTag = 9 + // FieldJSONNameTag is the tag number of the JSON name element in a field + // descriptor proto. + FieldJSONNameTag = 10 + // FieldProto3OptionalTag is the tag number of the proto3_optional element + // in a descriptor proto. + FieldProto3OptionalTag = 17 + // OneofNameTag is the tag number of the name element in a one-of + // descriptor proto. + OneofNameTag = 1 + // OneofOptionsTag is the tag number of the options element in a one-of + // descriptor proto. + OneofOptionsTag = 2 + // OneofOptionsFeaturesTag is the tag number of the features field in the + // OneofOptions proto. + OneofOptionsFeaturesTag = 1 + // EnumNameTag is the tag number of the name element in an enum descriptor + // proto. + EnumNameTag = 1 + // EnumValuesTag is the tag number of the values element in an enum + // descriptor proto. + EnumValuesTag = 2 + // EnumOptionsTag is the tag number of the options element in an enum + // descriptor proto. + EnumOptionsTag = 3 + // EnumOptionsFeaturesTag is the tag number of the features field in the + // EnumOptions proto. + EnumOptionsFeaturesTag = 7 + // EnumReservedRangesTag is the tag number of the reserved ranges element in + // an enum descriptor proto. + EnumReservedRangesTag = 4 + // EnumReservedNamesTag is the tag number of the reserved names element in + // an enum descriptor proto. + EnumReservedNamesTag = 5 + // EnumValNameTag is the tag number of the name element in an enum value + // descriptor proto. + EnumValNameTag = 1 + // EnumValNumberTag is the tag number of the number element in an enum + // value descriptor proto. + EnumValNumberTag = 2 + // EnumValOptionsTag is the tag number of the options element in an enum + // value descriptor proto. + EnumValOptionsTag = 3 + // EnumValOptionsFeaturesTag is the tag number of the features field in the + // EnumValueOptions proto. + EnumValOptionsFeaturesTag = 2 + // ServiceNameTag is the tag number of the name element in a service + // descriptor proto. + ServiceNameTag = 1 + // ServiceMethodsTag is the tag number of the methods element in a service + // descriptor proto. + ServiceMethodsTag = 2 + // ServiceOptionsTag is the tag number of the options element in a service + // descriptor proto. + ServiceOptionsTag = 3 + // ServiceOptionsFeaturesTag is the tag number of the features field in the + // ServiceOptions proto. + ServiceOptionsFeaturesTag = 34 + // MethodNameTag is the tag number of the name element in a method + // descriptor proto. + MethodNameTag = 1 + // MethodInputTag is the tag number of the input type element in a method + // descriptor proto. + MethodInputTag = 2 + // MethodOutputTag is the tag number of the output type element in a method + // descriptor proto. + MethodOutputTag = 3 + // MethodOptionsTag is the tag number of the options element in a method + // descriptor proto. + MethodOptionsTag = 4 + // MethodOptionsFeaturesTag is the tag number of the features field in the + // MethodOptions proto. + MethodOptionsFeaturesTag = 35 + // MethodInputStreamTag is the tag number of the input stream flag in a + // method descriptor proto. + MethodInputStreamTag = 5 + // MethodOutputStreamTag is the tag number of the output stream flag in a + // method descriptor proto. + MethodOutputStreamTag = 6 + + // UninterpretedOptionsTag is the tag number of the uninterpreted options + // element. All *Options messages use the same tag for the field that stores + // uninterpreted options. + UninterpretedOptionsTag = 999 + + // UninterpretedNameTag is the tag number of the name element in an + // uninterpreted options proto. + UninterpretedNameTag = 2 + // UninterpretedIdentTag is the tag number of the identifier value in an + // uninterpreted options proto. + UninterpretedIdentTag = 3 + // UninterpretedPosIntTag is the tag number of the positive int value in an + // uninterpreted options proto. + UninterpretedPosIntTag = 4 + // UninterpretedNegIntTag is the tag number of the negative int value in an + // uninterpreted options proto. + UninterpretedNegIntTag = 5 + // UninterpretedDoubleTag is the tag number of the double value in an + // uninterpreted options proto. + UninterpretedDoubleTag = 6 + // UninterpretedStringTag is the tag number of the string value in an + // uninterpreted options proto. + UninterpretedStringTag = 7 + // UninterpretedAggregateTag is the tag number of the aggregate value in an + // uninterpreted options proto. + UninterpretedAggregateTag = 8 + // UninterpretedNameNameTag is the tag number of the name element in an + // uninterpreted option name proto. + UninterpretedNameNameTag = 1 + + // AnyTypeURLTag is the tag number of the type_url field of the Any proto. + AnyTypeURLTag = 1 + // AnyValueTag is the tag number of the value field of the Any proto. + AnyValueTag = 2 + + // FeatureSetFieldPresenceTag is the tag number of the field_presence field + // in the FeatureSet proto. + FeatureSetFieldPresenceTag = 1 + // FeatureSetEnumTypeTag is the tag number of the enum_type field in the + // FeatureSet proto. + FeatureSetEnumTypeTag = 2 + // FeatureSetRepeatedFieldEncodingTag is the tag number of the repeated_field_encoding + // field in the FeatureSet proto. + FeatureSetRepeatedFieldEncodingTag = 3 + // FeatureSetUTF8ValidationTag is the tag number of the utf8_validation field + // in the FeatureSet proto. + FeatureSetUTF8ValidationTag = 4 + // FeatureSetMessageEncodingTag is the tag number of the message_encoding + // field in the FeatureSet proto. + FeatureSetMessageEncodingTag = 5 + // FeatureSetJSONFormatTag is the tag number of the json_format field in + // the FeatureSet proto. + FeatureSetJSONFormatTag = 6 +) diff --git a/vendor/github.com/bufbuild/protocompile/internal/types.go b/vendor/github.com/bufbuild/protocompile/internal/types.go new file mode 100644 index 0000000..04090a8 --- /dev/null +++ b/vendor/github.com/bufbuild/protocompile/internal/types.go @@ -0,0 +1,35 @@ +// Copyright 2020-2024 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import "google.golang.org/protobuf/types/descriptorpb" + +var FieldTypes = map[string]descriptorpb.FieldDescriptorProto_Type{ + "double": descriptorpb.FieldDescriptorProto_TYPE_DOUBLE, + "float": descriptorpb.FieldDescriptorProto_TYPE_FLOAT, + "int32": descriptorpb.FieldDescriptorProto_TYPE_INT32, + "int64": descriptorpb.FieldDescriptorProto_TYPE_INT64, + "uint32": descriptorpb.FieldDescriptorProto_TYPE_UINT32, + "uint64": descriptorpb.FieldDescriptorProto_TYPE_UINT64, + "sint32": descriptorpb.FieldDescriptorProto_TYPE_SINT32, + "sint64": descriptorpb.FieldDescriptorProto_TYPE_SINT64, + "fixed32": descriptorpb.FieldDescriptorProto_TYPE_FIXED32, + "fixed64": descriptorpb.FieldDescriptorProto_TYPE_FIXED64, + "sfixed32": descriptorpb.FieldDescriptorProto_TYPE_SFIXED32, + "sfixed64": descriptorpb.FieldDescriptorProto_TYPE_SFIXED64, + "bool": descriptorpb.FieldDescriptorProto_TYPE_BOOL, + "string": descriptorpb.FieldDescriptorProto_TYPE_STRING, + "bytes": descriptorpb.FieldDescriptorProto_TYPE_BYTES, +} diff --git a/vendor/github.com/bufbuild/protocompile/internal/util.go b/vendor/github.com/bufbuild/protocompile/internal/util.go new file mode 100644 index 0000000..569cb3f --- /dev/null +++ b/vendor/github.com/bufbuild/protocompile/internal/util.go @@ -0,0 +1,244 @@ +// Copyright 2020-2024 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + "bytes" + "unicode" + "unicode/utf8" + + "google.golang.org/protobuf/reflect/protoreflect" +) + +// JSONName returns the default JSON name for a field with the given name. +// This mirrors the algorithm in protoc: +// +// https://github.com/protocolbuffers/protobuf/blob/v21.3/src/google/protobuf/descriptor.cc#L95 +func JSONName(name string) string { + var js []rune + nextUpper := false + for _, r := range name { + if r == '_' { + nextUpper = true + continue + } + if nextUpper { + nextUpper = false + js = append(js, unicode.ToUpper(r)) + } else { + js = append(js, r) + } + } + return string(js) +} + +// InitCap returns the given field name, but with the first letter capitalized. +func InitCap(name string) string { + r, sz := utf8.DecodeRuneInString(name) + return string(unicode.ToUpper(r)) + name[sz:] +} + +// CreatePrefixList returns a list of package prefixes to search when resolving +// a symbol name. If the given package is blank, it returns only the empty +// string. If the given package contains only one token, e.g. "foo", it returns +// that token and the empty string, e.g. ["foo", ""]. Otherwise, it returns +// successively shorter prefixes of the package and then the empty string. For +// example, for a package named "foo.bar.baz" it will return the following list: +// +// ["foo.bar.baz", "foo.bar", "foo", ""] +func CreatePrefixList(pkg string) []string { + if pkg == "" { + return []string{""} + } + + numDots := 0 + // one pass to pre-allocate the returned slice + for i := 0; i < len(pkg); i++ { + if pkg[i] == '.' { + numDots++ + } + } + if numDots == 0 { + return []string{pkg, ""} + } + + prefixes := make([]string, numDots+2) + // second pass to fill in returned slice + for i := 0; i < len(pkg); i++ { + if pkg[i] == '.' { + prefixes[numDots] = pkg[:i] + numDots-- + } + } + prefixes[0] = pkg + + return prefixes +} + +func WriteEscapedBytes(buf *bytes.Buffer, b []byte) { + // This uses the same algorithm as the protoc C++ code for escaping strings. + // The protoc C++ code in turn uses the abseil C++ library's CEscape function: + // https://github.com/abseil/abseil-cpp/blob/934f613818ffcb26c942dff4a80be9a4031c662c/absl/strings/escaping.cc#L406 + for _, c := range b { + switch c { + case '\n': + buf.WriteString("\\n") + case '\r': + buf.WriteString("\\r") + case '\t': + buf.WriteString("\\t") + case '"': + buf.WriteString("\\\"") + case '\'': + buf.WriteString("\\'") + case '\\': + buf.WriteString("\\\\") + default: + if c >= 0x20 && c < 0x7f { + // simple printable characters + buf.WriteByte(c) + } else { + // use octal escape for all other values + buf.WriteRune('\\') + buf.WriteByte('0' + ((c >> 6) & 0x7)) + buf.WriteByte('0' + ((c >> 3) & 0x7)) + buf.WriteByte('0' + (c & 0x7)) + } + } + } +} + +// IsZeroLocation returns true if the given loc is a zero value +// (which is returned from queries that have no result). +func IsZeroLocation(loc protoreflect.SourceLocation) bool { + return loc.Path == nil && + loc.StartLine == 0 && + loc.StartColumn == 0 && + loc.EndLine == 0 && + loc.EndColumn == 0 && + loc.LeadingDetachedComments == nil && + loc.LeadingComments == "" && + loc.TrailingComments == "" && + loc.Next == 0 +} + +// ComputePath computes the source location path for the given descriptor. +// The boolean value indicates whether the result is valid. If the path +// cannot be computed for d, the function returns nil, false. +func ComputePath(d protoreflect.Descriptor) (protoreflect.SourcePath, bool) { + _, ok := d.(protoreflect.FileDescriptor) + if ok { + return nil, true + } + var path protoreflect.SourcePath + for { + p := d.Parent() + switch d := d.(type) { + case protoreflect.FileDescriptor: + return reverse(path), true + case protoreflect.MessageDescriptor: + path = append(path, int32(d.Index())) + switch p.(type) { + case protoreflect.FileDescriptor: + path = append(path, FileMessagesTag) + case protoreflect.MessageDescriptor: + path = append(path, MessageNestedMessagesTag) + default: + return nil, false + } + case protoreflect.FieldDescriptor: + path = append(path, int32(d.Index())) + switch p.(type) { + case protoreflect.FileDescriptor: + if d.IsExtension() { + path = append(path, FileExtensionsTag) + } else { + return nil, false + } + case protoreflect.MessageDescriptor: + if d.IsExtension() { + path = append(path, MessageExtensionsTag) + } else { + path = append(path, MessageFieldsTag) + } + default: + return nil, false + } + case protoreflect.OneofDescriptor: + path = append(path, int32(d.Index())) + if _, ok := p.(protoreflect.MessageDescriptor); ok { + path = append(path, MessageOneofsTag) + } else { + return nil, false + } + case protoreflect.EnumDescriptor: + path = append(path, int32(d.Index())) + switch p.(type) { + case protoreflect.FileDescriptor: + path = append(path, FileEnumsTag) + case protoreflect.MessageDescriptor: + path = append(path, MessageEnumsTag) + default: + return nil, false + } + case protoreflect.EnumValueDescriptor: + path = append(path, int32(d.Index())) + if _, ok := p.(protoreflect.EnumDescriptor); ok { + path = append(path, EnumValuesTag) + } else { + return nil, false + } + case protoreflect.ServiceDescriptor: + path = append(path, int32(d.Index())) + if _, ok := p.(protoreflect.FileDescriptor); ok { + path = append(path, FileServicesTag) + } else { + return nil, false + } + case protoreflect.MethodDescriptor: + path = append(path, int32(d.Index())) + if _, ok := p.(protoreflect.ServiceDescriptor); ok { + path = append(path, ServiceMethodsTag) + } else { + return nil, false + } + } + d = p + } +} + +// CanPack returns true if a repeated field of the given kind +// can use packed encoding. +func CanPack(k protoreflect.Kind) bool { + switch k { + case protoreflect.MessageKind, protoreflect.GroupKind, protoreflect.StringKind, protoreflect.BytesKind: + return false + default: + return true + } +} + +func ClonePath(path protoreflect.SourcePath) protoreflect.SourcePath { + clone := make(protoreflect.SourcePath, len(path)) + copy(clone, path) + return clone +} + +func reverse(p protoreflect.SourcePath) protoreflect.SourcePath { + for i, j := 0, len(p)-1; i < j; i, j = i+1, j-1 { + p[i], p[j] = p[j], p[i] + } + return p +} |
