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/node/bindnode/node.go b/node/bindnode/node.go index 7930fa10..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) @@ -44,8 +46,11 @@ 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) _ datamodel.MapAssembler = (*_unionAssembler)(nil) _ datamodel.MapAssembler = (*_unionAssemblerRepr)(nil) diff --git a/node/bindnode/repr.go b/node/bindnode/repr.go index 993defbd..3c08a2b8 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,26 @@ 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) { + return nil, datamodel.ErrNotExists{Segment: datamodel.PathSegmentOfInt(idx)} + } + 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 { + return buildListpairsField(basicnode.NewString(field.Name()), reprNode(value)) + } + curField++ + } + return nil, datamodel.ErrNotExists{Segment: datamodel.PathSegmentOfInt(idx)} default: v, err := (*_node)(w).LookupByIndex(idx) if err != nil { @@ -272,6 +292,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()) + return &iter default: iter, _ := (*_node)(w).ListIterator().(*_listIterator) if iter == nil { @@ -319,21 +344,76 @@ type _tupleIteratorRepr struct { } func (w *_tupleIteratorRepr) Next() (index int64, value datamodel.Node, _ error) { -_skipAbsent: - _, 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(w.nextIndex), 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) { + 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, reprNode(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-- { @@ -353,6 +433,8 @@ func (w *_nodeRepr) Length() int64 { return w.lengthMinusAbsents() case schema.StructRepresentation_Tuple: return w.lengthMinusTrailingAbsents() + case schema.StructRepresentation_ListPairs: + return w.lengthMinusAbsents() case schema.UnionRepresentation_Keyed: return (*_node)(w).Length() case schema.UnionRepresentation_Kinded: @@ -625,7 +707,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 +942,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 +1056,8 @@ func (w *_listStructAssemblerRepr) AssembleValue() datamodel.NodeAssembler { } entryAsm = assemblerRepr(entryAsm) return entryAsm + case schema.StructRepresentation_ListPairs: + return &_listpairsFieldAssemblerRepr{parent: (*_structAssembler)(w)} default: return _errorAssembler{fmt.Errorf("bindnode AssembleValue TODO: %T", stg)} } @@ -981,7 +1065,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) @@ -992,6 +1076,112 @@ 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, + } +} + +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") +} + +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: + asm := w.parent.AssembleValue() + return assemblerRepr(asm.(*_assembler)) + 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 @@ -1064,20 +1254,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) } 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..f9e50c19 --- /dev/null +++ b/node/tests/schemaStructReprListpairs.go @@ -0,0 +1,327 @@ +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/node/basicnode" + "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.SpawnList("List__String", "String", false)) + 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", "List__String", false, false), + }, + 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) { + 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(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, 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) + }) + }) + + 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").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") + 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(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, 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, 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) + }) + }) + + 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").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) + 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) + 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(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, 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) + }) + 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) + }) + }) + + 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) + }) + }) +} 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) } 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: