marshal.go 4.49 KB
Newer Older
Eric Myhre's avatar
Eric Myhre committed
1 2 3
package dagjson

import (
4
	"encoding/base64"
Eric Myhre's avatar
Eric Myhre committed
5
	"fmt"
6
	"sort"
Eric Myhre's avatar
Eric Myhre committed
7 8 9 10 11 12 13 14 15

	"github.com/polydawn/refmt/shared"
	"github.com/polydawn/refmt/tok"

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

// This should be identical to the general feature in the parent package,
16
// except for the `case ipld.Kind_Link` block,
Eric Myhre's avatar
Eric Myhre committed
17 18
// which is dag-json's special sauce for schemafree links.

Will Scott's avatar
Will Scott committed
19
func Marshal(n ipld.Node, sink shared.TokenSink, allowLinks bool) error {
Eric Myhre's avatar
Eric Myhre committed
20
	var tk tok.Token
21 22
	switch n.Kind() {
	case ipld.Kind_Invalid:
23
		return fmt.Errorf("cannot traverse a node that is absent")
24
	case ipld.Kind_Null:
Eric Myhre's avatar
Eric Myhre committed
25 26 27
		tk.Type = tok.TNull
		_, err := sink.Step(&tk)
		return err
28
	case ipld.Kind_Map:
Eric Myhre's avatar
Eric Myhre committed
29 30
		// Emit start of map.
		tk.Type = tok.TMapOpen
31
		tk.Length = int(n.Length()) // TODO: overflow check
Eric Myhre's avatar
Eric Myhre committed
32 33 34
		if _, err := sink.Step(&tk); err != nil {
			return err
		}
35 36 37 38 39 40
		// Collect map entries, then sort by key
		type entry struct {
			key   string
			value ipld.Node
		}
		entries := []entry{}
41 42
		for itr := n.MapIterator(); !itr.Done(); {
			k, v, err := itr.Next()
Eric Myhre's avatar
Eric Myhre committed
43 44 45
			if err != nil {
				return err
			}
46
			keyStr, err := k.AsString()
47
			if err != nil {
Eric Myhre's avatar
Eric Myhre committed
48 49
				return err
			}
50 51 52 53 54 55 56
			entries = append(entries, entry{keyStr, v})
		}
		sort.Slice(entries, func(i, j int) bool { return entries[i].key < entries[j].key })
		// Emit map contents (and recurse).
		for _, e := range entries {
			tk.Type = tok.TString
			tk.Str = e.key
57
			if _, err := sink.Step(&tk); err != nil {
Eric Myhre's avatar
Eric Myhre committed
58 59
				return err
			}
60
			if err := Marshal(e.value, sink, allowLinks); err != nil {
Eric Myhre's avatar
Eric Myhre committed
61 62 63 64 65 66 67
				return err
			}
		}
		// Emit map close.
		tk.Type = tok.TMapClose
		_, err := sink.Step(&tk)
		return err
68
	case ipld.Kind_List:
Eric Myhre's avatar
Eric Myhre committed
69 70 71
		// Emit start of list.
		tk.Type = tok.TArrOpen
		l := n.Length()
72
		tk.Length = int(l) // TODO: overflow check
Eric Myhre's avatar
Eric Myhre committed
73 74 75 76
		if _, err := sink.Step(&tk); err != nil {
			return err
		}
		// Emit list contents (and recurse).
77
		for i := int64(0); i < l; i++ {
78
			v, err := n.LookupByIndex(i)
Eric Myhre's avatar
Eric Myhre committed
79 80 81
			if err != nil {
				return err
			}
Will Scott's avatar
Will Scott committed
82
			if err := Marshal(v, sink, allowLinks); err != nil {
Eric Myhre's avatar
Eric Myhre committed
83 84 85 86 87 88 89
				return err
			}
		}
		// Emit list close.
		tk.Type = tok.TArrClose
		_, err := sink.Step(&tk)
		return err
90
	case ipld.Kind_Bool:
Eric Myhre's avatar
Eric Myhre committed
91 92 93 94 95 96 97 98
		v, err := n.AsBool()
		if err != nil {
			return err
		}
		tk.Type = tok.TBool
		tk.Bool = v
		_, err = sink.Step(&tk)
		return err
99
	case ipld.Kind_Int:
Eric Myhre's avatar
Eric Myhre committed
100 101 102 103 104 105 106 107
		v, err := n.AsInt()
		if err != nil {
			return err
		}
		tk.Type = tok.TInt
		tk.Int = int64(v)
		_, err = sink.Step(&tk)
		return err
108
	case ipld.Kind_Float:
Eric Myhre's avatar
Eric Myhre committed
109 110 111 112 113 114 115 116
		v, err := n.AsFloat()
		if err != nil {
			return err
		}
		tk.Type = tok.TFloat64
		tk.Float64 = v
		_, err = sink.Step(&tk)
		return err
117
	case ipld.Kind_String:
Eric Myhre's avatar
Eric Myhre committed
118 119 120 121 122 123 124 125
		v, err := n.AsString()
		if err != nil {
			return err
		}
		tk.Type = tok.TString
		tk.Str = v
		_, err = sink.Step(&tk)
		return err
126
	case ipld.Kind_Bytes:
Eric Myhre's avatar
Eric Myhre committed
127 128 129 130
		v, err := n.AsBytes()
		if err != nil {
			return err
		}
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
		if allowLinks {
			// Precisely seven tokens to emit:
			tk.Type = tok.TMapOpen
			tk.Length = 1
			if _, err = sink.Step(&tk); err != nil {
				return err
			}
			tk.Type = tok.TString
			tk.Str = "/"
			if _, err = sink.Step(&tk); err != nil {
				return err
			}
			tk.Type = tok.TMapOpen
			tk.Length = 1
			if _, err = sink.Step(&tk); err != nil {
				return err
			}
			tk.Type = tok.TString
			tk.Str = "bytes"
			if _, err = sink.Step(&tk); err != nil {
				return err
			}
			tk.Str = base64.StdEncoding.EncodeToString(v)
			if _, err = sink.Step(&tk); err != nil {
				return err
			}
			tk.Type = tok.TMapClose
			if _, err = sink.Step(&tk); err != nil {
				return err
			}
			tk.Type = tok.TMapClose
			if _, err = sink.Step(&tk); err != nil {
				return err
			}
			return nil
		} else {
			tk.Type = tok.TBytes
			tk.Bytes = v
			_, err = sink.Step(&tk)
			return err
		}
172
	case ipld.Kind_Link:
Will Scott's avatar
Will Scott committed
173 174 175
		if !allowLinks {
			return fmt.Errorf("cannot Marshal ipld links to JSON")
		}
Eric Myhre's avatar
Eric Myhre committed
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
		v, err := n.AsLink()
		if err != nil {
			return err
		}
		switch lnk := v.(type) {
		case cidlink.Link:
			// Precisely four tokens to emit:
			tk.Type = tok.TMapOpen
			tk.Length = 1
			if _, err = sink.Step(&tk); err != nil {
				return err
			}
			tk.Type = tok.TString
			tk.Str = "/"
			if _, err = sink.Step(&tk); err != nil {
				return err
			}
			tk.Str = lnk.Cid.String()
			if _, err = sink.Step(&tk); err != nil {
				return err
			}
			tk.Type = tok.TMapClose
			if _, err = sink.Step(&tk); err != nil {
				return err
			}
			return nil
		default:
203
			return fmt.Errorf("schemafree link emission only supported by this codec for CID type links")
Eric Myhre's avatar
Eric Myhre committed
204 205 206 207 208
		}
	default:
		panic("unreachable")
	}
}