Commit 61bf5b5d authored by Eric Myhre's avatar Eric Myhre

codegen: semantic builder for a struct got!

And new tests.
Signed-off-by: default avatarEric Myhre <hash@exultant.us>
parent 51279471
......@@ -9,6 +9,7 @@ import (
ipld "github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/encoding"
"github.com/ipld/go-ipld-prime/fluent"
ipldfree "github.com/ipld/go-ipld-prime/impl/free"
)
// TokenSourceBucket acts like a TokenSource by yielding tokens from a pre-made
......@@ -24,6 +25,13 @@ func (tb *TokenSourceBucket) Step(yield *tok.Token) (done bool, err error) {
return tb.read > len(tb.tokens), nil
}
func plz(n ipld.Node, e error) ipld.Node {
if e != nil {
panic(e)
}
return n
}
func TestScalarUnmarshal(t *testing.T) {
t.Run("string node", func(t *testing.T) {
tb := &TokenSourceBucket{tokens: []tok.Token{
......@@ -37,3 +45,47 @@ func TestScalarUnmarshal(t *testing.T) {
Wish(t, tb.read, ShouldEqual, 1)
})
}
// n.b. testing unmarshal is something different (this doesn't exercise
// representation node/nodebuilder, just the semantic/type level one).
func TestStructBuilder(t *testing.T) {
t.Run("stroct", func(t *testing.T) {
t.Run("all fields set", func(t *testing.T) {
mb, err := Stroct__NodeBuilder{}.CreateMap()
Require(t, err, ShouldEqual, nil)
mb.Insert(ipldfree.String("f1"), plz(String__NodeBuilder{}.CreateString("a")))
mb.Insert(ipldfree.String("f2"), plz(String__NodeBuilder{}.CreateString("b")))
mb.Insert(ipldfree.String("f3"), plz(String__NodeBuilder{}.CreateString("c")))
mb.Insert(ipldfree.String("f4"), plz(String__NodeBuilder{}.CreateString("d")))
n, err := mb.Build()
Wish(t, err, ShouldEqual, nil)
Wish(t, n.ReprKind(), ShouldEqual, ipld.ReprKind_Map)
Wish(t, plz(n.LookupString("f1")), ShouldEqual, plz(String__NodeBuilder{}.CreateString("a")))
})
})
}
/*
soon...
func TestStructUnmarshal(t *testing.T) {
t.Run("stroct", func(t *testing.T) {
t.Run("all fields set", func(t *testing.T) {
tb := &TokenSourceBucket{tokens: []tok.Token{
{Type: tok.TMapOpen, Length: 4},
{Type: tok.TString, Str: "f1"}, {Type: tok.TString, Str: "a"},
{Type: tok.TString, Str: "f2"}, {Type: tok.TString, Str: "b"},
{Type: tok.TString, Str: "f3"}, {Type: tok.TString, Str: "c"},
{Type: tok.TString, Str: "f4"}, {Type: tok.TString, Str: "d"},
{Type: tok.TMapClose},
}}
nb := Stroct__NodeBuilder{}
n, err := encoding.Unmarshal(nb, tb)
// ... asserts ...
})
})
}
*/
......@@ -33,18 +33,78 @@ func (gk generateNbKindStruct) EmitNodebuilderType(w io.Writer) {
}
func (gk generateNbKindStruct) EmitNodebuilderMethodCreateMap(w io.Writer) {
// Some interesting edge cases to note:
// - This builder, being all about semantics and not at all about serialization,
// is order-insensitive.
// - We don't specially handle being given 'undef' as a value.
// It just falls into the "need a typed.Node" error bucket.
// - We only accept *codegenerated values* -- a typed.Node created
// in the same schema universe *isn't accepted*.
// REVIEW: We could try to accept those, but it might have perf/sloc costs,
// and it's hard to imagine a user story that gets here.
// - The has-been-set-if-required validation is fun; it only requires state
// for non-optional fields, and that often gets a little hard to follow
// because it gets wedged in with other logic tables around optionality.
// REVIEW: 'x, ok := v.({{ $field.Type.Name }})' might need some stars in it... sometimes.
doTemplate(`
func (nb {{ .Type.Name }}__NodeBuilder) CreateMap() (ipld.MapBuilder, error) {
return &{{ .Type.Name }}__MapBuilder{&{{ .Type.Name }}{}}, nil
return &{{ .Type.Name }}__MapBuilder{v:&{{ .Type.Name }}{}}, nil
}
type {{ .Type.Name }}__MapBuilder struct{
v *{{ .Type.Name }}
// TODO will probably have more state for validating
{{- range $field := .Type.Fields }}
{{- if not $field.IsOptional }}
{{ $field.Name }}__isset bool
{{- end}}
{{- end}}
}
func (mb *{{ .Type.Name }}__MapBuilder) Insert(k, v ipld.Node) error {
panic("TODO now")
ks, err := k.AsString()
if err != nil {
return ipld.ErrInvalidKey{"not a string: " + err.Error()}
}
switch ks {
{{- range $field := .Type.Fields }}
case "{{ $field.Name }}":
{{- if $field.IsNullable }}
if v.IsNull() {
mb.v.{{ $field.Name }} = nil
{{- if $field.IsOptional }}
mb.v.{{ $field.Name }}__exists = true
{{- else}}
mb.{{ $field.Name }}__isset = true
{{- end}}
return nil
}
{{- else}}
if v.IsNull() {
panic("type mismatch on struct field assignment: cannot assign null to non-nullable field") // FIXME need an error type for this
}
{{- end}}
tv, ok := v.(typed.Node)
if !ok {
panic("need typed.Node for insertion into struct") // FIXME need an error type for this
}
x, ok := v.({{ $field.Type.Name }})
if !ok {
panic("field '{{$field.Name}}' in type {{.Type.Name}} is type {{$field.Type.Name}}; cannot assign "+tv.Type().Name()) // FIXME need an error type for this
}
{{- if and $field.IsOptional $field.IsNullable }}
mb.v.{{ $field.Name }} = &x
mb.v.{{ $field.Name }}__exists = true
{{- else if or $field.IsOptional $field.IsNullable }}
mb.v.{{ $field.Name }} = &x
{{- else}}
mb.v.{{ $field.Name }} = x
mb.{{ $field.Name }}__isset = true
{{- end}}
{{- end}}
default:
return typed.ErrNoSuchField{Type: nil /*TODO:typelit*/, FieldName: ks}
}
return nil
}
func (mb *{{ .Type.Name }}__MapBuilder) Delete(k ipld.Node) error {
......@@ -52,6 +112,13 @@ func (gk generateNbKindStruct) EmitNodebuilderMethodCreateMap(w io.Writer) {
}
func (mb *{{ .Type.Name }}__MapBuilder) Build() (ipld.Node, error) {
{{- range $field := .Type.Fields }}
{{- if not $field.IsOptional }}
if !mb.{{ $field.Name }}__isset {
panic("missing required field '{{$field.Name}}' in building struct {{ .Type.Name }}") // FIXME need an error type for this
}
{{- end}}
{{- end}}
v := mb.v
mb = nil
return v, nil
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment