blob: ad294553fe03ad15192b6209dbd4fd983d384a63 [file] [log] [blame]
Herbie Ong7b828bc2019-02-08 19:56:24 -08001// 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
Damien Neil5c5b5312019-05-14 12:44:37 -07005package protojson
Herbie Ong7b828bc2019-02-08 19:56:24 -08006
7import (
8 "encoding/base64"
Herbie Ong0b0f4032019-03-18 19:06:15 -07009 "fmt"
Herbie Ong7b828bc2019-02-08 19:56:24 -080010 "sort"
11
Damien Neile89e6242019-05-13 23:55:40 -070012 "google.golang.org/protobuf/internal/encoding/json"
Joe Tsai5ae10aa2019-07-11 18:23:08 -070013 "google.golang.org/protobuf/internal/encoding/messageset"
14 "google.golang.org/protobuf/internal/errors"
15 "google.golang.org/protobuf/internal/flags"
Damien Neile89e6242019-05-13 23:55:40 -070016 "google.golang.org/protobuf/internal/pragma"
17 "google.golang.org/protobuf/proto"
18 pref "google.golang.org/protobuf/reflect/protoreflect"
19 "google.golang.org/protobuf/reflect/protoregistry"
Herbie Ong7b828bc2019-02-08 19:56:24 -080020)
21
Joe Tsai74b14602020-01-06 15:44:09 -080022const defaultIndent = " "
23
24// Format formats the message as a multiline string.
25// This function is only intended for human consumption and ignores errors.
26// Do not depend on the output being stable. It may change over time across
27// different versions of the program.
28func Format(m proto.Message) string {
29 return MarshalOptions{Multiline: true}.Format(m)
30}
31
Herbie Ong7b828bc2019-02-08 19:56:24 -080032// Marshal writes the given proto.Message in JSON format using default options.
Herbie Ong9e356de2019-08-05 11:40:38 +080033// Do not depend on the output being stable. It may change over time across
Herbie Ong582ab3d2019-09-06 15:56:09 -070034// different versions of the program.
Herbie Ong7b828bc2019-02-08 19:56:24 -080035func Marshal(m proto.Message) ([]byte, error) {
36 return MarshalOptions{}.Marshal(m)
37}
38
39// MarshalOptions is a configurable JSON format marshaler.
40type MarshalOptions struct {
41 pragma.NoUnkeyedLiterals
42
Herbie Ong329be5b2019-03-27 14:47:59 -070043 // AllowPartial allows messages that have missing required fields to marshal
44 // without returning an error. If AllowPartial is false (the default),
45 // Marshal will return error if there are any missing required fields.
46 AllowPartial bool
47
Herbie Ong956cd6d2019-09-06 15:17:22 -070048 // UseProtoNames uses proto field name instead of lowerCamelCase name in JSON
49 // field names.
50 UseProtoNames bool
51
Herbie Ong9111f3b2019-09-06 14:35:09 -070052 // UseEnumNumbers emits enum values as numbers.
53 UseEnumNumbers bool
54
Herbie Ong984e5282019-09-06 00:29:48 -070055 // EmitUnpopulated specifies whether to emit unpopulated fields. It does not
56 // emit unpopulated oneof fields or unpopulated extension fields.
57 // The JSON value emitted for unpopulated fields are as follows:
Herbie Ong956cd6d2019-09-06 15:17:22 -070058 // ╔═══════╤════════════════════════════╗
59 // ║ JSON │ Protobuf field ║
60 // ╠═══════╪════════════════════════════╣
61 // ║ false │ proto3 boolean fields ║
62 // ║ 0 │ proto3 numeric fields ║
63 // ║ "" │ proto3 string/bytes fields ║
64 // ║ null │ proto2 scalar fields ║
65 // ║ null │ message fields ║
66 // ║ [] │ list fields ║
67 // ║ {} │ map fields ║
68 // ╚═══════╧════════════════════════════╝
Herbie Ong984e5282019-09-06 00:29:48 -070069 EmitUnpopulated bool
70
Joe Tsai74b14602020-01-06 15:44:09 -080071 // Multiline specifies whether the marshaler should format the output in
72 // indented-form with every textual element on a new line.
73 // If Indent is an empty string, then an arbitrary indent is chosen.
74 Multiline bool
75
76 // Indent specifies the set of indentation characters to use in a multiline
77 // formatted output such that every entry is preceded by Indent and
78 // terminated by a newline. If non-empty, then Multiline is treated as true.
79 // Indent can only be composed of space or tab characters.
Herbie Ong0b0f4032019-03-18 19:06:15 -070080 Indent string
81
Joe Tsai1c283042019-05-14 14:28:19 -070082 // Resolver is used for looking up types when expanding google.protobuf.Any
83 // messages. If nil, this defaults to using protoregistry.GlobalTypes.
84 Resolver interface {
Damien Neil95faac22019-06-19 10:03:37 -070085 protoregistry.ExtensionTypeResolver
Joe Tsai1c283042019-05-14 14:28:19 -070086 protoregistry.MessageTypeResolver
87 }
Herbie Ong7b828bc2019-02-08 19:56:24 -080088}
89
Joe Tsai74b14602020-01-06 15:44:09 -080090// Format formats the message as a string.
91// This method is only intended for human consumption and ignores errors.
92// Do not depend on the output being stable. It may change over time across
93// different versions of the program.
94func (o MarshalOptions) Format(m proto.Message) string {
95 if m == nil || !m.ProtoReflect().IsValid() {
96 return "<nil>" // invalid syntax, but okay since this is for debugging
97 }
98 o.AllowPartial = true
99 b, _ := o.Marshal(m)
100 return string(b)
101}
102
Herbie Ong0b0f4032019-03-18 19:06:15 -0700103// Marshal marshals the given proto.Message in the JSON format using options in
Herbie Ong582ab3d2019-09-06 15:56:09 -0700104// MarshalOptions. Do not depend on the output being stable. It may change over
105// time across different versions of the program.
Herbie Ong7b828bc2019-02-08 19:56:24 -0800106func (o MarshalOptions) Marshal(m proto.Message) ([]byte, error) {
Joe Tsai74b14602020-01-06 15:44:09 -0800107 if o.Multiline && o.Indent == "" {
108 o.Indent = defaultIndent
109 }
110 if o.Resolver == nil {
111 o.Resolver = protoregistry.GlobalTypes
112 }
113
Herbie Ongd2ece132020-01-07 16:45:24 -0800114 internalEnc, err := json.NewEncoder(o.Indent)
Herbie Ong87608a72019-03-06 14:32:24 -0800115 if err != nil {
116 return nil, err
117 }
118
Herbie Ongd2ece132020-01-07 16:45:24 -0800119 enc := encoder{internalEnc, o}
120 if err := enc.marshalMessage(m.ProtoReflect()); err != nil {
Herbie Ong7b828bc2019-02-08 19:56:24 -0800121 return nil, err
122 }
Damien Neil8c86fc52019-06-19 09:28:29 -0700123 if o.AllowPartial {
Herbie Ongd2ece132020-01-07 16:45:24 -0800124 return enc.Bytes(), nil
Damien Neil4686e232019-04-05 13:31:40 -0700125 }
Herbie Ongd2ece132020-01-07 16:45:24 -0800126 return enc.Bytes(), proto.IsInitialized(m)
127}
128
129type encoder struct {
130 *json.Encoder
131 opts MarshalOptions
Herbie Ong87608a72019-03-06 14:32:24 -0800132}
133
134// marshalMessage marshals the given protoreflect.Message.
Herbie Ongd2ece132020-01-07 16:45:24 -0800135func (e encoder) marshalMessage(m pref.Message) error {
Joe Tsai0fc49f82019-05-01 12:29:25 -0700136 if isCustomType(m.Descriptor().FullName()) {
Herbie Ongd2ece132020-01-07 16:45:24 -0800137 return e.marshalCustomType(m)
Herbie Ong0b0f4032019-03-18 19:06:15 -0700138 }
139
Herbie Ongd2ece132020-01-07 16:45:24 -0800140 e.StartObject()
141 defer e.EndObject()
142 if err := e.marshalFields(m); err != nil {
Herbie Ong0b0f4032019-03-18 19:06:15 -0700143 return err
144 }
Herbie Ong87608a72019-03-06 14:32:24 -0800145
Damien Neil8c86fc52019-06-19 09:28:29 -0700146 return nil
Herbie Ong0b0f4032019-03-18 19:06:15 -0700147}
148
149// marshalFields marshals the fields in the given protoreflect.Message.
Herbie Ongd2ece132020-01-07 16:45:24 -0800150func (e encoder) marshalFields(m pref.Message) error {
Joe Tsai5ae10aa2019-07-11 18:23:08 -0700151 messageDesc := m.Descriptor()
Joe Tsai1799d112019-08-08 13:31:59 -0700152 if !flags.ProtoLegacy && messageset.IsMessageSet(messageDesc) {
Joe Tsai5ae10aa2019-07-11 18:23:08 -0700153 return errors.New("no support for proto1 MessageSets")
154 }
155
Herbie Ongf83d5bb2019-03-14 00:01:27 -0700156 // Marshal out known fields.
Joe Tsai5ae10aa2019-07-11 18:23:08 -0700157 fieldDescs := messageDesc.Fields()
Joe Tsaifc5f8c32019-09-17 22:32:53 -0700158 for i := 0; i < fieldDescs.Len(); {
Herbie Ong7b828bc2019-02-08 19:56:24 -0800159 fd := fieldDescs.Get(i)
Joe Tsaifc5f8c32019-09-17 22:32:53 -0700160 if od := fd.ContainingOneof(); od != nil {
161 fd = m.WhichOneof(od)
162 i += od.Fields().Len()
163 if fd == nil {
164 continue // unpopulated oneofs are not affected by EmitUnpopulated
165 }
166 } else {
167 i++
168 }
169
Herbie Ong984e5282019-09-06 00:29:48 -0700170 val := m.Get(fd)
Joe Tsai378c1322019-04-25 23:48:08 -0700171 if !m.Has(fd) {
Herbie Ongd2ece132020-01-07 16:45:24 -0800172 if !e.opts.EmitUnpopulated {
Herbie Ong984e5282019-09-06 00:29:48 -0700173 continue
174 }
175 isProto2Scalar := fd.Syntax() == pref.Proto2 && fd.Default().IsValid()
176 isSingularMessage := fd.Cardinality() != pref.Repeated && fd.Message() != nil
177 if isProto2Scalar || isSingularMessage {
178 // Use invalid value to emit null.
179 val = pref.Value{}
180 }
Herbie Ong7b828bc2019-02-08 19:56:24 -0800181 }
182
Herbie Ong87608a72019-03-06 14:32:24 -0800183 name := fd.JSONName()
Herbie Ongd2ece132020-01-07 16:45:24 -0800184 if e.opts.UseProtoNames {
Herbie Ong956cd6d2019-09-06 15:17:22 -0700185 name = string(fd.Name())
186 // Use type name for group field name.
187 if fd.Kind() == pref.GroupKind {
188 name = string(fd.Message().Name())
189 }
190 }
Herbie Ongd2ece132020-01-07 16:45:24 -0800191 if err := e.WriteName(name); err != nil {
Herbie Ong87608a72019-03-06 14:32:24 -0800192 return err
193 }
Herbie Ongd2ece132020-01-07 16:45:24 -0800194 if err := e.marshalValue(val, fd); err != nil {
Herbie Ong87608a72019-03-06 14:32:24 -0800195 return err
Herbie Ong7b828bc2019-02-08 19:56:24 -0800196 }
197 }
Herbie Ongf83d5bb2019-03-14 00:01:27 -0700198
199 // Marshal out extensions.
Herbie Ongd2ece132020-01-07 16:45:24 -0800200 if err := e.marshalExtensions(m); err != nil {
Herbie Ongf83d5bb2019-03-14 00:01:27 -0700201 return err
202 }
Damien Neil8c86fc52019-06-19 09:28:29 -0700203 return nil
Herbie Ong7b828bc2019-02-08 19:56:24 -0800204}
205
Herbie Ong87608a72019-03-06 14:32:24 -0800206// marshalValue marshals the given protoreflect.Value.
Herbie Ongd2ece132020-01-07 16:45:24 -0800207func (e encoder) marshalValue(val pref.Value, fd pref.FieldDescriptor) error {
Joe Tsaiac31a352019-05-13 14:32:56 -0700208 switch {
209 case fd.IsList():
Herbie Ongd2ece132020-01-07 16:45:24 -0800210 return e.marshalList(val.List(), fd)
Joe Tsaiac31a352019-05-13 14:32:56 -0700211 case fd.IsMap():
Herbie Ongd2ece132020-01-07 16:45:24 -0800212 return e.marshalMap(val.Map(), fd)
Joe Tsaiac31a352019-05-13 14:32:56 -0700213 default:
Herbie Ongd2ece132020-01-07 16:45:24 -0800214 return e.marshalSingular(val, fd)
Herbie Ong7b828bc2019-02-08 19:56:24 -0800215 }
Herbie Ong7b828bc2019-02-08 19:56:24 -0800216}
217
Herbie Ong87608a72019-03-06 14:32:24 -0800218// marshalSingular marshals the given non-repeated field value. This includes
219// all scalar types, enums, messages, and groups.
Herbie Ongd2ece132020-01-07 16:45:24 -0800220func (e encoder) marshalSingular(val pref.Value, fd pref.FieldDescriptor) error {
Herbie Ong984e5282019-09-06 00:29:48 -0700221 if !val.IsValid() {
Herbie Ongd2ece132020-01-07 16:45:24 -0800222 e.WriteNull()
Herbie Ong984e5282019-09-06 00:29:48 -0700223 return nil
224 }
225
Herbie Ong87608a72019-03-06 14:32:24 -0800226 switch kind := fd.Kind(); kind {
227 case pref.BoolKind:
Herbie Ongd2ece132020-01-07 16:45:24 -0800228 e.WriteBool(val.Bool())
Herbie Ong87608a72019-03-06 14:32:24 -0800229
230 case pref.StringKind:
Herbie Ongd2ece132020-01-07 16:45:24 -0800231 if e.WriteString(val.String()) != nil {
232 return errors.InvalidUTF8(string(fd.FullName()))
Herbie Ong87608a72019-03-06 14:32:24 -0800233 }
234
235 case pref.Int32Kind, pref.Sint32Kind, pref.Sfixed32Kind:
Herbie Ongd2ece132020-01-07 16:45:24 -0800236 e.WriteInt(val.Int())
Herbie Ong87608a72019-03-06 14:32:24 -0800237
238 case pref.Uint32Kind, pref.Fixed32Kind:
Herbie Ongd2ece132020-01-07 16:45:24 -0800239 e.WriteUint(val.Uint())
Herbie Ong7b828bc2019-02-08 19:56:24 -0800240
241 case pref.Int64Kind, pref.Sint64Kind, pref.Uint64Kind,
242 pref.Sfixed64Kind, pref.Fixed64Kind:
Herbie Ong87608a72019-03-06 14:32:24 -0800243 // 64-bit integers are written out as JSON string.
Herbie Ongd2ece132020-01-07 16:45:24 -0800244 e.WriteString(val.String())
Herbie Ong7b828bc2019-02-08 19:56:24 -0800245
Herbie Ong87608a72019-03-06 14:32:24 -0800246 case pref.FloatKind:
247 // Encoder.WriteFloat handles the special numbers NaN and infinites.
Herbie Ongd2ece132020-01-07 16:45:24 -0800248 e.WriteFloat(val.Float(), 32)
Herbie Ong87608a72019-03-06 14:32:24 -0800249
250 case pref.DoubleKind:
251 // Encoder.WriteFloat handles the special numbers NaN and infinites.
Herbie Ongd2ece132020-01-07 16:45:24 -0800252 e.WriteFloat(val.Float(), 64)
Herbie Ong7b828bc2019-02-08 19:56:24 -0800253
254 case pref.BytesKind:
Herbie Ongd2ece132020-01-07 16:45:24 -0800255 e.WriteString(base64.StdEncoding.EncodeToString(val.Bytes()))
Herbie Ong7b828bc2019-02-08 19:56:24 -0800256
257 case pref.EnumKind:
Joe Tsaid24bc722019-04-15 23:39:09 -0700258 if fd.Enum().FullName() == "google.protobuf.NullValue" {
Herbie Ongd2ece132020-01-07 16:45:24 -0800259 e.WriteNull()
Herbie Ong87608a72019-03-06 14:32:24 -0800260 } else {
Herbie Ong9111f3b2019-09-06 14:35:09 -0700261 desc := fd.Enum().Values().ByNumber(val.Enum())
Herbie Ongd2ece132020-01-07 16:45:24 -0800262 if e.opts.UseEnumNumbers || desc == nil {
263 e.WriteInt(int64(val.Enum()))
Herbie Ong9111f3b2019-09-06 14:35:09 -0700264 } else {
Herbie Ongd2ece132020-01-07 16:45:24 -0800265 e.WriteString(string(desc.Name()))
Herbie Ong9111f3b2019-09-06 14:35:09 -0700266 }
Herbie Ong7b828bc2019-02-08 19:56:24 -0800267 }
Herbie Ong7b828bc2019-02-08 19:56:24 -0800268
269 case pref.MessageKind, pref.GroupKind:
Herbie Ongd2ece132020-01-07 16:45:24 -0800270 if err := e.marshalMessage(val.Message()); err != nil {
Herbie Ong87608a72019-03-06 14:32:24 -0800271 return err
272 }
Herbie Ong7b828bc2019-02-08 19:56:24 -0800273
Herbie Ong87608a72019-03-06 14:32:24 -0800274 default:
Herbie Ong0b0f4032019-03-18 19:06:15 -0700275 panic(fmt.Sprintf("%v has unknown kind: %v", fd.FullName(), kind))
Herbie Ong87608a72019-03-06 14:32:24 -0800276 }
Damien Neil8c86fc52019-06-19 09:28:29 -0700277 return nil
Herbie Ong7b828bc2019-02-08 19:56:24 -0800278}
279
Herbie Ong87608a72019-03-06 14:32:24 -0800280// marshalList marshals the given protoreflect.List.
Herbie Ongd2ece132020-01-07 16:45:24 -0800281func (e encoder) marshalList(list pref.List, fd pref.FieldDescriptor) error {
282 e.StartArray()
283 defer e.EndArray()
Herbie Ong87608a72019-03-06 14:32:24 -0800284
Herbie Ong87608a72019-03-06 14:32:24 -0800285 for i := 0; i < list.Len(); i++ {
Herbie Ong7b828bc2019-02-08 19:56:24 -0800286 item := list.Get(i)
Herbie Ongd2ece132020-01-07 16:45:24 -0800287 if err := e.marshalSingular(item, fd); err != nil {
Herbie Ong87608a72019-03-06 14:32:24 -0800288 return err
Herbie Ong7b828bc2019-02-08 19:56:24 -0800289 }
Herbie Ong7b828bc2019-02-08 19:56:24 -0800290 }
Damien Neil8c86fc52019-06-19 09:28:29 -0700291 return nil
Herbie Ong7b828bc2019-02-08 19:56:24 -0800292}
293
294type mapEntry struct {
295 key pref.MapKey
296 value pref.Value
297}
298
Herbie Ong87608a72019-03-06 14:32:24 -0800299// marshalMap marshals given protoreflect.Map.
Herbie Ongd2ece132020-01-07 16:45:24 -0800300func (e encoder) marshalMap(mmap pref.Map, fd pref.FieldDescriptor) error {
301 e.StartObject()
302 defer e.EndObject()
Herbie Ong87608a72019-03-06 14:32:24 -0800303
Herbie Ong7b828bc2019-02-08 19:56:24 -0800304 // Get a sorted list based on keyType first.
305 entries := make([]mapEntry, 0, mmap.Len())
306 mmap.Range(func(key pref.MapKey, val pref.Value) bool {
307 entries = append(entries, mapEntry{key: key, value: val})
308 return true
309 })
Joe Tsaiac31a352019-05-13 14:32:56 -0700310 sortMap(fd.MapKey().Kind(), entries)
Herbie Ong7b828bc2019-02-08 19:56:24 -0800311
Herbie Ong87608a72019-03-06 14:32:24 -0800312 // Write out sorted list.
Herbie Ong7b828bc2019-02-08 19:56:24 -0800313 for _, entry := range entries {
Herbie Ongd2ece132020-01-07 16:45:24 -0800314 if err := e.WriteName(entry.key.String()); err != nil {
Herbie Ong87608a72019-03-06 14:32:24 -0800315 return err
Herbie Ong7b828bc2019-02-08 19:56:24 -0800316 }
Herbie Ongd2ece132020-01-07 16:45:24 -0800317 if err := e.marshalSingular(entry.value, fd.MapValue()); err != nil {
Herbie Ong87608a72019-03-06 14:32:24 -0800318 return err
319 }
Herbie Ong7b828bc2019-02-08 19:56:24 -0800320 }
Damien Neil8c86fc52019-06-19 09:28:29 -0700321 return nil
Herbie Ong7b828bc2019-02-08 19:56:24 -0800322}
323
Herbie Ongf83d5bb2019-03-14 00:01:27 -0700324// sortMap orders list based on value of key field for deterministic ordering.
Herbie Ong7b828bc2019-02-08 19:56:24 -0800325func sortMap(keyKind pref.Kind, values []mapEntry) {
Herbie Ongf83d5bb2019-03-14 00:01:27 -0700326 sort.Slice(values, func(i, j int) bool {
327 switch keyKind {
328 case pref.Int32Kind, pref.Sint32Kind, pref.Sfixed32Kind,
329 pref.Int64Kind, pref.Sint64Kind, pref.Sfixed64Kind:
Herbie Ong7b828bc2019-02-08 19:56:24 -0800330 return values[i].key.Int() < values[j].key.Int()
Herbie Ongf83d5bb2019-03-14 00:01:27 -0700331
332 case pref.Uint32Kind, pref.Fixed32Kind,
333 pref.Uint64Kind, pref.Fixed64Kind:
Herbie Ong7b828bc2019-02-08 19:56:24 -0800334 return values[i].key.Uint() < values[j].key.Uint()
335 }
Herbie Ongf83d5bb2019-03-14 00:01:27 -0700336 return values[i].key.String() < values[j].key.String()
337 })
338}
339
340// marshalExtensions marshals extension fields.
Herbie Ongd2ece132020-01-07 16:45:24 -0800341func (e encoder) marshalExtensions(m pref.Message) error {
Joe Tsai378c1322019-04-25 23:48:08 -0700342 type entry struct {
343 key string
344 value pref.Value
345 desc pref.FieldDescriptor
Herbie Ong7b828bc2019-02-08 19:56:24 -0800346 }
Herbie Ongf83d5bb2019-03-14 00:01:27 -0700347
Herbie Ongf83d5bb2019-03-14 00:01:27 -0700348 // Get a sorted list based on field key first.
Joe Tsai378c1322019-04-25 23:48:08 -0700349 var entries []entry
350 m.Range(func(fd pref.FieldDescriptor, v pref.Value) bool {
351 if !fd.IsExtension() {
352 return true
353 }
Joe Tsai378c1322019-04-25 23:48:08 -0700354
Joe Tsai5ae10aa2019-07-11 18:23:08 -0700355 // For MessageSet extensions, the name used is the parent message.
Joe Tsaid4211502019-07-02 14:58:02 -0700356 name := fd.FullName()
Joe Tsai5ae10aa2019-07-11 18:23:08 -0700357 if messageset.IsMessageSetExtension(fd) {
358 name = name.Parent()
Herbie Ongf83d5bb2019-03-14 00:01:27 -0700359 }
360
Joe Tsai378c1322019-04-25 23:48:08 -0700361 // Use [name] format for JSON field name.
362 entries = append(entries, entry{
363 key: string(name),
364 value: v,
365 desc: fd,
366 })
Herbie Ongf83d5bb2019-03-14 00:01:27 -0700367 return true
368 })
369
370 // Sort extensions lexicographically.
371 sort.Slice(entries, func(i, j int) bool {
372 return entries[i].key < entries[j].key
373 })
374
375 // Write out sorted list.
Herbie Ongf83d5bb2019-03-14 00:01:27 -0700376 for _, entry := range entries {
377 // JSON field name is the proto field name enclosed in [], similar to
378 // textproto. This is consistent with Go v1 lib. C++ lib v3.7.0 does not
379 // marshal out extension fields.
Herbie Ongd2ece132020-01-07 16:45:24 -0800380 if err := e.WriteName("[" + entry.key + "]"); err != nil {
Herbie Ongf83d5bb2019-03-14 00:01:27 -0700381 return err
382 }
Herbie Ongd2ece132020-01-07 16:45:24 -0800383 if err := e.marshalValue(entry.value, entry.desc); err != nil {
Herbie Ongf83d5bb2019-03-14 00:01:27 -0700384 return err
385 }
386 }
Damien Neil8c86fc52019-06-19 09:28:29 -0700387 return nil
Herbie Ongf83d5bb2019-03-14 00:01:27 -0700388}