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

codegen: assembler for struct with map representation validates all non-optional fields are present #121

Merged
merged 1 commit into from
Dec 4, 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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ When a release tag is made, this block of bullet points will just slide down to
- ... mostly. Some manual configuration may sometimes be required to make sure the generated structure wouldn't have an infinite memory size. We'll keep working on making this smoother in the future.
- Field symbol overrides now work properly. (E.g., if you have a schema with a field called "type", you can make that work now. Just needs a field symbol override in the Adjunct Config when doing codegen!)
- Codegen'd link types now implemented the `schema.TypedLinkNode` interface where applicable.
- Structs now actually validate all required fields are present before allowing themselves to finish building.
- Structs now actually validate all required fields are present before allowing themselves to finish building. Ditto for their map representations.
- Much more testing. And we've got a nice new declarative testcase system that makes it easier to write descriptions of how data should behave (at both the typed and representation view levels), and then just call one function to run exhaustive tests to make sure it looks the same from every inspectable API.
- Change: codegen now outputs a fixed set of files. (Previously, it output one file per type in your schema.) This makes codegen much more managable; if you remove a type from your schema, you don't have to chase down the orphaned file. It's also just plain less clutter to look at on the filesystem.
- Demo: as proof of the kind of work that can be done now with codegen, we've implemented the IPLD Schema schema -- the schema that describes IPLD Schema declarations -- using codegen. It's pretty neat.
Expand Down
16 changes: 15 additions & 1 deletion schema/gen/go/genStructReprMap.go
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,21 @@ func (g structReprMapReprBuilderGenerator) emitMapAssemblerMethods(w io.Writer)
case maState_finished:
panic("invalid state: Finish cannot be called on an assembler that's already finished")
}
//FIXME check if all required fields are set
if ma.s & fieldBits__{{ $type | TypeSymbol }}_sufficient != fieldBits__{{ $type | TypeSymbol }}_sufficient {
err := ipld.ErrMissingRequiredField{Missing: make([]string, 0)}
{{- range $i, $field := .Type.Fields }}
{{- if not $field.IsMaybe}}
if ma.s & fieldBit__{{ $type | TypeSymbol }}_{{ $field | FieldSymbolUpper }} == 0 {
{{- if $field | $type.RepresentationStrategy.FieldHasRename }}
err.Missing = append(err.Missing, "{{ $field.Name }} (serial:\"{{ $field | $type.RepresentationStrategy.GetFieldKey }}\")")
{{- else}}
err.Missing = append(err.Missing, "{{ $field.Name }}")
{{- end}}
}
{{- end}}
{{- end}}
return err
}
ma.state = maState_finished
*ma.m = schema.Maybe_Value
return nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,68 @@ import (
"github.com/ipld/go-ipld-prime/schema"
)

func TestRequiredFields(t *testing.T) {
t.Parallel()

ts := schema.TypeSystem{}
ts.Init()
adjCfg := &AdjunctCfg{}
ts.Accumulate(schema.SpawnString("String"))
ts.Accumulate(schema.SpawnStruct("StructOne",
[]schema.StructField{
schema.SpawnStructField("a", "String", false, false),
schema.SpawnStructField("b", "String", false, false),
},
schema.SpawnStructRepresentationMap(map[string]string{
// no renames. we expect a simpler error message in this case.
}),
))
ts.Accumulate(schema.SpawnStruct("StructTwo",
[]schema.StructField{
schema.SpawnStructField("a", "String", false, false),
schema.SpawnStructField("b", "String", false, false),
},
schema.SpawnStructRepresentationMap(map[string]string{
"b": "z",
}),
))

prefix := "struct-required-fields"
pkgName := "main"
genAndCompileAndTest(t, prefix, pkgName, ts, adjCfg, func(t *testing.T, getPrototypeByName func(string) ipld.NodePrototype) {
t.Run("building-type-without-required-fields-errors", func(t *testing.T) {
np := getPrototypeByName("StructOne")

nb := np.NewBuilder()
ma, _ := nb.BeginMap(0)
err := ma.Finish()

Wish(t, err, ShouldBeSameTypeAs, ipld.ErrMissingRequiredField{})
Wish(t, err.Error(), ShouldEqual, `missing required fields: a,b`)
})
t.Run("building-representation-without-required-fields-errors", func(t *testing.T) {
nrp := getPrototypeByName("StructOne.Repr")

nb := nrp.NewBuilder()
ma, _ := nb.BeginMap(0)
err := ma.Finish()

Wish(t, err, ShouldBeSameTypeAs, ipld.ErrMissingRequiredField{})
Wish(t, err.Error(), ShouldEqual, `missing required fields: a,b`)
})
t.Run("building-representation-with-renames-without-required-fields-errors", func(t *testing.T) {
nrp := getPrototypeByName("StructTwo.Repr")

nb := nrp.NewBuilder()
ma, _ := nb.BeginMap(0)
err := ma.Finish()

Wish(t, err, ShouldBeSameTypeAs, ipld.ErrMissingRequiredField{})
Wish(t, err.Error(), ShouldEqual, `missing required fields: a,b (serial:"z")`)
})
})
}

func TestStructNesting(t *testing.T) {
t.Parallel()

Expand Down
23 changes: 0 additions & 23 deletions schema/gen/go/testStructsContainingMaybe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,27 +171,4 @@ func TestStructsContainingMaybe(t *testing.T) {
}
})
})

genAndCompileAndTest(t, "stroct3", "main", ts, adjCfg, func(t *testing.T, getPrototypeByName func(string) ipld.NodePrototype) {
t.Run("insufficient", func(t *testing.T) {
nrp := getPrototypeByName("Stroct")
t.Run("typed-create", func(t *testing.T) {
b := nrp.NewBuilder()
mb, err := b.BeginMap(0)
if err != nil {
t.Fatal(err)
}
v, err := mb.AssembleEntry("f1")
if err != nil {
t.Fatal(err)
}
v.AssignString("v1")

err = mb.Finish()
if _, ok := err.(ipld.ErrMissingRequiredField); !ok {
t.Fatalf("Expected error for missing field, got %v", err)
}
})
})
})
}
5 changes: 5 additions & 0 deletions schema/typeMethods.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,11 @@ func (r StructRepresentation_Map) GetFieldKey(field StructField) string {
return field.name
}

func (r StructRepresentation_Map) FieldHasRename(field StructField) bool {
_, ok := r.renames[field.name]
return ok
}

func (r StructRepresentation_Stringjoin) GetDelim() string {
return r.sep
}
Expand Down