marshal.go 2.88 KB
Newer Older
Rod Vagg's avatar
Rod Vagg committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
package dagpb

import (
	"fmt"
	"io"

	ipld "github.com/ipld/go-ipld-prime"
	cidlink "github.com/ipld/go-ipld-prime/linking/cid"
	pb "github.com/rvagg/go-dagpb/pb"
)

func Marshal(inNode ipld.Node, out io.Writer) error {
	builder := Type.PBNode.NewBuilder()
	if err := builder.AssignNode(inNode); err != nil {
		return err
	}
	node := builder.Build()

	curField := pb.TypeLinks
	var linksIter ipld.ListIterator
	var link ipld.Node

	tokenSource := func() (pb.Token, error) {
		if curField == pb.TypeLinks {
			links, err := node.LookupByString("Links")
			if err != nil {
				return pb.Token{}, err
			}
			if links.Length() == 0 {
				curField = pb.TypeData
			} else {
				curField = pb.TypeHash
				linksIter = links.ListIterator()
				_, link, err = linksIter.Next()
				if err != nil {
					return pb.Token{}, err
				}
			}
		}

		if curField == pb.TypeData {
			curField = pb.TypeEnd

			d, err := node.LookupByString("Data")
			if err != nil {
				return pb.Token{}, err
			}
			if !d.IsAbsent() {
				b, err := d.AsBytes()
				if err != nil {
					return pb.Token{}, err
				}
				return pb.Token{Type: pb.TypeData, Bytes: b}, nil
			}
		}

		if curField == pb.TypeEnd {
			return pb.Token{Type: pb.TypeEnd}, nil
		}

		for {
			if curField == pb.TypeHash {
				curField = pb.TypeName

				d, err := link.LookupByString("Hash")
				if err != nil {
					return pb.Token{}, err
				}
				l, err := d.AsLink()
				if err != nil {
					return pb.Token{}, err
				}
				if cl, ok := l.(cidlink.Link); ok {
					return pb.Token{Type: pb.TypeHash, Cid: &cl.Cid}, nil
				}
				return pb.Token{}, fmt.Errorf("unexpected Link type [%v]", l)
			}

			if curField == pb.TypeName {
				curField = pb.TypeTSize

				nameNode, err := link.LookupByString("Name")
				if err != nil {
					return pb.Token{}, err
				}
				if !nameNode.IsAbsent() {
					name, err := nameNode.AsString()
					if err != nil {
						return pb.Token{}, err
					}
					return pb.Token{Type: pb.TypeName, Bytes: []byte(name)}, nil
				}
			}

			if curField == pb.TypeTSize {
				curField = pb.TypeLinkEnd

				tsizeNode, err := link.LookupByString("Tsize")
				if err != nil {
					return pb.Token{}, err
				}
				if !tsizeNode.IsAbsent() {
					tsize, err := tsizeNode.AsInt()
					if err != nil {
						return pb.Token{}, err
					}
					if tsize < 0 {
						return pb.Token{}, fmt.Errorf("Link has negative Tsize value [%v]", tsize)
					}
					return pb.Token{Type: pb.TypeTSize, Int: uint64(tsize)}, nil
				}
			}

			if curField == pb.TypeLinkEnd {
				if linksIter.Done() {
					curField = pb.TypeData
				} else {
					curField = pb.TypeHash
					var err error
					_, link, err = linksIter.Next()
					if err != nil {
						return pb.Token{}, err
					}
				}
				return pb.Token{Type: pb.TypeLinkEnd}, nil
			}

			if curField != pb.TypeHash {
				return pb.Token{}, fmt.Errorf("unexpected and invalid token state")
			}
		}
	}

	return pb.Marshal(out, tokenSource)
}