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 msgfmt implements a text marshaler combining the desirable features 6// of both the JSON and proto text formats. 7// It is optimized for human readability and has no associated deserializer. 8package msgfmt 9 10import ( 11 "bytes" 12 "fmt" 13 "reflect" 14 "sort" 15 "strconv" 16 "strings" 17 "time" 18 19 "google.golang.org/protobuf/encoding/protowire" 20 "google.golang.org/protobuf/internal/detrand" 21 "google.golang.org/protobuf/internal/genid" 22 "google.golang.org/protobuf/internal/order" 23 "google.golang.org/protobuf/proto" 24 "google.golang.org/protobuf/reflect/protoreflect" 25 "google.golang.org/protobuf/reflect/protoregistry" 26) 27 28// Format returns a formatted string for the message. 29func Format(m proto.Message) string { 30 return string(appendMessage(nil, m.ProtoReflect())) 31} 32 33// FormatValue returns a formatted string for an arbitrary value. 34func FormatValue(v protoreflect.Value, fd protoreflect.FieldDescriptor) string { 35 return string(appendValue(nil, v, fd)) 36} 37 38func appendValue(b []byte, v protoreflect.Value, fd protoreflect.FieldDescriptor) []byte { 39 switch v := v.Interface().(type) { 40 case nil: 41 return append(b, "<invalid>"...) 42 case bool, int32, int64, uint32, uint64, float32, float64: 43 return append(b, fmt.Sprint(v)...) 44 case string: 45 return append(b, strconv.Quote(string(v))...) 46 case []byte: 47 return append(b, strconv.Quote(string(v))...) 48 case protoreflect.EnumNumber: 49 return appendEnum(b, v, fd) 50 case protoreflect.Message: 51 return appendMessage(b, v) 52 case protoreflect.List: 53 return appendList(b, v, fd) 54 case protoreflect.Map: 55 return appendMap(b, v, fd) 56 default: 57 panic(fmt.Sprintf("invalid type: %T", v)) 58 } 59} 60 61func appendEnum(b []byte, v protoreflect.EnumNumber, fd protoreflect.FieldDescriptor) []byte { 62 if fd != nil { 63 if ev := fd.Enum().Values().ByNumber(v); ev != nil { 64 return append(b, ev.Name()...) 65 } 66 } 67 return strconv.AppendInt(b, int64(v), 10) 68} 69 70func appendMessage(b []byte, m protoreflect.Message) []byte { 71 if b2 := appendKnownMessage(b, m); b2 != nil { 72 return b2 73 } 74 75 b = append(b, '{') 76 order.RangeFields(m, order.IndexNameFieldOrder, func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool { 77 b = append(b, fd.TextName()...) 78 b = append(b, ':') 79 b = appendValue(b, v, fd) 80 b = append(b, delim()...) 81 return true 82 }) 83 b = appendUnknown(b, m.GetUnknown()) 84 b = bytes.TrimRight(b, delim()) 85 b = append(b, '}') 86 return b 87} 88 89var protocmpMessageType = reflect.TypeOf(map[string]interface{}(nil)) 90 91func appendKnownMessage(b []byte, m protoreflect.Message) []byte { 92 md := m.Descriptor() 93 fds := md.Fields() 94 switch md.FullName() { 95 case genid.Any_message_fullname: 96 var msgVal protoreflect.Message 97 url := m.Get(fds.ByNumber(genid.Any_TypeUrl_field_number)).String() 98 if v := reflect.ValueOf(m); v.Type().ConvertibleTo(protocmpMessageType) { 99 // For protocmp.Message, directly obtain the sub-message value 100 // which is stored in structured form, rather than as raw bytes. 101 m2 := v.Convert(protocmpMessageType).Interface().(map[string]interface{}) 102 v, ok := m2[string(genid.Any_Value_field_name)].(proto.Message) 103 if !ok { 104 return nil 105 } 106 msgVal = v.ProtoReflect() 107 } else { 108 val := m.Get(fds.ByNumber(genid.Any_Value_field_number)).Bytes() 109 mt, err := protoregistry.GlobalTypes.FindMessageByURL(url) 110 if err != nil { 111 return nil 112 } 113 msgVal = mt.New() 114 err = proto.UnmarshalOptions{AllowPartial: true}.Unmarshal(val, msgVal.Interface()) 115 if err != nil { 116 return nil 117 } 118 } 119 120 b = append(b, '{') 121 b = append(b, "["+url+"]"...) 122 b = append(b, ':') 123 b = appendMessage(b, msgVal) 124 b = append(b, '}') 125 return b 126 127 case genid.Timestamp_message_fullname: 128 secs := m.Get(fds.ByNumber(genid.Timestamp_Seconds_field_number)).Int() 129 nanos := m.Get(fds.ByNumber(genid.Timestamp_Nanos_field_number)).Int() 130 if nanos < 0 || nanos >= 1e9 { 131 return nil 132 } 133 t := time.Unix(secs, nanos).UTC() 134 x := t.Format("2006-01-02T15:04:05.000000000") // RFC 3339 135 x = strings.TrimSuffix(x, "000") 136 x = strings.TrimSuffix(x, "000") 137 x = strings.TrimSuffix(x, ".000") 138 return append(b, x+"Z"...) 139 140 case genid.Duration_message_fullname: 141 sign := "" 142 secs := m.Get(fds.ByNumber(genid.Duration_Seconds_field_number)).Int() 143 nanos := m.Get(fds.ByNumber(genid.Duration_Nanos_field_number)).Int() 144 if nanos <= -1e9 || nanos >= 1e9 || (secs > 0 && nanos < 0) || (secs < 0 && nanos > 0) { 145 return nil 146 } 147 if secs < 0 || nanos < 0 { 148 sign, secs, nanos = "-", -1*secs, -1*nanos 149 } 150 x := fmt.Sprintf("%s%d.%09d", sign, secs, nanos) 151 x = strings.TrimSuffix(x, "000") 152 x = strings.TrimSuffix(x, "000") 153 x = strings.TrimSuffix(x, ".000") 154 return append(b, x+"s"...) 155 156 case genid.BoolValue_message_fullname, 157 genid.Int32Value_message_fullname, 158 genid.Int64Value_message_fullname, 159 genid.UInt32Value_message_fullname, 160 genid.UInt64Value_message_fullname, 161 genid.FloatValue_message_fullname, 162 genid.DoubleValue_message_fullname, 163 genid.StringValue_message_fullname, 164 genid.BytesValue_message_fullname: 165 fd := fds.ByNumber(genid.WrapperValue_Value_field_number) 166 return appendValue(b, m.Get(fd), fd) 167 } 168 169 return nil 170} 171 172func appendUnknown(b []byte, raw protoreflect.RawFields) []byte { 173 rs := make(map[protoreflect.FieldNumber][]protoreflect.RawFields) 174 for len(raw) > 0 { 175 num, _, n := protowire.ConsumeField(raw) 176 rs[num] = append(rs[num], raw[:n]) 177 raw = raw[n:] 178 } 179 180 var ns []protoreflect.FieldNumber 181 for n := range rs { 182 ns = append(ns, n) 183 } 184 sort.Slice(ns, func(i, j int) bool { return ns[i] < ns[j] }) 185 186 for _, n := range ns { 187 var leftBracket, rightBracket string 188 if len(rs[n]) > 1 { 189 leftBracket, rightBracket = "[", "]" 190 } 191 192 b = strconv.AppendInt(b, int64(n), 10) 193 b = append(b, ':') 194 b = append(b, leftBracket...) 195 for _, r := range rs[n] { 196 num, typ, n := protowire.ConsumeTag(r) 197 r = r[n:] 198 switch typ { 199 case protowire.VarintType: 200 v, _ := protowire.ConsumeVarint(r) 201 b = strconv.AppendInt(b, int64(v), 10) 202 case protowire.Fixed32Type: 203 v, _ := protowire.ConsumeFixed32(r) 204 b = append(b, fmt.Sprintf("0x%08x", v)...) 205 case protowire.Fixed64Type: 206 v, _ := protowire.ConsumeFixed64(r) 207 b = append(b, fmt.Sprintf("0x%016x", v)...) 208 case protowire.BytesType: 209 v, _ := protowire.ConsumeBytes(r) 210 b = strconv.AppendQuote(b, string(v)) 211 case protowire.StartGroupType: 212 v, _ := protowire.ConsumeGroup(num, r) 213 b = append(b, '{') 214 b = appendUnknown(b, v) 215 b = bytes.TrimRight(b, delim()) 216 b = append(b, '}') 217 default: 218 panic(fmt.Sprintf("invalid type: %v", typ)) 219 } 220 b = append(b, delim()...) 221 } 222 b = bytes.TrimRight(b, delim()) 223 b = append(b, rightBracket...) 224 b = append(b, delim()...) 225 } 226 return b 227} 228 229func appendList(b []byte, v protoreflect.List, fd protoreflect.FieldDescriptor) []byte { 230 b = append(b, '[') 231 for i := 0; i < v.Len(); i++ { 232 b = appendValue(b, v.Get(i), fd) 233 b = append(b, delim()...) 234 } 235 b = bytes.TrimRight(b, delim()) 236 b = append(b, ']') 237 return b 238} 239 240func appendMap(b []byte, v protoreflect.Map, fd protoreflect.FieldDescriptor) []byte { 241 b = append(b, '{') 242 order.RangeEntries(v, order.GenericKeyOrder, func(k protoreflect.MapKey, v protoreflect.Value) bool { 243 b = appendValue(b, k.Value(), fd.MapKey()) 244 b = append(b, ':') 245 b = appendValue(b, v, fd.MapValue()) 246 b = append(b, delim()...) 247 return true 248 }) 249 b = bytes.TrimRight(b, delim()) 250 b = append(b, '}') 251 return b 252} 253 254func delim() string { 255 // Deliberately introduce instability into the message string to 256 // discourage users from depending on it. 257 if detrand.Bool() { 258 return " " 259 } 260 return ", " 261} 262