Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add stablejson marshaler #83

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@ pulsar:
proto-gen:
(cd proto; buf generate)
mv proto/cosmos_proto/cosmos.pb.go .
(cd internal; buf generate)


.PHONY: proto_gen pulsar
2 changes: 1 addition & 1 deletion cosmos.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

66 changes: 66 additions & 0 deletions encoding/stablejson/any.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package stablejson

import (
"fmt"
"io"

"github.com/pkg/errors"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
)

const (
typeUrlName protoreflect.Name = "type_url"
valueName protoreflect.Name = "value"
)

func (opts MarshalOptions) marshalAny(message protoreflect.Message, writer io.Writer) error {
fields := message.Descriptor().Fields()
typeUrlField := fields.ByName(typeUrlName)
if typeUrlField == nil {
return fmt.Errorf("expected type_url field")
}

_, err := writer.Write([]byte("{"))
if err != nil {
return err
}

typeUrl := message.Get(typeUrlField).String()
resolver := opts.Resolver
if resolver == nil {
resolver = protoregistry.GlobalTypes
}

typ, err := resolver.FindMessageByURL(typeUrl)
if err != nil {
return errors.Wrapf(err, "can't resolve type URL %s", typeUrl)
}

_, err = fmt.Fprintf(writer, `"@type_url":%q`, typeUrl)
if err != nil {
return err
}

valueField := fields.ByName(valueName)
if valueField == nil {
return fmt.Errorf("expected value field")
}

valueBz := message.Get(valueField).Bytes()

valueMsg := typ.New()
err = proto.Unmarshal(valueBz, valueMsg.Interface())
if err != nil {
return err
}

err = opts.marshalMessageFields(valueMsg, writer, false)
if err != nil {
return err
}

_, err = writer.Write([]byte("}"))
return err
}
58 changes: 58 additions & 0 deletions encoding/stablejson/duration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package stablejson

import (
"fmt"
io "io"

"google.golang.org/protobuf/reflect/protoreflect"
)

func marshalDuration(writer io.Writer, message protoreflect.Message) error {
// PROTO3 SPEC:
// Generated output always contains 0, 3, 6, or 9 fractional digits, depending on required precision, followed by
// the suffix "s". Accepted are any fractional digits (also none) as long as they fit into nano-seconds precision
// and the suffix "s" is required.

fields := message.Descriptor().Fields()
secondsField := fields.ByName(secondsName)
if secondsField == nil {
return fmt.Errorf("expected seconds field")
}

seconds := message.Get(secondsField).Int()
_, err := fmt.Fprintf(writer, `"%d`, seconds)
if err != nil {
return err
}

nanosField := fields.ByName(nanosName)
if nanosField == nil {
return fmt.Errorf("expected nanos field")
}

nanos := message.Get(nanosField).Int()
if nanos != 0 {
if nanos > 0 {
if seconds < 0 {
return fmt.Errorf("seconds and nanos must be of the same sign for duration %v", message)
}

_, err := fmt.Fprintf(writer, `.%d`, nanos)
if err != nil {
return err
}
} else {
if seconds > 0 {
return fmt.Errorf("seconds and nanos must be of the same sign for duration %v", message)
}

_, err := fmt.Fprintf(writer, ".%d", -nanos)
if err != nil {
return err
}
}
}

_, err = writer.Write([]byte(`s"`))
return err
}
24 changes: 24 additions & 0 deletions encoding/stablejson/enum.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package stablejson

import (
"fmt"
"io"

"google.golang.org/protobuf/reflect/protoreflect"
)

func (opts MarshalOptions) marshalEnum(fieldDescriptor protoreflect.FieldDescriptor, value protoreflect.EnumNumber, writer io.Writer) error {
enumDescriptor := fieldDescriptor.Enum()
if enumDescriptor == nil {
return fmt.Errorf("expected enum descriptor for %s", fieldDescriptor.FullName())
}

enumValueDescriptor := enumDescriptor.Values().ByNumber(value)
var err error
if enumValueDescriptor != nil {
_, err = fmt.Fprintf(writer, "%q", enumValueDescriptor.Name())
} else {
_, err = fmt.Fprintf(writer, "%d", value)
}
return err
}
29 changes: 29 additions & 0 deletions encoding/stablejson/fieldmask.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package stablejson

import (
"fmt"
io "io"
"strings"

"google.golang.org/protobuf/reflect/protoreflect"
)

const (
pathsName protoreflect.Name = "paths"
)

func marshalFieldMask(writer io.Writer, value protoreflect.Message) error {
field := value.Descriptor().Fields().ByName(pathsName)
if field == nil {
return fmt.Errorf("expected to find field %s", pathsName)
}

paths := value.Get(field).List()
n := paths.Len()
strs := make([]string, n)
for i := 0; i < n; i++ {
strs[i] = paths.Get(i).String()
}
_, _ = fmt.Fprintf(writer, "%q", strings.Join(strs, ","))
return nil
}
25 changes: 25 additions & 0 deletions encoding/stablejson/float.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package stablejson

import (
"io"
"math"
"strconv"
)

func marshalFloat(writer io.Writer, x float64) error {
// PROTO3 SPEC:
// JSON value will be a number or one of the special string values "NaN", "Infinity", and "-Infinity".
// Either numbers or strings are accepted. Exponent notation is also accepted.
// -0 is considered equivalent to 0.
var err error
if math.IsInf(x, -1) {
_, err = writer.Write([]byte("-Infinity"))
} else if math.IsInf(x, 1) {
_, err = writer.Write([]byte("Infinity"))
} else if math.IsNaN(x) {
_, err = writer.Write([]byte("NaN"))
} else {
_, err = writer.Write([]byte(strconv.FormatFloat(x, 'f', -1, 64)))
}
return err
}
9 changes: 9 additions & 0 deletions encoding/stablejson/internal/buf.gen.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
version: v1
managed:
enabled: true
go_package_prefix:
default: github.com/cosmos/cosmos-proto/internal
plugins:
- name: go
out: .
opt: paths=source_relative
9 changes: 9 additions & 0 deletions encoding/stablejson/internal/buf.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
version: v1
lint:
use:
- DEFAULT
except:
- PACKAGE_VERSION_SUFFIX
breaking:
ignore:
- testpb
Loading