Skip to content

Commit f8d654d

Browse files
committed
codegen: assembler for struct with map representation now validates all non-optional fields are present.
This continues what #111 did and adds the same logic to the map representation. The actual state tracking works the same way (and was mostly already there). Rearranged the tests slightly. Made error messages include both field name and serial key when they differ due to a rename directive. (It's possible this error would get nicer if it used a list of StructField instead of just strings, but it would also get more complicated. Maybe revisit later.)
1 parent 9b27b8a commit f8d654d

File tree

5 files changed

+83
-25
lines changed

5 files changed

+83
-25
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ When a release tag is made, this block of bullet points will just slide down to
5555
- ... 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.
5656
- 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!)
5757
- Codegen'd link types now implemented the `schema.TypedLinkNode` interface where applicable.
58-
- Structs now actually validate all required fields are present before allowing themselves to finish building.
58+
- Structs now actually validate all required fields are present before allowing themselves to finish building. Ditto for their map representations.
5959
- 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.
6060
- 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.
6161
- 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.

schema/gen/go/genStructReprMap.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,21 @@ func (g structReprMapReprBuilderGenerator) emitMapAssemblerMethods(w io.Writer)
543543
case maState_finished:
544544
panic("invalid state: Finish cannot be called on an assembler that's already finished")
545545
}
546-
//FIXME check if all required fields are set
546+
if ma.s & fieldBits__{{ $type | TypeSymbol }}_sufficient != fieldBits__{{ $type | TypeSymbol }}_sufficient {
547+
err := ipld.ErrMissingRequiredField{Missing: make([]string, 0)}
548+
{{- range $i, $field := .Type.Fields }}
549+
{{- if not $field.IsMaybe}}
550+
if ma.s & fieldBit__{{ $type | TypeSymbol }}_{{ $field | FieldSymbolUpper }} == 0 {
551+
{{- if $field | $type.RepresentationStrategy.FieldHasRename }}
552+
err.Missing = append(err.Missing, "{{ $field.Name }} (serial:\"{{ $field | $type.RepresentationStrategy.GetFieldKey }}\")")
553+
{{- else}}
554+
err.Missing = append(err.Missing, "{{ $field.Name }}")
555+
{{- end}}
556+
}
557+
{{- end}}
558+
{{- end}}
559+
return err
560+
}
547561
ma.state = maState_finished
548562
*ma.m = schema.Maybe_Value
549563
return nil

schema/gen/go/testStructNesting_test.go renamed to schema/gen/go/testStruct_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,68 @@ import (
1111
"github.com/ipld/go-ipld-prime/schema"
1212
)
1313

14+
func TestRequiredFields(t *testing.T) {
15+
t.Parallel()
16+
17+
ts := schema.TypeSystem{}
18+
ts.Init()
19+
adjCfg := &AdjunctCfg{}
20+
ts.Accumulate(schema.SpawnString("String"))
21+
ts.Accumulate(schema.SpawnStruct("StructOne",
22+
[]schema.StructField{
23+
schema.SpawnStructField("a", "String", false, false),
24+
schema.SpawnStructField("b", "String", false, false),
25+
},
26+
schema.SpawnStructRepresentationMap(map[string]string{
27+
// no renames. we expect a simpler error message in this case.
28+
}),
29+
))
30+
ts.Accumulate(schema.SpawnStruct("StructTwo",
31+
[]schema.StructField{
32+
schema.SpawnStructField("a", "String", false, false),
33+
schema.SpawnStructField("b", "String", false, false),
34+
},
35+
schema.SpawnStructRepresentationMap(map[string]string{
36+
"b": "z",
37+
}),
38+
))
39+
40+
prefix := "struct-required-fields"
41+
pkgName := "main"
42+
genAndCompileAndTest(t, prefix, pkgName, ts, adjCfg, func(t *testing.T, getPrototypeByName func(string) ipld.NodePrototype) {
43+
t.Run("building-type-without-required-fields-errors", func(t *testing.T) {
44+
np := getPrototypeByName("StructOne")
45+
46+
nb := np.NewBuilder()
47+
ma, _ := nb.BeginMap(0)
48+
err := ma.Finish()
49+
50+
Wish(t, err, ShouldBeSameTypeAs, ipld.ErrMissingRequiredField{})
51+
Wish(t, err.Error(), ShouldEqual, `missing required fields: a,b`)
52+
})
53+
t.Run("building-representation-without-required-fields-errors", func(t *testing.T) {
54+
nrp := getPrototypeByName("StructOne.Repr")
55+
56+
nb := nrp.NewBuilder()
57+
ma, _ := nb.BeginMap(0)
58+
err := ma.Finish()
59+
60+
Wish(t, err, ShouldBeSameTypeAs, ipld.ErrMissingRequiredField{})
61+
Wish(t, err.Error(), ShouldEqual, `missing required fields: a,b`)
62+
})
63+
t.Run("building-representation-with-renames-without-required-fields-errors", func(t *testing.T) {
64+
nrp := getPrototypeByName("StructTwo.Repr")
65+
66+
nb := nrp.NewBuilder()
67+
ma, _ := nb.BeginMap(0)
68+
err := ma.Finish()
69+
70+
Wish(t, err, ShouldBeSameTypeAs, ipld.ErrMissingRequiredField{})
71+
Wish(t, err.Error(), ShouldEqual, `missing required fields: a,b (serial:"z")`)
72+
})
73+
})
74+
}
75+
1476
func TestStructNesting(t *testing.T) {
1577
t.Parallel()
1678

schema/gen/go/testStructsContainingMaybe_test.go

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -171,27 +171,4 @@ func TestStructsContainingMaybe(t *testing.T) {
171171
}
172172
})
173173
})
174-
175-
genAndCompileAndTest(t, "stroct3", "main", ts, adjCfg, func(t *testing.T, getPrototypeByName func(string) ipld.NodePrototype) {
176-
t.Run("insufficient", func(t *testing.T) {
177-
nrp := getPrototypeByName("Stroct")
178-
t.Run("typed-create", func(t *testing.T) {
179-
b := nrp.NewBuilder()
180-
mb, err := b.BeginMap(0)
181-
if err != nil {
182-
t.Fatal(err)
183-
}
184-
v, err := mb.AssembleEntry("f1")
185-
if err != nil {
186-
t.Fatal(err)
187-
}
188-
v.AssignString("v1")
189-
190-
err = mb.Finish()
191-
if _, ok := err.(ipld.ErrMissingRequiredField); !ok {
192-
t.Fatalf("Expected error for missing field, got %v", err)
193-
}
194-
})
195-
})
196-
})
197174
}

schema/typeMethods.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,11 @@ func (r StructRepresentation_Map) GetFieldKey(field StructField) string {
226226
return field.name
227227
}
228228

229+
func (r StructRepresentation_Map) FieldHasRename(field StructField) bool {
230+
_, ok := r.renames[field.name]
231+
return ok
232+
}
233+
229234
func (r StructRepresentation_Stringjoin) GetDelim() string {
230235
return r.sep
231236
}

0 commit comments

Comments
 (0)