podman
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
5package protojson6
7import (8"encoding/base64"9"fmt"10
11"google.golang.org/protobuf/internal/encoding/json"12"google.golang.org/protobuf/internal/encoding/messageset"13"google.golang.org/protobuf/internal/errors"14"google.golang.org/protobuf/internal/filedesc"15"google.golang.org/protobuf/internal/flags"16"google.golang.org/protobuf/internal/genid"17"google.golang.org/protobuf/internal/order"18"google.golang.org/protobuf/internal/pragma"19"google.golang.org/protobuf/proto"20"google.golang.org/protobuf/reflect/protoreflect"21"google.golang.org/protobuf/reflect/protoregistry"22)
23
24const defaultIndent = " "25
26// Format formats the message as a multiline string.
27// This function is only intended for human consumption and ignores errors.
28// Do not depend on the output being stable. It may change over time across
29// different versions of the program.
30func Format(m proto.Message) string {31return MarshalOptions{Multiline: true}.Format(m)32}
33
34// Marshal writes the given [proto.Message] in JSON format using default options.
35// Do not depend on the output being stable. It may change over time across
36// different versions of the program.
37func Marshal(m proto.Message) ([]byte, error) {38return MarshalOptions{}.Marshal(m)39}
40
41// MarshalOptions is a configurable JSON format marshaler.
42type MarshalOptions struct {43pragma.NoUnkeyedLiterals44
45// Multiline specifies whether the marshaler should format the output in46// indented-form with every textual element on a new line.47// If Indent is an empty string, then an arbitrary indent is chosen.48Multiline bool49
50// Indent specifies the set of indentation characters to use in a multiline51// formatted output such that every entry is preceded by Indent and52// terminated by a newline. If non-empty, then Multiline is treated as true.53// Indent can only be composed of space or tab characters.54Indent string55
56// AllowPartial allows messages that have missing required fields to marshal57// without returning an error. If AllowPartial is false (the default),58// Marshal will return error if there are any missing required fields.59AllowPartial bool60
61// UseProtoNames uses proto field name instead of lowerCamelCase name in JSON62// field names.63UseProtoNames bool64
65// UseEnumNumbers emits enum values as numbers.66UseEnumNumbers bool67
68// EmitUnpopulated specifies whether to emit unpopulated fields. It does not69// emit unpopulated oneof fields or unpopulated extension fields.70// The JSON value emitted for unpopulated fields are as follows:71// ╔═══════╤════════════════════════════╗72// ║ JSON │ Protobuf field ║73// ╠═══════╪════════════════════════════╣74// ║ false │ proto3 boolean fields ║75// ║ 0 │ proto3 numeric fields ║76// ║ "" │ proto3 string/bytes fields ║77// ║ null │ proto2 scalar fields ║78// ║ null │ message fields ║79// ║ [] │ list fields ║80// ║ {} │ map fields ║81// ╚═══════╧════════════════════════════╝82EmitUnpopulated bool83
84// EmitDefaultValues specifies whether to emit default-valued primitive fields,85// empty lists, and empty maps. The fields affected are as follows:86// ╔═══════╤════════════════════════════════════════╗87// ║ JSON │ Protobuf field ║88// ╠═══════╪════════════════════════════════════════╣89// ║ false │ non-optional scalar boolean fields ║90// ║ 0 │ non-optional scalar numeric fields ║91// ║ "" │ non-optional scalar string/byte fields ║92// ║ [] │ empty repeated fields ║93// ║ {} │ empty map fields ║94// ╚═══════╧════════════════════════════════════════╝95//96// Behaves similarly to EmitUnpopulated, but does not emit "null"-value fields,97// i.e. presence-sensing fields that are omitted will remain omitted to preserve98// presence-sensing.99// EmitUnpopulated takes precedence over EmitDefaultValues since the former generates100// a strict superset of the latter.101EmitDefaultValues bool102
103// Resolver is used for looking up types when expanding google.protobuf.Any104// messages. If nil, this defaults to using protoregistry.GlobalTypes.105Resolver interface {106protoregistry.ExtensionTypeResolver107protoregistry.MessageTypeResolver108}109}
110
111// Format formats the message as a string.
112// This method is only intended for human consumption and ignores errors.
113// Do not depend on the output being stable. It may change over time across
114// different versions of the program.
115func (o MarshalOptions) Format(m proto.Message) string {116if m == nil || !m.ProtoReflect().IsValid() {117return "<nil>" // invalid syntax, but okay since this is for debugging118}119o.AllowPartial = true120b, _ := o.Marshal(m)121return string(b)122}
123
124// Marshal marshals the given [proto.Message] in the JSON format using options in
125// MarshalOptions. Do not depend on the output being stable. It may change over
126// time across different versions of the program.
127func (o MarshalOptions) Marshal(m proto.Message) ([]byte, error) {128return o.marshal(nil, m)129}
130
131// MarshalAppend appends the JSON format encoding of m to b,
132// returning the result.
133func (o MarshalOptions) MarshalAppend(b []byte, m proto.Message) ([]byte, error) {134return o.marshal(b, m)135}
136
137// marshal is a centralized function that all marshal operations go through.
138// For profiling purposes, avoid changing the name of this function or
139// introducing other code paths for marshal that do not go through this.
140func (o MarshalOptions) marshal(b []byte, m proto.Message) ([]byte, error) {141if o.Multiline && o.Indent == "" {142o.Indent = defaultIndent143}144if o.Resolver == nil {145o.Resolver = protoregistry.GlobalTypes146}147
148internalEnc, err := json.NewEncoder(b, o.Indent)149if err != nil {150return nil, err151}152
153// Treat nil message interface as an empty message,154// in which case the output in an empty JSON object.155if m == nil {156return append(b, '{', '}'), nil157}158
159enc := encoder{internalEnc, o}160if err := enc.marshalMessage(m.ProtoReflect(), ""); err != nil {161return nil, err162}163if o.AllowPartial {164return enc.Bytes(), nil165}166return enc.Bytes(), proto.CheckInitialized(m)167}
168
169type encoder struct {170*json.Encoder171opts MarshalOptions
172}
173
174// typeFieldDesc is a synthetic field descriptor used for the "@type" field.
175var typeFieldDesc = func() protoreflect.FieldDescriptor {176var fd filedesc.Field177fd.L0.FullName = "@type"178fd.L0.Index = -1179fd.L1.Cardinality = protoreflect.Optional180fd.L1.Kind = protoreflect.StringKind181return &fd182}()183
184// typeURLFieldRanger wraps a protoreflect.Message and modifies its Range method
185// to additionally iterate over a synthetic field for the type URL.
186type typeURLFieldRanger struct {187order.FieldRanger188typeURL string189}
190
191func (m typeURLFieldRanger) Range(f func(protoreflect.FieldDescriptor, protoreflect.Value) bool) {192if !f(typeFieldDesc, protoreflect.ValueOfString(m.typeURL)) {193return194}195m.FieldRanger.Range(f)196}
197
198// unpopulatedFieldRanger wraps a protoreflect.Message and modifies its Range
199// method to additionally iterate over unpopulated fields.
200type unpopulatedFieldRanger struct {201protoreflect.Message202
203skipNull bool204}
205
206func (m unpopulatedFieldRanger) Range(f func(protoreflect.FieldDescriptor, protoreflect.Value) bool) {207fds := m.Descriptor().Fields()208for i := 0; i < fds.Len(); i++ {209fd := fds.Get(i)210if m.Has(fd) || fd.ContainingOneof() != nil {211continue // ignore populated fields and fields within a oneofs212}213
214v := m.Get(fd)215isProto2Scalar := fd.Syntax() == protoreflect.Proto2 && fd.Default().IsValid()216isSingularMessage := fd.Cardinality() != protoreflect.Repeated && fd.Message() != nil217if isProto2Scalar || isSingularMessage {218if m.skipNull {219continue220}221v = protoreflect.Value{} // use invalid value to emit null222}223if !f(fd, v) {224return225}226}227m.Message.Range(f)228}
229
230// marshalMessage marshals the fields in the given protoreflect.Message.
231// If the typeURL is non-empty, then a synthetic "@type" field is injected
232// containing the URL as the value.
233func (e encoder) marshalMessage(m protoreflect.Message, typeURL string) error {234if !flags.ProtoLegacy && messageset.IsMessageSet(m.Descriptor()) {235return errors.New("no support for proto1 MessageSets")236}237
238if marshal := wellKnownTypeMarshaler(m.Descriptor().FullName()); marshal != nil {239return marshal(e, m)240}241
242e.StartObject()243defer e.EndObject()244
245var fields order.FieldRanger = m246switch {247case e.opts.EmitUnpopulated:248fields = unpopulatedFieldRanger{Message: m, skipNull: false}249case e.opts.EmitDefaultValues:250fields = unpopulatedFieldRanger{Message: m, skipNull: true}251}252if typeURL != "" {253fields = typeURLFieldRanger{fields, typeURL}254}255
256var err error257order.RangeFields(fields, order.IndexNameFieldOrder, func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {258name := fd.JSONName()259if e.opts.UseProtoNames {260name = fd.TextName()261}262
263if err = e.WriteName(name); err != nil {264return false265}266if err = e.marshalValue(v, fd); err != nil {267return false268}269return true270})271return err272}
273
274// marshalValue marshals the given protoreflect.Value.
275func (e encoder) marshalValue(val protoreflect.Value, fd protoreflect.FieldDescriptor) error {276switch {277case fd.IsList():278return e.marshalList(val.List(), fd)279case fd.IsMap():280return e.marshalMap(val.Map(), fd)281default:282return e.marshalSingular(val, fd)283}284}
285
286// marshalSingular marshals the given non-repeated field value. This includes
287// all scalar types, enums, messages, and groups.
288func (e encoder) marshalSingular(val protoreflect.Value, fd protoreflect.FieldDescriptor) error {289if !val.IsValid() {290e.WriteNull()291return nil292}293
294switch kind := fd.Kind(); kind {295case protoreflect.BoolKind:296e.WriteBool(val.Bool())297
298case protoreflect.StringKind:299if e.WriteString(val.String()) != nil {300return errors.InvalidUTF8(string(fd.FullName()))301}302
303case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind:304e.WriteInt(val.Int())305
306case protoreflect.Uint32Kind, protoreflect.Fixed32Kind:307e.WriteUint(val.Uint())308
309case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Uint64Kind,310protoreflect.Sfixed64Kind, protoreflect.Fixed64Kind:311// 64-bit integers are written out as JSON string.312e.WriteString(val.String())313
314case protoreflect.FloatKind:315// Encoder.WriteFloat handles the special numbers NaN and infinites.316e.WriteFloat(val.Float(), 32)317
318case protoreflect.DoubleKind:319// Encoder.WriteFloat handles the special numbers NaN and infinites.320e.WriteFloat(val.Float(), 64)321
322case protoreflect.BytesKind:323e.WriteString(base64.StdEncoding.EncodeToString(val.Bytes()))324
325case protoreflect.EnumKind:326if fd.Enum().FullName() == genid.NullValue_enum_fullname {327e.WriteNull()328} else {329desc := fd.Enum().Values().ByNumber(val.Enum())330if e.opts.UseEnumNumbers || desc == nil {331e.WriteInt(int64(val.Enum()))332} else {333e.WriteString(string(desc.Name()))334}335}336
337case protoreflect.MessageKind, protoreflect.GroupKind:338if err := e.marshalMessage(val.Message(), ""); err != nil {339return err340}341
342default:343panic(fmt.Sprintf("%v has unknown kind: %v", fd.FullName(), kind))344}345return nil346}
347
348// marshalList marshals the given protoreflect.List.
349func (e encoder) marshalList(list protoreflect.List, fd protoreflect.FieldDescriptor) error {350e.StartArray()351defer e.EndArray()352
353for i := 0; i < list.Len(); i++ {354item := list.Get(i)355if err := e.marshalSingular(item, fd); err != nil {356return err357}358}359return nil360}
361
362// marshalMap marshals given protoreflect.Map.
363func (e encoder) marshalMap(mmap protoreflect.Map, fd protoreflect.FieldDescriptor) error {364e.StartObject()365defer e.EndObject()366
367var err error368order.RangeEntries(mmap, order.GenericKeyOrder, func(k protoreflect.MapKey, v protoreflect.Value) bool {369if err = e.WriteName(k.String()); err != nil {370return false371}372if err = e.marshalSingular(v, fd.MapValue()); err != nil {373return false374}375return true376})377return err378}
379