Skip to content
This repository has been archived by the owner on Jan 17, 2022. It is now read-only.

Update ipld prime, use proper code-gen #5

Merged
merged 1 commit into from
Sep 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 52 additions & 51 deletions coding.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (

"github.com/ipfs/go-cid"
merkledag_pb "github.com/ipfs/go-merkledag/pb"
"github.com/ipld/go-ipld-prime/fluent"
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
"github.com/ipld/go-ipld-prime/schema"
)

// byteAccessor is a reader interface that can access underlying bytes
Expand All @@ -17,8 +17,8 @@ type byteAccesor interface {
}

// DecodeDagProto is a fast path decoding to protobuf
// from PBNode__NodeBuilders
func (nb _PBNode__NodeBuilder) DecodeDagProto(r io.Reader) error {
// from PBNode__Builders
func (nb *_PBNode__Builder) DecodeDagProto(r io.Reader) error {
var pbn merkledag_pb.PBNode
var encoded []byte
var err error
Expand All @@ -34,64 +34,63 @@ func (nb _PBNode__NodeBuilder) DecodeDagProto(r io.Reader) error {
if err := pbn.Unmarshal(encoded); err != nil {
return fmt.Errorf("unmarshal failed. %v", err)
}
pbLinks := make([]PBLink, 0, len(pbn.Links))
for _, link := range pbn.Links {
hash, err := cid.Cast(link.GetHash())
return fluent.Recover(func() {
fb := fluent.WrapAssembler(nb)
fb.CreateMap(0, func(fmb fluent.MapAssembler) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd mildly rather that these be -1 when they're saying "shrug idk deal with it". The contract about this isn't strict, but there are some logic branches that look at 0 as a "ah, expect 0" and look at -1 as "ah, take a guess". It's also helpful for reading.

... but none of those codepaths happen apply here in a codegen'd struct, so, this is a nit.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to improve the godoc on this, I've realized. Mea culpa.

fmb.AssembleEntry("Links").CreateList(len(pbn.Links), func(flb fluent.ListAssembler) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I think @rvagg 's comment about the links list here suggests there should be a branch on if len(pbn.Link) == 0 where we then skip assembling a data-model Links entry at all.

I can't decide if I think that's weird or not, though, now that I think about it. I'm hedging on if I think we should update our IPLD Schema describing this to just say that the list is required... and then if it's zero-length, sure, we can encode it to the proto wire format as missing.

That's a discussion that probably @rvagg and I should hash out on our specs documents about it though. If this is the behavior this code has had already, I think it's fine to keep it today. (I don't want to force you through testing a change like this in either direction today...)

for _, link := range pbn.Links {
hash, err := cid.Cast(link.GetHash())

if err != nil {
return fmt.Errorf("unmarshal failed. %v", err)
}
pbLinks = append(pbLinks, PBLink{
d: PBLink__Content{
Hash: MaybeLink{
Maybe: schema.Maybe_Value,
Value: Link{cidlink.Link{Cid: hash}},
},
Name: MaybeString{
Maybe: schema.Maybe_Value,
Value: String{link.GetName()},
},
Tsize: MaybeInt{
Maybe: schema.Maybe_Value,
Value: Int{int(link.GetTsize())},
},
},
if err != nil {
panic(fluent.Error{Err: fmt.Errorf("unmarshal failed. %v", err)})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rvagg : I went deep on exactly what happens here if the protobuf has a nil value for hash, because of its relevance to our other discussions about documenting this behavior:

tl;dr: a lack of hash here is not actually tolerated by this code: we get an error returned here.

(In detail: cid.Cast passes the nil down until some varint code tries to read from it, and at that point we get cid.ErrVarintBuffSmall and that bubbles back up from here.)

So I think this is a pretty solid argument that we don't have to consider Hash an optional in our IPLD Schema describing this.

}
flb.AssembleValue().CreateMap(0, func(fmb fluent.MapAssembler) {
fmb.AssembleEntry("Hash").AssignLink(cidlink.Link{Cid: hash})
fmb.AssembleEntry("Name").AssignString(link.GetName())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm moderately confused what the protobuf library is doing here -- we had to hand the protobuf lib a pointer to the name string elsewhere, because it's option in protobuf's mind, right? But here it's returning a string (no pointer). Does this panic if the string is missing? Or silently coerce it to an empty string?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea it silently coerces it tp empty string. Which is weird behavior except -- that's exactly what go-merkledag uses in in IPFS. My basic approach is follow as close as possible so as to not break things.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, cool. I appreciate that, thanks for talking through it with us so we have it also as input to the other standardization quests @rvagg mentioned :)

fmb.AssembleEntry("Tsize").AssignInt(int(link.GetTsize()))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question as above about name strings. Does this panic if the field isn't in the protobuf? Or silently coerce it to a zero?

})
}
})
fmb.AssembleEntry("Data").AssignBytes(pbn.GetData())
})
}
nb.nd.d.Links.x = pbLinks
nb.nd.d.Data.x = pbn.GetData()
return nil
})
}

// EncodeDagProto is a fast path encoding to protobuf
// for PBNode types
func (nd PBNode) EncodeDagProto(w io.Writer) error {
pbn := new(merkledag_pb.PBNode)
pbn.Links = make([]*merkledag_pb.PBLink, 0, len(nd.d.Links.x))
for _, link := range nd.d.Links.x {
pbn.Links = make([]*merkledag_pb.PBLink, 0, nd.FieldLinks().Length())
linksIter := nd.FieldLinks().ListIterator()
for !linksIter.Done() {
_, nlink, err := linksIter.Next()
if err != nil {
return err
}
link := nlink.(PBLink)
var hash []byte
if link.d.Hash.Maybe == schema.Maybe_Value {
cid := link.d.Hash.Value.x.(cidlink.Link).Cid
if link.FieldHash().Exists() {
cid := link.FieldHash().Must().Link().(cidlink.Link).Cid
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this field really is optional according to the schema, a Must here is fragile, isn't it? This is fine. Exists is checked right above the Must. (Silly pinhole-optimizer brain of mine...) Leaving the comment here to remind myself I already checked this.

if cid.Defined() {
hash = cid.Bytes()
}
}
var name *string
if link.d.Name.Maybe == schema.Maybe_Value {
tmp := link.d.Name.Value.x
if link.FieldName().Exists() {
tmp := link.FieldName().Must().String()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd write a review about how all this causes unfortunate allocations, but... it's unavoidable in interacting with protobufs, as I understand it. Ah well.

(Fun fact if we ever come back to optimize this though, without just tearing deep through and doing the parse protobuf ourselves completely: I think we could bump these tmp things into a struct and make sure the whole thing escapes to heap at once, and it'll shave a constant factor off.)

name = &tmp
}
var tsize *uint64
if link.d.Tsize.Maybe == schema.Maybe_Value {
tmp := uint64(link.d.Tsize.Value.x)
if link.FieldTsize().Exists() {
tmp := uint64(link.FieldTsize().Must().Int())
tsize = &tmp
}
pbn.Links = append(pbn.Links, &merkledag_pb.PBLink{
Hash: hash,
Name: name,
Tsize: tsize})
}
pbn.Data = nd.d.Data.x
pbn.Data = nd.FieldData().Bytes()
data, err := pbn.Marshal()
if err != nil {
return fmt.Errorf("marshal failed. %v", err)
Expand All @@ -104,25 +103,27 @@ func (nd PBNode) EncodeDagProto(w io.Writer) error {
}

// DecodeDagRaw is a fast path decoding to protobuf
// from RawNode__NodeBuilders
func (nb _RawNode__NodeBuilder) DecodeDagRaw(r io.Reader) error {
byteBuf, ok := r.(byteAccesor)
if ok {
nb.nd.x = byteBuf.Bytes()
return nil
}
data, err := ioutil.ReadAll(r)
if err != nil {
return fmt.Errorf("io error during unmarshal. %v", err)
}
nb.nd.x = data
return nil
// from RawNode__Builders
func (nb *_RawNode__Builder) DecodeDagRaw(r io.Reader) error {
return fluent.Recover(func() {
fnb := fluent.WrapAssembler(nb)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will definitely cause at least one extra alloc, and idk if it's paying for itself in simplicity added in this case. But I'm not complaining enough to demand a change, either. Probably not a big issue unless we profile it and find out that it is.

byteBuf, ok := r.(byteAccesor)
if ok {
fnb.AssignBytes(byteBuf.Bytes())
return
}
data, err := ioutil.ReadAll(r)
if err != nil {
panic(fluent.Error{Err: fmt.Errorf("io error during unmarshal. %v", err)})
}
fnb.AssignBytes(data)
})
}

// EncodeDagRaw is a fast path encoding to protobuf
// for RawNode types
func (nd RawNode) EncodeDagRaw(w io.Writer) error {
_, err := w.Write(nd.x)
_, err := w.Write(nd.Bytes())
if err != nil {
return fmt.Errorf(" error during marshal. %v", err)
}
Expand Down
14 changes: 0 additions & 14 deletions common_gen.go

This file was deleted.

2 changes: 1 addition & 1 deletion common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func randomBytes(n int64) []byte {
}

func makeRawNode(randBytes []byte) (ipld.Node, error) {
raw_nb := dagpb.Style.Raw.NewBuilder()
raw_nb := dagpb.Type.RawNode.NewBuilder()
err := raw_nb.AssignBytes(randBytes)
if err != nil {
return nil, err
Expand Down
93 changes: 26 additions & 67 deletions gen/main.go
Original file line number Diff line number Diff line change
@@ -1,83 +1,42 @@
package main

import (
"io/ioutil"
"os"
"regexp"
"os/exec"

"github.com/ipld/go-ipld-prime/schema"
gengo "github.com/ipld/go-ipld-prime/schema/gen/go"
)

func main() {
openOrPanic := func(filename string) *os.File {
y, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
if err != nil {
panic(err)
}
return y
}

tString := schema.SpawnString("String")
tInt := schema.SpawnInt("Int")
tLink := schema.SpawnLink("Link")
tBytes := schema.SpawnBytes("Bytes")

tPBLink := schema.SpawnStruct("PBLink",
[]schema.StructField{
schema.SpawnStructField("Hash", tLink, true, false),
schema.SpawnStructField("Name", tString, true, false),
schema.SpawnStructField("Tsize", tInt, true, false),
},
schema.StructRepresentation_Map{},
)

tPBLinks := schema.SpawnList("PBLinks", tPBLink, false)

tPBNode := schema.SpawnStruct("PBNode",
[]schema.StructField{
schema.SpawnStructField("Links", tPBLinks, false, false),
schema.SpawnStructField("Data", tBytes, false, false),
},
schema.StructRepresentation_Map{},
)

tRaw := schema.SpawnBytes("RawNode")

ts := schema.TypeSystem{}
ts.Init()
adjCfg := &gengo.AdjunctCfg{}

pkgName := "dagpb"

f := openOrPanic("common_gen.go")
gengo.EmitInternalEnums(pkgName, f)
ts.Accumulate(schema.SpawnString("String"))
ts.Accumulate(schema.SpawnInt("Int"))
ts.Accumulate(schema.SpawnLink("Link"))
ts.Accumulate(schema.SpawnBytes("Bytes"))

f = openOrPanic("pb_node_gen.go")
gengo.EmitFileHeader(pkgName, f)
gengo.EmitEntireType(gengo.NewStringReprStringGenerator(pkgName, tString, adjCfg), f)
gengo.EmitEntireType(gengo.NewIntReprIntGenerator(pkgName, tInt, adjCfg), f)
gengo.EmitEntireType(gengo.NewBytesReprBytesGenerator(pkgName, tBytes, adjCfg), f)
gengo.EmitEntireType(gengo.NewLinkReprLinkGenerator(pkgName, tLink, adjCfg), f)
gengo.EmitEntireType(gengo.NewStructReprMapGenerator(pkgName, tPBLink, adjCfg), f)
gengo.EmitEntireType(gengo.NewListReprListGenerator(pkgName, tPBLinks, adjCfg), f)
gengo.EmitEntireType(gengo.NewStructReprMapGenerator(pkgName, tPBNode, adjCfg), f)

if err := f.Close(); err != nil {
panic(err)
}
read, err := ioutil.ReadFile("pb_node_gen.go")
if err != nil {
panic(err)
}

re := regexp.MustCompile("ipld\\.ErrInvalidKey\\{[^}]*\\}")
newContents := re.ReplaceAll(read, []byte("err"))

err = ioutil.WriteFile("pb_node_gen.go", []byte(newContents), 0)
if err != nil {
panic(err)
}

f = openOrPanic("raw_node_gen.go")
gengo.EmitFileHeader("dagpb", f)
gengo.EmitEntireType(gengo.NewBytesReprBytesGenerator(pkgName, tRaw, adjCfg), f)
ts.Accumulate(schema.SpawnStruct("PBLink",
[]schema.StructField{
schema.SpawnStructField("Hash", "Link", true, false),
schema.SpawnStructField("Name", "String", true, false),
schema.SpawnStructField("Tsize", "Int", true, false),
},
schema.SpawnStructRepresentationMap(nil),
))
ts.Accumulate(schema.SpawnList("PBLinks", "PBLink", false))
ts.Accumulate(schema.SpawnStruct("PBNode",
[]schema.StructField{
schema.SpawnStructField("Links", "PBLinks", false, false),
schema.SpawnStructField("Data", "Bytes", false, false),
},
schema.SpawnStructRepresentationMap(nil),
))
ts.Accumulate(schema.SpawnBytes("RawNode"))
gengo.Generate(".", pkgName, ts, adjCfg)
exec.Command("go", "fmt").Run()
}
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ require (
github.com/ipfs/go-ipld-format v0.2.0
github.com/ipfs/go-merkledag v0.3.1
github.com/ipfs/go-unixfs v0.2.4
github.com/ipld/go-ipld-prime v0.5.0
github.com/ipld/go-ipld-prime v0.5.1-0.20200828233916-988837377a7f
github.com/jbenet/go-random v0.0.0-20190219211222-123a90aedc0c
github.com/multiformats/go-multihash v0.0.13
github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/warpfork/go-wish v0.0.0-20200122115046-b9ea61034e4a
)
12 changes: 8 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,14 @@ github.com/ipfs/go-unixfs v0.2.4 h1:6NwppOXefWIyysZ4LR/qUBPvXd5//8J3jiMdvpbw6Lo=
github.com/ipfs/go-unixfs v0.2.4/go.mod h1:SUdisfUjNoSDzzhGVxvCL9QO/nKdwXdr+gbMUdqcbYw=
github.com/ipfs/go-verifcid v0.0.1 h1:m2HI7zIuR5TFyQ1b79Da5N9dnnCP1vcu2QqawmWlK2E=
github.com/ipfs/go-verifcid v0.0.1/go.mod h1:5Hrva5KBeIog4A+UpqlaIU+DEstipcJYQQZc0g37pY0=
github.com/ipld/go-ipld-prime v0.0.2-0.20200428162820-8b59dc292b8e h1:ZISbJlM0urTANR9KRfRaqlBmyOj5uUtxs2r4Up9IXsA=
github.com/ipld/go-ipld-prime v0.0.2-0.20200428162820-8b59dc292b8e/go.mod h1:uVIwe/u0H4VdKv3kaN1ck7uCb6yD9cFLS9/ELyXbsw8=
github.com/ipld/go-ipld-prime v0.4.0 h1:ySDtWeWl+TDMokXlwGANSMeD5TN618cZp9NnxqZ452M=
github.com/ipld/go-ipld-prime v0.4.0/go.mod h1:uVIwe/u0H4VdKv3kaN1ck7uCb6yD9cFLS9/ELyXbsw8=
github.com/ipld/go-ipld-prime v0.5.0 h1:kr3nB6/JcFpc3Yj7vveXYuiVyZJzWUkJyLMjQbnoswE=
github.com/ipld/go-ipld-prime v0.5.0/go.mod h1:uVIwe/u0H4VdKv3kaN1ck7uCb6yD9cFLS9/ELyXbsw8=
github.com/ipld/go-ipld-prime v0.5.1-0.20200708164621-32d5243a3682 h1:BBlAChiaj88IN00/ZimqlTlMCDqgTlmsnKgYn8IcdLg=
github.com/ipld/go-ipld-prime v0.5.1-0.20200708164621-32d5243a3682/go.mod h1:uVIwe/u0H4VdKv3kaN1ck7uCb6yD9cFLS9/ELyXbsw8=
github.com/ipld/go-ipld-prime v0.5.1-0.20200727194823-8eda117b2d38 h1:4iP/8krnm/p2MjSvBKlu1vc/gZ2hYE0J2j5LECVTkWg=
github.com/ipld/go-ipld-prime v0.5.1-0.20200727194823-8eda117b2d38/go.mod h1:uVIwe/u0H4VdKv3kaN1ck7uCb6yD9cFLS9/ELyXbsw8=
github.com/ipld/go-ipld-prime v0.5.1-0.20200828233916-988837377a7f h1:XpOuNQ5GbXxUcSukbQcW9jkE7REpaFGJU2/T00fo9kA=
github.com/ipld/go-ipld-prime v0.5.1-0.20200828233916-988837377a7f/go.mod h1:0xEgdD6MKbZ1vF0GC+YcR/C4SQCAlRuOjIJ2i0HxqzM=
github.com/jackpal/gateway v1.0.5 h1:qzXWUJfuMdlLMtt0a3Dgt+xkWQiA5itDEITVJtuSwMc=
github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA=
github.com/jackpal/go-nat-pmp v1.0.1 h1:i0LektDkO1QlrTm/cSuP+PyBCDnYvjPLGl4LdWEMiaA=
Expand Down Expand Up @@ -338,6 +340,8 @@ github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUr
github.com/smartystreets/goconvey v0.0.0-20190222223459-a17d461953aa/go.mod h1:2RVY1rIf+2J2o/IM9+vPq9RzmHDSseB7FoXiSNIUsoU=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spacemonkeygo/openssl v0.0.0-20181017203307-c2dcc5cca94a h1:/eS3yfGjQKG+9kayBkj0ip1BGhq6zJ3eaVksphxAaek=
github.com/spacemonkeygo/openssl v0.0.0-20181017203307-c2dcc5cca94a/go.mod h1:7AyxJNCJ7SBZ1MfVQCWD6Uqo2oubI2Eq2y2eqf+A5r0=
github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 h1:RC6RW7j+1+HkWaX/Yh71Ee5ZHaHYt7ZP4sQgUrm6cDU=
Expand Down
30 changes: 30 additions & 0 deletions minima.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions multicodec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func TestRoundTripRaw(t *testing.T) {
})
t.Run("decoding", func(t *testing.T) {
buf := bytes.NewBuffer(randBytes)
nb := dagpb.Style.Raw.NewBuilder()
nb := dagpb.Type.RawNode.NewBuilder()
err := dagpb.RawDecoder(nb, buf)
Wish(t, err, ShouldEqual, nil)
rawNode2 := nb.Build()
Expand All @@ -47,15 +47,15 @@ func TestRoundTripProtbuf(t *testing.T) {

data := nd3.RawData()
ibuf := bytes.NewBuffer(data)
nb := dagpb.Style.Protobuf.NewBuilder()
nb := dagpb.Type.PBNode.NewBuilder()
err := dagpb.PBDecoder(nb, ibuf)
Wish(t, err, ShouldEqual, nil)
pbNode := nb.Build()
t.Run("encode/decode equivalency", func(t *testing.T) {
var buf bytes.Buffer
err := dagpb.PBEncoder(pbNode, &buf)
Wish(t, err, ShouldEqual, nil)
nb = dagpb.Style.Protobuf.NewBuilder()
nb = dagpb.Type.PBNode.NewBuilder()
err = dagpb.PBDecoder(nb, &buf)
pbNode2 := nb.Build()
Wish(t, err, ShouldEqual, nil)
Expand Down
Loading