istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/util/protomarshal/protomarshal.go (about) 1 // Copyright Istio Authors 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 15 // Package protomarshal provides operations to marshal and unmarshal protobuf objects. 16 // Unlike the rest of this repo, which uses the new google.golang.org/protobuf API, this package 17 // explicitly uses the legacy jsonpb package. This is due to a number of compatibility concerns with the new API: 18 // * https://github.com/golang/protobuf/issues/1374 19 // * https://github.com/golang/protobuf/issues/1373 20 package protomarshal 21 22 import ( 23 "bytes" 24 "encoding/json" 25 "errors" 26 "strings" 27 28 "github.com/golang/protobuf/jsonpb" // nolint: depguard 29 legacyproto "github.com/golang/protobuf/proto" // nolint: staticcheck 30 "google.golang.org/protobuf/encoding/protojson" // nolint: depguard 31 "google.golang.org/protobuf/proto" 32 "google.golang.org/protobuf/reflect/protoreflect" 33 "google.golang.org/protobuf/reflect/protoregistry" 34 "sigs.k8s.io/yaml" 35 36 "istio.io/istio/pkg/log" 37 ) 38 39 var ( 40 unmarshaler = jsonpb.Unmarshaler{AllowUnknownFields: true} 41 strictUnmarshaler = jsonpb.Unmarshaler{} 42 ) 43 44 func Unmarshal(b []byte, m proto.Message) error { 45 return strictUnmarshaler.Unmarshal(bytes.NewReader(b), legacyproto.MessageV1(m)) 46 } 47 48 func UnmarshalString(s string, m proto.Message) error { 49 return Unmarshal([]byte(s), m) 50 } 51 52 func UnmarshalAllowUnknown(b []byte, m proto.Message) error { 53 return unmarshaler.Unmarshal(bytes.NewReader(b), legacyproto.MessageV1(m)) 54 } 55 56 type resolver interface { 57 protoregistry.MessageTypeResolver 58 protoregistry.ExtensionTypeResolver 59 } 60 61 func UnmarshalAllowUnknownWithAnyResolver(anyResolver resolver, b []byte, m proto.Message) error { 62 return (&protojson.UnmarshalOptions{ 63 DiscardUnknown: true, 64 Resolver: anyResolver, 65 }).Unmarshal(b, m) 66 } 67 68 // ToJSON marshals a proto to canonical JSON 69 func ToJSON(msg proto.Message) (string, error) { 70 return ToJSONWithIndent(msg, "") 71 } 72 73 // Marshal marshals a proto to canonical JSON 74 func Marshal(msg proto.Message) ([]byte, error) { 75 res, err := ToJSONWithIndent(msg, "") 76 if err != nil { 77 return nil, err 78 } 79 return []byte(res), err 80 } 81 82 // MarshalIndent marshals a proto to canonical JSON with indentation 83 func MarshalIndent(msg proto.Message, indent string) ([]byte, error) { 84 res, err := ToJSONWithIndent(msg, indent) 85 if err != nil { 86 return nil, err 87 } 88 return []byte(res), err 89 } 90 91 // MarshalIndentWithGlobalTypesResolver marshals a proto to canonical JSON with indentation 92 // and multiline while using generic types resolver 93 func MarshalIndentWithGlobalTypesResolver(msg proto.Message, indent string) ([]byte, error) { 94 return protojson.MarshalOptions{ 95 Multiline: true, 96 Indent: indent, 97 }.Marshal(msg) 98 } 99 100 // MarshalProtoNames marshals a proto to canonical JSON original protobuf names 101 func MarshalProtoNames(msg proto.Message) ([]byte, error) { 102 if msg == nil { 103 return nil, errors.New("unexpected nil message") 104 } 105 106 // Marshal from proto to json bytes 107 m := jsonpb.Marshaler{OrigName: true} 108 buf := &bytes.Buffer{} 109 err := m.Marshal(buf, legacyproto.MessageV1(msg)) 110 if err != nil { 111 return nil, err 112 } 113 return buf.Bytes(), nil 114 } 115 116 // ToJSONWithIndent marshals a proto to canonical JSON with pretty printed string 117 func ToJSONWithIndent(msg proto.Message, indent string) (string, error) { 118 return ToJSONWithOptions(msg, indent, false) 119 } 120 121 // ToJSONWithOptions marshals a proto to canonical JSON with options to indent and 122 // print enums' int values 123 func ToJSONWithOptions(msg proto.Message, indent string, enumsAsInts bool) (string, error) { 124 if msg == nil { 125 return "", errors.New("unexpected nil message") 126 } 127 128 // Marshal from proto to json bytes 129 m := jsonpb.Marshaler{Indent: indent, EnumsAsInts: enumsAsInts} 130 return m.MarshalToString(legacyproto.MessageV1(msg)) 131 } 132 133 func ToJSONWithAnyResolver(msg proto.Message, indent string, anyResolver jsonpb.AnyResolver) (string, error) { 134 if msg == nil { 135 return "", errors.New("unexpected nil message") 136 } 137 138 m := jsonpb.Marshaler{ 139 Indent: indent, 140 AnyResolver: anyResolver, 141 } 142 return m.MarshalToString(legacyproto.MessageV1(msg)) 143 } 144 145 // ToYAML marshals a proto to canonical YAML 146 func ToYAML(msg proto.Message) (string, error) { 147 js, err := ToJSON(msg) 148 if err != nil { 149 return "", err 150 } 151 yml, err := yaml.JSONToYAML([]byte(js)) 152 return string(yml), err 153 } 154 155 // ToJSONMap converts a proto message to a generic map using canonical JSON encoding 156 // JSON encoding is specified here: https://developers.google.com/protocol-buffers/docs/proto3#json 157 func ToJSONMap(msg proto.Message) (map[string]any, error) { 158 js, err := ToJSON(msg) 159 if err != nil { 160 return nil, err 161 } 162 163 // Unmarshal from json bytes to go map 164 var data map[string]any 165 err = json.Unmarshal([]byte(js), &data) 166 if err != nil { 167 return nil, err 168 } 169 170 return data, nil 171 } 172 173 // ApplyJSON unmarshals a JSON string into a proto message. 174 func ApplyJSON(js string, pb proto.Message) error { 175 reader := strings.NewReader(js) 176 m := jsonpb.Unmarshaler{} 177 if err := m.Unmarshal(reader, legacyproto.MessageV1(pb)); err != nil { 178 log.Debugf("Failed to decode proto: %q. Trying decode with AllowUnknownFields=true", err) 179 m.AllowUnknownFields = true 180 reader.Reset(js) 181 return m.Unmarshal(reader, legacyproto.MessageV1(pb)) 182 } 183 return nil 184 } 185 186 // ApplyJSONStrict unmarshals a JSON string into a proto message. 187 func ApplyJSONStrict(js string, pb proto.Message) error { 188 reader := strings.NewReader(js) 189 m := jsonpb.Unmarshaler{} 190 return m.Unmarshal(reader, legacyproto.MessageV1(pb)) 191 } 192 193 // ApplyYAML unmarshals a YAML string into a proto message. 194 // Unknown fields are allowed. 195 func ApplyYAML(yml string, pb proto.Message) error { 196 js, err := yaml.YAMLToJSON([]byte(yml)) 197 if err != nil { 198 return err 199 } 200 return ApplyJSON(string(js), pb) 201 } 202 203 // ApplyYAMLStrict unmarshals a YAML string into a proto message. 204 // Unknown fields are not allowed. 205 func ApplyYAMLStrict(yml string, pb proto.Message) error { 206 js, err := yaml.YAMLToJSON([]byte(yml)) 207 if err != nil { 208 return err 209 } 210 return ApplyJSONStrict(string(js), pb) 211 } 212 213 func ShallowCopy(dst, src proto.Message) { 214 dm := dst.ProtoReflect() 215 sm := src.ProtoReflect() 216 if dm.Type() != sm.Type() { 217 panic("mismatching type") 218 } 219 proto.Reset(dst) 220 sm.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool { 221 dm.Set(fd, v) 222 return true 223 }) 224 }