mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-11-01 23:10:01 +00:00
560 lines
14 KiB
Go
560 lines
14 KiB
Go
// Copyright 2015 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package jsonpb
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"reflect"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
"google.golang.org/protobuf/encoding/protojson"
|
|
protoV2 "google.golang.org/protobuf/proto"
|
|
"google.golang.org/protobuf/reflect/protoreflect"
|
|
"google.golang.org/protobuf/reflect/protoregistry"
|
|
)
|
|
|
|
const wrapJSONMarshalV2 = false
|
|
|
|
// Marshaler is a configurable object for marshaling protocol buffer messages
|
|
// to the specified JSON representation.
|
|
type Marshaler struct {
|
|
// OrigName specifies whether to use the original protobuf name for fields.
|
|
OrigName bool
|
|
|
|
// EnumsAsInts specifies whether to render enum values as integers,
|
|
// as opposed to string values.
|
|
EnumsAsInts bool
|
|
|
|
// EmitDefaults specifies whether to render fields with zero values.
|
|
EmitDefaults bool
|
|
|
|
// Indent controls whether the output is compact or not.
|
|
// If empty, the output is compact JSON. Otherwise, every JSON object
|
|
// entry and JSON array value will be on its own line.
|
|
// Each line will be preceded by repeated copies of Indent, where the
|
|
// number of copies is the current indentation depth.
|
|
Indent string
|
|
|
|
// AnyResolver is used to resolve the google.protobuf.Any well-known type.
|
|
// If unset, the global registry is used by default.
|
|
AnyResolver AnyResolver
|
|
}
|
|
|
|
// JSONPBMarshaler is implemented by protobuf messages that customize the
|
|
// way they are marshaled to JSON. Messages that implement this should also
|
|
// implement JSONPBUnmarshaler so that the custom format can be parsed.
|
|
//
|
|
// The JSON marshaling must follow the proto to JSON specification:
|
|
// https://developers.google.com/protocol-buffers/docs/proto3#json
|
|
//
|
|
// Deprecated: Custom types should implement protobuf reflection instead.
|
|
type JSONPBMarshaler interface {
|
|
MarshalJSONPB(*Marshaler) ([]byte, error)
|
|
}
|
|
|
|
// Marshal serializes a protobuf message as JSON into w.
|
|
func (jm *Marshaler) Marshal(w io.Writer, m proto.Message) error {
|
|
b, err := jm.marshal(m)
|
|
if len(b) > 0 {
|
|
if _, err := w.Write(b); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
// MarshalToString serializes a protobuf message as JSON in string form.
|
|
func (jm *Marshaler) MarshalToString(m proto.Message) (string, error) {
|
|
b, err := jm.marshal(m)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(b), nil
|
|
}
|
|
|
|
func (jm *Marshaler) marshal(m proto.Message) ([]byte, error) {
|
|
v := reflect.ValueOf(m)
|
|
if m == nil || (v.Kind() == reflect.Ptr && v.IsNil()) {
|
|
return nil, errors.New("Marshal called with nil")
|
|
}
|
|
|
|
// Check for custom marshalers first since they may not properly
|
|
// implement protobuf reflection that the logic below relies on.
|
|
if jsm, ok := m.(JSONPBMarshaler); ok {
|
|
return jsm.MarshalJSONPB(jm)
|
|
}
|
|
|
|
if wrapJSONMarshalV2 {
|
|
opts := protojson.MarshalOptions{
|
|
UseProtoNames: jm.OrigName,
|
|
UseEnumNumbers: jm.EnumsAsInts,
|
|
EmitUnpopulated: jm.EmitDefaults,
|
|
Indent: jm.Indent,
|
|
}
|
|
if jm.AnyResolver != nil {
|
|
opts.Resolver = anyResolver{jm.AnyResolver}
|
|
}
|
|
return opts.Marshal(proto.MessageReflect(m).Interface())
|
|
} else {
|
|
// Check for unpopulated required fields first.
|
|
m2 := proto.MessageReflect(m)
|
|
if err := protoV2.CheckInitialized(m2.Interface()); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
w := jsonWriter{Marshaler: jm}
|
|
err := w.marshalMessage(m2, "", "")
|
|
return w.buf, err
|
|
}
|
|
}
|
|
|
|
type jsonWriter struct {
|
|
*Marshaler
|
|
buf []byte
|
|
}
|
|
|
|
func (w *jsonWriter) write(s string) {
|
|
w.buf = append(w.buf, s...)
|
|
}
|
|
|
|
func (w *jsonWriter) marshalMessage(m protoreflect.Message, indent, typeURL string) error {
|
|
if jsm, ok := proto.MessageV1(m.Interface()).(JSONPBMarshaler); ok {
|
|
b, err := jsm.MarshalJSONPB(w.Marshaler)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if typeURL != "" {
|
|
// we are marshaling this object to an Any type
|
|
var js map[string]*json.RawMessage
|
|
if err = json.Unmarshal(b, &js); err != nil {
|
|
return fmt.Errorf("type %T produced invalid JSON: %v", m.Interface(), err)
|
|
}
|
|
turl, err := json.Marshal(typeURL)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal type URL %q to JSON: %v", typeURL, err)
|
|
}
|
|
js["@type"] = (*json.RawMessage)(&turl)
|
|
if b, err = json.Marshal(js); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
w.write(string(b))
|
|
return nil
|
|
}
|
|
|
|
md := m.Descriptor()
|
|
fds := md.Fields()
|
|
|
|
// Handle well-known types.
|
|
const secondInNanos = int64(time.Second / time.Nanosecond)
|
|
switch wellKnownType(md.FullName()) {
|
|
case "Any":
|
|
return w.marshalAny(m, indent)
|
|
case "BoolValue", "BytesValue", "StringValue",
|
|
"Int32Value", "UInt32Value", "FloatValue",
|
|
"Int64Value", "UInt64Value", "DoubleValue":
|
|
fd := fds.ByNumber(1)
|
|
return w.marshalValue(fd, m.Get(fd), indent)
|
|
case "Duration":
|
|
const maxSecondsInDuration = 315576000000
|
|
// "Generated output always contains 0, 3, 6, or 9 fractional digits,
|
|
// depending on required precision."
|
|
s := m.Get(fds.ByNumber(1)).Int()
|
|
ns := m.Get(fds.ByNumber(2)).Int()
|
|
if s < -maxSecondsInDuration || s > maxSecondsInDuration {
|
|
return fmt.Errorf("seconds out of range %v", s)
|
|
}
|
|
if ns <= -secondInNanos || ns >= secondInNanos {
|
|
return fmt.Errorf("ns out of range (%v, %v)", -secondInNanos, secondInNanos)
|
|
}
|
|
if (s > 0 && ns < 0) || (s < 0 && ns > 0) {
|
|
return errors.New("signs of seconds and nanos do not match")
|
|
}
|
|
var sign string
|
|
if s < 0 || ns < 0 {
|
|
sign, s, ns = "-", -1*s, -1*ns
|
|
}
|
|
x := fmt.Sprintf("%s%d.%09d", sign, s, ns)
|
|
x = strings.TrimSuffix(x, "000")
|
|
x = strings.TrimSuffix(x, "000")
|
|
x = strings.TrimSuffix(x, ".000")
|
|
w.write(fmt.Sprintf(`"%vs"`, x))
|
|
return nil
|
|
case "Timestamp":
|
|
// "RFC 3339, where generated output will always be Z-normalized
|
|
// and uses 0, 3, 6 or 9 fractional digits."
|
|
s := m.Get(fds.ByNumber(1)).Int()
|
|
ns := m.Get(fds.ByNumber(2)).Int()
|
|
if ns < 0 || ns >= secondInNanos {
|
|
return fmt.Errorf("ns out of range [0, %v)", secondInNanos)
|
|
}
|
|
t := time.Unix(s, ns).UTC()
|
|
// time.RFC3339Nano isn't exactly right (we need to get 3/6/9 fractional digits).
|
|
x := t.Format("2006-01-02T15:04:05.000000000")
|
|
x = strings.TrimSuffix(x, "000")
|
|
x = strings.TrimSuffix(x, "000")
|
|
x = strings.TrimSuffix(x, ".000")
|
|
w.write(fmt.Sprintf(`"%vZ"`, x))
|
|
return nil
|
|
case "Value":
|
|
// JSON value; which is a null, number, string, bool, object, or array.
|
|
od := md.Oneofs().Get(0)
|
|
fd := m.WhichOneof(od)
|
|
if fd == nil {
|
|
return errors.New("nil Value")
|
|
}
|
|
return w.marshalValue(fd, m.Get(fd), indent)
|
|
case "Struct", "ListValue":
|
|
// JSON object or array.
|
|
fd := fds.ByNumber(1)
|
|
return w.marshalValue(fd, m.Get(fd), indent)
|
|
}
|
|
|
|
w.write("{")
|
|
if w.Indent != "" {
|
|
w.write("\n")
|
|
}
|
|
|
|
firstField := true
|
|
if typeURL != "" {
|
|
if err := w.marshalTypeURL(indent, typeURL); err != nil {
|
|
return err
|
|
}
|
|
firstField = false
|
|
}
|
|
|
|
for i := 0; i < fds.Len(); {
|
|
fd := fds.Get(i)
|
|
if od := fd.ContainingOneof(); od != nil {
|
|
fd = m.WhichOneof(od)
|
|
i += od.Fields().Len()
|
|
if fd == nil {
|
|
continue
|
|
}
|
|
} else {
|
|
i++
|
|
}
|
|
|
|
v := m.Get(fd)
|
|
|
|
if !m.Has(fd) {
|
|
if !w.EmitDefaults || fd.ContainingOneof() != nil {
|
|
continue
|
|
}
|
|
if fd.Cardinality() != protoreflect.Repeated && (fd.Message() != nil || fd.Syntax() == protoreflect.Proto2) {
|
|
v = protoreflect.Value{} // use "null" for singular messages or proto2 scalars
|
|
}
|
|
}
|
|
|
|
if !firstField {
|
|
w.writeComma()
|
|
}
|
|
if err := w.marshalField(fd, v, indent); err != nil {
|
|
return err
|
|
}
|
|
firstField = false
|
|
}
|
|
|
|
// Handle proto2 extensions.
|
|
if md.ExtensionRanges().Len() > 0 {
|
|
// Collect a sorted list of all extension descriptor and values.
|
|
type ext struct {
|
|
desc protoreflect.FieldDescriptor
|
|
val protoreflect.Value
|
|
}
|
|
var exts []ext
|
|
m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
|
|
if fd.IsExtension() {
|
|
exts = append(exts, ext{fd, v})
|
|
}
|
|
return true
|
|
})
|
|
sort.Slice(exts, func(i, j int) bool {
|
|
return exts[i].desc.Number() < exts[j].desc.Number()
|
|
})
|
|
|
|
for _, ext := range exts {
|
|
if !firstField {
|
|
w.writeComma()
|
|
}
|
|
if err := w.marshalField(ext.desc, ext.val, indent); err != nil {
|
|
return err
|
|
}
|
|
firstField = false
|
|
}
|
|
}
|
|
|
|
if w.Indent != "" {
|
|
w.write("\n")
|
|
w.write(indent)
|
|
}
|
|
w.write("}")
|
|
return nil
|
|
}
|
|
|
|
func (w *jsonWriter) writeComma() {
|
|
if w.Indent != "" {
|
|
w.write(",\n")
|
|
} else {
|
|
w.write(",")
|
|
}
|
|
}
|
|
|
|
func (w *jsonWriter) marshalAny(m protoreflect.Message, indent string) error {
|
|
// "If the Any contains a value that has a special JSON mapping,
|
|
// it will be converted as follows: {"@type": xxx, "value": yyy}.
|
|
// Otherwise, the value will be converted into a JSON object,
|
|
// and the "@type" field will be inserted to indicate the actual data type."
|
|
md := m.Descriptor()
|
|
typeURL := m.Get(md.Fields().ByNumber(1)).String()
|
|
rawVal := m.Get(md.Fields().ByNumber(2)).Bytes()
|
|
|
|
var m2 protoreflect.Message
|
|
if w.AnyResolver != nil {
|
|
mi, err := w.AnyResolver.Resolve(typeURL)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
m2 = proto.MessageReflect(mi)
|
|
} else {
|
|
mt, err := protoregistry.GlobalTypes.FindMessageByURL(typeURL)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
m2 = mt.New()
|
|
}
|
|
|
|
if err := protoV2.Unmarshal(rawVal, m2.Interface()); err != nil {
|
|
return err
|
|
}
|
|
|
|
if wellKnownType(m2.Descriptor().FullName()) == "" {
|
|
return w.marshalMessage(m2, indent, typeURL)
|
|
}
|
|
|
|
w.write("{")
|
|
if w.Indent != "" {
|
|
w.write("\n")
|
|
}
|
|
if err := w.marshalTypeURL(indent, typeURL); err != nil {
|
|
return err
|
|
}
|
|
w.writeComma()
|
|
if w.Indent != "" {
|
|
w.write(indent)
|
|
w.write(w.Indent)
|
|
w.write(`"value": `)
|
|
} else {
|
|
w.write(`"value":`)
|
|
}
|
|
if err := w.marshalMessage(m2, indent+w.Indent, ""); err != nil {
|
|
return err
|
|
}
|
|
if w.Indent != "" {
|
|
w.write("\n")
|
|
w.write(indent)
|
|
}
|
|
w.write("}")
|
|
return nil
|
|
}
|
|
|
|
func (w *jsonWriter) marshalTypeURL(indent, typeURL string) error {
|
|
if w.Indent != "" {
|
|
w.write(indent)
|
|
w.write(w.Indent)
|
|
}
|
|
w.write(`"@type":`)
|
|
if w.Indent != "" {
|
|
w.write(" ")
|
|
}
|
|
b, err := json.Marshal(typeURL)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
w.write(string(b))
|
|
return nil
|
|
}
|
|
|
|
// marshalField writes field description and value to the Writer.
|
|
func (w *jsonWriter) marshalField(fd protoreflect.FieldDescriptor, v protoreflect.Value, indent string) error {
|
|
if w.Indent != "" {
|
|
w.write(indent)
|
|
w.write(w.Indent)
|
|
}
|
|
w.write(`"`)
|
|
switch {
|
|
case fd.IsExtension():
|
|
// For message set, use the fname of the message as the extension name.
|
|
name := string(fd.FullName())
|
|
if isMessageSet(fd.ContainingMessage()) {
|
|
name = strings.TrimSuffix(name, ".message_set_extension")
|
|
}
|
|
|
|
w.write("[" + name + "]")
|
|
case w.OrigName:
|
|
name := string(fd.Name())
|
|
if fd.Kind() == protoreflect.GroupKind {
|
|
name = string(fd.Message().Name())
|
|
}
|
|
w.write(name)
|
|
default:
|
|
w.write(string(fd.JSONName()))
|
|
}
|
|
w.write(`":`)
|
|
if w.Indent != "" {
|
|
w.write(" ")
|
|
}
|
|
return w.marshalValue(fd, v, indent)
|
|
}
|
|
|
|
func (w *jsonWriter) marshalValue(fd protoreflect.FieldDescriptor, v protoreflect.Value, indent string) error {
|
|
switch {
|
|
case fd.IsList():
|
|
w.write("[")
|
|
comma := ""
|
|
lv := v.List()
|
|
for i := 0; i < lv.Len(); i++ {
|
|
w.write(comma)
|
|
if w.Indent != "" {
|
|
w.write("\n")
|
|
w.write(indent)
|
|
w.write(w.Indent)
|
|
w.write(w.Indent)
|
|
}
|
|
if err := w.marshalSingularValue(fd, lv.Get(i), indent+w.Indent); err != nil {
|
|
return err
|
|
}
|
|
comma = ","
|
|
}
|
|
if w.Indent != "" {
|
|
w.write("\n")
|
|
w.write(indent)
|
|
w.write(w.Indent)
|
|
}
|
|
w.write("]")
|
|
return nil
|
|
case fd.IsMap():
|
|
kfd := fd.MapKey()
|
|
vfd := fd.MapValue()
|
|
mv := v.Map()
|
|
|
|
// Collect a sorted list of all map keys and values.
|
|
type entry struct{ key, val protoreflect.Value }
|
|
var entries []entry
|
|
mv.Range(func(k protoreflect.MapKey, v protoreflect.Value) bool {
|
|
entries = append(entries, entry{k.Value(), v})
|
|
return true
|
|
})
|
|
sort.Slice(entries, func(i, j int) bool {
|
|
switch kfd.Kind() {
|
|
case protoreflect.BoolKind:
|
|
return !entries[i].key.Bool() && entries[j].key.Bool()
|
|
case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind, protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind:
|
|
return entries[i].key.Int() < entries[j].key.Int()
|
|
case protoreflect.Uint32Kind, protoreflect.Fixed32Kind, protoreflect.Uint64Kind, protoreflect.Fixed64Kind:
|
|
return entries[i].key.Uint() < entries[j].key.Uint()
|
|
case protoreflect.StringKind:
|
|
return entries[i].key.String() < entries[j].key.String()
|
|
default:
|
|
panic("invalid kind")
|
|
}
|
|
})
|
|
|
|
w.write(`{`)
|
|
comma := ""
|
|
for _, entry := range entries {
|
|
w.write(comma)
|
|
if w.Indent != "" {
|
|
w.write("\n")
|
|
w.write(indent)
|
|
w.write(w.Indent)
|
|
w.write(w.Indent)
|
|
}
|
|
|
|
s := fmt.Sprint(entry.key.Interface())
|
|
b, err := json.Marshal(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
w.write(string(b))
|
|
|
|
w.write(`:`)
|
|
if w.Indent != "" {
|
|
w.write(` `)
|
|
}
|
|
|
|
if err := w.marshalSingularValue(vfd, entry.val, indent+w.Indent); err != nil {
|
|
return err
|
|
}
|
|
comma = ","
|
|
}
|
|
if w.Indent != "" {
|
|
w.write("\n")
|
|
w.write(indent)
|
|
w.write(w.Indent)
|
|
}
|
|
w.write(`}`)
|
|
return nil
|
|
default:
|
|
return w.marshalSingularValue(fd, v, indent)
|
|
}
|
|
}
|
|
|
|
func (w *jsonWriter) marshalSingularValue(fd protoreflect.FieldDescriptor, v protoreflect.Value, indent string) error {
|
|
switch {
|
|
case !v.IsValid():
|
|
w.write("null")
|
|
return nil
|
|
case fd.Message() != nil:
|
|
return w.marshalMessage(v.Message(), indent+w.Indent, "")
|
|
case fd.Enum() != nil:
|
|
if fd.Enum().FullName() == "google.protobuf.NullValue" {
|
|
w.write("null")
|
|
return nil
|
|
}
|
|
|
|
vd := fd.Enum().Values().ByNumber(v.Enum())
|
|
if vd == nil || w.EnumsAsInts {
|
|
w.write(strconv.Itoa(int(v.Enum())))
|
|
} else {
|
|
w.write(`"` + string(vd.Name()) + `"`)
|
|
}
|
|
return nil
|
|
default:
|
|
switch v.Interface().(type) {
|
|
case float32, float64:
|
|
switch {
|
|
case math.IsInf(v.Float(), +1):
|
|
w.write(`"Infinity"`)
|
|
return nil
|
|
case math.IsInf(v.Float(), -1):
|
|
w.write(`"-Infinity"`)
|
|
return nil
|
|
case math.IsNaN(v.Float()):
|
|
w.write(`"NaN"`)
|
|
return nil
|
|
}
|
|
case int64, uint64:
|
|
w.write(fmt.Sprintf(`"%d"`, v.Interface()))
|
|
return nil
|
|
}
|
|
|
|
b, err := json.Marshal(v.Interface())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
w.write(string(b))
|
|
return nil
|
|
}
|
|
}
|