Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

JSON: add support for default repeating values #149

Merged
merged 10 commits into from
Oct 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/linter.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@

bin/
tmp/

169 changes: 42 additions & 127 deletions pkg/codec/json/array.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
36 changes: 36 additions & 0 deletions pkg/codec/json/decode.go
Original file line number Diff line number Diff line change
@@ -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
}
43 changes: 43 additions & 0 deletions pkg/codec/json/encode.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
87 changes: 87 additions & 0 deletions pkg/codec/json/enum.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading