Skip to content

Commit

Permalink
added support for full two-way json encoding/decoding for standard json
Browse files Browse the repository at this point in the history
  • Loading branch information
xmcqueen committed Jun 26, 2022
1 parent 5ec5a5e commit 441afb4
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 46 deletions.
14 changes: 13 additions & 1 deletion codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,19 @@ func NewCodecForStandardJSON(schemaSpecification string) (*Codec, error) {
return NewCodecFrom(schemaSpecification, &codecBuilder{
buildCodecForTypeDescribedByMap,
buildCodecForTypeDescribedByString,
buildCodecForTypeDescribedBySliceJSON,
buildCodecForTypeDescribedBySliceOneWayJson,
})
}

func NewCodecForStandardJsonOneWay(schemaSpecification string) (*Codec, error) {
return NewCodecForStandardJSON(schemaSpecification)
}

func NewCodecForStandardJsonFull(schemaSpecification string) (*Codec, error) {
return NewCodecFrom(schemaSpecification, &codecBuilder{
buildCodecForTypeDescribedByMap,
buildCodecForTypeDescribedByString,
buildCodecForTypeDescribedBySliceTwoWayJson,
})
}

Expand Down
27 changes: 25 additions & 2 deletions text_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,18 +63,41 @@ func testTextDecodePass(t *testing.T, schema string, datum interface{}, encoded
}
toNativeAndCompare(t, schema, datum, encoded, codec)
}
func testJSONDecodePass(t *testing.T, schema string, datum interface{}, encoded []byte) {
func testJsonDecodePass(t *testing.T, schema string, datum interface{}, encoded []byte) {
t.Helper()
codec, err := NewCodecFrom(schema, &codecBuilder{
buildCodecForTypeDescribedByMap,
buildCodecForTypeDescribedByString,
buildCodecForTypeDescribedBySliceJSON,
buildCodecForTypeDescribedBySliceOneWayJson,
})
if err != nil {
t.Fatalf("schema: %s; %s", schema, err)
}
toNativeAndCompare(t, schema, datum, encoded, codec)
}
func testNativeToTextualJsonPass(t *testing.T, schema string, datum interface{}, encoded []byte) {
t.Helper()
codec, err := NewCodecFrom(schema, &codecBuilder{
buildCodecForTypeDescribedByMap,
buildCodecForTypeDescribedByString,
buildCodecForTypeDescribedBySliceTwoWayJson,
})
if err != nil {
t.Fatalf("schema: %s; %s", schema, err)
}
toTextualAndCompare(t, schema, datum, encoded, codec)
}
func toTextualAndCompare(t *testing.T, schema string, datum interface{}, encoded []byte, codec *Codec) {
t.Helper()
decoded, err := codec.TextualFromNative(nil, datum)
if err != nil {
t.Fatalf("schema: %s; %s", schema, err)
}
if !bytes.Equal(decoded, encoded) {
t.Errorf("GOT: %v; WANT: %v", string(decoded), string(encoded))
}
}

func toNativeAndCompare(t *testing.T, schema string, datum interface{}, encoded []byte, codec *Codec) {
t.Helper()
decoded, remaining, err := codec.NativeFromTextual(encoded)
Expand Down
79 changes: 67 additions & 12 deletions union.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func makeCodecInfo(st map[string]*Codec, enclosingNamespace string, schemaArray

}

func nativeFromBinary(cr *codecInfo) func(buf []byte) (interface{}, []byte, error) {
func unionNativeFromBinary(cr *codecInfo) func(buf []byte) (interface{}, []byte, error) {

return func(buf []byte) (interface{}, []byte, error) {
var decoded interface{}
Expand All @@ -114,7 +114,7 @@ func nativeFromBinary(cr *codecInfo) func(buf []byte) (interface{}, []byte, erro
return Union(cr.allowedTypes[index], decoded), buf, nil
}
}
func binaryFromNative(cr *codecInfo) func(buf []byte, datum interface{}) ([]byte, error) {
func unionBinaryFromNative(cr *codecInfo) func(buf []byte, datum interface{}) ([]byte, error) {
return func(buf []byte, datum interface{}) ([]byte, error) {
switch v := datum.(type) {
case nil:
Expand All @@ -141,7 +141,7 @@ func binaryFromNative(cr *codecInfo) func(buf []byte, datum interface{}) ([]byte
return nil, fmt.Errorf("cannot encode binary union: non-nil Union values ought to be specified with Go map[string]interface{}, with single key equal to type name, and value equal to datum value: %v; received: %T", cr.allowedTypes, datum)
}
}
func nativeFromTextual(cr *codecInfo) func(buf []byte) (interface{}, []byte, error) {
func unionNativeFromTextual(cr *codecInfo) func(buf []byte) (interface{}, []byte, error) {
return func(buf []byte) (interface{}, []byte, error) {
if len(buf) >= 4 && bytes.Equal(buf[:4], []byte("null")) {
if _, ok := cr.indexFromName["null"]; ok {
Expand All @@ -159,7 +159,7 @@ func nativeFromTextual(cr *codecInfo) func(buf []byte) (interface{}, []byte, err
return datum, buf, nil
}
}
func textualFromNative(cr *codecInfo) func(buf []byte, datum interface{}) ([]byte, error) {
func unionTextualFromNative(cr *codecInfo) func(buf []byte, datum interface{}) ([]byte, error) {
return func(buf []byte, datum interface{}) ([]byte, error) {
switch v := datum.(type) {
case nil:
Expand Down Expand Up @@ -196,6 +196,37 @@ func textualFromNative(cr *codecInfo) func(buf []byte, datum interface{}) ([]byt
return nil, fmt.Errorf("cannot encode textual union: non-nil values ought to be specified with Go map[string]interface{}, with single key equal to type name, and value equal to datum value: %v; received: %T", cr.allowedTypes, datum)
}
}
func textualJsonFromNativeAvro(cr *codecInfo) func(buf []byte, datum interface{}) ([]byte, error) {
return func(buf []byte, datum interface{}) ([]byte, error) {
switch v := datum.(type) {
case nil:
_, ok := cr.indexFromName["null"]
if !ok {
return nil, fmt.Errorf("cannot encode textual union: no member schema types support datum: allowed types: %v; received: %T", cr.allowedTypes, datum)
}
return append(buf, "null"...), nil
case map[string]interface{}:
if len(v) != 1 {
return nil, fmt.Errorf("cannot encode textual union: non-nil Union values ought to be specified with Go map[string]interface{}, with single key equal to type name, and value equal to datum value: %v; received: %T", cr.allowedTypes, datum)
}
// will execute exactly once
for key, value := range v {
index, ok := cr.indexFromName[key]
if !ok {
return nil, fmt.Errorf("cannot encode textual union: no member schema types support datum: allowed types: %v; received: %T", cr.allowedTypes, datum)
}
var err error
c := cr.codecFromIndex[index]
buf, err = c.textualFromNative(buf, value)
if err != nil {
return nil, fmt.Errorf("cannot encode textual union: %s", err)
}
return buf, nil
}
}
return nil, fmt.Errorf("cannot encode textual union: non-nil values ought to be specified with Go map[string]interface{}, with single key equal to type name, and value equal to datum value: %v; received: %T", cr.allowedTypes, datum)
}
}
func buildCodecForTypeDescribedBySlice(st map[string]*Codec, enclosingNamespace string, schemaArray []interface{}, cb *codecBuilder) (*Codec, error) {
if len(schemaArray) == 0 {
return nil, errors.New("Union ought to have one or more members")
Expand All @@ -213,10 +244,10 @@ func buildCodecForTypeDescribedBySlice(st map[string]*Codec, enclosingNamespace
schemaOriginal: cr.codecFromIndex[0].typeName.fullName,

typeName: &name{"union", nullNamespace},
nativeFromBinary: nativeFromBinary(&cr),
binaryFromNative: binaryFromNative(&cr),
nativeFromTextual: nativeFromTextual(&cr),
textualFromNative: textualFromNative(&cr),
nativeFromBinary: unionNativeFromBinary(&cr),
binaryFromNative: unionBinaryFromNative(&cr),
nativeFromTextual: unionNativeFromTextual(&cr),
textualFromNative: unionTextualFromNative(&cr),
}
return rv, nil
}
Expand Down Expand Up @@ -246,7 +277,31 @@ func buildCodecForTypeDescribedBySlice(st map[string]*Codec, enclosingNamespace
// and then it will remain avro-json object
// avro data is not serialized back into standard json
// the data goes to avro-json and stays that way
func buildCodecForTypeDescribedBySliceJSON(st map[string]*Codec, enclosingNamespace string, schemaArray []interface{}, cb *codecBuilder) (*Codec, error) {
func buildCodecForTypeDescribedBySliceOneWayJson(st map[string]*Codec, enclosingNamespace string, schemaArray []interface{}, cb *codecBuilder) (*Codec, error) {
if len(schemaArray) == 0 {
return nil, errors.New("Union ought to have one or more members")
}

cr, err := makeCodecInfo(st, enclosingNamespace, schemaArray, cb)
if err != nil {
return nil, err
}

rv := &Codec{
// NOTE: To support record field default values, union schema set to the
// type name of first member
// TODO: add/change to schemaCanonical below
schemaOriginal: cr.codecFromIndex[0].typeName.fullName,

typeName: &name{"union", nullNamespace},
nativeFromBinary: unionNativeFromBinary(&cr),
binaryFromNative: unionBinaryFromNative(&cr),
nativeFromTextual: nativeAvroFromTextualJson(&cr),
textualFromNative: unionTextualFromNative(&cr),
}
return rv, nil
}
func buildCodecForTypeDescribedBySliceTwoWayJson(st map[string]*Codec, enclosingNamespace string, schemaArray []interface{}, cb *codecBuilder) (*Codec, error) {
if len(schemaArray) == 0 {
return nil, errors.New("Union ought to have one or more members")
}
Expand All @@ -263,10 +318,10 @@ func buildCodecForTypeDescribedBySliceJSON(st map[string]*Codec, enclosingNamesp
schemaOriginal: cr.codecFromIndex[0].typeName.fullName,

typeName: &name{"union", nullNamespace},
nativeFromBinary: nativeFromBinary(&cr),
binaryFromNative: binaryFromNative(&cr),
nativeFromBinary: unionNativeFromBinary(&cr),
binaryFromNative: unionBinaryFromNative(&cr),
nativeFromTextual: nativeAvroFromTextualJson(&cr),
textualFromNative: textualFromNative(&cr),
textualFromNative: textualJsonFromNativeAvro(&cr),
}
return rv, nil
}
Expand Down
Loading

0 comments on commit 441afb4

Please sign in to comment.