Skip to content

Commit

Permalink
Sort map entries marshalling dag-cbor
Browse files Browse the repository at this point in the history
  • Loading branch information
rvagg committed Jul 16, 2021
1 parent fc47eb2 commit 20a0caf
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 7 deletions.
27 changes: 23 additions & 4 deletions codec/dagcbor/marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package dagcbor

import (
"fmt"
"sort"

"github.com/polydawn/refmt/shared"
"github.com/polydawn/refmt/tok"
Expand Down Expand Up @@ -34,21 +35,39 @@ func marshal(n ipld.Node, tk *tok.Token, sink shared.TokenSink, allowLinks bool)
if _, err := sink.Step(tk); err != nil {
return err
}
// Emit map contents (and recurse).
// Collect map entries, then sort by key
type entry struct {
key string
value ipld.Node
}
entries := []entry{}
for itr := n.MapIterator(); !itr.Done(); {
k, v, err := itr.Next()
if err != nil {
return err
}
tk.Type = tok.TString
tk.Str, err = k.AsString()
keyStr, err := k.AsString()
if err != nil {
return err
}
entries = append(entries, entry{keyStr, v})
}
// RFC7049 style sort as per DAG-CBOR spec
sort.Slice(entries, func(i, j int) bool {
li, lj := len(entries[i].key), len(entries[j].key)
if li == lj {
return entries[i].key < entries[j].key
}
return li < lj
})
// Emit map contents (and recurse).
for _, e := range entries {
tk.Type = tok.TString
tk.Str = e.key
if _, err := sink.Step(tk); err != nil {
return err
}
if err := marshal(v, tk, sink, allowLinks); err != nil {
if err := marshal(e.value, tk, sink, allowLinks); err != nil {
return err
}
}
Expand Down
2 changes: 1 addition & 1 deletion codec/dagcbor/roundtripCidlink_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,5 @@ func TestRoundtripCidlink(t *testing.T) {

n2, err := lsys.Load(ipld.LinkContext{}, lnk, basicnode.Prototype.Any)
Require(t, err, ShouldEqual, nil)
Wish(t, n2, ShouldEqual, n)
Wish(t, n2, ShouldEqual, nSorted)
}
20 changes: 18 additions & 2 deletions codec/dagcbor/roundtrip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,23 @@ var n = fluent.MustBuildMap(basicnode.Prototype__Map{}, 4, func(na fluent.MapAss
})
})
})
var serial = "\xa4eplainkolde stringcmap\xa2cone\x01ctwo\x02dlist\x82ethreedfourfnested\xa1fdeeper\x81fthings"
var nSorted = fluent.MustBuildMap(basicnode.Prototype__Map{}, 4, func(na fluent.MapAssembler) {
na.AssembleEntry("map").CreateMap(2, func(na fluent.MapAssembler) {
na.AssembleEntry("one").AssignInt(1)
na.AssembleEntry("two").AssignInt(2)
})
na.AssembleEntry("list").CreateList(2, func(na fluent.ListAssembler) {
na.AssembleValue().AssignString("three")
na.AssembleValue().AssignString("four")
})
na.AssembleEntry("plain").AssignString("olde string")
na.AssembleEntry("nested").CreateMap(1, func(na fluent.MapAssembler) {
na.AssembleEntry("deeper").CreateList(1, func(na fluent.ListAssembler) {
na.AssembleValue().AssignString("things")
})
})
})
var serial = "\xa4cmap\xa2cone\x01ctwo\x02dlist\x82ethreedfoureplainkolde stringfnested\xa1fdeeper\x81fthings"

func TestRoundtrip(t *testing.T) {
t.Run("encoding", func(t *testing.T) {
Expand All @@ -44,7 +60,7 @@ func TestRoundtrip(t *testing.T) {
nb := basicnode.Prototype__Map{}.NewBuilder()
err := Decode(nb, buf)
Require(t, err, ShouldEqual, nil)
Wish(t, nb.Build(), ShouldEqual, n)
Wish(t, nb.Build(), ShouldEqual, nSorted)
})
}

Expand Down

0 comments on commit 20a0caf

Please sign in to comment.