khenaidoo | 7d3c558 | 2021-08-11 18:09:44 -0400 | [diff] [blame] | 1 | // Copyright 2019 The Go Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
| 5 | package proto |
| 6 | |
| 7 | import ( |
| 8 | "google.golang.org/protobuf/reflect/protoreflect" |
| 9 | ) |
| 10 | |
| 11 | // HasExtension reports whether an extension field is populated. |
| 12 | // It returns false if m is invalid or if xt does not extend m. |
| 13 | func HasExtension(m Message, xt protoreflect.ExtensionType) bool { |
Akash Reddy Kankanala | 92dfdf8 | 2025-03-23 22:07:09 +0530 | [diff] [blame^] | 14 | // Treat nil message interface or descriptor as an empty message; no populated |
| 15 | // fields. |
| 16 | if m == nil || xt == nil { |
khenaidoo | 7d3c558 | 2021-08-11 18:09:44 -0400 | [diff] [blame] | 17 | return false |
| 18 | } |
| 19 | |
| 20 | // As a special-case, we reports invalid or mismatching descriptors |
| 21 | // as always not being populated (since they aren't). |
Akash Reddy Kankanala | 92dfdf8 | 2025-03-23 22:07:09 +0530 | [diff] [blame^] | 22 | mr := m.ProtoReflect() |
| 23 | xd := xt.TypeDescriptor() |
| 24 | if mr.Descriptor() != xd.ContainingMessage() { |
khenaidoo | 7d3c558 | 2021-08-11 18:09:44 -0400 | [diff] [blame] | 25 | return false |
| 26 | } |
| 27 | |
Akash Reddy Kankanala | 92dfdf8 | 2025-03-23 22:07:09 +0530 | [diff] [blame^] | 28 | return mr.Has(xd) |
khenaidoo | 7d3c558 | 2021-08-11 18:09:44 -0400 | [diff] [blame] | 29 | } |
| 30 | |
| 31 | // ClearExtension clears an extension field such that subsequent |
Akash Reddy Kankanala | 92dfdf8 | 2025-03-23 22:07:09 +0530 | [diff] [blame^] | 32 | // [HasExtension] calls return false. |
khenaidoo | 7d3c558 | 2021-08-11 18:09:44 -0400 | [diff] [blame] | 33 | // It panics if m is invalid or if xt does not extend m. |
| 34 | func ClearExtension(m Message, xt protoreflect.ExtensionType) { |
| 35 | m.ProtoReflect().Clear(xt.TypeDescriptor()) |
| 36 | } |
| 37 | |
| 38 | // GetExtension retrieves the value for an extension field. |
| 39 | // If the field is unpopulated, it returns the default value for |
| 40 | // scalars and an immutable, empty value for lists or messages. |
| 41 | // It panics if xt does not extend m. |
Akash Reddy Kankanala | 92dfdf8 | 2025-03-23 22:07:09 +0530 | [diff] [blame^] | 42 | // |
| 43 | // The type of the value is dependent on the field type of the extension. |
| 44 | // For extensions generated by protoc-gen-go, the Go type is as follows: |
| 45 | // |
| 46 | // ╔═══════════════════╤═════════════════════════╗ |
| 47 | // ║ Go type │ Protobuf kind ║ |
| 48 | // ╠═══════════════════╪═════════════════════════╣ |
| 49 | // ║ bool │ bool ║ |
| 50 | // ║ int32 │ int32, sint32, sfixed32 ║ |
| 51 | // ║ int64 │ int64, sint64, sfixed64 ║ |
| 52 | // ║ uint32 │ uint32, fixed32 ║ |
| 53 | // ║ uint64 │ uint64, fixed64 ║ |
| 54 | // ║ float32 │ float ║ |
| 55 | // ║ float64 │ double ║ |
| 56 | // ║ string │ string ║ |
| 57 | // ║ []byte │ bytes ║ |
| 58 | // ║ protoreflect.Enum │ enum ║ |
| 59 | // ║ proto.Message │ message, group ║ |
| 60 | // ╚═══════════════════╧═════════════════════════╝ |
| 61 | // |
| 62 | // The protoreflect.Enum and proto.Message types are the concrete Go type |
| 63 | // associated with the named enum or message. Repeated fields are represented |
| 64 | // using a Go slice of the base element type. |
| 65 | // |
| 66 | // If a generated extension descriptor variable is directly passed to |
| 67 | // GetExtension, then the call should be followed immediately by a |
| 68 | // type assertion to the expected output value. For example: |
| 69 | // |
| 70 | // mm := proto.GetExtension(m, foopb.E_MyExtension).(*foopb.MyMessage) |
| 71 | // |
| 72 | // This pattern enables static analysis tools to verify that the asserted type |
| 73 | // matches the Go type associated with the extension field and |
| 74 | // also enables a possible future migration to a type-safe extension API. |
| 75 | // |
| 76 | // Since singular messages are the most common extension type, the pattern of |
| 77 | // calling HasExtension followed by GetExtension may be simplified to: |
| 78 | // |
| 79 | // if mm := proto.GetExtension(m, foopb.E_MyExtension).(*foopb.MyMessage); mm != nil { |
| 80 | // ... // make use of mm |
| 81 | // } |
| 82 | // |
| 83 | // The mm variable is non-nil if and only if HasExtension reports true. |
| 84 | func GetExtension(m Message, xt protoreflect.ExtensionType) any { |
khenaidoo | 7d3c558 | 2021-08-11 18:09:44 -0400 | [diff] [blame] | 85 | // Treat nil message interface as an empty message; return the default. |
| 86 | if m == nil { |
| 87 | return xt.InterfaceOf(xt.Zero()) |
| 88 | } |
| 89 | |
| 90 | return xt.InterfaceOf(m.ProtoReflect().Get(xt.TypeDescriptor())) |
| 91 | } |
| 92 | |
| 93 | // SetExtension stores the value of an extension field. |
| 94 | // It panics if m is invalid, xt does not extend m, or if type of v |
| 95 | // is invalid for the specified extension field. |
Akash Reddy Kankanala | 92dfdf8 | 2025-03-23 22:07:09 +0530 | [diff] [blame^] | 96 | // |
| 97 | // The type of the value is dependent on the field type of the extension. |
| 98 | // For extensions generated by protoc-gen-go, the Go type is as follows: |
| 99 | // |
| 100 | // ╔═══════════════════╤═════════════════════════╗ |
| 101 | // ║ Go type │ Protobuf kind ║ |
| 102 | // ╠═══════════════════╪═════════════════════════╣ |
| 103 | // ║ bool │ bool ║ |
| 104 | // ║ int32 │ int32, sint32, sfixed32 ║ |
| 105 | // ║ int64 │ int64, sint64, sfixed64 ║ |
| 106 | // ║ uint32 │ uint32, fixed32 ║ |
| 107 | // ║ uint64 │ uint64, fixed64 ║ |
| 108 | // ║ float32 │ float ║ |
| 109 | // ║ float64 │ double ║ |
| 110 | // ║ string │ string ║ |
| 111 | // ║ []byte │ bytes ║ |
| 112 | // ║ protoreflect.Enum │ enum ║ |
| 113 | // ║ proto.Message │ message, group ║ |
| 114 | // ╚═══════════════════╧═════════════════════════╝ |
| 115 | // |
| 116 | // The protoreflect.Enum and proto.Message types are the concrete Go type |
| 117 | // associated with the named enum or message. Repeated fields are represented |
| 118 | // using a Go slice of the base element type. |
| 119 | // |
| 120 | // If a generated extension descriptor variable is directly passed to |
| 121 | // SetExtension (e.g., foopb.E_MyExtension), then the value should be a |
| 122 | // concrete type that matches the expected Go type for the extension descriptor |
| 123 | // so that static analysis tools can verify type correctness. |
| 124 | // This also enables a possible future migration to a type-safe extension API. |
| 125 | func SetExtension(m Message, xt protoreflect.ExtensionType, v any) { |
khenaidoo | 7d3c558 | 2021-08-11 18:09:44 -0400 | [diff] [blame] | 126 | xd := xt.TypeDescriptor() |
| 127 | pv := xt.ValueOf(v) |
| 128 | |
| 129 | // Specially treat an invalid list, map, or message as clear. |
| 130 | isValid := true |
| 131 | switch { |
| 132 | case xd.IsList(): |
| 133 | isValid = pv.List().IsValid() |
| 134 | case xd.IsMap(): |
| 135 | isValid = pv.Map().IsValid() |
| 136 | case xd.Message() != nil: |
| 137 | isValid = pv.Message().IsValid() |
| 138 | } |
| 139 | if !isValid { |
| 140 | m.ProtoReflect().Clear(xd) |
| 141 | return |
| 142 | } |
| 143 | |
| 144 | m.ProtoReflect().Set(xd, pv) |
| 145 | } |
| 146 | |
| 147 | // RangeExtensions iterates over every populated extension field in m in an |
| 148 | // undefined order, calling f for each extension type and value encountered. |
| 149 | // It returns immediately if f returns false. |
| 150 | // While iterating, mutating operations may only be performed |
| 151 | // on the current extension field. |
Akash Reddy Kankanala | 92dfdf8 | 2025-03-23 22:07:09 +0530 | [diff] [blame^] | 152 | func RangeExtensions(m Message, f func(protoreflect.ExtensionType, any) bool) { |
khenaidoo | 7d3c558 | 2021-08-11 18:09:44 -0400 | [diff] [blame] | 153 | // Treat nil message interface as an empty message; nothing to range over. |
| 154 | if m == nil { |
| 155 | return |
| 156 | } |
| 157 | |
| 158 | m.ProtoReflect().Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool { |
| 159 | if fd.IsExtension() { |
| 160 | xt := fd.(protoreflect.ExtensionTypeDescriptor).Type() |
| 161 | vi := xt.InterfaceOf(v) |
| 162 | return f(xt, vi) |
| 163 | } |
| 164 | return true |
| 165 | }) |
| 166 | } |