Skip to content

Commit

Permalink
feat!: Node#Length() now has an error return
Browse files Browse the repository at this point in the history
Closes: #531

ADL and other Node implementations that need to perform non-trivial work in
calculating their Length(), such as by loading multiple child blocks, may
encounter errors which should not be silently ignored.
  • Loading branch information
rvagg committed Jul 7, 2023
1 parent 65bfa53 commit fd4241a
Show file tree
Hide file tree
Showing 59 changed files with 537 additions and 229 deletions.
4 changes: 2 additions & 2 deletions adl/rot13adl/rot13node.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ func (*_R13String) MapIterator() datamodel.MapIterator {
func (*_R13String) ListIterator() datamodel.ListIterator {
return nil
}
func (*_R13String) Length() int64 {
return -1
func (*_R13String) Length() (int64, error) {
return -1, nil
}
func (*_R13String) IsAbsent() bool {
return false
Expand Down
4 changes: 2 additions & 2 deletions adl/rot13adl/rot13substrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ func (*_Substrate) MapIterator() datamodel.MapIterator {
func (*_Substrate) ListIterator() datamodel.ListIterator {
return nil
}
func (*_Substrate) Length() int64 {
return -1
func (*_Substrate) Length() (int64, error) {
return -1, nil
}
func (*_Substrate) IsAbsent() bool {
return false
Expand Down
31 changes: 22 additions & 9 deletions codec/dagcbor/marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@ func marshal(n datamodel.Node, tk *tok.Token, sink shared.TokenSink, options Enc
case datamodel.Kind_List:
// Emit start of list.
tk.Type = tok.TArrOpen
l := n.Length()
l, err := n.Length()
if err != nil {
return err
}
tk.Length = int(l) // TODO: overflow check
if _, err := sink.Step(tk); err != nil {
return err
Expand All @@ -87,7 +90,7 @@ func marshal(n datamodel.Node, tk *tok.Token, sink shared.TokenSink, options Enc
}
// Emit list close.
tk.Type = tok.TArrClose
_, err := sink.Step(tk)
_, err = sink.Step(tk)
return err
case datamodel.Kind_Bool:
v, err := n.AsBool()
Expand Down Expand Up @@ -174,8 +177,11 @@ func marshal(n datamodel.Node, tk *tok.Token, sink shared.TokenSink, options Enc
func marshalMap(n datamodel.Node, tk *tok.Token, sink shared.TokenSink, options EncodeOptions) error {
// Emit start of map.
tk.Type = tok.TMapOpen
expectedLength := int(n.Length())
tk.Length = expectedLength // TODO: overflow check
expectedLength, err := n.Length()
if err != nil {
return err
}
tk.Length = int(expectedLength) // TODO: overflow check
if _, err := sink.Step(tk); err != nil {
return err
}
Expand All @@ -197,7 +203,7 @@ func marshalMap(n datamodel.Node, tk *tok.Token, sink shared.TokenSink, options
}
entries = append(entries, entry{keyStr, v})
}
if len(entries) != expectedLength {
if len(entries) != int(expectedLength) {
return fmt.Errorf("map Length() does not match number of MapIterator() entries")
}
// Apply the desired sort function.
Expand Down Expand Up @@ -229,7 +235,7 @@ func marshalMap(n datamodel.Node, tk *tok.Token, sink shared.TokenSink, options
}
} else { // no sorting
// Emit map contents (and recurse).
var entryCount int
var entryCount int64
for itr := n.MapIterator(); !itr.Done(); {
k, v, err := itr.Next()
if err != nil {
Expand All @@ -254,7 +260,7 @@ func marshalMap(n datamodel.Node, tk *tok.Token, sink shared.TokenSink, options
}
// Emit map close.
tk.Type = tok.TMapClose
_, err := sink.Step(tk)
_, err = sink.Step(tk)
return err
}

Expand All @@ -272,7 +278,11 @@ func EncodedLength(n datamodel.Node) (int64, error) {
case datamodel.Kind_Null:
return 1, nil // 0xf6
case datamodel.Kind_Map:
length := uintLength(uint64(n.Length())) // length prefixed major 5
l, err := n.Length()
if err != nil {
return 0, err
}
length := uintLength(uint64(l)) // length prefixed major 5
for itr := n.MapIterator(); !itr.Done(); {
k, v, err := itr.Next()
if err != nil {
Expand All @@ -291,7 +301,10 @@ func EncodedLength(n datamodel.Node) (int64, error) {
}
return length, nil
case datamodel.Kind_List:
nl := n.Length()
nl, err := n.Length()
if err != nil {
return 0, err
}
length := uintLength(uint64(nl)) // length prefixed major 4
for i := int64(0); i < nl; i++ {
v, err := n.LookupByIndex(i)
Expand Down
20 changes: 13 additions & 7 deletions codec/dagjson/marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,11 @@ func Marshal(n datamodel.Node, sink shared.TokenSink, options EncodeOptions) err
case datamodel.Kind_Map:
// Emit start of map.
tk.Type = tok.TMapOpen
expectedLength := int(n.Length())
tk.Length = expectedLength // TODO: overflow check
expectedLength, err := n.Length()
if err != nil {
return err
}
tk.Length = int(expectedLength) // TODO: overflow check
if _, err := sink.Step(&tk); err != nil {
return err
}
Expand All @@ -84,7 +87,7 @@ func Marshal(n datamodel.Node, sink shared.TokenSink, options EncodeOptions) err
}
entries = append(entries, entry{keyStr, v})
}
if len(entries) != expectedLength {
if len(entries) != int(expectedLength) {
return fmt.Errorf("map Length() does not match number of MapIterator() entries")
}
// Apply the desired sort function.
Expand All @@ -104,7 +107,7 @@ func Marshal(n datamodel.Node, sink shared.TokenSink, options EncodeOptions) err
})
}
// Emit map contents (and recurse).
var entryCount int
var entryCount int64
for _, e := range entries {
tk.Type = tok.TString
tk.Str = e.key
Expand Down Expand Up @@ -141,12 +144,15 @@ func Marshal(n datamodel.Node, sink shared.TokenSink, options EncodeOptions) err
}
// Emit map close.
tk.Type = tok.TMapClose
_, err := sink.Step(&tk)
_, err = sink.Step(&tk)
return err
case datamodel.Kind_List:
// Emit start of list.
tk.Type = tok.TArrOpen
l := n.Length()
l, err := n.Length()
if err != nil {
return err
}
tk.Length = int(l) // TODO: overflow check
if _, err := sink.Step(&tk); err != nil {
return err
Expand All @@ -163,7 +169,7 @@ func Marshal(n datamodel.Node, sink shared.TokenSink, options EncodeOptions) err
}
// Emit list close.
tk.Type = tok.TArrClose
_, err := sink.Step(&tk)
_, err = sink.Step(&tk)
return err
case datamodel.Kind_Bool:
v, err := n.AsBool()
Expand Down
3 changes: 2 additions & 1 deletion codecHelpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ func Example_unmarshal_withSchema() {
n, err := ipld.Unmarshal(serial, json.Decode, &foobar, typesys.TypeByName("Foobar"))
fmt.Printf("error: %v\n", err)
fmt.Printf("go struct: %v\n", foobar)
fmt.Printf("node kind and length: %s, %d\n", n.Kind(), n.Length())
l, _ := n.Length()
fmt.Printf("node kind and length: %s, %d\n", n.Kind(), l)
fmt.Printf("node lookup 'foo': %q\n", must.String(must.Node(n.LookupByString("foo"))))

// Output:
Expand Down
12 changes: 10 additions & 2 deletions datamodel/copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,11 @@ func Copy(n Node, na NodeAssembler) error {
}
return na.AssignLink(v)
case Kind_Map:
ma, err := na.BeginMap(n.Length())
l, err := n.Length()
if err != nil {
return err
}
ma, err := na.BeginMap(l)
if err != nil {
return err
}
Expand All @@ -89,7 +93,11 @@ func Copy(n Node, na NodeAssembler) error {
}
return ma.Finish()
case Kind_List:
la, err := na.BeginList(n.Length())
l, err := n.Length()
if err != nil {
return err
}
la, err := na.BeginList(l)
if err != nil {
return err
}
Expand Down
20 changes: 18 additions & 2 deletions datamodel/equal.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,15 @@ func DeepEqual(x, y Node) bool {

// Recursive kinds.
case Kind_Map:
if x.Length() != y.Length() {
xl, err := x.Length()
if err != nil {
panic(err)
}
yl, err := y.Length()
if err != nil {
panic(err)
}
if xl != yl {
return false
}
xitr := x.MapIterator()
Expand All @@ -129,7 +137,15 @@ func DeepEqual(x, y Node) bool {
}
return true
case Kind_List:
if x.Length() != y.Length() {
xl, err := x.Length()
if err != nil {
panic(err)
}
yl, err := y.Length()
if err != nil {
panic(err)
}
if xl != yl {
return false
}
xitr := x.ListIterator()
Expand Down
2 changes: 1 addition & 1 deletion datamodel/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ type Node interface {

// Length returns the length of a list, or the number of entries in a map,
// or -1 if the node is not of list nor map kind.
Length() int64
Length() (int64, error)

// Absent nodes are returned when traversing a struct field that is
// defined by a schema but unset in the data. (Absent nodes are not
Expand Down
8 changes: 4 additions & 4 deletions datamodel/unit.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ func (nullNode) MapIterator() MapIterator {
func (nullNode) ListIterator() ListIterator {
return nil
}
func (nullNode) Length() int64 {
return -1
func (nullNode) Length() (int64, error) {
return -1, nil
}
func (nullNode) IsAbsent() bool {
return false
Expand Down Expand Up @@ -110,8 +110,8 @@ func (absentNode) MapIterator() MapIterator {
func (absentNode) ListIterator() ListIterator {
return nil
}
func (absentNode) Length() int64 {
return -1
func (absentNode) Length() (int64, error) {
return -1, nil
}
func (absentNode) IsAbsent() bool {
return true
Expand Down
3 changes: 2 additions & 1 deletion examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ func Example_unmarshalData() {
n := nb.Build() // Call 'Build' to get the resulting Node. (It's immutable!)

fmt.Printf("the data decoded was a %s kind\n", n.Kind())
fmt.Printf("the length of the node is %d\n", n.Length())
l, _ := n.Length()
fmt.Printf("the length of the node is %d\n", l)

// Output:
// the data decoded was a map kind
Expand Down
24 changes: 18 additions & 6 deletions fluent/fluentBuilder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,15 @@ func TestBuild(t *testing.T) {
})
})
qt.Check(t, n.Kind(), qt.Equals, datamodel.Kind_Map)
qt.Check(t, n.Length(), qt.Equals, int64(3))
l, err := n.Length()
qt.Check(t, err, qt.IsNil)
qt.Check(t, l, qt.Equals, int64(3))
qt.Check(t, must.String(must.Node(n.LookupByString("k1"))), qt.Equals, "fine")
qt.Check(t, must.String(must.Node(n.LookupByString("k2"))), qt.Equals, "super")
n = must.Node(n.LookupByString("k3"))
qt.Check(t, n.Length(), qt.Equals, int64(3))
l, err = n.Length()
qt.Check(t, err, qt.IsNil)
qt.Check(t, l, qt.Equals, int64(3))
qt.Check(t, must.String(must.Node(n.LookupByString("k31"))), qt.Equals, "thanks")
qt.Check(t, must.String(must.Node(n.LookupByString("k32"))), qt.Equals, "for")
qt.Check(t, must.String(must.Node(n.LookupByString("k33"))), qt.Equals, "asking")
Expand All @@ -56,16 +60,24 @@ func TestBuild(t *testing.T) {
})
})
qt.Check(t, n.Kind(), qt.Equals, datamodel.Kind_List)
qt.Check(t, n.Length(), qt.Equals, int64(1))
l, err := n.Length()
qt.Check(t, err, qt.IsNil)
qt.Check(t, l, qt.Equals, int64(1))
n = must.Node(n.LookupByIndex(0))
qt.Check(t, n.Kind(), qt.Equals, datamodel.Kind_List)
qt.Check(t, n.Length(), qt.Equals, int64(1))
l, err = n.Length()
qt.Check(t, err, qt.IsNil)
qt.Check(t, l, qt.Equals, int64(1))
n = must.Node(n.LookupByIndex(0))
qt.Check(t, n.Kind(), qt.Equals, datamodel.Kind_List)
qt.Check(t, n.Length(), qt.Equals, int64(1))
l, err = n.Length()
qt.Check(t, err, qt.IsNil)
qt.Check(t, l, qt.Equals, int64(1))
n = must.Node(n.LookupByIndex(0))
qt.Check(t, n.Kind(), qt.Equals, datamodel.Kind_List)
qt.Check(t, n.Length(), qt.Equals, int64(1))
l, err = n.Length()
qt.Check(t, err, qt.IsNil)
qt.Check(t, l, qt.Equals, int64(1))
n = must.Node(n.LookupByIndex(0))
qt.Check(t, n.Kind(), qt.Equals, datamodel.Kind_Int)
qt.Check(t, must.Int(n), qt.Equals, int64(2))
Expand Down
16 changes: 12 additions & 4 deletions fluent/reflect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,15 @@ func TestReflect(t *testing.T) {
qt.Check(t, err, qt.IsNil)
qt.Check(t, n.Kind(), qt.Equals, datamodel.Kind_Map)
t.Run("CorrectContents", func(t *testing.T) {
qt.Check(t, n.Length(), qt.Equals, int64(3))
l, err := n.Length()
qt.Check(t, err, qt.IsNil)
qt.Check(t, l, qt.Equals, int64(3))
qt.Check(t, must.String(must.Node(n.LookupByString("k1"))), qt.Equals, "fine")
qt.Check(t, must.String(must.Node(n.LookupByString("k2"))), qt.Equals, "super")
n := must.Node(n.LookupByString("k3"))
qt.Check(t, n.Length(), qt.Equals, int64(3))
l, err = n.Length()
qt.Check(t, err, qt.IsNil)
qt.Check(t, l, qt.Equals, int64(3))
qt.Check(t, must.String(must.Node(n.LookupByString("k31"))), qt.Equals, "thanks")
qt.Check(t, must.String(must.Node(n.LookupByString("k32"))), qt.Equals, "for")
qt.Check(t, must.String(must.Node(n.LookupByString("k33"))), qt.Equals, "asking")
Expand Down Expand Up @@ -69,11 +73,15 @@ func TestReflect(t *testing.T) {
qt.Check(t, err, qt.IsNil)
qt.Check(t, n.Kind(), qt.Equals, datamodel.Kind_Map)
t.Run("CorrectContents", func(t *testing.T) {
qt.Check(t, n.Length(), qt.Equals, int64(3))
l, err := n.Length()
qt.Check(t, err, qt.IsNil)
qt.Check(t, l, qt.Equals, int64(3))
qt.Check(t, must.String(must.Node(n.LookupByString("X"))), qt.Equals, "fine")
qt.Check(t, must.String(must.Node(n.LookupByString("Z"))), qt.Equals, "super")
n := must.Node(n.LookupByString("M"))
qt.Check(t, n.Length(), qt.Equals, int64(2))
l, err = n.Length()
qt.Check(t, err, qt.IsNil)
qt.Check(t, l, qt.Equals, int64(2))
qt.Check(t, must.String(must.Node(n.LookupByString("A"))), qt.Equals, "thanks")
qt.Check(t, must.String(must.Node(n.LookupByString("B"))), qt.Equals, "really")
})
Expand Down
12 changes: 10 additions & 2 deletions fluent/toInterfaceValue.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ func ToInterface(node datamodel.Node) (interface{}, error) {
case datamodel.Kind_Link:
return node.AsLink()
case datamodel.Kind_Map:
outMap := make(map[string]interface{}, node.Length())
l, err := node.Length()
if err != nil {
return nil, err
}
outMap := make(map[string]interface{}, l)
for mi := node.MapIterator(); !mi.Done(); {
k, v, err := mi.Next()
if err != nil {
Expand All @@ -52,7 +56,11 @@ func ToInterface(node datamodel.Node) (interface{}, error) {
}
return outMap, nil
case datamodel.Kind_List:
outList := make([]interface{}, 0, node.Length())
l, err := node.Length()
if err != nil {
return nil, err
}
outList := make([]interface{}, 0, l)
for li := node.ListIterator(); !li.Done(); {
_, v, err := li.Next()
if err != nil {
Expand Down
3 changes: 2 additions & 1 deletion linking/linkingExamples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ func ExampleLinkSystem_Load() {
}

// Tada! We have the data as node that we can traverse and use as desired.
fmt.Printf("we loaded a %s with %d entries\n", n.Kind(), n.Length())
l, _ := n.Length()
fmt.Printf("we loaded a %s with %d entries\n", n.Kind(), l)

// Output:
// we loaded a map with 1 entries
Expand Down
Loading

0 comments on commit fd4241a

Please sign in to comment.