diff --git a/.github/workflows/linter.yaml b/.github/workflows/linter.yaml index 6c66e224..e19c3ce8 100644 --- a/.github/workflows/linter.yaml +++ b/.github/workflows/linter.yaml @@ -30,6 +30,10 @@ jobs: run: | go mod tidy -v go mod vendor + + # tmp ignore due to issue: https://github.com/francoispqt/gojay/pull/157 + # this ignore should be removed once the pull request has been merged + git update-index --assume-unchanged vendor/github.com/francoispqt/gojay/* - name: Git changes run: | diff --git a/.gitignore b/.gitignore index f52d087e..91bf7bdf 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ bin/ tmp/ + diff --git a/pkg/codec/json/array.go b/pkg/codec/json/array.go index 9eabac01..367674e9 100644 --- a/pkg/codec/json/array.go +++ b/pkg/codec/json/array.go @@ -4,159 +4,74 @@ import ( "github.com/francoispqt/gojay" "github.com/jexia/semaphore/pkg/references" "github.com/jexia/semaphore/pkg/specs" - "github.com/jexia/semaphore/pkg/specs/types" ) -// NewArray constructs a new JSON array encoder/decoder -func NewArray(resource string, template specs.Template, refs references.Store) *Array { - // skip arrays which have no elements - if len(template.Repeated) == 0 && template.Reference == nil { - return nil - } - - var reference *references.Reference - if template.Reference != nil { - reference = refs.Load(template.Reference.Resource, template.Reference.Path) - } - - if template.Reference != nil && reference == nil { - return nil - } +// Array represents a JSON array. +type Array struct { + resource string + template specs.Template + repeated specs.Repeated + reference *specs.PropertyReference + store references.Store +} - template, err := template.Repeated.Template() +// NewArray creates a new array to be JSON encoded/decoded. +func NewArray(resource string, repeated specs.Repeated, reference *specs.PropertyReference, store references.Store) *Array { + template, err := repeated.Template() if err != nil { panic(err) } - generator := &Array{ - resource: resource, - template: template, - ref: reference, + return &Array{ + resource: resource, + template: template, + repeated: repeated, + reference: reference, + store: store, } - - if template.Repeated != nil { - generator.keys = len(template.Repeated) - } - - return generator } -// Array represents a JSON array -type Array struct { - resource string - template specs.Template - ref *references.Reference - keys int -} +// MarshalJSONArray encodes the array into the given gojay encoder. +func (array *Array) MarshalJSONArray(encoder *gojay.Encoder) { + if array.reference == nil { + for _, template := range array.repeated { + encodeElement(encoder, "", template, array.store) + } -// MarshalJSONArray encodes the array into the given gojay encoder -func (array *Array) MarshalJSONArray(enc *gojay.Encoder) { - if array == nil || array.ref == nil { return } - for _, store := range array.ref.Repeated { - switch { - case array.template.Message != nil: - object := NewObject(array.resource, array.template.Message, store) - if object == nil { - break - } - - enc.AddObject(object) - break - case array.template.Repeated != nil: - array := NewArray(array.resource, array.template, store) - if array == nil { - break - } + var reference = array.store.Load(array.reference.Resource, array.reference.Path) - enc.AddArray(array) - break - case array.template.Enum != nil: - // TODO: check if enums in arrays work - if array.template.Reference == nil { - break - } - - ref := store.Load("", "") - if ref == nil || ref.Enum == nil { - break - } - - key := array.template.Enum.Positions[*ref.Enum].Key - AddType(enc, types.String, key) - break - case array.template.Scalar != nil: - val := array.template.Scalar.Default + if reference == nil { + return + } - ref := store.Load("", "") - if ref != nil { - val = ref.Value - } + for _, store := range reference.Repeated { + array.template.Reference = new(specs.PropertyReference) - AddType(enc, array.template.Scalar.Type, val) - break - } + encodeElement(encoder, array.resource, array.template, store) } } -// UnmarshalJSONArray unmarshals the given specs into the configured reference store -func (array *Array) UnmarshalJSONArray(dec *gojay.Decoder) error { - if array == nil { - return nil - } - - // FIXME: array.keys is derrived from a wrong value - store := references.NewReferenceStore(array.keys) +// UnmarshalJSONArray unmarshals the given specs into the configured reference store. +func (array *Array) UnmarshalJSONArray(decoder *gojay.Decoder) error { + store := references.NewReferenceStore(0) - switch { - case array.template.Message != nil: - object := NewObject(array.resource, array.template.Message, store) - err := dec.AddObject(object) - if err != nil { - return err - } - - array.ref.Append(store) - break - case array.template.Repeated != nil: - array := NewArray(array.resource, array.template, store) - err := dec.AddArray(array) - if err != nil { - return err - } - - array.ref.Append(store) - break - case array.template.Enum != nil: - var key string - err := dec.AddString(&key) - if err != nil { - return err - } - - enum := array.template.Enum.Keys[key] - if enum != nil { - store.StoreEnum("", "", enum.Position) - array.ref.Append(store) - } - break - case array.template.Scalar != nil: - val, err := DecodeType(dec, array.template.Scalar.Type) - if err != nil { - return err + if array.reference != nil { + var reference = array.store.Load(array.reference.Resource, array.reference.Path) + if reference == nil { + return nil } - store.StoreValue("", "", val) - array.ref.Append(store) - break + reference.Append(store) } - return nil + // NOTE: always consume an array even if the reference is not set + return decodeElement(decoder, "", "", array.template, store) } -// IsNil returns whether the given array is null or not +// IsNil returns whether the given array is null or not. func (array *Array) IsNil() bool { - return false + return array == nil } diff --git a/pkg/codec/json/decode.go b/pkg/codec/json/decode.go new file mode 100644 index 00000000..02180e0f --- /dev/null +++ b/pkg/codec/json/decode.go @@ -0,0 +1,36 @@ +package json + +import ( + "github.com/francoispqt/gojay" + "github.com/jexia/semaphore/pkg/references" + "github.com/jexia/semaphore/pkg/specs" +) + +func decodeElement(decoder *gojay.Decoder, resource, path string, template specs.Template, store references.Store) error { + var reference = specs.PropertyReference{ + Resource: resource, + Path: path, + } + + switch { + case template.Message != nil: + object := NewObject(resource, template.Message, store) + if object == nil { + break + } + + return decoder.Object(object) + case template.Repeated != nil: + store.StoreReference(resource, &references.Reference{Path: path}) + + return decoder.Array( + NewArray(resource, template.Repeated, &reference, store), + ) + case template.Enum != nil: + return NewEnum("", template.Enum, &reference, store).UnmarshalJSONEnum(decoder) + case template.Scalar != nil: + return NewScalar("", template.Scalar, &reference, store).UnmarshalJSONScalar(decoder) + } + + return nil +} diff --git a/pkg/codec/json/encode.go b/pkg/codec/json/encode.go new file mode 100644 index 00000000..a086720e --- /dev/null +++ b/pkg/codec/json/encode.go @@ -0,0 +1,43 @@ +package json + +import ( + "github.com/francoispqt/gojay" + "github.com/jexia/semaphore/pkg/references" + "github.com/jexia/semaphore/pkg/specs" +) + +func encodeElement(encoder *gojay.Encoder, resource string, template specs.Template, store references.Store) { + switch { + case template.Message != nil: + encoder.Object( + NewObject(resource, template.Message, store), + ) + case template.Repeated != nil: + encoder.Array( + NewArray(resource, template.Repeated, template.Reference, store), + ) + case template.Enum != nil: + NewEnum("", template.Enum, template.Reference, store).MarshalJSONEnum(encoder) + case template.Scalar != nil: + NewScalar("", template.Scalar, template.Reference, store).MarshalJSONScalar(encoder) + } +} + +func encodeElementKey(encoder *gojay.Encoder, resource, key string, template specs.Template, store references.Store) { + switch { + case template.Message != nil: + encoder.ObjectKey( + key, + NewObject(resource, template.Message, store), + ) + case template.Repeated != nil: + encoder.ArrayKey( + key, + NewArray(resource, template.Repeated, template.Reference, store), + ) + case template.Enum != nil: + NewEnum(key, template.Enum, template.Reference, store).MarshalJSONEnumKey(encoder) + case template.Scalar != nil: + NewScalar(key, template.Scalar, template.Reference, store).MarshalJSONScalarKey(encoder) + } +} diff --git a/pkg/codec/json/enum.go b/pkg/codec/json/enum.go new file mode 100644 index 00000000..4bff0caf --- /dev/null +++ b/pkg/codec/json/enum.go @@ -0,0 +1,87 @@ +package json + +import ( + "github.com/francoispqt/gojay" + "github.com/jexia/semaphore/pkg/references" + "github.com/jexia/semaphore/pkg/specs" + "github.com/jexia/semaphore/pkg/specs/types" +) + +// Enum is a vrapper over specs.Enum providing JSON encoding/decoding. +type Enum struct { + name string + enum *specs.Enum + reference *specs.PropertyReference + store references.Store +} + +// NewEnum creates a new enum by wrapping provided specs.Enum. +func NewEnum(name string, enum *specs.Enum, reference *specs.PropertyReference, store references.Store) *Enum { + return &Enum{ + name: name, + enum: enum, + reference: reference, + store: store, + } +} + +func (enum *Enum) value() *specs.EnumValue { + if enum.reference == nil { + return nil + } + + var reference = enum.store.Load(enum.reference.Resource, enum.reference.Path) + if reference == nil || reference.Enum == nil { + return nil + } + + if position := reference.Enum; position != nil { + return enum.enum.Positions[*reference.Enum] + } + + return nil +} + +// MarshalJSONEnum marshals enum which is not an object field (array element or +// standalone enum). +func (enum Enum) MarshalJSONEnum(encoder *gojay.Encoder) { + var value interface{} + if enumValue := enum.value(); enumValue != nil { + value = enumValue.Key + } + + AddType(encoder, types.String, value) +} + +// MarshalJSONEnumKey marshals enum which is an object field. +func (enum Enum) MarshalJSONEnumKey(encoder *gojay.Encoder) { + var enumValue = enum.value() + if enumValue == nil { + return + } + + AddTypeKey(encoder, enum.name, types.String, enumValue.Key) +} + +// UnmarshalJSONEnum unmarshals enum value from decoder to the reference store. +func (enum Enum) UnmarshalJSONEnum(decoder *gojay.Decoder) error { + var key string + if err := decoder.AddString(&key); err != nil { + return err + } + + var ( + reference = &references.Reference{ + Path: enum.reference.Path, + } + enumValue = enum.enum.Keys[key] + ) + + if enumValue != nil { + reference.Enum = &enumValue.Position + } + + enum.store.StoreReference(enum.reference.Resource, reference) + + return nil +} diff --git a/pkg/codec/json/errors.go b/pkg/codec/json/errors.go index a6ba1fa1..24a06366 100644 --- a/pkg/codec/json/errors.go +++ b/pkg/codec/json/errors.go @@ -1,17 +1,13 @@ package json -import ( - "fmt" - - "github.com/jexia/semaphore/pkg/prettyerr" -) +import "github.com/jexia/semaphore/pkg/prettyerr" // ErrUndefinedSpecs occurs when spacs are nil type ErrUndefinedSpecs struct{} // Error returns a description of the given error as a string func (e ErrUndefinedSpecs) Error() string { - return fmt.Sprint("no object specs defined") + return "no object specs defined" } // Prettify returns the prettified version of the given error diff --git a/pkg/codec/json/json.go b/pkg/codec/json/json.go index daedba06..c5e81cb2 100644 --- a/pkg/codec/json/json.go +++ b/pkg/codec/json/json.go @@ -1,9 +1,8 @@ package json import ( - "bytes" + "bufio" "io" - "io/ioutil" "github.com/francoispqt/gojay" "github.com/jexia/semaphore/pkg/codec" @@ -11,18 +10,19 @@ import ( "github.com/jexia/semaphore/pkg/specs" ) -// NewConstructor constructs a new JSON constructor -func NewConstructor() *Constructor { - return &Constructor{} -} +// Constructor is capable of constructing new codec managers for the given resource and specs. +type Constructor struct{} -// Constructor is capable of constructing new codec managers for the given resource and specs -type Constructor struct { -} +// NewConstructor constructs a new JSON constructor. +func NewConstructor() *Constructor { return &Constructor{} } + +// Name returns the name of the JSON codec constructor. +func (constructor *Constructor) Name() string { return "json" } -// Name returns the name of the JSON codec constructor -func (constructor *Constructor) Name() string { - return "json" +// Manager manages a specs object and allows to encode/decode messages. +type Manager struct { + resource string + property *specs.Property } // New constructs a new JSON codec manager @@ -33,63 +33,65 @@ func (constructor *Constructor) New(resource string, specs *specs.ParameterMap) return &Manager{ resource: resource, - specs: specs.Property, + property: specs.Property, }, nil } -// Manager manages a specs object and allows to encode/decode messages -type Manager struct { - resource string - specs *specs.Property -} - // Name returns the proto codec name -func (manager *Manager) Name() string { - return "json" -} +func (manager *Manager) Name() string { return "json" } // Property returns the manager property which is used to marshal and unmarshal data -func (manager *Manager) Property() *specs.Property { - return manager.specs -} +func (manager *Manager) Property() *specs.Property { return manager.property } // Marshal marshals the given reference store into a JSON message. // This method is called during runtime to encode a new message with the values stored inside the given reference store -func (manager *Manager) Marshal(refs references.Store) (io.Reader, error) { - if manager.specs == nil { +func (manager *Manager) Marshal(store references.Store) (io.Reader, error) { + if manager.property == nil { return nil, nil } - object := NewObject(manager.resource, manager.specs.Message, refs) - bb, err := gojay.MarshalJSONObject(object) - if err != nil { - return nil, err - } + var ( + reader, writer = io.Pipe() + encoder = gojay.BorrowEncoder(writer) + ) + + go func() { + defer encoder.Release() + + encodeElement(encoder, manager.resource, manager.property.Template, store) + + if _, err := encoder.Write(); err != nil { + _ = writer.CloseWithError(err) - return bytes.NewBuffer(bb), nil + return + } + + writer.Close() + }() + + return reader, nil } // Unmarshal unmarshals the given JSON io reader into the given reference store. // This method is called during runtime to decode a new message and store it inside the given reference store -func (manager *Manager) Unmarshal(reader io.Reader, refs references.Store) error { - if manager.specs == nil { +func (manager *Manager) Unmarshal(reader io.Reader, store references.Store) error { + if manager.property == nil { return nil } - bb, err := ioutil.ReadAll(reader) - if err != nil { - return err - } + var ( + buff = bufio.NewReader(reader) + _, err = buff.ReadByte() + ) - if len(bb) == 0 { + if err == io.EOF { return nil } - object := NewObject(manager.resource, manager.specs.Message, refs) - err = gojay.UnmarshalJSONObject(bb, object) - if err != nil { - return err - } + _ = buff.UnreadByte() + + var decoder = gojay.NewDecoder(buff) + defer decoder.Release() - return nil + return decodeElement(decoder, manager.resource, manager.property.Path, manager.property.Template, store) } diff --git a/pkg/codec/json/json_test.go b/pkg/codec/json/json_test.go index 1543b70f..f578853c 100644 --- a/pkg/codec/json/json_test.go +++ b/pkg/codec/json/json_test.go @@ -5,19 +5,18 @@ import ( "encoding/json" "io/ioutil" "path/filepath" - "reflect" "testing" "github.com/jexia/semaphore" "github.com/jexia/semaphore/cmd/semaphore/daemon/providers" "github.com/jexia/semaphore/pkg/broker" "github.com/jexia/semaphore/pkg/broker/logger" + "github.com/jexia/semaphore/pkg/codec/tests" "github.com/jexia/semaphore/pkg/functions" "github.com/jexia/semaphore/pkg/providers/hcl" "github.com/jexia/semaphore/pkg/providers/mock" "github.com/jexia/semaphore/pkg/references" "github.com/jexia/semaphore/pkg/specs" - "github.com/jexia/semaphore/pkg/specs/template" ) func NewMock() (specs.FlowListInterface, error) { @@ -52,73 +51,6 @@ func NewMock() (specs.FlowListInterface, error) { return collection.FlowListInterface, nil } -func ValidateStore(t *testing.T, prop *specs.Property, resource string, origin string, input map[string]interface{}, store references.Store) { - if prop.Message == nil { - t.Fatalf("%s, property message not set", prop.Path) - } - - for key, value := range input { - nprop := prop.Message[key] - if nprop == nil { - nprop = prop - } - - path := template.JoinPath(origin, key) - nested, is := value.(map[string]interface{}) - if is { - ValidateStore(t, nprop, resource, path, nested, store) - continue - } - - repeated, is := value.([]map[string]interface{}) - if is { - repeating := store.Load(resource, path) - if repeating == nil { - t.Fatalf("resource not found %s:%s", resource, path) - } - - for index, store := range repeating.Repeated { - ValidateStore(t, nprop, resource, path, repeated[index], store) - } - continue - } - - values, is := value.([]interface{}) - if is { - repeating := store.Load(resource, path) - if repeating == nil { - t.Fatalf("resource not found %s:%s", resource, path) - } - - for index, store := range repeating.Repeated { - // small wrapper that allows to reuse functionalities - wrapper := map[string]interface{}{ - "": values[index], - } - - ValidateStore(t, nprop, "", "", wrapper, store) - } - continue - } - - ref := store.Load(resource, path) - if ref == nil { - t.Fatalf("resource not found %s:%s", resource, path) - } - - if ref.Enum != nil && nprop.Enum != nil { - if nprop.Enum.Positions[*ref.Enum] == nil { - t.Fatalf("unexpected enum value at %s '%+v', expected '%+v'", path, ref.Enum, value) - } - continue - } - - if ref.Value != value { - t.Fatalf("unexpected value at %s '%+v', expected '%+v'", path, ref.Value, value) - } - } -} - func BenchmarkSimpleMarshal(b *testing.B) { input := map[string]interface{}{ "message": "message", @@ -150,7 +82,9 @@ func BenchmarkSimpleMarshal(b *testing.B) { b.Fatal(err) } - ioutil.ReadAll(reader) + if _, err := ioutil.ReadAll(reader); err != nil { + b.Fatal(err) + } } } @@ -187,7 +121,9 @@ func BenchmarkNestedMarshal(b *testing.B) { b.Fatal(err) } - ioutil.ReadAll(reader) + if _, err := ioutil.ReadAll(reader); err != nil { + b.Fatal(err) + } } } @@ -226,7 +162,9 @@ func BenchmarkRepeatedMessagesMarshal(b *testing.B) { b.Fatal(err) } - ioutil.ReadAll(reader) + if _, err := ioutil.ReadAll(reader); err != nil { + b.Fatal(err) + } } } @@ -263,7 +201,9 @@ func BenchmarkRepeatedValuesMarshal(b *testing.B) { b.Fatal(err) } - ioutil.ReadAll(reader) + if _, err := ioutil.ReadAll(reader); err != nil { + b.Fatal(err) + } } } @@ -446,103 +386,174 @@ func TestMarshal(t *testing.T) { } flow := flows.Get("complete") - req := flow.GetNodes().Get("first").Call.Request + schema := flow.GetNodes().Get("first").Call.Request - constructor := &Constructor{} - manager, err := constructor.New("input", req) - if err != nil { - t.Fatal(err) + type test struct { + input map[string]interface{} + schema *specs.ParameterMap + expected string } - tests := map[string]map[string]interface{}{ + tests := map[string]test{ + "nil schema": { + schema: new(specs.ParameterMap), + }, + "scalar from reference": { + input: map[string]interface{}{ + "integer": int32(42), + }, + schema: &specs.ParameterMap{ + Property: func() *specs.Property { + var property = tests.PropInteger() + property.Reference = &specs.PropertyReference{ + Resource: "input", + Path: "integer", + } + + return property + }(), + }, + expected: `42`, + }, + "scalar default value": { + input: map[string]interface{}{ + "integer": int32(42), + }, + schema: &specs.ParameterMap{ + Property: func() *specs.Property { + var property = tests.PropInteger() + property.Scalar.Default = int32(42) + + return property + }(), + }, + expected: `42`, + }, + "array empty": { + schema: tests.SchemaArrayDefaultEmpty, + expected: `[null]`, + }, + "array default reference": { + input: map[string]interface{}{ + "string": "foo", + }, + schema: tests.SchemaArrayWithValues, + expected: `["foo","bar"]`, + }, + "array default no reference value": { + input: map[string]interface{}{}, + schema: tests.SchemaArrayWithValues, + expected: `[null,"bar"]`, + }, + "array of arrays": { + input: map[string]interface{}{}, + schema: tests.SchemaArrayOfArrays, + expected: `[[null,"bar"]]`, + }, "simple": { - "message": "some message", - "nested": map[string]interface{}{}, + input: map[string]interface{}{ + "message": "some message", + "nested": map[string]interface{}{}, + }, + schema: schema, + expected: `{"message":"some message","nested":{},"repeating":[],"repeating_values":[],"repeating_enum":[]}`, }, "nested": { - "nested": map[string]interface{}{ - "value": "some message", + input: map[string]interface{}{ + "nested": map[string]interface{}{ + "value": "some message", + }, }, + schema: schema, + expected: `{"nested":{"value":"some message"},"repeating":[],"repeating_values":[],"repeating_enum":[]}`, }, "enum": { - "nested": map[string]interface{}{}, - "enum": references.Enum("PENDING", 2), + input: map[string]interface{}{ + "nested": map[string]interface{}{}, + "enum": references.Enum("PENDING", 2), + }, + schema: schema, + expected: `{"nested":{},"repeating":[],"repeating_values":[],"enum":"PENDING","repeating_enum":[]}`, }, "repeating_enum": { - "nested": map[string]interface{}{}, - "repeating_enum": []interface{}{ - references.Enum("UNKNOWN", 1), - references.Enum("PENDING", 2), + input: map[string]interface{}{ + "repeating_enum": []interface{}{ + references.Enum("UNKNOWN", 1), + references.Enum("PENDING", 2), + }, }, + schema: schema, + expected: `{"nested":{},"repeating":[],"repeating_values":[],"repeating_enum":["UNKNOWN","PENDING"]}`, }, - "repeating": { - "nested": map[string]interface{}{}, - "repeating": []map[string]interface{}{ - { - "value": "repeating value", - }, - { - "value": "repeating value", + "repeating objects": { + input: map[string]interface{}{ + "repeating": []map[string]interface{}{ + { + "value": "repeating value", + }, + { + "value": "repeating value", + }, }, }, + schema: schema, + expected: `{"nested":{},"repeating":[{"value":"repeating value"},{"value":"repeating value"}],"repeating_values":[],"repeating_enum":[]}`, }, - "repeating_values": { - "nested": map[string]interface{}{}, - "repeating_values": []interface{}{ - "repeating value", - "repeating value", + "repeating values from reference": { + input: map[string]interface{}{ + "repeating_values": []interface{}{ + "repeating one", + "repeating two", + }, }, + schema: schema, + expected: `{"nested":{},"repeating":[],"repeating_values":["repeating one","repeating two"],"repeating_enum":[]}`, }, "complex": { - "message": "hello world", - "nested": map[string]interface{}{ - "value": "nested value", - }, - "repeating": []map[string]interface{}{ - { - "value": "repeating value", + input: map[string]interface{}{ + "message": "hello world", + "nested": map[string]interface{}{ + "value": "nested value", }, - { - "value": "repeating value", + "repeating": []map[string]interface{}{ + { + "value": "repeating value", + }, + { + "value": "repeating value", + }, }, }, + schema: schema, + expected: `{"message":"hello world","nested":{"value":"nested value"},"repeating":[{"value":"repeating value"},{"value":"repeating value"}],"repeating_values":[],"repeating_enum":[]}`, }, } - for key, input := range tests { + for key, test := range tests { t.Run(key, func(t *testing.T) { - inputAsJSON, err := json.Marshal(input) + constructor := &Constructor{} + manager, err := constructor.New("input", test.schema) if err != nil { t.Fatal(err) } - refs := references.NewReferenceStore(len(input)) - refs.StoreValues("input", "", input) + refs := references.NewReferenceStore(len(test.input)) + refs.StoreValues("input", "", test.input) reader, err := manager.Marshal(refs) if err != nil { t.Fatal(err) } - responseAsJSON, err := ioutil.ReadAll(reader) - if err != nil { - t.Fatal(err) - } - - result := map[string]interface{}{} - err = json.Unmarshal(responseAsJSON, &result) - if err != nil { - t.Fatal(err) - } - - expected := map[string]interface{}{} - err = json.Unmarshal(inputAsJSON, &expected) - if err != nil { - t.Fatal(err) - } + if test.expected != "" { + data, err := ioutil.ReadAll(reader) + if err != nil { + t.Fatal(err) + } - if !reflect.DeepEqual(expected, result) { - t.Errorf("unexpected response %s, expected %s", string(responseAsJSON), string(inputAsJSON)) + if actual := string(data); actual != test.expected { + t.Errorf("unexpected output %s, expected %s", data, test.expected) + } } }) } @@ -561,83 +572,214 @@ func TestUnmarshal(t *testing.T) { t.Fatal(err) } - flow := flows.Get("complete") - req := flow.GetNodes().Get("first").Call.Request + var ( + flow = flows.Get("complete") + schema = flow.GetNodes().Get("first").Call.Request + ) - constructor := &Constructor{} - manager, err := constructor.New("input", req) - if err != nil { - t.Fatal(err) + type test struct { + input string + schema *specs.ParameterMap + expected tests.Expect } - tests := map[string]map[string]interface{}{ + testsCases := map[string]test{ + "nil schema": { + schema: new(specs.ParameterMap), + }, + "empty": { + input: ``, + schema: schema, + }, + "array": { + input: `[null,"bar"]`, + schema: tests.SchemaArrayDefaultEmpty, + expected: tests.Expect{ + Nested: map[string]tests.Expect{ + "array": { + Repeated: []tests.Expect{ + { + Value: nil, + }, + { + Value: "bar", + }, + }, + }, + }, + }, + }, + "array of arrays": { + input: `[[null,"bar"]]`, + schema: tests.SchemaArrayOfArrays, + expected: tests.Expect{ + Nested: map[string]tests.Expect{ + "array": { + Repeated: []tests.Expect{ + { + Repeated: []tests.Expect{ + { + Value: nil, + }, + { + Value: "bar", + }, + }, + }, + }, + }, + }, + }, + }, "simple": { - "message": "some message", - "nested": map[string]interface{}{}, + input: `{"message":"some message"}`, + schema: schema, + expected: tests.Expect{ + Nested: map[string]tests.Expect{ + "message": { + Value: "some message", + }, + }, + }, }, "nested": { - "nested": map[string]interface{}{ - "value": "some message", + input: `{"nested":{"value":"some message"}}`, + schema: schema, + expected: tests.Expect{ + Nested: map[string]tests.Expect{ + "nested.value": { + Value: "some message", + }, + }, }, }, "enum": { - "enum": "PENDING", + input: `{"enum":"PENDING"}`, + schema: schema, + expected: tests.Expect{ + Nested: map[string]tests.Expect{ + "enum": { + Enum: func() *int32 { i := int32(2); return &i }(), + }, + }, + }, }, "repeating_enum": { - "repeating_enum": []interface{}{ - "UNKNOWN", - "PENDING", + input: `{"repeating_enum":["UNKNOWN","PENDING"]}`, + schema: schema, + expected: tests.Expect{ + Nested: map[string]tests.Expect{ + "repeating_enum": { + Repeated: []tests.Expect{ + { + Enum: func() *int32 { i := int32(1); return &i }(), + }, + { + Enum: func() *int32 { i := int32(2); return &i }(), + }, + }, + }, + }, }, }, - "repeating": { - "nested": map[string]interface{}{}, - "repeating": []map[string]interface{}{ - { - "value": "repeating value", - }, - { - "value": "repeating value", + "repeating_values": { + input: `{"repeating_values":["repeating one","repeating two"]}`, + schema: schema, + expected: tests.Expect{ + Nested: map[string]tests.Expect{ + "repeating_values": { + Repeated: []tests.Expect{ + { + Value: "repeating one", + }, + { + Value: "repeating two", + }, + }, + }, }, }, }, - "repeating_values": { - "nested": map[string]interface{}{}, - "repeating_values": []interface{}{ - "repeating value", - "repeating value", + "repeating objects": { + input: `{"repeating":[{"value":"repeating one"},{"value":"repeating two"}]}`, + schema: schema, + expected: tests.Expect{ + Nested: map[string]tests.Expect{ + "repeating": { + Repeated: []tests.Expect{ + { + Nested: map[string]tests.Expect{ + "repeating.value": { + Value: "repeating one", + }, + }, + }, + { + Nested: map[string]tests.Expect{ + "repeating.value": { + Value: "repeating two", + }, + }, + }, + }, + }, + }, }, }, "complex": { - "message": "hello world", - "nested": map[string]interface{}{ - "value": "nested value", - }, - "repeating": []map[string]interface{}{ - { - "value": "repeating value", - }, - { - "value": "repeating value", + input: `{"message":"hello world","nested":{"value":"hello nested world"},"repeating":[{"value":"repeating one"},{"value":"repeating two"}]}`, + schema: schema, + expected: tests.Expect{ + Nested: map[string]tests.Expect{ + "message": { + Value: "hello world", + }, + "nested": { + Nested: map[string]tests.Expect{ + "value": { + Value: "hello nested world", + }, + }, + }, + "repeating": { + Repeated: []tests.Expect{ + { + Nested: map[string]tests.Expect{ + "repeating.value": { + Value: "repeating one", + }, + }, + }, + { + Nested: map[string]tests.Expect{ + "repeating.value": { + Value: "repeating two", + }, + }, + }, + }, + }, }, }, }, } - for key, input := range tests { + for key, test := range testsCases { t.Run(key, func(t *testing.T) { - inputAsJSON, err := json.Marshal(input) + constructor := &Constructor{} + manager, err := constructor.New("input", test.schema) if err != nil { t.Fatal(err) } - store := references.NewReferenceStore(len(input)) - err = manager.Unmarshal(bytes.NewBuffer(inputAsJSON), store) + store := references.NewReferenceStore(0) + err = manager.Unmarshal(bytes.NewBuffer([]byte(test.input)), store) if err != nil { t.Fatal(err) } - ValidateStore(t, req.Property, "input", "", input, store) + tests.Assert(t, "input", "", store, test.expected) }) } } diff --git a/pkg/codec/json/object.go b/pkg/codec/json/object.go index 39361ebc..44697b92 100644 --- a/pkg/codec/json/object.go +++ b/pkg/codec/json/object.go @@ -4,154 +4,45 @@ import ( "github.com/francoispqt/gojay" "github.com/jexia/semaphore/pkg/references" "github.com/jexia/semaphore/pkg/specs" - "github.com/jexia/semaphore/pkg/specs/types" ) -// NewObject constructs a new object encoder/decoder for the given specs -func NewObject(resource string, items specs.Message, refs references.Store) *Object { - return &Object{ - resource: resource, - length: len(items), - refs: refs, - specs: items, - } -} - // Object represents a JSON object type Object struct { resource string - specs specs.Message - refs references.Store + message specs.Message + store references.Store length int } -// MarshalJSONObject encodes the given specs object into the given gojay encoder -func (object *Object) MarshalJSONObject(encoder *gojay.Encoder) { - if object == nil { - return +// NewObject constructs a new object encoder/decoder for the given specs +func NewObject(resource string, message specs.Message, store references.Store) *Object { + return &Object{ + resource: resource, + message: message, + store: store, + length: len(message), } +} - for _, prop := range object.specs { - switch { - case prop.Repeated != nil: - array := NewArray(object.resource, prop.Template, object.refs) - if array == nil { - break - } - - encoder.AddArrayKey(prop.Name, array) - break - case prop.Message != nil: - result := NewObject(object.resource, prop.Message, object.refs) - if result == nil { - break - } - - encoder.AddObjectKey(prop.Name, result) - break - case prop.Enum != nil: - if prop.Reference == nil { - break - } - - ref := object.refs.Load(prop.Reference.Resource, prop.Reference.Path) - if ref == nil || ref.Enum == nil { - break - } - - enum := prop.Enum.Positions[*ref.Enum] - if enum == nil { - break - } - - AddTypeKey(encoder, prop.Name, types.String, enum.Key) - break - case prop.Scalar != nil: - val := prop.Scalar.Default - - if prop.Reference != nil { - ref := object.refs.Load(prop.Reference.Resource, prop.Reference.Path) - if ref != nil && ref.Value != nil { - val = ref.Value - } - } - - if val == nil { - break - } - - AddTypeKey(encoder, prop.Name, prop.Scalar.Type, val) - break - } - +// MarshalJSONObject encodes the given specs object into the given gojay encoder +func (object *Object) MarshalJSONObject(encoder *gojay.Encoder) { + for _, prop := range object.message.SortedProperties() { + encodeElementKey(encoder, object.resource, prop.Name, prop.Template, object.store) } } // UnmarshalJSONObject unmarshals the given specs into the configured reference store -func (object *Object) UnmarshalJSONObject(dec *gojay.Decoder, key string) error { +func (object *Object) UnmarshalJSONObject(decoder *gojay.Decoder, key string) error { if object == nil { return nil } - property, has := object.specs[key] + property, has := object.message[key] if !has { return nil } - switch { - case property.Message != nil: - object := NewObject(object.resource, property.Message, object.refs) - if object == nil { - break - } - - return dec.AddObject(object) - case property.Repeated != nil: - ref := &references.Reference{ - Path: property.Path, - } - - array := NewArray(object.resource, property.Template, object.refs) - if array == nil { - break - } - - err := dec.AddArray(array) - if err != nil { - return err - } - - object.refs.StoreReference(object.resource, ref) - return nil - } - - ref := &references.Reference{ - Path: property.Path, - } - - switch { - case property.Enum != nil: - var key string - dec.AddString(&key) - - enum := property.Enum.Keys[key] - if enum != nil { - ref.Enum = &enum.Position - } - - break - case property.Scalar != nil: - value, err := DecodeType(dec, property.Scalar.Type) - if err != nil { - return err - } - - ref.Value = value - break - } - - object.refs.StoreReference(object.resource, ref) - return nil + return decodeElement(decoder, object.resource, property.Path, property.Template, object.store) } // NKeys returns the amount of available keys inside the given object @@ -161,5 +52,5 @@ func (object *Object) NKeys() int { // IsNil returns whether the given object is null or not func (object *Object) IsNil() bool { - return false + return object == nil } diff --git a/pkg/codec/json/scalar.go b/pkg/codec/json/scalar.go new file mode 100644 index 00000000..6d66e77b --- /dev/null +++ b/pkg/codec/json/scalar.go @@ -0,0 +1,70 @@ +package json + +import ( + "github.com/francoispqt/gojay" + "github.com/jexia/semaphore/pkg/references" + "github.com/jexia/semaphore/pkg/specs" +) + +// Scalar wraps specs.Scalar to be JSON encoded/decoded. +type Scalar struct { + name string + scalar *specs.Scalar + reference *specs.PropertyReference + store references.Store +} + +// NewScalar creates new specs.Scalar wrapper. +func NewScalar(name string, scalar *specs.Scalar, reference *specs.PropertyReference, store references.Store) *Scalar { + return &Scalar{ + name: name, + scalar: scalar, + reference: reference, + store: store, + } +} + +func (scalar Scalar) value() interface{} { + var value = scalar.scalar.Default + + if scalar.reference != nil { + var reference = scalar.store.Load(scalar.reference.Resource, scalar.reference.Path) + if reference != nil && reference.Value != nil { + value = reference.Value + } + } + + return value +} + +// MarshalJSONScalar marshals standalone scalar to JSON. +func (scalar Scalar) MarshalJSONScalar(encoder *gojay.Encoder) { + AddType(encoder, scalar.scalar.Type, scalar.value()) +} + +// MarshalJSONScalarKey marshals scalar as an object field. +func (scalar Scalar) MarshalJSONScalarKey(encoder *gojay.Encoder) { + var value = scalar.value() + if value == nil { + return + } + + AddTypeKey(encoder, scalar.name, scalar.scalar.Type, value) +} + +// UnmarshalJSONScalar unmarshals scalar from decoder to the reference store. +func (scalar Scalar) UnmarshalJSONScalar(decoder *gojay.Decoder) error { + value, err := DecodeType(decoder, scalar.scalar.Type) + if err != nil { + return err + } + + var reference = &references.Reference{ + Path: scalar.reference.Path, + Value: value, + } + + scalar.store.StoreReference(scalar.reference.Resource, reference) + + return nil +} diff --git a/pkg/codec/json/tests/schema.yaml b/pkg/codec/json/tests/schema.yaml index b3777b7d..284e1615 100644 --- a/pkg/codec/json/tests/schema.yaml +++ b/pkg/codec/json/tests/schema.yaml @@ -4,12 +4,14 @@ properties: template: message: "message": + position: 1 name: "message" label: "optional" template: scalar: type: "string" "nested": + position: 2 name: "nested" label: "optional" template: @@ -22,6 +24,7 @@ properties: type: "string" "repeating": + position: 3 name: "repeating" label: "optional" template: @@ -34,6 +37,7 @@ properties: scalar: type: "string" "repeating_values": + position: 4 name: "repeating_values" label: "optional" template: @@ -41,30 +45,32 @@ properties: - scalar: type: "string" "enum": - name: "enum" - label: "optional" - template: - enum: - name: "STATUS" - keys: - "UNKNOWN": - key: "UNKNOWN" - position: 1 - description: "unknown status" - "PENDING": - key: "PENDING" - position: 2 - description: "pending status" - positions: - 1: - key: "UNKNOWN" - position: 1 - description: "unknown status" - 2: - key: "PENDING" - position: 2 - description: "pending status" + position: 5 + name: "enum" + label: "optional" + template: + enum: + name: "STATUS" + keys: + "UNKNOWN": + key: "UNKNOWN" + position: 1 + description: "unknown status" + "PENDING": + key: "PENDING" + position: 2 + description: "pending status" + positions: + 1: + key: "UNKNOWN" + position: 1 + description: "unknown status" + 2: + key: "PENDING" + position: 2 + description: "pending status" "repeating_enum": + position: 6 name: "repeating_enum" label: "optional" template: diff --git a/pkg/codec/json/types.go b/pkg/codec/json/types.go index 44ac626f..536ca55b 100644 --- a/pkg/codec/json/types.go +++ b/pkg/codec/json/types.go @@ -51,6 +51,13 @@ func AddTypeKey(encoder *gojay.Encoder, key string, typed types.Type, value inte // AddType encodes the given value into the given encoder func AddType(encoder *gojay.Encoder, typed types.Type, value interface{}) { + // do not skip NULL values while encoding array elements + if value == nil { + encoder.AddNull() + + return + } + switch typed { case types.Double: encoder.AddFloat64(Float64Empty(value)) @@ -132,7 +139,9 @@ func DecodeType(decoder *gojay.Decoder, prop types.Type) (interface{}, error) { return value, err case types.Bytes: var raw string - decoder.AddString(&raw) + if err := decoder.AddString(&raw); err != nil { + return nil, err + } value := make([]byte, len(raw)) _, err := base64.StdEncoding.Decode(value, []byte(raw)) diff --git a/pkg/codec/tests/assert.go b/pkg/codec/tests/assert.go new file mode 100644 index 00000000..fc4204f5 --- /dev/null +++ b/pkg/codec/tests/assert.go @@ -0,0 +1,69 @@ +package tests + +import ( + "testing" + + "github.com/jexia/semaphore/pkg/references" +) + +type Expect struct { + Value interface{} + Enum *int32 + Repeated []Expect + Nested map[string]Expect +} + +func buildPath(prefix, property string) string { + if prefix == "" { + return property + } + + return prefix + "." + property +} + +func Assert(t *testing.T, resource, path string, store references.Store, input Expect) { + ref := store.Load(resource, path) + + switch { + case input.Nested != nil: + for key, value := range input.Nested { + Assert(t, resource, buildPath(path, key), store, value) + } + case input.Enum != nil: + if ref == nil { + t.Fatalf("reference %q was expected to be set", path) + } + + if ref.Enum == nil { + t.Fatalf("reference %q was expected to have a enum value", path) + } + + if *input.Enum != *ref.Enum { + t.Errorf("reference %q was expected to have enum value [%d], not [%d]", path, *input.Enum, *ref.Enum) + } + case input.Value != nil: + if ref == nil { + t.Fatalf("reference %q was expected to be set", path) + } + + if ref.Value != input.Value { + t.Errorf("reference %q was expected to be %T(%v), got %T(%v)", path, input.Value, input.Value, ref.Value, ref.Value) + } + case input.Repeated != nil: + if ref == nil { + t.Fatalf("reference %q was expected to be set", path) + } + + if ref.Repeated == nil { + t.Fatalf("reference %q was expected to have a repeated value", path) + } + + if expected, actual := len(input.Repeated), len(ref.Repeated); actual != expected { + t.Fatalf("invalid number of repeated values, expected %d, got %d", expected, actual) + } + + for index, expected := range input.Repeated { + Assert(t, "", "", ref.Repeated[index], expected) + } + } +} diff --git a/pkg/codec/xml/schema_test.go b/pkg/codec/tests/schema.go similarity index 89% rename from pkg/codec/xml/schema_test.go rename to pkg/codec/tests/schema.go index f646d9ea..deb299c9 100644 --- a/pkg/codec/xml/schema_test.go +++ b/pkg/codec/tests/schema.go @@ -1,4 +1,4 @@ -package xml +package tests import ( "github.com/jexia/semaphore/pkg/specs" @@ -7,7 +7,7 @@ import ( "github.com/jexia/semaphore/pkg/specs/types" ) -func propString() *specs.Property { +func PropString() *specs.Property { return &specs.Property{ Name: "string", Path: "string", @@ -20,7 +20,7 @@ func propString() *specs.Property { } } -func propInteger() *specs.Property { +func PropInteger() *specs.Property { return &specs.Property{ Name: "integer", Path: "integer", @@ -33,20 +33,20 @@ func propInteger() *specs.Property { } } -func propArray() *specs.Property { +func PropArray() *specs.Property { return &specs.Property{ Name: "array", Path: "array", Label: labels.Optional, Template: specs.Template{ Repeated: specs.Repeated{ - propString().Template, + PropString().Template, }, }, } } -func propEnum() *specs.Property { +func PropEnum() *specs.Property { return &specs.Property{ Name: "status", Path: "status", @@ -90,7 +90,8 @@ var ( Repeated: specs.Repeated{ { Scalar: &specs.Scalar{ - Type: types.String, + Type: types.String, + Default: nil, }, }, }, @@ -132,14 +133,14 @@ var ( Template: specs.Template{ Message: specs.Message{ "integer": func() *specs.Property { - var clone = propInteger() + var clone = PropInteger() clone.Position = 1 clone.Path = "root." + clone.Path return clone }(), "array": func() *specs.Property { - var clone = propArray() + var clone = PropArray() clone.Position = 2 clone.Path = "root." + clone.Path @@ -150,6 +151,37 @@ var ( }, } + SchemaArrayOfArrays = &specs.ParameterMap{ + Property: &specs.Property{ + Name: "array", + Path: "array", + Label: labels.Optional, + Template: specs.Template{ + Repeated: specs.Repeated{ + { + Repeated: specs.Repeated{ + { + Reference: &specs.PropertyReference{ + Resource: template.InputResource, + Path: "string", + }, + Scalar: &specs.Scalar{ + Type: types.String, + }, + }, + { + Scalar: &specs.Scalar{ + Type: types.String, + Default: "bar", + }, + }, + }, + }, + }, + }, + }, + } + SchemaObject = &specs.ParameterMap{ Property: &specs.Property{ Name: "root", @@ -157,14 +189,14 @@ var ( Template: specs.Template{ Message: specs.Message{ "status": func() *specs.Property { - var clone = propEnum() + var clone = PropEnum() clone.Position = 1 clone.Path = "root." + clone.Path return clone }(), "integer": func() *specs.Property { - var clone = propInteger() + var clone = PropInteger() clone.Position = 2 clone.Path = "root." + clone.Path @@ -189,14 +221,14 @@ var ( Template: specs.Template{ Message: specs.Message{ "status": func() *specs.Property { - var clone = propEnum() + var clone = PropEnum() clone.Position = 1 clone.Path = "root.nested." + clone.Path return clone }(), "integer": func() *specs.Property { - var clone = propInteger() + var clone = PropInteger() clone.Position = 2 clone.Path = "root.nested." + clone.Path @@ -206,7 +238,7 @@ var ( }, }, "string": func() *specs.Property { - var clone = propString() + var clone = PropString() clone.Position = 2 clone.Path = "root." + clone.Path diff --git a/pkg/codec/xml/xml.go b/pkg/codec/xml/xml.go index d89fb5d6..2447c024 100644 --- a/pkg/codec/xml/xml.go +++ b/pkg/codec/xml/xml.go @@ -65,13 +65,13 @@ func (manager *Manager) Marshal(refs references.Store) (io.Reader, error) { manager.property.Template, refs, ); err != nil { - writer.CloseWithError(err) + _ = writer.CloseWithError(err) return } if err := encoder.Flush(); err != nil { - writer.CloseWithError(err) + _ = writer.CloseWithError(err) return } diff --git a/pkg/codec/xml/xml_test.go b/pkg/codec/xml/xml_test.go index e08940ed..e488a960 100644 --- a/pkg/codec/xml/xml_test.go +++ b/pkg/codec/xml/xml_test.go @@ -8,6 +8,7 @@ import ( "strings" "testing" + "github.com/jexia/semaphore/pkg/codec/tests" "github.com/jexia/semaphore/pkg/references" "github.com/jexia/semaphore/pkg/specs" "github.com/jexia/semaphore/pkg/specs/template" @@ -29,7 +30,7 @@ func TestName(t *testing.T) { } }) - manager, err := xml.New("mock", SchemaObject) + manager, err := xml.New("mock", tests.SchemaObject) if err != nil { t.Fatal(err) } @@ -55,20 +56,20 @@ func TestMarshal(t *testing.T) { var tests = map[string]test{ "array empty": { - schema: SchemaArrayDefaultEmpty, + schema: tests.SchemaArrayDefaultEmpty, }, "array default reference": { input: map[string]interface{}{ "string": "foo", }, - schema: SchemaArrayWithValues, + schema: tests.SchemaArrayWithValues, expected: "foobar", }, "simple": { input: map[string]interface{}{ "message": "hello world", }, - schema: SchemaObjectComplex, + schema: tests.SchemaObjectComplex, expected: "hello world", }, "enum": { @@ -76,7 +77,7 @@ func TestMarshal(t *testing.T) { "nested": map[string]interface{}{}, "status": references.Enum("PENDING", 1), }, - schema: SchemaObjectComplex, + schema: tests.SchemaObjectComplex, expected: "PENDING", }, "nested": { @@ -86,7 +87,7 @@ func TestMarshal(t *testing.T) { "second": "bar", }, }, - schema: SchemaObjectComplex, + schema: tests.SchemaObjectComplex, expected: "foobar", }, "repeating string": { @@ -97,7 +98,7 @@ func TestMarshal(t *testing.T) { nil, // TODO: nil (null) values should not be ignored }, }, - schema: SchemaObjectComplex, + schema: tests.SchemaObjectComplex, expected: "repeating onerepeating two", }, "repeating enum": { @@ -107,7 +108,7 @@ func TestMarshal(t *testing.T) { references.Enum("PENDING", 1), }, }, - schema: SchemaObjectComplex, + schema: tests.SchemaObjectComplex, expected: "UNKNOWNPENDING", }, "repeating nested": { @@ -121,7 +122,7 @@ func TestMarshal(t *testing.T) { }, }, }, - schema: SchemaObjectComplex, + schema: tests.SchemaObjectComplex, expected: "repeating onerepeating two", }, "complex": { @@ -141,7 +142,7 @@ func TestMarshal(t *testing.T) { }, }, }, - schema: SchemaObjectComplex, + schema: tests.SchemaObjectComplex, expected: "42hello worldfoobarrepeating onerepeating two", }, } @@ -173,10 +174,6 @@ func TestMarshal(t *testing.T) { } } -type readerFunc func([]byte) (int, error) - -func (fn readerFunc) Read(p []byte) (int, error) { return fn(p) } - func errorString(err error) string { if err != nil { return err.Error() @@ -189,17 +186,17 @@ func TestUnmarshal(t *testing.T) { type test struct { input io.Reader schema *specs.ParameterMap - expected expect + expected tests.Expect error error } - tests := map[string]test{ + testCases := map[string]test{ "empty scalar with unexpected element": { input: strings.NewReader( "", ), schema: &specs.ParameterMap{ - Property: propInteger(), + Property: tests.PropInteger(), }, error: errFailedToDecode{ errStack{ @@ -219,7 +216,7 @@ func TestUnmarshal(t *testing.T) { "42", ), schema: &specs.ParameterMap{ - Property: propInteger(), + Property: tests.PropInteger(), }, error: errFailedToDecode{ errStack{ @@ -238,7 +235,7 @@ func TestUnmarshal(t *testing.T) { "foo", ), schema: &specs.ParameterMap{ - Property: propInteger(), + Property: tests.PropInteger(), }, error: errFailedToDecode{ errStack{ @@ -252,12 +249,12 @@ func TestUnmarshal(t *testing.T) { "", ), schema: &specs.ParameterMap{ - Property: propInteger(), + Property: tests.PropInteger(), }, - expected: expect{ - nested: map[string]expect{ + expected: tests.Expect{ + Nested: map[string]tests.Expect{ "integer": { - value: nil, + Value: nil, }, }, }, @@ -267,12 +264,12 @@ func TestUnmarshal(t *testing.T) { "42", ), schema: &specs.ParameterMap{ - Property: propInteger(), + Property: tests.PropInteger(), }, - expected: expect{ - nested: map[string]expect{ + expected: tests.Expect{ + Nested: map[string]tests.Expect{ "integer": { - value: int32(42), + Value: int32(42), }, }, }, @@ -282,7 +279,7 @@ func TestUnmarshal(t *testing.T) { "", ), schema: &specs.ParameterMap{ - Property: propEnum(), + Property: tests.PropEnum(), }, error: errFailedToDecode{ errStack{ @@ -302,7 +299,7 @@ func TestUnmarshal(t *testing.T) { "UNKNOWN", ), schema: &specs.ParameterMap{ - Property: propEnum(), + Property: tests.PropEnum(), }, error: errFailedToDecode{ errStack{ @@ -321,7 +318,7 @@ func TestUnmarshal(t *testing.T) { "foo", ), schema: &specs.ParameterMap{ - Property: propEnum(), + Property: tests.PropEnum(), }, error: errFailedToDecode{ errStack{ @@ -335,7 +332,7 @@ func TestUnmarshal(t *testing.T) { "", ), schema: &specs.ParameterMap{ - Property: propEnum(), + Property: tests.PropEnum(), }, }, "enum": { @@ -343,12 +340,12 @@ func TestUnmarshal(t *testing.T) { "PENDING", ), schema: &specs.ParameterMap{ - Property: propEnum(), + Property: tests.PropEnum(), }, - expected: expect{ - nested: map[string]expect{ + expected: tests.Expect{ + Nested: map[string]tests.Expect{ "status": { - enum: func() *int32 { i := int32(1); return &i }(), + Enum: func() *int32 { i := int32(1); return &i }(), }, }, }, @@ -360,14 +357,14 @@ func TestUnmarshal(t *testing.T) { 42 `, ), - schema: SchemaObject, - expected: expect{ - nested: map[string]expect{ + schema: tests.SchemaObject, + expected: tests.Expect{ + Nested: map[string]tests.Expect{ "root.status": { - enum: func() *int32 { i := int32(1); return &i }(), + Enum: func() *int32 { i := int32(1); return &i }(), }, "root.integer": { - value: int32(42), + Value: int32(42), }, }, }, @@ -382,7 +379,7 @@ func TestUnmarshal(t *testing.T) { foobar `, ), - schema: SchemaObjectNested, + schema: tests.SchemaObjectNested, error: errFailedToDecode{ errStack: errStack{ property: "root", @@ -410,17 +407,17 @@ func TestUnmarshal(t *testing.T) { foobar `, ), - schema: SchemaObjectNested, - expected: expect{ - nested: map[string]expect{ + schema: tests.SchemaObjectNested, + expected: tests.Expect{ + Nested: map[string]tests.Expect{ "root.nested.status": { - enum: func() *int32 { i := int32(1); return &i }(), + Enum: func() *int32 { i := int32(1); return &i }(), }, "root.nested.integer": { - value: int32(42), + Value: int32(42), }, "root.string": { - value: "foobar", + Value: "foobar", }, }, }, @@ -432,20 +429,20 @@ func TestUnmarshal(t *testing.T) { bar`, ), schema: &specs.ParameterMap{ - Property: propArray(), + Property: tests.PropArray(), }, - expected: expect{ - nested: map[string]expect{ + expected: tests.Expect{ + Nested: map[string]tests.Expect{ "array": { - repeated: []expect{ + Repeated: []tests.Expect{ { - value: "foo", + Value: "foo", }, { - value: nil, + Value: nil, }, { - value: "bar", + Value: "bar", }, }, }, @@ -461,22 +458,22 @@ func TestUnmarshal(t *testing.T) { bar `, ), - schema: SchemaNestedArray, - expected: expect{ - nested: map[string]expect{ + schema: tests.SchemaNestedArray, + expected: tests.Expect{ + Nested: map[string]tests.Expect{ "root.integer": { - value: int32(42), + Value: int32(42), }, "root.array": { - repeated: []expect{ + Repeated: []tests.Expect{ { - value: "foo", + Value: "foo", }, { - value: nil, + Value: nil, }, { - value: "bar", + Value: "bar", }, }, }, @@ -502,41 +499,41 @@ func TestUnmarshal(t *testing.T) { `, ), - schema: SchemaObjectComplex, - expected: expect{ - nested: map[string]expect{ + schema: tests.SchemaObjectComplex, + expected: tests.Expect{ + Nested: map[string]tests.Expect{ "root.repeating_string": { - repeated: []expect{ + Repeated: []tests.Expect{ { - value: "foo", + Value: "foo", }, { - value: "bar", + Value: "bar", }, }, }, "root.message": { - value: "hello world", + Value: "hello world", }, "root.nested.first": { - value: "foo", + Value: "foo", }, "root.nested.second": { - value: "bar", + Value: "bar", }, "root.repeating": { - repeated: []expect{ + Repeated: []tests.Expect{ { - nested: map[string]expect{ + Nested: map[string]tests.Expect{ "value": { - value: "repeating one", + Value: "repeating one", }, }, }, { - nested: map[string]expect{ + Nested: map[string]tests.Expect{ "value": { - value: "repeating two", + Value: "repeating two", }, }, }, @@ -547,7 +544,7 @@ func TestUnmarshal(t *testing.T) { }, } - for title, test := range tests { + for title, test := range testCases { t.Run(title, func(t *testing.T) { xml := NewConstructor() if xml == nil { @@ -559,8 +556,8 @@ func TestUnmarshal(t *testing.T) { t.Fatal(err) } - var refs = references.NewReferenceStore(0) - err = manager.Unmarshal(test.input, refs) + var store = references.NewReferenceStore(0) + err = manager.Unmarshal(test.input, store) if test.error != nil { if err == nil { @@ -577,49 +574,7 @@ func TestUnmarshal(t *testing.T) { } } - assert(t, "mock", "", refs, test.expected) + tests.Assert(t, "mock", "", store, test.expected) }) } } - -type expect struct { - value interface{} - enum *int32 - repeated []expect - nested map[string]expect -} - -func assert(t *testing.T, resource, path string, store references.Store, input expect) { - ref := store.Load(resource, path) - - switch { - case input.nested != nil: - for key, value := range input.nested { - assert(t, resource, key, store, value) - } - case input.enum != nil: - if ref == nil || ref.Enum == nil { - t.Fatalf("reference %q was expected to have a enum value", path) - } - - if *input.enum != *ref.Enum { - t.Errorf("reference %q was expected to have enum value [%d], not [%d]", path, *input.enum, *ref.Enum) - } - case input.value != nil: - if ref == nil || ref.Value != input.value { - t.Errorf("reference %q was expected to be %T(%v), got %T(%v)", path, input.value, input.value, ref.Value, ref.Value) - } - case input.repeated != nil: - if ref == nil || ref.Repeated == nil { - t.Fatalf("reference %q was expected to have a repeated value", path) - } - - if expected, actual := len(input.repeated), len(ref.Repeated); actual != expected { - t.Fatalf("invalid number of repeated values, expected %d, got %d", expected, actual) - } - - for index, expected := range input.repeated { - assert(t, "", "", ref.Repeated[index], expected) - } - } -} diff --git a/vendor/github.com/francoispqt/gojay/encode.go b/vendor/github.com/francoispqt/gojay/encode.go index 92edaafa..577c3c24 100644 --- a/vendor/github.com/francoispqt/gojay/encode.go +++ b/vendor/github.com/francoispqt/gojay/encode.go @@ -196,7 +196,11 @@ func (enc *Encoder) Write() (int, error) { return i, err } -func (enc *Encoder) getPreviousRune() byte { +func (enc *Encoder) getPreviousRune() (byte, bool) { last := len(enc.buf) - 1 - return enc.buf[last] + if last < 0 { + return ' ', false + } + + return enc.buf[last], true } diff --git a/vendor/github.com/francoispqt/gojay/encode_array.go b/vendor/github.com/francoispqt/gojay/encode_array.go index 5e9d49e8..286bbd15 100644 --- a/vendor/github.com/francoispqt/gojay/encode_array.go +++ b/vendor/github.com/francoispqt/gojay/encode_array.go @@ -62,8 +62,8 @@ func (enc *Encoder) AddArrayKeyNullEmpty(key string, v MarshalerJSONArray) { func (enc *Encoder) Array(v MarshalerJSONArray) { if v.IsNil() { enc.grow(3) - r := enc.getPreviousRune() - if r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '[' { enc.writeByte(',') } enc.writeByte('[') @@ -71,8 +71,8 @@ func (enc *Encoder) Array(v MarshalerJSONArray) { return } enc.grow(100) - r := enc.getPreviousRune() - if r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '[' { enc.writeByte(',') } enc.writeByte('[') @@ -87,8 +87,8 @@ func (enc *Encoder) ArrayOmitEmpty(v MarshalerJSONArray) { return } enc.grow(4) - r := enc.getPreviousRune() - if r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '[' { enc.writeByte(',') } enc.writeByte('[') @@ -100,8 +100,8 @@ func (enc *Encoder) ArrayOmitEmpty(v MarshalerJSONArray) { // value must implement Marshaler func (enc *Encoder) ArrayNullEmpty(v MarshalerJSONArray) { enc.grow(4) - r := enc.getPreviousRune() - if r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '[' { enc.writeByte(',') } if v.IsNil() { @@ -123,8 +123,8 @@ func (enc *Encoder) ArrayKey(key string, v MarshalerJSONArray) { } if v.IsNil() { enc.grow(2 + len(key)) - r := enc.getPreviousRune() - if r != '{' { + r, ok := enc.getPreviousRune() + if ok && r != '{' { enc.writeByte(',') } enc.writeByte('"') @@ -134,8 +134,8 @@ func (enc *Encoder) ArrayKey(key string, v MarshalerJSONArray) { return } enc.grow(5 + len(key)) - r := enc.getPreviousRune() - if r != '{' { + r, ok := enc.getPreviousRune() + if ok && r != '{' { enc.writeByte(',') } enc.writeByte('"') @@ -157,8 +157,8 @@ func (enc *Encoder) ArrayKeyOmitEmpty(key string, v MarshalerJSONArray) { return } enc.grow(5 + len(key)) - r := enc.getPreviousRune() - if r != '{' { + r, ok := enc.getPreviousRune() + if ok && r != '{' { enc.writeByte(',') } enc.writeByte('"') @@ -177,8 +177,8 @@ func (enc *Encoder) ArrayKeyNullEmpty(key string, v MarshalerJSONArray) { } } enc.grow(5 + len(key)) - r := enc.getPreviousRune() - if r != '{' { + r, ok := enc.getPreviousRune() + if ok && r != '{' { enc.writeByte(',') } if v.IsNil() { diff --git a/vendor/github.com/francoispqt/gojay/encode_bool.go b/vendor/github.com/francoispqt/gojay/encode_bool.go index 253e0378..60f647a4 100644 --- a/vendor/github.com/francoispqt/gojay/encode_bool.go +++ b/vendor/github.com/francoispqt/gojay/encode_bool.go @@ -62,8 +62,8 @@ func (enc *Encoder) AddBoolKeyNullEmpty(key string, v bool) { // Bool adds a bool to be encoded, must be used inside a slice or array encoding (does not encode a key) func (enc *Encoder) Bool(v bool) { enc.grow(5) - r := enc.getPreviousRune() - if r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '[' { enc.writeByte(',') } if v { @@ -79,8 +79,8 @@ func (enc *Encoder) BoolOmitEmpty(v bool) { return } enc.grow(5) - r := enc.getPreviousRune() - if r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '[' { enc.writeByte(',') } enc.writeString("true") @@ -89,8 +89,8 @@ func (enc *Encoder) BoolOmitEmpty(v bool) { // BoolNullEmpty adds a bool to be encoded, must be used inside a slice or array encoding (does not encode a key) func (enc *Encoder) BoolNullEmpty(v bool) { enc.grow(5) - r := enc.getPreviousRune() - if r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '[' { enc.writeByte(',') } if v == false { @@ -108,8 +108,8 @@ func (enc *Encoder) BoolKey(key string, value bool) { } } enc.grow(5 + len(key)) - r := enc.getPreviousRune() - if r != '{' { + r, ok := enc.getPreviousRune() + if ok && r != '{' { enc.writeByte(',') } enc.writeByte('"') @@ -130,8 +130,8 @@ func (enc *Encoder) BoolKeyOmitEmpty(key string, v bool) { return } enc.grow(5 + len(key)) - r := enc.getPreviousRune() - if r != '{' { + r, ok := enc.getPreviousRune() + if ok && r != '{' { enc.writeByte(',') } enc.writeByte('"') @@ -149,8 +149,8 @@ func (enc *Encoder) BoolKeyNullEmpty(key string, v bool) { } } enc.grow(5 + len(key)) - r := enc.getPreviousRune() - if r != '{' { + r, ok := enc.getPreviousRune() + if ok && r != '{' { enc.writeByte(',') } enc.writeByte('"') diff --git a/vendor/github.com/francoispqt/gojay/encode_embedded_json.go b/vendor/github.com/francoispqt/gojay/encode_embedded_json.go index 4c99a057..6cfee9fb 100644 --- a/vendor/github.com/francoispqt/gojay/encode_embedded_json.go +++ b/vendor/github.com/francoispqt/gojay/encode_embedded_json.go @@ -25,8 +25,8 @@ func (enc *Encoder) encodeEmbeddedJSON(v *EmbeddedJSON) ([]byte, error) { // it expects the JSON to be of proper format. func (enc *Encoder) AddEmbeddedJSON(v *EmbeddedJSON) { enc.grow(len(*v) + 4) - r := enc.getPreviousRune() - if r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '[' { enc.writeByte(',') } enc.writeBytes(*v) @@ -40,8 +40,8 @@ func (enc *Encoder) AddEmbeddedJSONOmitEmpty(v *EmbeddedJSON) { if v == nil || len(*v) == 0 { return } - r := enc.getPreviousRune() - if r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '[' { enc.writeByte(',') } enc.writeBytes(*v) @@ -58,8 +58,8 @@ func (enc *Encoder) AddEmbeddedJSONKey(key string, v *EmbeddedJSON) { } } enc.grow(len(key) + len(*v) + 5) - r := enc.getPreviousRune() - if r != '{' { + r, ok := enc.getPreviousRune() + if ok && r != '{' { enc.writeByte(',') } enc.writeByte('"') @@ -82,8 +82,8 @@ func (enc *Encoder) AddEmbeddedJSONKeyOmitEmpty(key string, v *EmbeddedJSON) { return } enc.grow(len(key) + len(*v) + 5) - r := enc.getPreviousRune() - if r != '{' { + r, ok := enc.getPreviousRune() + if ok && r != '{' { enc.writeByte(',') } enc.writeByte('"') diff --git a/vendor/github.com/francoispqt/gojay/encode_null.go b/vendor/github.com/francoispqt/gojay/encode_null.go index cec4e639..ec3a6164 100644 --- a/vendor/github.com/francoispqt/gojay/encode_null.go +++ b/vendor/github.com/francoispqt/gojay/encode_null.go @@ -8,8 +8,8 @@ func (enc *Encoder) AddNull() { // Null adds a `null` to be encoded. Must be used while encoding an array.` func (enc *Encoder) Null() { enc.grow(5) - r := enc.getPreviousRune() - if r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '[' { enc.writeByte(',') } enc.writeBytes(nullBytes) @@ -28,8 +28,8 @@ func (enc *Encoder) NullKey(key string) { } } enc.grow(5 + len(key)) - r := enc.getPreviousRune() - if r != '{' { + r, ok := enc.getPreviousRune() + if ok && r != '{' { enc.writeByte(',') } enc.writeByte('"') diff --git a/vendor/github.com/francoispqt/gojay/encode_number_float.go b/vendor/github.com/francoispqt/gojay/encode_number_float.go index b45f8442..eff7a67c 100644 --- a/vendor/github.com/francoispqt/gojay/encode_number_float.go +++ b/vendor/github.com/francoispqt/gojay/encode_number_float.go @@ -121,8 +121,8 @@ func (enc *Encoder) AddFloat64OmitEmpty(v float64) { // Float64 adds a float64 to be encoded, must be used inside a slice or array encoding (does not encode a key) func (enc *Encoder) Float64(v float64) { enc.grow(10) - r := enc.getPreviousRune() - if r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '[' { enc.writeByte(',') } enc.buf = strconv.AppendFloat(enc.buf, v, 'f', -1, 64) @@ -135,8 +135,8 @@ func (enc *Encoder) Float64OmitEmpty(v float64) { return } enc.grow(10) - r := enc.getPreviousRune() - if r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '[' { enc.writeByte(',') } enc.buf = strconv.AppendFloat(enc.buf, v, 'f', -1, 64) @@ -146,8 +146,8 @@ func (enc *Encoder) Float64OmitEmpty(v float64) { // must be used inside a slice or array encoding (does not encode a key). func (enc *Encoder) Float64NullEmpty(v float64) { enc.grow(10) - r := enc.getPreviousRune() - if r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '[' { enc.writeByte(',') } if v == 0 { @@ -175,8 +175,8 @@ func (enc *Encoder) Float64Key(key string, value float64) { return } } - r := enc.getPreviousRune() - if r != '{' { + r, ok := enc.getPreviousRune() + if ok && r != '{' { enc.writeByte(',') } enc.grow(10) @@ -198,8 +198,8 @@ func (enc *Encoder) Float64KeyOmitEmpty(key string, v float64) { return } enc.grow(10 + len(key)) - r := enc.getPreviousRune() - if r != '{' { + r, ok := enc.getPreviousRune() + if ok && r != '{' { enc.writeByte(',') } enc.writeByte('"') @@ -217,8 +217,8 @@ func (enc *Encoder) Float64KeyNullEmpty(key string, v float64) { } } enc.grow(10 + len(key)) - r := enc.getPreviousRune() - if r != '{' { + r, ok := enc.getPreviousRune() + if ok && r != '{' { enc.writeByte(',') } enc.writeByte('"') @@ -250,8 +250,8 @@ func (enc *Encoder) AddFloat32NullEmpty(v float32) { // Float32 adds a float32 to be encoded, must be used inside a slice or array encoding (does not encode a key) func (enc *Encoder) Float32(v float32) { - r := enc.getPreviousRune() - if r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '[' { enc.writeByte(',') } enc.buf = strconv.AppendFloat(enc.buf, float64(v), 'f', -1, 32) @@ -264,8 +264,8 @@ func (enc *Encoder) Float32OmitEmpty(v float32) { return } enc.grow(10) - r := enc.getPreviousRune() - if r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '[' { enc.writeByte(',') } enc.buf = strconv.AppendFloat(enc.buf, float64(v), 'f', -1, 32) @@ -275,8 +275,8 @@ func (enc *Encoder) Float32OmitEmpty(v float32) { // must be used inside a slice or array encoding (does not encode a key). func (enc *Encoder) Float32NullEmpty(v float32) { enc.grow(10) - r := enc.getPreviousRune() - if r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '[' { enc.writeByte(',') } if v == 0 { @@ -311,8 +311,8 @@ func (enc *Encoder) Float32Key(key string, v float32) { } } enc.grow(10 + len(key)) - r := enc.getPreviousRune() - if r != '{' { + r, ok := enc.getPreviousRune() + if ok && r != '{' { enc.writeByte(',') } enc.writeByte('"') @@ -334,8 +334,8 @@ func (enc *Encoder) Float32KeyOmitEmpty(key string, v float32) { return } enc.grow(10 + len(key)) - r := enc.getPreviousRune() - if r != '{' { + r, ok := enc.getPreviousRune() + if ok && r != '{' { enc.writeByte(',') } enc.writeByte('"') @@ -353,8 +353,8 @@ func (enc *Encoder) Float32KeyNullEmpty(key string, v float32) { } } enc.grow(10 + len(key)) - r := enc.getPreviousRune() - if r != '{' { + r, ok := enc.getPreviousRune() + if ok && r != '{' { enc.writeByte(',') } enc.writeByte('"') diff --git a/vendor/github.com/francoispqt/gojay/encode_number_int.go b/vendor/github.com/francoispqt/gojay/encode_number_int.go index 2c4bbe34..244a2121 100644 --- a/vendor/github.com/francoispqt/gojay/encode_number_int.go +++ b/vendor/github.com/francoispqt/gojay/encode_number_int.go @@ -60,8 +60,8 @@ func (enc *Encoder) AddIntNullEmpty(v int) { // Int adds an int to be encoded, must be used inside a slice or array encoding (does not encode a key) func (enc *Encoder) Int(v int) { enc.grow(10) - r := enc.getPreviousRune() - if r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '[' { enc.writeByte(',') } enc.buf = strconv.AppendInt(enc.buf, int64(v), 10) @@ -74,8 +74,8 @@ func (enc *Encoder) IntOmitEmpty(v int) { return } enc.grow(10) - r := enc.getPreviousRune() - if r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '[' { enc.writeByte(',') } enc.buf = strconv.AppendInt(enc.buf, int64(v), 10) @@ -85,8 +85,8 @@ func (enc *Encoder) IntOmitEmpty(v int) { // must be used inside a slice or array encoding (does not encode a key). func (enc *Encoder) IntNullEmpty(v int) { enc.grow(10) - r := enc.getPreviousRune() - if r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '[' { enc.writeByte(',') } if v == 0 { @@ -121,8 +121,8 @@ func (enc *Encoder) IntKey(key string, v int) { } } enc.grow(10 + len(key)) - r := enc.getPreviousRune() - if r != '{' { + r, ok := enc.getPreviousRune() + if ok && r != '{' { enc.writeByte(',') } enc.writeByte('"') @@ -143,8 +143,8 @@ func (enc *Encoder) IntKeyOmitEmpty(key string, v int) { return } enc.grow(10 + len(key)) - r := enc.getPreviousRune() - if r != '{' && r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '{' && r != '[' { enc.writeByte(',') } enc.writeByte('"') @@ -162,8 +162,8 @@ func (enc *Encoder) IntKeyNullEmpty(key string, v int) { } } enc.grow(10 + len(key)) - r := enc.getPreviousRune() - if r != '{' && r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '{' && r != '[' { enc.writeByte(',') } enc.writeByte('"') @@ -196,8 +196,8 @@ func (enc *Encoder) AddInt64NullEmpty(v int64) { // Int64 adds an int to be encoded, must be used inside a slice or array encoding (does not encode a key) func (enc *Encoder) Int64(v int64) { enc.grow(10) - r := enc.getPreviousRune() - if r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '[' { enc.writeByte(',') } enc.buf = strconv.AppendInt(enc.buf, v, 10) @@ -210,8 +210,8 @@ func (enc *Encoder) Int64OmitEmpty(v int64) { return } enc.grow(10) - r := enc.getPreviousRune() - if r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '[' { enc.writeByte(',') } enc.buf = strconv.AppendInt(enc.buf, v, 10) @@ -221,8 +221,8 @@ func (enc *Encoder) Int64OmitEmpty(v int64) { // must be used inside a slice or array encoding (does not encode a key). func (enc *Encoder) Int64NullEmpty(v int64) { enc.grow(10) - r := enc.getPreviousRune() - if r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '[' { enc.writeByte(',') } if v == 0 { @@ -257,8 +257,8 @@ func (enc *Encoder) Int64Key(key string, v int64) { } } enc.grow(10 + len(key)) - r := enc.getPreviousRune() - if r != '{' { + r, ok := enc.getPreviousRune() + if ok && r != '{' { enc.writeByte(',') } enc.writeByte('"') @@ -274,8 +274,8 @@ func (enc *Encoder) Int64KeyOmitEmpty(key string, v int64) { return } enc.grow(10 + len(key)) - r := enc.getPreviousRune() - if r != '{' { + r, ok := enc.getPreviousRune() + if ok && r != '{' { enc.writeByte(',') } enc.writeByte('"') @@ -293,8 +293,8 @@ func (enc *Encoder) Int64KeyNullEmpty(key string, v int64) { } } enc.grow(10 + len(key)) - r := enc.getPreviousRune() - if r != '{' { + r, ok := enc.getPreviousRune() + if ok && r != '{' { enc.writeByte(',') } enc.writeByte('"') diff --git a/vendor/github.com/francoispqt/gojay/encode_number_uint.go b/vendor/github.com/francoispqt/gojay/encode_number_uint.go index cd69b13f..1e2b94f5 100644 --- a/vendor/github.com/francoispqt/gojay/encode_number_uint.go +++ b/vendor/github.com/francoispqt/gojay/encode_number_uint.go @@ -41,8 +41,8 @@ func (enc *Encoder) AddUint64NullEmpty(v uint64) { // Uint64 adds an int to be encoded, must be used inside a slice or array encoding (does not encode a key) func (enc *Encoder) Uint64(v uint64) { enc.grow(10) - r := enc.getPreviousRune() - if r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '[' { enc.writeByte(',') } enc.buf = strconv.AppendUint(enc.buf, v, 10) @@ -55,8 +55,8 @@ func (enc *Encoder) Uint64OmitEmpty(v uint64) { return } enc.grow(10) - r := enc.getPreviousRune() - if r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '[' { enc.writeByte(',') } enc.buf = strconv.AppendUint(enc.buf, v, 10) @@ -66,8 +66,8 @@ func (enc *Encoder) Uint64OmitEmpty(v uint64) { // must be used inside a slice or array encoding (does not encode a key). func (enc *Encoder) Uint64NullEmpty(v uint64) { enc.grow(10) - r := enc.getPreviousRune() - if r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '[' { enc.writeByte(',') } if v == 0 { @@ -102,8 +102,8 @@ func (enc *Encoder) Uint64Key(key string, v uint64) { } } enc.grow(10 + len(key)) - r := enc.getPreviousRune() - if r != '{' { + r, ok := enc.getPreviousRune() + if ok && r != '{' { enc.writeByte(',') } enc.writeByte('"') @@ -124,8 +124,8 @@ func (enc *Encoder) Uint64KeyOmitEmpty(key string, v uint64) { return } enc.grow(10 + len(key)) - r := enc.getPreviousRune() - if r != '{' && r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '{' && r != '[' { enc.writeByte(',') } enc.writeByte('"') @@ -143,8 +143,8 @@ func (enc *Encoder) Uint64KeyNullEmpty(key string, v uint64) { } } enc.grow(10 + len(key)) - r := enc.getPreviousRune() - if r != '{' && r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '{' && r != '[' { enc.writeByte(',') } enc.writeByte('"') diff --git a/vendor/github.com/francoispqt/gojay/encode_object.go b/vendor/github.com/francoispqt/gojay/encode_object.go index 5f2c8cf3..60dd7e38 100644 --- a/vendor/github.com/francoispqt/gojay/encode_object.go +++ b/vendor/github.com/francoispqt/gojay/encode_object.go @@ -102,8 +102,8 @@ func (enc *Encoder) AddObjectKeyNullEmpty(key string, v MarshalerJSONObject) { func (enc *Encoder) Object(v MarshalerJSONObject) { if v.IsNil() { enc.grow(2) - r := enc.getPreviousRune() - if r != '{' && r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '{' && r != '[' { enc.writeByte(',') } enc.writeByte('{') @@ -111,8 +111,8 @@ func (enc *Encoder) Object(v MarshalerJSONObject) { return } enc.grow(4) - r := enc.getPreviousRune() - if r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '[' { enc.writeByte(',') } enc.writeByte('{') @@ -135,8 +135,8 @@ func (enc *Encoder) Object(v MarshalerJSONObject) { func (enc *Encoder) ObjectWithKeys(v MarshalerJSONObject, keys []string) { if v.IsNil() { enc.grow(2) - r := enc.getPreviousRune() - if r != '{' && r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '{' && r != '[' { enc.writeByte(',') } enc.writeByte('{') @@ -144,8 +144,8 @@ func (enc *Encoder) ObjectWithKeys(v MarshalerJSONObject, keys []string) { return } enc.grow(4) - r := enc.getPreviousRune() - if r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '[' { enc.writeByte(',') } enc.writeByte('{') @@ -171,8 +171,8 @@ func (enc *Encoder) ObjectOmitEmpty(v MarshalerJSONObject) { return } enc.grow(2) - r := enc.getPreviousRune() - if r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '[' { enc.writeByte(',') } enc.writeByte('{') @@ -195,8 +195,8 @@ func (enc *Encoder) ObjectOmitEmpty(v MarshalerJSONObject) { // value must implement MarshalerJSONObject func (enc *Encoder) ObjectNullEmpty(v MarshalerJSONObject) { enc.grow(2) - r := enc.getPreviousRune() - if r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '[' { enc.writeByte(',') } if v.IsNil() { @@ -228,8 +228,8 @@ func (enc *Encoder) ObjectKey(key string, v MarshalerJSONObject) { } if v.IsNil() { enc.grow(2 + len(key)) - r := enc.getPreviousRune() - if r != '{' { + r, ok := enc.getPreviousRune() + if ok && r != '{' { enc.writeByte(',') } enc.writeByte('"') @@ -239,8 +239,8 @@ func (enc *Encoder) ObjectKey(key string, v MarshalerJSONObject) { return } enc.grow(5 + len(key)) - r := enc.getPreviousRune() - if r != '{' { + r, ok := enc.getPreviousRune() + if ok && r != '{' { enc.writeByte(',') } enc.writeByte('"') @@ -270,8 +270,8 @@ func (enc *Encoder) ObjectKeyWithKeys(key string, value MarshalerJSONObject, key } if value.IsNil() { enc.grow(2 + len(key)) - r := enc.getPreviousRune() - if r != '{' { + r, ok := enc.getPreviousRune() + if ok && r != '{' { enc.writeByte(',') } enc.writeByte('"') @@ -281,8 +281,8 @@ func (enc *Encoder) ObjectKeyWithKeys(key string, value MarshalerJSONObject, key return } enc.grow(5 + len(key)) - r := enc.getPreviousRune() - if r != '{' { + r, ok := enc.getPreviousRune() + if ok && r != '{' { enc.writeByte(',') } enc.writeByte('"') @@ -311,8 +311,8 @@ func (enc *Encoder) ObjectKeyOmitEmpty(key string, v MarshalerJSONObject) { return } enc.grow(5 + len(key)) - r := enc.getPreviousRune() - if r != '{' { + r, ok := enc.getPreviousRune() + if ok && r != '{' { enc.writeByte(',') } enc.writeByte('"') @@ -342,8 +342,8 @@ func (enc *Encoder) ObjectKeyNullEmpty(key string, v MarshalerJSONObject) { } } enc.grow(5 + len(key)) - r := enc.getPreviousRune() - if r != '{' { + r, ok := enc.getPreviousRune() + if ok && r != '{' { enc.writeByte(',') } enc.writeByte('"') diff --git a/vendor/github.com/francoispqt/gojay/encode_string.go b/vendor/github.com/francoispqt/gojay/encode_string.go index 438c773f..c5882abe 100644 --- a/vendor/github.com/francoispqt/gojay/encode_string.go +++ b/vendor/github.com/francoispqt/gojay/encode_string.go @@ -67,8 +67,8 @@ func (enc *Encoder) AddStringKeyNullEmpty(key, v string) { // String adds a string to be encoded, must be used inside a slice or array encoding (does not encode a key) func (enc *Encoder) String(v string) { enc.grow(len(v) + 4) - r := enc.getPreviousRune() - if r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '[' { enc.writeTwoBytes(',', '"') } else { enc.writeByte('"') @@ -83,8 +83,8 @@ func (enc *Encoder) StringOmitEmpty(v string) { if v == "" { return } - r := enc.getPreviousRune() - if r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '[' { enc.writeTwoBytes(',', '"') } else { enc.writeByte('"') @@ -96,9 +96,9 @@ func (enc *Encoder) StringOmitEmpty(v string) { // StringNullEmpty adds a string to be encoded or skips it if it is zero value. // Must be used inside a slice or array encoding (does not encode a key) func (enc *Encoder) StringNullEmpty(v string) { - r := enc.getPreviousRune() + r, ok := enc.getPreviousRune() if v == "" { - if r != '[' { + if ok && r != '[' { enc.writeByte(',') enc.writeBytes(nullBytes) } else { @@ -123,8 +123,8 @@ func (enc *Encoder) StringKey(key, v string) { } } enc.grow(len(key) + len(v) + 5) - r := enc.getPreviousRune() - if r != '{' { + r, ok := enc.getPreviousRune() + if ok && r != '{' { enc.writeTwoBytes(',', '"') } else { enc.writeByte('"') @@ -147,8 +147,8 @@ func (enc *Encoder) StringKeyOmitEmpty(key, v string) { return } enc.grow(len(key) + len(v) + 5) - r := enc.getPreviousRune() - if r != '{' { + r, ok := enc.getPreviousRune() + if ok && r != '{' { enc.writeTwoBytes(',', '"') } else { enc.writeByte('"') @@ -168,8 +168,8 @@ func (enc *Encoder) StringKeyNullEmpty(key, v string) { } } enc.grow(len(key) + len(v) + 5) - r := enc.getPreviousRune() - if r != '{' { + r, ok := enc.getPreviousRune() + if ok && r != '{' { enc.writeTwoBytes(',', '"') } else { enc.writeByte('"') diff --git a/vendor/github.com/francoispqt/gojay/encode_time.go b/vendor/github.com/francoispqt/gojay/encode_time.go index 6f99e342..15e4446e 100644 --- a/vendor/github.com/francoispqt/gojay/encode_time.go +++ b/vendor/github.com/francoispqt/gojay/encode_time.go @@ -38,8 +38,8 @@ func (enc *Encoder) TimeKey(key string, t *time.Time, format string) { } } enc.grow(10 + len(key)) - r := enc.getPreviousRune() - if r != '{' { + r, ok := enc.getPreviousRune() + if ok && r != '{' { enc.writeTwoBytes(',', '"') } else { enc.writeByte('"') @@ -58,8 +58,8 @@ func (enc *Encoder) AddTime(t *time.Time, format string) { // Time adds an *time.Time to be encoded with the given format, must be used inside a slice or array encoding (does not encode a key) func (enc *Encoder) Time(t *time.Time, format string) { enc.grow(10) - r := enc.getPreviousRune() - if r != '[' { + r, ok := enc.getPreviousRune() + if ok && r != '[' { enc.writeByte(',') } enc.writeByte('"')