From 285e23141e60521f3e62b049c9305af794627088 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Thu, 20 Apr 2023 23:05:04 +1000 Subject: [PATCH 1/6] feat(bindnode): support listpairs struct representation --- node/bindnode/node.go | 2 + node/bindnode/repr.go | 101 ++++++++++++++- node/tests/schema.go | 1 + node/tests/schemaStructReprListpairs.go | 163 ++++++++++++++++++++++++ schema/tmpBuilders.go | 3 + schema/type.go | 2 + schema/typeMethods.go | 2 + 7 files changed, 269 insertions(+), 5 deletions(-) create mode 100644 node/tests/schemaStructReprListpairs.go diff --git a/node/bindnode/node.go b/node/bindnode/node.go index 7930fa10..26de865a 100644 --- a/node/bindnode/node.go +++ b/node/bindnode/node.go @@ -44,8 +44,10 @@ var ( _ datamodel.ListAssembler = (*_listAssembler)(nil) _ datamodel.ListAssembler = (*_listAssemblerRepr)(nil) + _ datamodel.ListAssembler = (*_listStructAssemblerRepr)(nil) _ datamodel.ListIterator = (*_listIterator)(nil) _ datamodel.ListIterator = (*_tupleIteratorRepr)(nil) + _ datamodel.ListIterator = (*_listpairsIteratorRepr)(nil) _ datamodel.MapAssembler = (*_unionAssembler)(nil) _ datamodel.MapAssembler = (*_unionAssemblerRepr)(nil) diff --git a/node/bindnode/repr.go b/node/bindnode/repr.go index 993defbd..b7d3c14e 100644 --- a/node/bindnode/repr.go +++ b/node/bindnode/repr.go @@ -53,7 +53,7 @@ func (w *_nodeRepr) Kind() datamodel.Kind { return datamodel.Kind_String case schema.StructRepresentation_Map: return datamodel.Kind_Map - case schema.StructRepresentation_Tuple: + case schema.StructRepresentation_Tuple, schema.StructRepresentation_ListPairs: return datamodel.Kind_List case schema.UnionRepresentation_Keyed: return datamodel.Kind_Map @@ -174,6 +174,31 @@ func (w *_nodeRepr) LookupByIndex(idx int64) (datamodel.Node, error) { return nil, err } return reprNode(v), nil + case schema.StructRepresentation_ListPairs: + fields := w.schemaType.(*schema.TypeStruct).Fields() + if idx < 0 || int(idx) >= len(fields)*2 { + return nil, datamodel.ErrNotExists{Segment: datamodel.PathSegmentOfInt(idx)} + } + isName := idx%2 == 0 + idx = idx / 2 + var curField int64 + for _, field := range fields { + value, err := (*_node)(w).LookupByString(field.Name()) + if err != nil { + return nil, err + } + if value.IsAbsent() { + continue + } + if curField == idx { + if isName { + return basicnode.NewString(field.Name()), nil + } + return reprNode(value), nil + } + curField++ + } + return nil, datamodel.ErrNotExists{Segment: datamodel.PathSegmentOfInt(idx)} default: v, err := (*_node)(w).LookupByIndex(idx) if err != nil { @@ -272,6 +297,11 @@ func (w *_nodeRepr) ListIterator() datamodel.ListIterator { iter := _tupleIteratorRepr{cfg: w.cfg, schemaType: typ, fields: typ.Fields(), val: w.val} iter.reprEnd = int(w.lengthMinusTrailingAbsents()) return &iter + case schema.StructRepresentation_ListPairs: + typ := w.schemaType.(*schema.TypeStruct) + iter := _listpairsIteratorRepr{cfg: w.cfg, schemaType: typ, fields: typ.Fields(), val: w.val} + iter.reprEnd = int(w.lengthMinusTrailingAbsents()) * 2 + return &iter default: iter, _ := (*_node)(w).ListIterator().(*_listIterator) if iter == nil { @@ -320,6 +350,7 @@ type _tupleIteratorRepr struct { func (w *_tupleIteratorRepr) Next() (index int64, value datamodel.Node, _ error) { _skipAbsent: + idx := w.nextIndex _, value, err := (*_structIterator)(w).Next() if err != nil { return 0, nil, err @@ -327,13 +358,61 @@ _skipAbsent: if w.nextIndex > w.reprEnd { goto _skipAbsent } - return int64(w.nextIndex), reprNode(value), nil + return int64(idx), reprNode(value), nil } func (w *_tupleIteratorRepr) Done() bool { return w.nextIndex >= w.reprEnd } +type _listpairsIteratorRepr struct { + cfg config + schemaType *schema.TypeStruct + fields []schema.StructField + val reflect.Value // non-pointer + nextIndex int + + // these are only used in repr.go + reprEnd int +} + +func (w *_listpairsIteratorRepr) Next() (index int64, value datamodel.Node, _ error) { +_skipAbsent: + idx := w.nextIndex + if w.nextIndex%2 == 0 { + // field name + if w.Done() { + return 0, nil, datamodel.ErrIteratorOverread{} + } + field := w.fields[w.nextIndex/2] + w.nextIndex++ + if field.IsOptional() { + val := w.val.Field(w.nextIndex) + if val.IsNil() { + goto _skipAbsent + } + } + key := basicnode.NewString(field.Name()) + return int64(idx), key, nil + } + // field value + // set nextIndex to a value that will be correct for a _structIterator#Next() call. + w.nextIndex = (idx - 1) / 2 + _, value, err := (*_structIterator)(w).Next() + w.nextIndex = idx + 1 + if err != nil { + return 0, nil, err + } + if value.IsAbsent() || w.nextIndex > w.reprEnd { + goto _skipAbsent + } + return int64(idx), reprNode(value), nil +} + +func (w *_listpairsIteratorRepr) Done() bool { + return w.nextIndex >= w.reprEnd +} + func (w *_nodeRepr) lengthMinusTrailingAbsents() int64 { fields := w.schemaType.(*schema.TypeStruct).Fields() for i := len(fields) - 1; i >= 0; i-- { @@ -353,6 +432,8 @@ func (w *_nodeRepr) Length() int64 { return w.lengthMinusAbsents() case schema.StructRepresentation_Tuple: return w.lengthMinusTrailingAbsents() + case schema.StructRepresentation_ListPairs: + return w.lengthMinusAbsents() * 2 case schema.UnionRepresentation_Keyed: return (*_node)(w).Length() case schema.UnionRepresentation_Kinded: @@ -625,7 +706,7 @@ func (w *_assemblerRepr) BeginList(sizeHint int64) (datamodel.ListAssembler, err switch stg := reprStrategy(w.schemaType).(type) { case schema.UnionRepresentation_Kinded: return w.asKinded(stg, datamodel.Kind_List).BeginList(sizeHint) - case schema.StructRepresentation_Tuple: + case schema.StructRepresentation_Tuple, schema.StructRepresentation_ListPairs: asm, err := (*_assembler)(w).BeginMap(sizeHint) if err != nil { return nil, err @@ -860,7 +941,7 @@ func (w *_structAssemblerRepr) AssembleKey() datamodel.NodeAssembler { AppropriateKind: datamodel.KindSet_JustMap, ActualKind: datamodel.Kind_String, }} - case schema.StructRepresentation_Tuple: + case schema.StructRepresentation_Tuple, schema.StructRepresentation_ListPairs: return _errorAssembler{datamodel.ErrWrongKind{ TypeName: w.schemaType.Name() + ".Repr", MethodName: "AssembleKey", @@ -974,6 +1055,16 @@ func (w *_listStructAssemblerRepr) AssembleValue() datamodel.NodeAssembler { } entryAsm = assemblerRepr(entryAsm) return entryAsm + case schema.StructRepresentation_ListPairs: + idx := w.nextIndex + w.nextIndex++ + if idx%2 == 0 { + asm := (*_structAssembler)(w).AssembleKey() + // ??? + return (*_assemblerRepr)(asm.(*_assembler)) + } + asm := (*_structAssembler)(w).AssembleValue() + return (*_assemblerRepr)(asm.(*_assembler)) default: return _errorAssembler{fmt.Errorf("bindnode AssembleValue TODO: %T", stg)} } @@ -981,7 +1072,7 @@ func (w *_listStructAssemblerRepr) AssembleValue() datamodel.NodeAssembler { func (w *_listStructAssemblerRepr) Finish() error { switch stg := reprStrategy(w.schemaType).(type) { - case schema.StructRepresentation_Tuple: + case schema.StructRepresentation_Tuple, schema.StructRepresentation_ListPairs: return (*_structAssembler)(w).Finish() default: return fmt.Errorf("bindnode Finish TODO: %T", stg) diff --git a/node/tests/schema.go b/node/tests/schema.go index f29ca700..0e0b7bca 100644 --- a/node/tests/schema.go +++ b/node/tests/schema.go @@ -19,6 +19,7 @@ var allSchemaTests = []struct { {"StructNesting", SchemaTestStructNesting}, {"StructReprStringjoin", SchemaTestStructReprStringjoin}, {"StructReprTuple", SchemaTestStructReprTuple}, + {"StructReprListPairs", SchemaTestStructReprListPairs}, {"StructsContainingMaybe", SchemaTestStructsContainingMaybe}, {"UnionKeyed", SchemaTestUnionKeyed}, {"UnionKeyedComplexChildren", SchemaTestUnionKeyedComplexChildren}, diff --git a/node/tests/schemaStructReprListpairs.go b/node/tests/schemaStructReprListpairs.go new file mode 100644 index 00000000..b5d3bb2f --- /dev/null +++ b/node/tests/schemaStructReprListpairs.go @@ -0,0 +1,163 @@ +package tests + +import ( + "testing" + + qt "github.com/frankban/quicktest" + + "github.com/ipld/go-ipld-prime/datamodel" + "github.com/ipld/go-ipld-prime/fluent" + "github.com/ipld/go-ipld-prime/must" + "github.com/ipld/go-ipld-prime/schema" +) + +func SchemaTestStructReprListPairs(t *testing.T, engine Engine) { + ts := schema.TypeSystem{} + ts.Init() + ts.Accumulate(schema.SpawnString("String")) + ts.Accumulate(schema.SpawnStruct("OneListPair", + []schema.StructField{ + schema.SpawnStructField("field", "String", false, false), + }, + schema.SpawnStructRepresentationListPairs(), + )) + ts.Accumulate(schema.SpawnStruct("FourListPairs", + []schema.StructField{ + schema.SpawnStructField("foo", "String", false, true), + schema.SpawnStructField("bar", "String", true, true), + schema.SpawnStructField("baz", "String", true, false), + schema.SpawnStructField("qux", "String", false, false), + }, + schema.SpawnStructRepresentationListPairs(), + )) + engine.Init(t, ts) + + t.Run("onelistpair works", func(t *testing.T) { + np := engine.PrototypeByName("OneListPair") + nrp := engine.PrototypeByName("OneListPair.Repr") + var n schema.TypedNode + t.Run("typed-create", func(t *testing.T) { + n = fluent.MustBuildMap(np, 1, func(ma fluent.MapAssembler) { + ma.AssembleEntry("field").AssignString("valoo") + }).(schema.TypedNode) + t.Run("typed-read", func(t *testing.T) { + qt.Assert(t, n.Kind(), qt.Equals, datamodel.Kind_Map) + qt.Check(t, n.Length(), qt.Equals, int64(1)) + qt.Check(t, must.String(must.Node(n.LookupByString("field"))), qt.Equals, "valoo") + }) + t.Run("repr-read", func(t *testing.T) { + nr := n.Representation() + qt.Assert(t, nr.Kind(), qt.Equals, datamodel.Kind_List) + qt.Check(t, nr.Length(), qt.Equals, int64(2)) + qt.Check(t, must.String(must.Node(nr.LookupByIndex(0))), qt.Equals, "field") + qt.Check(t, must.String(must.Node(nr.LookupByIndex(1))), qt.Equals, "valoo") + }) + }) + t.Run("repr-create", func(t *testing.T) { + nr := fluent.MustBuildList(nrp, 2, func(la fluent.ListAssembler) { + la.AssembleValue().AssignString("field") + la.AssembleValue().AssignString("valoo") + }) + qt.Check(t, n, NodeContentEquals, nr) + }) + }) + + t.Run("fourlistpairs works", func(t *testing.T) { + np := engine.PrototypeByName("FourListPairs") + nrp := engine.PrototypeByName("FourListPairs.Repr") + var n schema.TypedNode + t.Run("typed-create", func(t *testing.T) { + n = fluent.MustBuildMap(np, 4, func(ma fluent.MapAssembler) { + ma.AssembleEntry("foo").AssignString("0") + ma.AssembleEntry("bar").AssignString("1") + ma.AssembleEntry("baz").AssignString("2") + ma.AssembleEntry("qux").AssignString("3") + }).(schema.TypedNode) + t.Run("typed-read", func(t *testing.T) { + qt.Assert(t, n.Kind(), qt.Equals, datamodel.Kind_Map) + qt.Check(t, n.Length(), qt.Equals, int64(4)) + qt.Check(t, must.String(must.Node(n.LookupByString("foo"))), qt.Equals, "0") + qt.Check(t, must.String(must.Node(n.LookupByString("bar"))), qt.Equals, "1") + qt.Check(t, must.String(must.Node(n.LookupByString("baz"))), qt.Equals, "2") + qt.Check(t, must.String(must.Node(n.LookupByString("qux"))), qt.Equals, "3") + }) + t.Run("repr-read", func(t *testing.T) { + nr := n.Representation() + qt.Assert(t, nr.Kind(), qt.Equals, datamodel.Kind_List) + qt.Check(t, nr.Length(), qt.Equals, int64(8)) + qt.Check(t, must.String(must.Node(nr.LookupByIndex(0))), qt.Equals, "foo") + qt.Check(t, must.String(must.Node(nr.LookupByIndex(1))), qt.Equals, "0") + qt.Check(t, must.String(must.Node(nr.LookupByIndex(2))), qt.Equals, "bar") + qt.Check(t, must.String(must.Node(nr.LookupByIndex(3))), qt.Equals, "1") + qt.Check(t, must.String(must.Node(nr.LookupByIndex(4))), qt.Equals, "baz") + qt.Check(t, must.String(must.Node(nr.LookupByIndex(5))), qt.Equals, "2") + qt.Check(t, must.String(must.Node(nr.LookupByIndex(6))), qt.Equals, "qux") + qt.Check(t, must.String(must.Node(nr.LookupByIndex(7))), qt.Equals, "3") + }) + }) + t.Run("repr-create", func(t *testing.T) { + nr := fluent.MustBuildList(nrp, 8, func(la fluent.ListAssembler) { + la.AssembleValue().AssignString("foo") + la.AssembleValue().AssignString("0") + la.AssembleValue().AssignString("bar") + la.AssembleValue().AssignString("1") + la.AssembleValue().AssignString("baz") + la.AssembleValue().AssignString("2") + la.AssembleValue().AssignString("qux") + la.AssembleValue().AssignString("3") + }) + qt.Check(t, n, NodeContentEquals, nr) + }) + t.Run("repr-create out-of-order", func(t *testing.T) { + nr := fluent.MustBuildList(nrp, 8, func(la fluent.ListAssembler) { + la.AssembleValue().AssignString("bar") + la.AssembleValue().AssignString("1") + la.AssembleValue().AssignString("foo") + la.AssembleValue().AssignString("0") + la.AssembleValue().AssignString("qux") + la.AssembleValue().AssignString("3") + la.AssembleValue().AssignString("baz") + la.AssembleValue().AssignString("2") + }) + qt.Check(t, n, NodeContentEquals, nr) + }) + }) + + t.Run("fourlistpairs with absents", func(t *testing.T) { + np := engine.PrototypeByName("FourListPairs") + nrp := engine.PrototypeByName("FourListPairs.Repr") + var n schema.TypedNode + t.Run("typed-create", func(t *testing.T) { + n = fluent.MustBuildMap(np, 2, func(ma fluent.MapAssembler) { + ma.AssembleEntry("foo").AssignNull() + ma.AssembleEntry("qux").AssignString("3") + }).(schema.TypedNode) + t.Run("typed-read", func(t *testing.T) { + qt.Assert(t, n.Kind(), qt.Equals, datamodel.Kind_Map) + qt.Check(t, n.Length(), qt.Equals, int64(4)) + qt.Check(t, must.Node(n.LookupByString("foo")), qt.Equals, datamodel.Null) + qt.Check(t, must.Node(n.LookupByString("bar")), qt.Equals, datamodel.Absent) + qt.Check(t, must.Node(n.LookupByString("baz")), qt.Equals, datamodel.Absent) + qt.Check(t, must.String(must.Node(n.LookupByString("qux"))), qt.Equals, "3") + }) + t.Run("repr-read", func(t *testing.T) { + nr := n.Representation() + qt.Assert(t, nr.Kind(), qt.Equals, datamodel.Kind_List) + qt.Check(t, nr.Length(), qt.Equals, int64(4)) + qt.Check(t, must.String(must.Node(nr.LookupByIndex(0))), qt.Equals, "foo") + qt.Check(t, must.Node(nr.LookupByIndex(1)), qt.Equals, datamodel.Null) + qt.Check(t, must.String(must.Node(nr.LookupByIndex(2))), qt.Equals, "qux") + qt.Check(t, must.String(must.Node(nr.LookupByIndex(3))), qt.Equals, "3") + }) + }) + t.Run("repr-create", func(t *testing.T) { + nr := fluent.MustBuildList(nrp, 4, func(la fluent.ListAssembler) { + la.AssembleValue().AssignString("foo") + la.AssembleValue().AssignNull() + la.AssembleValue().AssignString("qux") + la.AssembleValue().AssignString("3") + }) + qt.Check(t, n, NodeContentEquals, nr) + }) + }) +} diff --git a/schema/tmpBuilders.go b/schema/tmpBuilders.go index eb7a8be4..526cb32e 100644 --- a/schema/tmpBuilders.go +++ b/schema/tmpBuilders.go @@ -122,6 +122,9 @@ func SpawnStructRepresentationMap2(renames map[string]string, implicits map[stri func SpawnStructRepresentationTuple() StructRepresentation_Tuple { return StructRepresentation_Tuple{} } +func SpawnStructRepresentationListPairs() StructRepresentation_ListPairs { + return StructRepresentation_ListPairs{} +} func SpawnStructRepresentationStringjoin(delim string) StructRepresentation_Stringjoin { return StructRepresentation_Stringjoin{delim} } diff --git a/schema/type.go b/schema/type.go index e322aad7..4932a613 100644 --- a/schema/type.go +++ b/schema/type.go @@ -224,6 +224,7 @@ type StructRepresentation interface{ _StructRepresentation() } func (StructRepresentation_Map) _StructRepresentation() {} func (StructRepresentation_Tuple) _StructRepresentation() {} +func (StructRepresentation_ListPairs) _StructRepresentation() {} func (StructRepresentation_StringPairs) _StructRepresentation() {} func (StructRepresentation_Stringjoin) _StructRepresentation() {} @@ -232,6 +233,7 @@ type StructRepresentation_Map struct { implicits map[string]ImplicitValue } type StructRepresentation_Tuple struct{} +type StructRepresentation_ListPairs struct{} //lint:ignore U1000 implementation TODO type StructRepresentation_StringPairs struct{ sep1, sep2 string } diff --git a/schema/typeMethods.go b/schema/typeMethods.go index c623c3fb..c6f4158f 100644 --- a/schema/typeMethods.go +++ b/schema/typeMethods.go @@ -55,6 +55,8 @@ func (t TypeStruct) RepresentationBehavior() datamodel.Kind { return datamodel.Kind_Map case StructRepresentation_Tuple: return datamodel.Kind_List + case StructRepresentation_ListPairs: + return datamodel.Kind_List case StructRepresentation_StringPairs: return datamodel.Kind_String case StructRepresentation_Stringjoin: From aea45fc307f2bca5113cee31e3100346b51625c1 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Tue, 25 Apr 2023 10:53:18 +1000 Subject: [PATCH 2/6] fix: make listpairs repr [[k1,v1],[k2,v2]...] --- node/bindnode/node.go | 3 + node/bindnode/repr.go | 182 ++++++++++++++++++------ node/tests/schemaStructReprListpairs.go | 172 +++++++++++++++------- 3 files changed, 269 insertions(+), 88 deletions(-) diff --git a/node/bindnode/node.go b/node/bindnode/node.go index 26de865a..04c1d760 100644 --- a/node/bindnode/node.go +++ b/node/bindnode/node.go @@ -36,6 +36,8 @@ var ( _ datamodel.NodeBuilder = (*_builderRepr)(nil) _ datamodel.NodeAssembler = (*_assembler)(nil) _ datamodel.NodeAssembler = (*_assemblerRepr)(nil) + _ datamodel.NodeAssembler = (*_errorAssembler)(nil) + _ datamodel.NodeAssembler = (*_listpairsFieldAssemblerRepr)(nil) _ datamodel.MapAssembler = (*_structAssembler)(nil) _ datamodel.MapAssembler = (*_structAssemblerRepr)(nil) @@ -45,6 +47,7 @@ var ( _ datamodel.ListAssembler = (*_listAssembler)(nil) _ datamodel.ListAssembler = (*_listAssemblerRepr)(nil) _ datamodel.ListAssembler = (*_listStructAssemblerRepr)(nil) + _ datamodel.ListAssembler = (*_listpairsFieldListAssemblerRepr)(nil) _ datamodel.ListIterator = (*_listIterator)(nil) _ datamodel.ListIterator = (*_tupleIteratorRepr)(nil) _ datamodel.ListIterator = (*_listpairsIteratorRepr)(nil) diff --git a/node/bindnode/repr.go b/node/bindnode/repr.go index b7d3c14e..cc351328 100644 --- a/node/bindnode/repr.go +++ b/node/bindnode/repr.go @@ -176,11 +176,9 @@ func (w *_nodeRepr) LookupByIndex(idx int64) (datamodel.Node, error) { return reprNode(v), nil case schema.StructRepresentation_ListPairs: fields := w.schemaType.(*schema.TypeStruct).Fields() - if idx < 0 || int(idx) >= len(fields)*2 { + if idx < 0 || int(idx) >= len(fields) { return nil, datamodel.ErrNotExists{Segment: datamodel.PathSegmentOfInt(idx)} } - isName := idx%2 == 0 - idx = idx / 2 var curField int64 for _, field := range fields { value, err := (*_node)(w).LookupByString(field.Name()) @@ -191,10 +189,7 @@ func (w *_nodeRepr) LookupByIndex(idx int64) (datamodel.Node, error) { continue } if curField == idx { - if isName { - return basicnode.NewString(field.Name()), nil - } - return reprNode(value), nil + return buildListpairsField(basicnode.NewString(field.Name()), value) } curField++ } @@ -300,7 +295,7 @@ func (w *_nodeRepr) ListIterator() datamodel.ListIterator { case schema.StructRepresentation_ListPairs: typ := w.schemaType.(*schema.TypeStruct) iter := _listpairsIteratorRepr{cfg: w.cfg, schemaType: typ, fields: typ.Fields(), val: w.val} - iter.reprEnd = int(w.lengthMinusTrailingAbsents()) * 2 + iter.reprEnd = int(w.lengthMinusTrailingAbsents()) return &iter default: iter, _ := (*_node)(w).ListIterator().(*_listIterator) @@ -378,41 +373,46 @@ type _listpairsIteratorRepr struct { func (w *_listpairsIteratorRepr) Next() (index int64, value datamodel.Node, _ error) { _skipAbsent: - idx := w.nextIndex - if w.nextIndex%2 == 0 { - // field name - if w.Done() { - return 0, nil, datamodel.ErrIteratorOverread{} - } - field := w.fields[w.nextIndex/2] - w.nextIndex++ - if field.IsOptional() { - val := w.val.Field(w.nextIndex) - if val.IsNil() { - goto _skipAbsent - } - } - key := basicnode.NewString(field.Name()) - return int64(idx), key, nil + if w.Done() { + return 0, nil, datamodel.ErrIteratorOverread{} } - // field value - // set nextIndex to a value that will be correct for a _structIterator#Next() call. - w.nextIndex = (idx - 1) / 2 - _, value, err := (*_structIterator)(w).Next() - w.nextIndex = idx + 1 + idx := w.nextIndex + key, value, err := (*_structIterator)(w).Next() if err != nil { return 0, nil, err } if value.IsAbsent() || w.nextIndex > w.reprEnd { goto _skipAbsent } - return int64(idx), reprNode(value), nil + field, err := buildListpairsField(key, value) + if err != nil { + return 0, nil, err + } + return int64(idx), field, nil } func (w *_listpairsIteratorRepr) Done() bool { return w.nextIndex >= w.reprEnd } +func buildListpairsField(key, value datamodel.Node) (datamodel.Node, error) { + nb := basicnode.Prototype.List.NewBuilder() + la, err := nb.BeginList(2) + if err != nil { + return nil, err + } + if err := la.AssembleValue().AssignNode(key); err != nil { + return nil, err + } + if err := la.AssembleValue().AssignNode(value); err != nil { + return nil, err + } + if err := la.Finish(); err != nil { + return nil, err + } + return nb.Build(), nil +} + func (w *_nodeRepr) lengthMinusTrailingAbsents() int64 { fields := w.schemaType.(*schema.TypeStruct).Fields() for i := len(fields) - 1; i >= 0; i-- { @@ -433,7 +433,7 @@ func (w *_nodeRepr) Length() int64 { case schema.StructRepresentation_Tuple: return w.lengthMinusTrailingAbsents() case schema.StructRepresentation_ListPairs: - return w.lengthMinusAbsents() * 2 + return w.lengthMinusAbsents() case schema.UnionRepresentation_Keyed: return (*_node)(w).Length() case schema.UnionRepresentation_Kinded: @@ -1056,15 +1056,7 @@ func (w *_listStructAssemblerRepr) AssembleValue() datamodel.NodeAssembler { entryAsm = assemblerRepr(entryAsm) return entryAsm case schema.StructRepresentation_ListPairs: - idx := w.nextIndex - w.nextIndex++ - if idx%2 == 0 { - asm := (*_structAssembler)(w).AssembleKey() - // ??? - return (*_assemblerRepr)(asm.(*_assembler)) - } - asm := (*_structAssembler)(w).AssembleValue() - return (*_assemblerRepr)(asm.(*_assembler)) + return &_listpairsFieldAssemblerRepr{parent: (*_structAssembler)(w)} default: return _errorAssembler{fmt.Errorf("bindnode AssembleValue TODO: %T", stg)} } @@ -1083,6 +1075,116 @@ func (w *_listStructAssemblerRepr) ValuePrototype(idx int64) datamodel.NodeProto panic("bindnode TODO: list ValuePrototype") } +type _listpairsFieldAssemblerRepr struct { + parent *_structAssembler +} + +func (w _listpairsFieldAssemblerRepr) BeginMap(int64) (datamodel.MapAssembler, error) { + return nil, datamodel.ErrWrongKind{ + TypeName: w.parent.schemaType.Name(), + MethodName: "BeginMap", + AppropriateKind: datamodel.KindSet_JustList, + ActualKind: datamodel.Kind_Map, + } +} +func (w *_listpairsFieldAssemblerRepr) BeginList(int64) (datamodel.ListAssembler, error) { + return &_listpairsFieldListAssemblerRepr{parent: w.parent}, nil +} +func (w _listpairsFieldAssemblerRepr) AssignNull() error { + return datamodel.ErrWrongKind{ + TypeName: w.parent.schemaType.Name(), + MethodName: "AssignNull", + AppropriateKind: datamodel.KindSet_JustList, + ActualKind: datamodel.Kind_Map, + } +} +func (w _listpairsFieldAssemblerRepr) AssignBool(bool) error { + return datamodel.ErrWrongKind{ + TypeName: w.parent.schemaType.Name(), + MethodName: "AssignBool", + AppropriateKind: datamodel.KindSet_JustList, + ActualKind: datamodel.Kind_Map, + } +} +func (w _listpairsFieldAssemblerRepr) AssignInt(int64) error { + return datamodel.ErrWrongKind{ + TypeName: w.parent.schemaType.Name(), + MethodName: "AssignInt", + AppropriateKind: datamodel.KindSet_JustList, + ActualKind: datamodel.Kind_Map, + } +} +func (w _listpairsFieldAssemblerRepr) AssignFloat(float64) error { + return datamodel.ErrWrongKind{ + TypeName: w.parent.schemaType.Name(), + MethodName: "AssignFloat", + AppropriateKind: datamodel.KindSet_JustList, + ActualKind: datamodel.Kind_Map, + } +} +func (w _listpairsFieldAssemblerRepr) AssignString(string) error { + return datamodel.ErrWrongKind{ + TypeName: w.parent.schemaType.Name(), + MethodName: "AssignString", + AppropriateKind: datamodel.KindSet_JustList, + ActualKind: datamodel.Kind_Map, + } +} +func (w _listpairsFieldAssemblerRepr) AssignBytes([]byte) error { + return datamodel.ErrWrongKind{ + TypeName: w.parent.schemaType.Name(), + MethodName: "AssignBytes", + AppropriateKind: datamodel.KindSet_JustList, + ActualKind: datamodel.Kind_Map, + } +} +func (w _listpairsFieldAssemblerRepr) AssignLink(datamodel.Link) error { + return datamodel.ErrWrongKind{ + TypeName: w.parent.schemaType.Name(), + MethodName: "AssignLink", + AppropriateKind: datamodel.KindSet_JustList, + ActualKind: datamodel.Kind_Map, + } +} + +// TODO: support this if it's a list? +func (w _listpairsFieldAssemblerRepr) AssignNode(datamodel.Node) error { + return datamodel.ErrWrongKind{ + TypeName: w.parent.schemaType.Name(), + MethodName: "AssignNode", + AppropriateKind: datamodel.KindSet_JustList, + ActualKind: datamodel.Kind_Map, + } +} +func (w _listpairsFieldAssemblerRepr) Prototype() datamodel.NodePrototype { + panic("bindnode TODO: listpairs field Prototype") +} + +type _listpairsFieldListAssemblerRepr struct { + parent *_structAssembler + idx int +} + +func (w *_listpairsFieldListAssemblerRepr) AssembleValue() datamodel.NodeAssembler { + w.idx++ + switch w.idx { + case 1: + return w.parent.AssembleKey() + case 2: + return w.parent.AssembleValue() + default: + return _errorAssembler{fmt.Errorf("bindnode: too many values in listpairs field")} + } +} + +func (w *_listpairsFieldListAssemblerRepr) Finish() error { + return nil +} + +func (w *_listpairsFieldListAssemblerRepr) ValuePrototype(idx int64) datamodel.NodePrototype { + panic("bindnode TODO: listpairs field ValuePrototype") +} + // Note that lists do not have any representation strategy right now. type _listAssemblerRepr _listAssembler diff --git a/node/tests/schemaStructReprListpairs.go b/node/tests/schemaStructReprListpairs.go index b5d3bb2f..c59c4fa7 100644 --- a/node/tests/schemaStructReprListpairs.go +++ b/node/tests/schemaStructReprListpairs.go @@ -15,6 +15,7 @@ func SchemaTestStructReprListPairs(t *testing.T, engine Engine) { ts := schema.TypeSystem{} ts.Init() ts.Accumulate(schema.SpawnString("String")) + ts.Accumulate(schema.SpawnList("List__String", "String", false)) ts.Accumulate(schema.SpawnStruct("OneListPair", []schema.StructField{ schema.SpawnStructField("field", "String", false, false), @@ -26,7 +27,7 @@ func SchemaTestStructReprListPairs(t *testing.T, engine Engine) { schema.SpawnStructField("foo", "String", false, true), schema.SpawnStructField("bar", "String", true, true), schema.SpawnStructField("baz", "String", true, false), - schema.SpawnStructField("qux", "String", false, false), + schema.SpawnStructField("qux", "List__String", false, false), }, schema.SpawnStructRepresentationListPairs(), )) @@ -48,15 +49,20 @@ func SchemaTestStructReprListPairs(t *testing.T, engine Engine) { t.Run("repr-read", func(t *testing.T) { nr := n.Representation() qt.Assert(t, nr.Kind(), qt.Equals, datamodel.Kind_List) - qt.Check(t, nr.Length(), qt.Equals, int64(2)) - qt.Check(t, must.String(must.Node(nr.LookupByIndex(0))), qt.Equals, "field") - qt.Check(t, must.String(must.Node(nr.LookupByIndex(1))), qt.Equals, "valoo") + qt.Check(t, nr.Length(), qt.Equals, int64(1)) + kv := must.Node(nr.LookupByIndex(0)) + qt.Assert(t, nr.Kind(), qt.Equals, datamodel.Kind_List) + qt.Check(t, kv.Length(), qt.Equals, int64(2)) + qt.Check(t, must.String(must.Node(kv.LookupByIndex(0))), qt.Equals, "field") + qt.Check(t, must.String(must.Node(kv.LookupByIndex(1))), qt.Equals, "valoo") }) }) t.Run("repr-create", func(t *testing.T) { - nr := fluent.MustBuildList(nrp, 2, func(la fluent.ListAssembler) { - la.AssembleValue().AssignString("field") - la.AssembleValue().AssignString("valoo") + nr := fluent.MustBuildList(nrp, 1, func(la fluent.ListAssembler) { + la.AssembleValue().CreateList(2, func(la fluent.ListAssembler) { + la.AssembleValue().AssignString("field") + la.AssembleValue().AssignString("valoo") + }) }) qt.Check(t, n, NodeContentEquals, nr) }) @@ -71,53 +77,99 @@ func SchemaTestStructReprListPairs(t *testing.T, engine Engine) { ma.AssembleEntry("foo").AssignString("0") ma.AssembleEntry("bar").AssignString("1") ma.AssembleEntry("baz").AssignString("2") - ma.AssembleEntry("qux").AssignString("3") + ma.AssembleEntry("qux").CreateList(2, func(la fluent.ListAssembler) { + la.AssembleValue().AssignString("3") + la.AssembleValue().AssignString("4") + }) }).(schema.TypedNode) + t.Run("typed-read", func(t *testing.T) { qt.Assert(t, n.Kind(), qt.Equals, datamodel.Kind_Map) qt.Check(t, n.Length(), qt.Equals, int64(4)) qt.Check(t, must.String(must.Node(n.LookupByString("foo"))), qt.Equals, "0") qt.Check(t, must.String(must.Node(n.LookupByString("bar"))), qt.Equals, "1") qt.Check(t, must.String(must.Node(n.LookupByString("baz"))), qt.Equals, "2") - qt.Check(t, must.String(must.Node(n.LookupByString("qux"))), qt.Equals, "3") + qux := must.Node(n.LookupByString("qux")) + qt.Assert(t, qux.Kind(), qt.Equals, datamodel.Kind_List) + qt.Check(t, qux.Length(), qt.Equals, int64(2)) + qt.Check(t, must.String(must.Node(qux.LookupByIndex(0))), qt.Equals, "3") + qt.Check(t, must.String(must.Node(qux.LookupByIndex(1))), qt.Equals, "4") }) t.Run("repr-read", func(t *testing.T) { nr := n.Representation() qt.Assert(t, nr.Kind(), qt.Equals, datamodel.Kind_List) - qt.Check(t, nr.Length(), qt.Equals, int64(8)) - qt.Check(t, must.String(must.Node(nr.LookupByIndex(0))), qt.Equals, "foo") - qt.Check(t, must.String(must.Node(nr.LookupByIndex(1))), qt.Equals, "0") - qt.Check(t, must.String(must.Node(nr.LookupByIndex(2))), qt.Equals, "bar") - qt.Check(t, must.String(must.Node(nr.LookupByIndex(3))), qt.Equals, "1") - qt.Check(t, must.String(must.Node(nr.LookupByIndex(4))), qt.Equals, "baz") - qt.Check(t, must.String(must.Node(nr.LookupByIndex(5))), qt.Equals, "2") - qt.Check(t, must.String(must.Node(nr.LookupByIndex(6))), qt.Equals, "qux") - qt.Check(t, must.String(must.Node(nr.LookupByIndex(7))), qt.Equals, "3") + qt.Check(t, nr.Length(), qt.Equals, int64(4)) + kv := must.Node(nr.LookupByIndex(0)) + qt.Assert(t, nr.Kind(), qt.Equals, datamodel.Kind_List) + qt.Check(t, kv.Length(), qt.Equals, int64(2)) + qt.Check(t, must.String(must.Node(kv.LookupByIndex(0))), qt.Equals, "foo") + qt.Check(t, must.String(must.Node(kv.LookupByIndex(1))), qt.Equals, "0") + kv = must.Node(nr.LookupByIndex(1)) + qt.Assert(t, nr.Kind(), qt.Equals, datamodel.Kind_List) + qt.Check(t, kv.Length(), qt.Equals, int64(2)) + qt.Check(t, must.String(must.Node(kv.LookupByIndex(0))), qt.Equals, "bar") + qt.Check(t, must.String(must.Node(kv.LookupByIndex(1))), qt.Equals, "1") + kv = must.Node(nr.LookupByIndex(2)) + qt.Assert(t, nr.Kind(), qt.Equals, datamodel.Kind_List) + qt.Check(t, kv.Length(), qt.Equals, int64(2)) + qt.Check(t, must.String(must.Node(kv.LookupByIndex(0))), qt.Equals, "baz") + qt.Check(t, must.String(must.Node(kv.LookupByIndex(1))), qt.Equals, "2") + kv = must.Node(nr.LookupByIndex(3)) + qt.Assert(t, nr.Kind(), qt.Equals, datamodel.Kind_List) + qt.Check(t, kv.Length(), qt.Equals, int64(2)) + qt.Check(t, must.String(must.Node(kv.LookupByIndex(0))), qt.Equals, "qux") + qux := must.Node(kv.LookupByIndex(1)) + qt.Assert(t, qux.Kind(), qt.Equals, datamodel.Kind_List) + qt.Check(t, qux.Length(), qt.Equals, int64(2)) + qt.Check(t, must.String(must.Node(qux.LookupByIndex(0))), qt.Equals, "3") + qt.Check(t, must.String(must.Node(qux.LookupByIndex(1))), qt.Equals, "4") }) }) t.Run("repr-create", func(t *testing.T) { - nr := fluent.MustBuildList(nrp, 8, func(la fluent.ListAssembler) { - la.AssembleValue().AssignString("foo") - la.AssembleValue().AssignString("0") - la.AssembleValue().AssignString("bar") - la.AssembleValue().AssignString("1") - la.AssembleValue().AssignString("baz") - la.AssembleValue().AssignString("2") - la.AssembleValue().AssignString("qux") - la.AssembleValue().AssignString("3") + nr := fluent.MustBuildList(nrp, 4, func(la fluent.ListAssembler) { + la.AssembleValue().CreateList(2, func(la fluent.ListAssembler) { + la.AssembleValue().AssignString("foo") + la.AssembleValue().AssignString("0") + }) + la.AssembleValue().CreateList(2, func(la fluent.ListAssembler) { + la.AssembleValue().AssignString("bar") + la.AssembleValue().AssignString("1") + }) + la.AssembleValue().CreateList(2, func(la fluent.ListAssembler) { + la.AssembleValue().AssignString("baz") + la.AssembleValue().AssignString("2") + }) + la.AssembleValue().CreateList(2, func(la fluent.ListAssembler) { + la.AssembleValue().AssignString("qux") + la.AssembleValue().CreateList(2, func(la fluent.ListAssembler) { + la.AssembleValue().AssignString("3") + la.AssembleValue().AssignString("4") + }) + }) }) qt.Check(t, n, NodeContentEquals, nr) }) t.Run("repr-create out-of-order", func(t *testing.T) { - nr := fluent.MustBuildList(nrp, 8, func(la fluent.ListAssembler) { - la.AssembleValue().AssignString("bar") - la.AssembleValue().AssignString("1") - la.AssembleValue().AssignString("foo") - la.AssembleValue().AssignString("0") - la.AssembleValue().AssignString("qux") - la.AssembleValue().AssignString("3") - la.AssembleValue().AssignString("baz") - la.AssembleValue().AssignString("2") + nr := fluent.MustBuildList(nrp, 4, func(la fluent.ListAssembler) { + la.AssembleValue().CreateList(2, func(la fluent.ListAssembler) { + la.AssembleValue().AssignString("bar") + la.AssembleValue().AssignString("1") + }) + la.AssembleValue().CreateList(2, func(la fluent.ListAssembler) { + la.AssembleValue().AssignString("foo") + la.AssembleValue().AssignString("0") + }) + la.AssembleValue().CreateList(2, func(la fluent.ListAssembler) { + la.AssembleValue().AssignString("qux") + la.AssembleValue().CreateList(2, func(la fluent.ListAssembler) { + la.AssembleValue().AssignString("3") + la.AssembleValue().AssignString("4") + }) + }) + la.AssembleValue().CreateList(2, func(la fluent.ListAssembler) { + la.AssembleValue().AssignString("baz") + la.AssembleValue().AssignString("2") + }) }) qt.Check(t, n, NodeContentEquals, nr) }) @@ -130,7 +182,10 @@ func SchemaTestStructReprListPairs(t *testing.T, engine Engine) { t.Run("typed-create", func(t *testing.T) { n = fluent.MustBuildMap(np, 2, func(ma fluent.MapAssembler) { ma.AssembleEntry("foo").AssignNull() - ma.AssembleEntry("qux").AssignString("3") + ma.AssembleEntry("qux").CreateList(2, func(la fluent.ListAssembler) { + la.AssembleValue().AssignString("1") + la.AssembleValue().AssignString("2") + }) }).(schema.TypedNode) t.Run("typed-read", func(t *testing.T) { qt.Assert(t, n.Kind(), qt.Equals, datamodel.Kind_Map) @@ -138,24 +193,45 @@ func SchemaTestStructReprListPairs(t *testing.T, engine Engine) { qt.Check(t, must.Node(n.LookupByString("foo")), qt.Equals, datamodel.Null) qt.Check(t, must.Node(n.LookupByString("bar")), qt.Equals, datamodel.Absent) qt.Check(t, must.Node(n.LookupByString("baz")), qt.Equals, datamodel.Absent) - qt.Check(t, must.String(must.Node(n.LookupByString("qux"))), qt.Equals, "3") + qux := must.Node(n.LookupByString("qux")) + qt.Assert(t, qux.Kind(), qt.Equals, datamodel.Kind_List) + qt.Check(t, qux.Length(), qt.Equals, int64(2)) + qt.Check(t, must.String(must.Node(qux.LookupByIndex(0))), qt.Equals, "1") + qt.Check(t, must.String(must.Node(qux.LookupByIndex(1))), qt.Equals, "2") }) t.Run("repr-read", func(t *testing.T) { nr := n.Representation() qt.Assert(t, nr.Kind(), qt.Equals, datamodel.Kind_List) - qt.Check(t, nr.Length(), qt.Equals, int64(4)) - qt.Check(t, must.String(must.Node(nr.LookupByIndex(0))), qt.Equals, "foo") - qt.Check(t, must.Node(nr.LookupByIndex(1)), qt.Equals, datamodel.Null) - qt.Check(t, must.String(must.Node(nr.LookupByIndex(2))), qt.Equals, "qux") - qt.Check(t, must.String(must.Node(nr.LookupByIndex(3))), qt.Equals, "3") + qt.Check(t, nr.Length(), qt.Equals, int64(2)) + kv := must.Node(nr.LookupByIndex(0)) + qt.Assert(t, nr.Kind(), qt.Equals, datamodel.Kind_List) + qt.Check(t, kv.Length(), qt.Equals, int64(2)) + qt.Check(t, must.String(must.Node(kv.LookupByIndex(0))), qt.Equals, "foo") + qt.Check(t, must.Node(kv.LookupByIndex(1)), qt.Equals, datamodel.Null) + kv = must.Node(nr.LookupByIndex(1)) + qt.Assert(t, nr.Kind(), qt.Equals, datamodel.Kind_List) + qt.Check(t, kv.Length(), qt.Equals, int64(2)) + qt.Check(t, must.String(must.Node(kv.LookupByIndex(0))), qt.Equals, "qux") + qux := must.Node(kv.LookupByIndex(1)) + qt.Assert(t, qux.Kind(), qt.Equals, datamodel.Kind_List) + qt.Check(t, qux.Length(), qt.Equals, int64(2)) + qt.Check(t, must.String(must.Node(qux.LookupByIndex(0))), qt.Equals, "1") + qt.Check(t, must.String(must.Node(qux.LookupByIndex(1))), qt.Equals, "2") }) }) t.Run("repr-create", func(t *testing.T) { - nr := fluent.MustBuildList(nrp, 4, func(la fluent.ListAssembler) { - la.AssembleValue().AssignString("foo") - la.AssembleValue().AssignNull() - la.AssembleValue().AssignString("qux") - la.AssembleValue().AssignString("3") + nr := fluent.MustBuildList(nrp, 2, func(la fluent.ListAssembler) { + la.AssembleValue().CreateList(2, func(la fluent.ListAssembler) { + la.AssembleValue().AssignString("foo") + la.AssembleValue().AssignNull() + }) + la.AssembleValue().CreateList(2, func(la fluent.ListAssembler) { + la.AssembleValue().AssignString("qux") + la.AssembleValue().CreateList(2, func(la fluent.ListAssembler) { + la.AssembleValue().AssignString("1") + la.AssembleValue().AssignString("2") + }) + }) }) qt.Check(t, n, NodeContentEquals, nr) }) From 9f519f1665d80673abe16af77d79bedd104d6e79 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Tue, 25 Apr 2023 10:56:48 +1000 Subject: [PATCH 3/6] fix: remove _skipAbsent labels --- node/bindnode/repr.go | 78 ++++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/node/bindnode/repr.go b/node/bindnode/repr.go index cc351328..b724f2f5 100644 --- a/node/bindnode/repr.go +++ b/node/bindnode/repr.go @@ -344,16 +344,16 @@ type _tupleIteratorRepr struct { } func (w *_tupleIteratorRepr) Next() (index int64, value datamodel.Node, _ error) { -_skipAbsent: - idx := w.nextIndex - _, value, err := (*_structIterator)(w).Next() - if err != nil { - return 0, nil, err - } - if w.nextIndex > w.reprEnd { - goto _skipAbsent + for { + idx := w.nextIndex + _, value, err := (*_structIterator)(w).Next() + if err != nil { + return 0, nil, err + } + if w.nextIndex <= w.reprEnd { + return int64(idx), reprNode(value), nil + } } - return int64(idx), reprNode(value), nil } func (w *_tupleIteratorRepr) Done() bool { @@ -372,23 +372,24 @@ type _listpairsIteratorRepr struct { } func (w *_listpairsIteratorRepr) Next() (index int64, value datamodel.Node, _ error) { -_skipAbsent: - if w.Done() { - return 0, nil, datamodel.ErrIteratorOverread{} - } - idx := w.nextIndex - key, value, err := (*_structIterator)(w).Next() - if err != nil { - return 0, nil, err - } - if value.IsAbsent() || w.nextIndex > w.reprEnd { - goto _skipAbsent - } - field, err := buildListpairsField(key, value) - if err != nil { - return 0, nil, err + for { + if w.Done() { + return 0, nil, datamodel.ErrIteratorOverread{} + } + idx := w.nextIndex + key, value, err := (*_structIterator)(w).Next() + if err != nil { + return 0, nil, err + } + if value.IsAbsent() || w.nextIndex > w.reprEnd { + continue + } + field, err := buildListpairsField(key, value) + if err != nil { + return 0, nil, err + } + return int64(idx), field, nil } - return int64(idx), field, nil } func (w *_listpairsIteratorRepr) Done() bool { @@ -1257,20 +1258,21 @@ type _structIteratorRepr _structIterator func (w *_structIteratorRepr) Next() (key, value datamodel.Node, _ error) { switch stg := reprStrategy(w.schemaType).(type) { case schema.StructRepresentation_Map: - _skipAbsent: - key, value, err := (*_structIterator)(w).Next() - if err != nil { - return nil, nil, err - } - if value.IsAbsent() { - goto _skipAbsent - } - keyStr, _ := key.AsString() - mappedKey := outboundMappedKey(stg, keyStr) - if mappedKey != keyStr { - key = basicnode.NewString(mappedKey) + for { + key, value, err := (*_structIterator)(w).Next() + if err != nil { + return nil, nil, err + } + if value.IsAbsent() { + continue + } + keyStr, _ := key.AsString() + mappedKey := outboundMappedKey(stg, keyStr) + if mappedKey != keyStr { + key = basicnode.NewString(mappedKey) + } + return key, reprNode(value), nil } - return key, reprNode(value), nil default: return nil, nil, fmt.Errorf("bindnode Next TODO: %T", stg) } From 5f65ed816638199be92e92ac8bc177efa7cffdb1 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Wed, 26 Apr 2023 13:37:14 +1000 Subject: [PATCH 4/6] fix(schema): handle parsing of "listpairs" in the DSL Ref: https://github.com/ipld/ipld/pull/281 --- .ipld | 2 +- schema/dmt/compile.go | 2 ++ schema/dsl/parse.go | 3 +++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.ipld b/.ipld index 9582ec21..90a07cc6 160000 --- a/.ipld +++ b/.ipld @@ -1 +1 @@ -Subproject commit 9582ec2122ab82f6bdf3a5a42c4f0e27c03f0a9f +Subproject commit 90a07cc6e16d7bc56c4b7aaec35cdc548c529a7a diff --git a/schema/dmt/compile.go b/schema/dmt/compile.go index 50e17f03..199ce88a 100644 --- a/schema/dmt/compile.go +++ b/schema/dmt/compile.go @@ -277,6 +277,8 @@ func spawnType(ts *schema.TypeSystem, name schema.TypeName, defn TypeDefn) (sche return nil, fmt.Errorf("stringjoin has empty join value") } repr = schema.SpawnStructRepresentationStringjoin(join) + case typ.Representation.StructRepresentation_Listpairs != nil: + repr = schema.SpawnStructRepresentationListPairs() default: return nil, fmt.Errorf("TODO: support other struct repr in schema package") } diff --git a/schema/dsl/parse.go b/schema/dsl/parse.go index b1cf2e0b..089ab31b 100644 --- a/schema/dsl/parse.go +++ b/schema/dsl/parse.go @@ -467,6 +467,9 @@ func (p *parser) typeStruct() (*dmt.TypeDefnStruct, error) { Join: join, } return defn, nil + case "listpairs": + defn.Representation.StructRepresentation_Listpairs = &dmt.StructRepresentation_Listpairs{} + return defn, nil default: return nil, p.errf("unknown struct repr: %q", reprName) } From 1fd7e14a6abb02cf9447bed30589ad390fe82d22 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Sat, 29 Apr 2023 12:03:52 +1000 Subject: [PATCH 5/6] fix(bindnode): listpairs repr assembler handles AssignNode --- node/bindnode/repr.go | 11 +++-------- node/tests/schemaStructReprListpairs.go | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/node/bindnode/repr.go b/node/bindnode/repr.go index b724f2f5..daab785f 100644 --- a/node/bindnode/repr.go +++ b/node/bindnode/repr.go @@ -1148,15 +1148,10 @@ func (w _listpairsFieldAssemblerRepr) AssignLink(datamodel.Link) error { } } -// TODO: support this if it's a list? -func (w _listpairsFieldAssemblerRepr) AssignNode(datamodel.Node) error { - return datamodel.ErrWrongKind{ - TypeName: w.parent.schemaType.Name(), - MethodName: "AssignNode", - AppropriateKind: datamodel.KindSet_JustList, - ActualKind: datamodel.Kind_Map, - } +func (w *_listpairsFieldAssemblerRepr) AssignNode(n datamodel.Node) error { + return datamodel.Copy(n, w) } + func (w _listpairsFieldAssemblerRepr) Prototype() datamodel.NodePrototype { panic("bindnode TODO: listpairs field Prototype") } diff --git a/node/tests/schemaStructReprListpairs.go b/node/tests/schemaStructReprListpairs.go index c59c4fa7..ce11c714 100644 --- a/node/tests/schemaStructReprListpairs.go +++ b/node/tests/schemaStructReprListpairs.go @@ -8,6 +8,7 @@ import ( "github.com/ipld/go-ipld-prime/datamodel" "github.com/ipld/go-ipld-prime/fluent" "github.com/ipld/go-ipld-prime/must" + "github.com/ipld/go-ipld-prime/node/basicnode" "github.com/ipld/go-ipld-prime/schema" ) @@ -235,5 +236,25 @@ func SchemaTestStructReprListPairs(t *testing.T, engine Engine) { }) qt.Check(t, n, NodeContentEquals, nr) }) + t.Run("repr-create with AssignNode", func(t *testing.T) { + nr := fluent.MustBuildList(basicnode.Prototype.Any, 2, func(la fluent.ListAssembler) { + la.AssembleValue().CreateList(2, func(la fluent.ListAssembler) { + la.AssembleValue().AssignString("foo") + la.AssembleValue().AssignNull() + }) + la.AssembleValue().CreateList(2, func(la fluent.ListAssembler) { + la.AssembleValue().AssignString("qux") + la.AssembleValue().CreateList(2, func(la fluent.ListAssembler) { + la.AssembleValue().AssignString("1") + la.AssembleValue().AssignString("2") + }) + }) + }) + builder := nrp.NewBuilder() + err := builder.AssignNode(nr) + qt.Assert(t, err, qt.IsNil) + anr := builder.Build() + qt.Check(t, n, NodeContentEquals, anr) + }) }) } From e3ab7042a924e8475eead1efd3776f5d654144f0 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Sat, 13 May 2023 18:45:25 +1000 Subject: [PATCH 6/6] fix(bindnode): listpairs value assembly handles complex reprs current impl works on scalar assemblies but borks when you try to assemble a child that has a non-scalar; so nested listpairs in listpairs doesn't work because we've not been passing representation assemblers properly --- node/bindnode/repr.go | 7 +-- node/tests/schemaStructReprListpairs.go | 67 +++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/node/bindnode/repr.go b/node/bindnode/repr.go index daab785f..3c08a2b8 100644 --- a/node/bindnode/repr.go +++ b/node/bindnode/repr.go @@ -189,7 +189,7 @@ func (w *_nodeRepr) LookupByIndex(idx int64) (datamodel.Node, error) { continue } if curField == idx { - return buildListpairsField(basicnode.NewString(field.Name()), value) + return buildListpairsField(basicnode.NewString(field.Name()), reprNode(value)) } curField++ } @@ -384,7 +384,7 @@ func (w *_listpairsIteratorRepr) Next() (index int64, value datamodel.Node, _ er if value.IsAbsent() || w.nextIndex > w.reprEnd { continue } - field, err := buildListpairsField(key, value) + field, err := buildListpairsField(key, reprNode(value)) if err != nil { return 0, nil, err } @@ -1167,7 +1167,8 @@ func (w *_listpairsFieldListAssemblerRepr) AssembleValue() datamodel.NodeAssembl case 1: return w.parent.AssembleKey() case 2: - return w.parent.AssembleValue() + asm := w.parent.AssembleValue() + return assemblerRepr(asm.(*_assembler)) default: return _errorAssembler{fmt.Errorf("bindnode: too many values in listpairs field")} } diff --git a/node/tests/schemaStructReprListpairs.go b/node/tests/schemaStructReprListpairs.go index ce11c714..f9e50c19 100644 --- a/node/tests/schemaStructReprListpairs.go +++ b/node/tests/schemaStructReprListpairs.go @@ -32,6 +32,13 @@ func SchemaTestStructReprListPairs(t *testing.T, engine Engine) { }, schema.SpawnStructRepresentationListPairs(), )) + ts.Accumulate(schema.SpawnStruct("NestedListPairs", + []schema.StructField{ + schema.SpawnStructField("str", "String", false, false), + schema.SpawnStructField("lp", "OneListPair", false, false), + }, + schema.SpawnStructRepresentationListPairs(), + )) engine.Init(t, ts) t.Run("onelistpair works", func(t *testing.T) { @@ -257,4 +264,64 @@ func SchemaTestStructReprListPairs(t *testing.T, engine Engine) { qt.Check(t, n, NodeContentEquals, anr) }) }) + + t.Run("nestedlistpairs works", func(t *testing.T) { + np := engine.PrototypeByName("NestedListPairs") + nrp := engine.PrototypeByName("NestedListPairs.Repr") + var n schema.TypedNode + t.Run("typed-create", func(t *testing.T) { + n = fluent.MustBuildMap(np, 1, func(ma fluent.MapAssembler) { + ma.AssembleEntry("str").AssignString("boop") + ma.AssembleEntry("lp").CreateMap(1, func(ma fluent.MapAssembler) { + ma.AssembleEntry("field").AssignString("valoo") + }) + }).(schema.TypedNode) + t.Run("typed-read", func(t *testing.T) { + qt.Assert(t, n.Kind(), qt.Equals, datamodel.Kind_Map) + qt.Check(t, n.Length(), qt.Equals, int64(2)) + qt.Check(t, must.String(must.Node(n.LookupByString("str"))), qt.Equals, "boop") + lp := must.Node(n.LookupByString("lp")) + qt.Check(t, lp.Kind(), qt.Equals, datamodel.Kind_Map) + qt.Check(t, must.String(must.Node(lp.LookupByString("field"))), qt.Equals, "valoo") + }) + t.Run("repr-read", func(t *testing.T) { + nr := n.Representation() + qt.Assert(t, nr.Kind(), qt.Equals, datamodel.Kind_List) + qt.Check(t, nr.Length(), qt.Equals, int64(2)) + kv := must.Node(nr.LookupByIndex(0)) + qt.Assert(t, nr.Kind(), qt.Equals, datamodel.Kind_List) + qt.Check(t, kv.Length(), qt.Equals, int64(2)) + qt.Check(t, must.String(must.Node(kv.LookupByIndex(0))), qt.Equals, "str") + qt.Check(t, must.String(must.Node(kv.LookupByIndex(1))), qt.Equals, "boop") + kv = must.Node(nr.LookupByIndex(1)) + qt.Assert(t, nr.Kind(), qt.Equals, datamodel.Kind_List) + qt.Check(t, kv.Length(), qt.Equals, int64(2)) + qt.Check(t, must.String(must.Node(kv.LookupByIndex(0))), qt.Equals, "lp") + lp := must.Node(kv.LookupByIndex(1)) + qt.Check(t, lp.Kind(), qt.Equals, datamodel.Kind_List) + kv = must.Node(lp.LookupByIndex(0)) + qt.Check(t, kv.Length(), qt.Equals, int64(2)) + qt.Check(t, must.String(must.Node(kv.LookupByIndex(0))), qt.Equals, "field") + qt.Check(t, must.String(must.Node(kv.LookupByIndex(1))), qt.Equals, "valoo") + }) + }) + t.Run("repr-create", func(t *testing.T) { + nr := fluent.MustBuildList(nrp, 1, func(la fluent.ListAssembler) { + la.AssembleValue().CreateList(2, func(la fluent.ListAssembler) { + la.AssembleValue().AssignString("str") + la.AssembleValue().AssignString("boop") + }) + la.AssembleValue().CreateList(2, func(la fluent.ListAssembler) { + la.AssembleValue().AssignString("lp") + la.AssembleValue().CreateList(2, func(la fluent.ListAssembler) { + la.AssembleValue().CreateList(2, func(la fluent.ListAssembler) { + la.AssembleValue().AssignString("field") + la.AssembleValue().AssignString("valoo") + }) + }) + }) + }) + qt.Check(t, n, NodeContentEquals, nr) + }) + }) }