Skip to content

Commit

Permalink
feat: add json encoding (#118)
Browse files Browse the repository at this point in the history
  • Loading branch information
nrwiersma authored Sep 15, 2021
1 parent c88db3c commit bf2e271
Show file tree
Hide file tree
Showing 4 changed files with 399 additions and 18 deletions.
127 changes: 115 additions & 12 deletions schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"sync/atomic"

"github.com/hamba/avro/pkg/crc64"
jsoniter "github.com/json-iterator/go"
"github.com/modern-go/concurrent"
)

Expand Down Expand Up @@ -311,6 +312,11 @@ func (s *PrimitiveSchema) String() string {
return `{"type":"` + string(s.typ) + `",` + s.logical.String() + `}`
}

// MarshalJSON marshals the schema to json.
func (s *PrimitiveSchema) MarshalJSON() ([]byte, error) {
return []byte(s.String()), nil
}

// Fingerprint returns the SHA256 fingerprint of the schema.
func (s *PrimitiveSchema) Fingerprint() [32]byte {
return s.fingerprinter.Fingerprint(s)
Expand Down Expand Up @@ -393,6 +399,26 @@ func (s *RecordSchema) String() string {
return `{"name":"` + s.FullName() + `","type":"` + typ + `","fields":[` + fields + `]}`
}

// MarshalJSON marshals the schema to json.
func (s *RecordSchema) MarshalJSON() ([]byte, error) {
typ := "record"
if s.isError {
typ = "error"
}

ss := struct {
Name string `json:"name"`
Type string `json:"type"`
Fields []*Field `json:"fields"`
}{
Name: s.FullName(),
Type: typ,
Fields: s.fields,
}

return jsoniter.Marshal(ss)
}

// Fingerprint returns the SHA256 fingerprint of the schema.
func (s *RecordSchema) Fingerprint() [32]byte {
return s.fingerprinter.Fingerprint(s)
Expand Down Expand Up @@ -443,34 +469,50 @@ func NewField(name string, typ Schema, def interface{}) (*Field, error) {
}

// Name returns the name of a field.
func (s *Field) Name() string {
return s.name
func (f *Field) Name() string {
return f.name
}

// Type returns the schema of a field.
func (s *Field) Type() Schema {
return s.typ
func (f *Field) Type() Schema {
return f.typ
}

// HasDefault determines if the field has a default value.
func (s *Field) HasDefault() bool {
return s.hasDef
func (f *Field) HasDefault() bool {
return f.hasDef
}

// Default returns the default of a field or nil.
//
// The only time a nil default is valid is for a Null Type.
func (s *Field) Default() interface{} {
if s.def == nullDefault {
func (f *Field) Default() interface{} {
if f.def == nullDefault {
return nil
}

return s.def
return f.def
}

// String returns the canonical form of a field.
func (s *Field) String() string {
return `{"name":"` + s.name + `","type":` + s.typ.String() + `}`
func (f *Field) String() string {
return `{"name":"` + f.name + `","type":` + f.typ.String() + `}`
}

// MarshalJSON marshals the schema to json.
func (f *Field) MarshalJSON() ([]byte, error) {
s := struct {
Name string `json:"name"`
Type Schema `json:"type"`
Default interface{} `json:"default,omitempty"`
}{
Name: f.name,
Type: f.typ,
}
if f.hasDef {
s.Default = f.def
}
return jsoniter.Marshal(s)
}

// EnumSchema is an Avro enum type schema.
Expand All @@ -480,6 +522,7 @@ type EnumSchema struct {
fingerprinter

symbols []string
def string
}

// NewEnumSchema creates a new enum schema instance.
Expand All @@ -493,7 +536,7 @@ func NewEnumSchema(name, namespace string, symbols []string) (*EnumSchema, error
return nil, errors.New("avro: enum must have a non-empty array of symbols")
}
for _, symbol := range symbols {
if err := validateName(symbol); err != nil {
if err = validateName(symbol); err != nil {
return nil, fmt.Errorf("avro: invalid symnol %s", symbol)
}
}
Expand Down Expand Up @@ -528,6 +571,22 @@ func (s *EnumSchema) String() string {
return `{"name":"` + s.FullName() + `","type":"enum","symbols":[` + symbols + `]}`
}

// MarshalJSON marshals the schema to json.
func (s *EnumSchema) MarshalJSON() ([]byte, error) {
ss := struct {
Name string `json:"name"`
Type string `json:"type"`
Symbols []string `json:"symbols"`
Default string `json:"default,omitempty"`
}{
Name: s.FullName(),
Type: "enum",
Symbols: s.symbols,
Default: s.def,
}
return jsoniter.Marshal(ss)
}

// Fingerprint returns the SHA256 fingerprint of the schema.
func (s *EnumSchema) Fingerprint() [32]byte {
return s.fingerprinter.Fingerprint(s)
Expand Down Expand Up @@ -569,6 +628,18 @@ func (s *ArraySchema) String() string {
return `{"type":"array","items":` + s.items.String() + `}`
}

// MarshalJSON marshals the schema to json.
func (s *ArraySchema) MarshalJSON() ([]byte, error) {
ss := struct {
Type string `json:"type"`
Items Schema `json:"items"`
}{
Type: "array",
Items: s.items,
}
return jsoniter.Marshal(ss)
}

// Fingerprint returns the SHA256 fingerprint of the schema.
func (s *ArraySchema) Fingerprint() [32]byte {
return s.fingerprinter.Fingerprint(s)
Expand Down Expand Up @@ -610,6 +681,18 @@ func (s *MapSchema) String() string {
return `{"type":"map","values":` + s.values.String() + `}`
}

// MarshalJSON marshals the schema to json.
func (s *MapSchema) MarshalJSON() ([]byte, error) {
ss := struct {
Type string `json:"type"`
Values Schema `json:"values"`
}{
Type: "map",
Values: s.values,
}
return jsoniter.Marshal(ss)
}

// Fingerprint returns the SHA256 fingerprint of the schema.
func (s *MapSchema) Fingerprint() [32]byte {
return s.fingerprinter.Fingerprint(s)
Expand Down Expand Up @@ -693,6 +776,11 @@ func (s *UnionSchema) String() string {
return `[` + types + `]`
}

// MarshalJSON marshals the schema to json.
func (s *UnionSchema) MarshalJSON() ([]byte, error) {
return jsoniter.Marshal(s.types)
}

// Fingerprint returns the SHA256 fingerprint of the schema.
func (s *UnionSchema) Fingerprint() [32]byte {
return s.fingerprinter.Fingerprint(s)
Expand Down Expand Up @@ -755,6 +843,11 @@ func (s *FixedSchema) String() string {
return `{"name":"` + s.FullName() + `","type":"fixed","size":` + size + logical + `}`
}

// MarshalJSON marshals the schema to json.
func (s *FixedSchema) MarshalJSON() ([]byte, error) {
return []byte(s.String()), nil
}

// Fingerprint returns the SHA256 fingerprint of the schema.
func (s *FixedSchema) Fingerprint() [32]byte {
return s.fingerprinter.Fingerprint(s)
Expand All @@ -780,6 +873,11 @@ func (s *NullSchema) String() string {
return `"null"`
}

// MarshalJSON marshals the schema to json.
func (s *NullSchema) MarshalJSON() ([]byte, error) {
return []byte(`"null"`), nil
}

// Fingerprint returns the SHA256 fingerprint of the schema.
func (s *NullSchema) Fingerprint() [32]byte {
return s.fingerprinter.Fingerprint(s)
Expand Down Expand Up @@ -817,6 +915,11 @@ func (s *RefSchema) String() string {
return `"` + s.actual.FullName() + `"`
}

// MarshalJSON marshals the schema to json.
func (s *RefSchema) MarshalJSON() ([]byte, error) {
return []byte(`"` + s.actual.FullName() + `"`), nil
}

// Fingerprint returns the SHA256 fingerprint of the schema.
func (s *RefSchema) Fingerprint() [32]byte {
return s.actual.Fingerprint()
Expand Down
13 changes: 9 additions & 4 deletions schema_canonical_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package avro_test

import (
"strconv"
"testing"

"github.com/hamba/avro"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// Test cases are taken from the reference implementation here:
Expand Down Expand Up @@ -260,10 +262,13 @@ func TestSchema_Canonical(t *testing.T) {
},
}

for _, tt := range tests {
s, err := avro.Parse(tt.input)
for i, test := range tests {
test := test
t.Run(strconv.Itoa(i), func(t *testing.T) {
s, err := avro.Parse(test.input)

assert.NoError(t, err)
assert.Equal(t, tt.canonical, s.String())
require.NoError(t, err)
assert.Equal(t, test.canonical, s.String())
})
}
}
Loading

0 comments on commit bf2e271

Please sign in to comment.