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

Add support for generating code which returns errors on unknown fields #162

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
18 changes: 18 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,21 @@ func (err InvalidUsagePooledEncoderError) Error() string {
// ErrUnmarshalPtrExpected is the error returned when unmarshal expects a pointer value,
// When using `dec.ObjectNull` or `dec.ArrayNull` for example.
var ErrUnmarshalPtrExpected = errors.New("Cannot unmarshal to given value, a pointer is expected")

const unknownFieldErrorMsg = "Encountered unknown field %q while unmarshal JSON to type '%T'"

// UnknownFieldError occurs when Decoding a object with a unknown field.
type UnknownFieldError string

func (err UnknownFieldError) Error() string {
return string(err)
}
func MakeUnknownFieldErr(v interface{}, key string) error {
return UnknownFieldError(
fmt.Sprintf(
unknownFieldErrorMsg,
key,
v,
),
)
}
4 changes: 4 additions & 0 deletions gojay/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ If you just want to the output to stdout, omit the -o flag.
- t Types to generate with all its dependencies (comma separated)
- a Annotation tag used to read metadata (default: json)
- o Output file (relative or absolute path)

Flags that alter the generated code:

- p Pool to reuse object (using sync.Pool)
- e Return a error on unknown fields (default: false)

Examples:

Expand Down
16 changes: 13 additions & 3 deletions gojay/codegen/generator_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package codegen

import (
"github.com/stretchr/testify/assert"
"github.com/viant/toolbox"
"log"
"path"
"testing"

"github.com/stretchr/testify/assert"
"github.com/viant/toolbox"
)

func TestGenerator_Generate(t *testing.T) {
Expand Down Expand Up @@ -45,7 +46,7 @@ func TestGenerator_Generate(t *testing.T) {
},
},
{
description: "struct with json annotation and time/foarmat|layouat generation",
description: "struct with json annotation and time/format|layout generation",
options: &Options{
Source: path.Join(parent, "annotated_struct"),
Types: []string{"Message"},
Expand All @@ -54,6 +55,15 @@ func TestGenerator_Generate(t *testing.T) {
TagName: "json",
},
},
{
description: "basic struct code generation with unknown field errors",
options: &Options{
Source: path.Join(parent, "unknown_struct"),
Types: []string{"Message"},
Dest: path.Join(parent, "unknown_struct", "encoding.go"),
ErrOnUnknown: true,
},
},
}

for _, useCase := range useCases {
Expand Down
27 changes: 15 additions & 12 deletions gojay/codegen/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import (
)

type Options struct {
Source string
Dest string
Types []string
PoolObjects bool
TagName string
Pkg string
Source string
Dest string
Types []string
PoolObjects bool
TagName string
Pkg string
ErrOnUnknown bool
}

func (o *Options) Validate() error {
Expand All @@ -29,12 +30,13 @@ func (o *Options) Validate() error {
}

const (
optionKeySource = "s"
optionKeyDest = "o"
optionKeyTypes = "t"
optionKeyTagName = "a"
optionKeyPoolObjects = "p"
optionKeyPkg = "pkg"
optionKeySource = "s"
optionKeyDest = "o"
optionKeyTypes = "t"
optionKeyTagName = "a"
optionKeyPoolObjects = "p"
optionKeyPkg = "pkg"
optionKeyErrOnUnknown = "e"
)

//NewOptionsWithFlagSet creates a new options for the supplide flagset
Expand All @@ -48,6 +50,7 @@ func NewOptionsWithFlagSet(set *flag.FlagSet) *Options {
result.TagName = set.Lookup(optionKeyTagName).Value.String()
result.Types = strings.Split(set.Lookup(optionKeyTypes).Value.String(), ",")
result.Pkg = set.Lookup(optionKeyPkg).Value.String()
result.ErrOnUnknown = toolbox.AsBoolean(set.Lookup(optionKeyErrOnUnknown).Value.String())
if result.Source == "" {
result.Source = url.NewResource(".").ParsedURL.Path
}
Expand Down
2 changes: 2 additions & 0 deletions gojay/codegen/struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func (s *Struct) generateEncoding(structInfo *toolbox.TypeInfo) (string, error)
DecodingCases string
Reset string
FieldCount int
ErrOnUnknown bool
}{
Receiver: s.Alias + " *" + s.Name,
DecodingCases: strings.Join(decodingCases, "\n"),
Expand All @@ -54,6 +55,7 @@ func (s *Struct) generateEncoding(structInfo *toolbox.TypeInfo) (string, error)
InitEmbedded: initEmbedded,
Reset: resetCode,
Alias: s.Alias,
ErrOnUnknown: s.options.ErrOnUnknown,
}
return expandBlockTemplate(encodingStructType, data)
}
Expand Down
16 changes: 13 additions & 3 deletions gojay/codegen/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,13 +174,23 @@ func ({{.Receiver}}) IsNil() bool {
func ({{.Receiver}}) UnmarshalJSONObject(dec *gojay.Decoder, k string) error {
{{.InitEmbedded}}
switch k {
{{.DecodingCases}}
{{.DecodingCases}}
{{if .ErrOnUnknown}}
default:
return gojay.MakeUnknownFieldErr({{.Alias}}, k)
{{end}}
}
return nil
}

// NKeys returns the number of keys to unmarshal
func ({{.Receiver}}) NKeys() int { return {{.FieldCount}} }
func ({{.Receiver}}) NKeys() int {
{{- if .ErrOnUnknown}}
return 0 // Unmarshal all keys so we can catch trailing unknown keys.
{{else}}
return {{.FieldCount}}
{{end -}}
}

{{.Reset}}

Expand Down Expand Up @@ -305,7 +315,7 @@ func expandTemplate(namespace string, dictionary map[int]string, key int, data i
}
temlate, err := template.New(id).Parse(textTemplate)
if err != nil {
return "", fmt.Errorf("fiailed to parse template %v %v, due to %v", namespace, key, err)
return "", fmt.Errorf("failed to parse template %v %v, due to %v", namespace, key, err)
}
writer := new(bytes.Buffer)
err = temlate.Execute(writer, data)
Expand Down
40 changes: 40 additions & 0 deletions gojay/codegen/test/unknown_struct/encoding.go

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

27 changes: 27 additions & 0 deletions gojay/codegen/test/unknown_struct/encoding_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package unknown_struct

import (
"testing"

"github.com/francoispqt/gojay"
"github.com/stretchr/testify/require"
)

var msg = &Message{
Id: 1022,
Name: "name acc",
}

var jsonData = `{
"Id": 1022,
"Name": "name acc",
"Price": 13.3
}`

func TestMessage_Unmarshal(t *testing.T) {
var err error
var data = []byte(jsonData)
message := &Message{}
err = gojay.UnmarshalJSONObject(data, message)
require.EqualError(t, err, gojay.MakeUnknownFieldErr(message, "Price").Error())
}
6 changes: 6 additions & 0 deletions gojay/codegen/test/unknown_struct/message.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package unknown_struct

type Message struct {
Id int
Name string
}
4 changes: 3 additions & 1 deletion gojay/gojay.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ package main

import (
"flag"
"github.com/francoispqt/gojay/gojay/codegen"
"log"

"github.com/francoispqt/gojay/gojay/codegen"
)

var pkg = flag.String("pkg", "", "the package name of the generated file")
Expand All @@ -12,6 +13,7 @@ var src = flag.String("s", "", "source dir or file (absolute or relative path)")
var types = flag.String("t", "", "types to generate")
var annotation = flag.String("a", "json", "annotation tag (default json)")
var poolObjects = flag.String("p", "", "generate code to reuse objects using sync.Pool")
var errOnUnknown = flag.String("e", "", "generate code to error on unknown fields")

func main() {
flag.Parse()
Expand Down