Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
jamietanna committed Aug 16, 2024
1 parent 0d2a587 commit ba95a2b
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 2 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module github.com/oapi-codegen/nullable

go 1.20

require gopkg.in/yaml.v3 v3.0.1
2 changes: 1 addition & 1 deletion internal/test/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ replace github.com/oapi-codegen/nullable => ../../
require (
github.com/oapi-codegen/nullable v0.0.0-00010101000000-000000000000
github.com/stretchr/testify v1.8.4
gopkg.in/yaml.v3 v3.0.1
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
79 changes: 78 additions & 1 deletion internal/test/nullable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import (
"testing"

"github.com/oapi-codegen/nullable"
"gopkg.in/yaml.v3"

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

type Obj struct {
Foo nullable.Nullable[string] `json:"foo,omitempty"` // note "omitempty" is important for fields that are optional
Foo nullable.Nullable[string] `json:"foo,omitempty",yaml:"foo,omitempty"` // note "omitempty" is important for fields that are optional
}

func TestNullable(t *testing.T) {
Expand Down Expand Up @@ -88,3 +89,79 @@ func serialize(o Obj, t *testing.T) string {
require.NoError(t, err)
return string(data)
}

func TestNullableYAML(t *testing.T) {
// --- parsing from json and serializing back to JSON

// -- case where there is an actual value
data := `foo: bar`
// deserialize from json
myObj := parseYAML(data, t)
require.Equal(t, myObj, Obj{Foo: nullable.Nullable[string]{true: "bar"}})
require.False(t, myObj.Foo.IsNull())
require.True(t, myObj.Foo.IsSpecified())
value, err := myObj.Foo.Get()
require.NoError(t, err)
require.Equal(t, "bar", value)
require.Equal(t, "bar", myObj.Foo.MustGet())
// serialize back to json: leads to the same data
require.Equal(t, data, serializeYAML(myObj, t))

// -- case where no value is specified: parsed from JSON
data = ``
// deserialize from json
myObj = parseYAML(data, t)
require.Equal(t, myObj, Obj{Foo: nil})
require.False(t, myObj.Foo.IsNull())
require.False(t, myObj.Foo.IsSpecified())
_, err = myObj.Foo.Get()
require.ErrorContains(t, err, "value is not specified")
// serialize back to json: leads to the same data
require.Equal(t, data, serializeYAML(myObj, t))

// -- case where the specified value is explicitly null
data = `foo:null`
// deserialize from json
myObj = parseYAML(data, t)
require.Equal(t, myObj, Obj{Foo: nullable.Nullable[string]{false: ""}})
require.True(t, myObj.Foo.IsNull())
require.True(t, myObj.Foo.IsSpecified())
_, err = myObj.Foo.Get()
require.ErrorContains(t, err, "value is null")
require.Panics(t, func() { myObj.Foo.MustGet() })
// serialize back to json: leads to the same data
require.Equal(t, data, serializeYAML(myObj, t))

// --- building objects from a Go client

// - case where there is an actual value
myObj = Obj{}
myObj.Foo.Set("bar")
require.Equal(t, `foo:"bar"`, serialize(myObj, t))

// - case where the value should be unspecified
myObj = Obj{}
// do nothing: unspecified by default
require.Equal(t, ``, serializeYAML(myObj, t))
// explicitly mark unspecified
myObj.Foo.SetUnspecified()
require.Equal(t, ``, serializeYAML(myObj, t))

// - case where the value should be null
myObj = Obj{}
myObj.Foo.SetNull()
require.Equal(t, `foo:null`, serialize(myObj, t))
}

func parseYAML(data string, t *testing.T) Obj {
var myObj Obj
err := yaml.Unmarshal([]byte(data), &myObj)
require.NoError(t, err)
return myObj
}

func serializeYAML(o Obj, t *testing.T) string {
data, err := yaml.Marshal(o)
require.NoError(t, err)
return string(data)
}
50 changes: 50 additions & 0 deletions nullable.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import (
"bytes"
"encoding/json"
"errors"
"fmt"
"reflect"

"gopkg.in/yaml.v3"

Check failure on line 10 in nullable.go

View workflow job for this annotation

GitHub Actions / Build (1.20)

missing go.sum entry for module providing package gopkg.in/yaml.v3 (imported by github.com/oapi-codegen/nullable); to add:

Check failure on line 10 in nullable.go

View workflow job for this annotation

GitHub Actions / Build (1.21)

missing go.sum entry for module providing package gopkg.in/yaml.v3 (imported by github.com/oapi-codegen/nullable); to add:

Check failure on line 10 in nullable.go

View workflow job for this annotation

GitHub Actions / Build (1.20)

missing go.sum entry for module providing package gopkg.in/yaml.v3 (imported by github.com/oapi-codegen/nullable); to add:

Check failure on line 10 in nullable.go

View workflow job for this annotation

GitHub Actions / Build (1.21)

missing go.sum entry for module providing package gopkg.in/yaml.v3 (imported by github.com/oapi-codegen/nullable); to add:
)

// Nullable is a generic type, which implements a field that can be one of three states:
Expand Down Expand Up @@ -115,3 +119,49 @@ func (t *Nullable[T]) UnmarshalJSON(data []byte) error {
t.Set(v)
return nil
}

// TODO pointer receiver https://github.com/go-yaml/yaml/issues/134#issuecomment-2044424851
func (t Nullable[T]) MarshalYAML() (interface{}, error) {
fmt.Println("MarshalYAML")
// if field was specified, and `null`, marshal it
if t.IsNull() {
return []byte("null"), nil
}

// if field was unspecified, and `omitempty` is set on the field's tags, `json.Marshal` will omit this field

// otherwise: we have a value, so marshal it
// fmt.Printf("t[true]: %v\n", t[true])
// b, _ := yaml.Marshal(t[true])
// fmt.Printf("b: %v\n", b)
// return yaml.Marshal(t[true])
vv := (t)[true]
fmt.Printf("vv: %v\n", vv)
fmt.Printf("reflect.ValueOf(vv): %v\n", reflect.ValueOf(vv))
return json.Marshal(t[true])
}

func (t *Nullable[T]) UnmarshalYAML(value *yaml.Node) error {

Check failure on line 144 in nullable.go

View workflow job for this annotation

GitHub Actions / Build (1.20)

undefined: yaml (typecheck)

Check failure on line 144 in nullable.go

View workflow job for this annotation

GitHub Actions / Build (1.21)

undefined: yaml (typecheck)

Check failure on line 144 in nullable.go

View workflow job for this annotation

GitHub Actions / Build (1.20)

undefined: yaml (typecheck)

Check failure on line 144 in nullable.go

View workflow job for this annotation

GitHub Actions / Build (1.21)

undefined: yaml (typecheck)
// if field is unspecified, UnmarshalJSON won't be called
// fmt.Printf("value: %v\n", value)
// value.Kind == yaml.Kind

fmt.Printf("value: %v\n", value)
fmt.Printf("value.Tag: %v\n", value.Tag)

////// // if field is specified, and `null`
////// if bytes.Equal(data, []byte("null")) {
////// t.SetNull()
////// return nil
////// }
// otherwise, we have an actual value, so parse it
var v T

fmt.Printf("reflect.TypeOf(v): %v\n", reflect.TypeOf(v))
if err := value.Decode(&v); err != nil {
return err
}
fmt.Printf("v: %v\n", v)
t.Set(v)
return nil
}

0 comments on commit ba95a2b

Please sign in to comment.