github.com/ipld/go-ipld-prime@v0.21.0/codec/dagjson/marshal.go (about) 1 package dagjson 2 3 import ( 4 "encoding/base64" 5 "fmt" 6 "io" 7 "sort" 8 9 "github.com/polydawn/refmt/json" 10 "github.com/polydawn/refmt/shared" 11 "github.com/polydawn/refmt/tok" 12 13 "github.com/ipld/go-ipld-prime/codec" 14 "github.com/ipld/go-ipld-prime/datamodel" 15 cidlink "github.com/ipld/go-ipld-prime/linking/cid" 16 ) 17 18 // This should be identical to the general feature in the parent package, 19 // except for the `case datamodel.Kind_Link` block, 20 // which is dag-json's special sauce for schemafree links. 21 22 // EncodeOptions can be used to customize the behavior of an encoding function. 23 // The Encode method on this struct fits the codec.Encoder function interface. 24 type EncodeOptions struct { 25 // If true, will encode nodes with a Link kind using the DAG-JSON 26 // `{"/":"cid string"}` form. 27 EncodeLinks bool 28 29 // If true, will encode nodes with a Bytes kind using the DAG-JSON 30 // `{"/":{"bytes":"base64 bytes..."}}` form. 31 EncodeBytes bool 32 33 // Control the sorting of map keys, using one of the `codec.MapSortMode_*` constants. 34 MapSortMode codec.MapSortMode 35 } 36 37 // Encode walks the given datamodel.Node and serializes it to the given io.Writer. 38 // Encode fits the codec.Encoder function interface. 39 // 40 // The behavior of the encoder can be customized by setting fields in the EncodeOptions struct before calling this method. 41 func (cfg EncodeOptions) Encode(n datamodel.Node, w io.Writer) error { 42 return Marshal(n, json.NewEncoder(w, json.EncodeOptions{}), cfg) 43 } 44 45 // Future work: we would like to remove the Marshal function, 46 // and in particular, stop seeing types from refmt (like shared.TokenSink) be visible. 47 // Right now, some kinds of configuration (e.g. for whitespace and prettyprint) are only available through interacting with the refmt types; 48 // we should improve our API so that this can be done with only our own types in this package. 49 50 // Marshal is a deprecated function. 51 // Please consider switching to EncodeOptions.Encode instead. 52 func Marshal(n datamodel.Node, sink shared.TokenSink, options EncodeOptions) error { 53 var tk tok.Token 54 switch n.Kind() { 55 case datamodel.Kind_Invalid: 56 return fmt.Errorf("cannot traverse a node that is absent") 57 case datamodel.Kind_Null: 58 tk.Type = tok.TNull 59 _, err := sink.Step(&tk) 60 return err 61 case datamodel.Kind_Map: 62 // Emit start of map. 63 tk.Type = tok.TMapOpen 64 expectedLength := int(n.Length()) 65 tk.Length = expectedLength // TODO: overflow check 66 if _, err := sink.Step(&tk); err != nil { 67 return err 68 } 69 if options.MapSortMode != codec.MapSortMode_None { 70 // Collect map entries, then sort by key 71 type entry struct { 72 key string 73 value datamodel.Node 74 } 75 entries := []entry{} 76 for itr := n.MapIterator(); !itr.Done(); { 77 k, v, err := itr.Next() 78 if err != nil { 79 return err 80 } 81 keyStr, err := k.AsString() 82 if err != nil { 83 return err 84 } 85 entries = append(entries, entry{keyStr, v}) 86 } 87 if len(entries) != expectedLength { 88 return fmt.Errorf("map Length() does not match number of MapIterator() entries") 89 } 90 // Apply the desired sort function. 91 switch options.MapSortMode { 92 case codec.MapSortMode_Lexical: 93 sort.Slice(entries, func(i, j int) bool { 94 return entries[i].key < entries[j].key 95 }) 96 case codec.MapSortMode_RFC7049: 97 sort.Slice(entries, func(i, j int) bool { 98 // RFC7049 style sort as per DAG-CBOR spec 99 li, lj := len(entries[i].key), len(entries[j].key) 100 if li == lj { 101 return entries[i].key < entries[j].key 102 } 103 return li < lj 104 }) 105 } 106 // Emit map contents (and recurse). 107 var entryCount int 108 for _, e := range entries { 109 tk.Type = tok.TString 110 tk.Str = e.key 111 entryCount++ 112 if _, err := sink.Step(&tk); err != nil { 113 return err 114 } 115 if err := Marshal(e.value, sink, options); err != nil { 116 return err 117 } 118 } 119 if entryCount != expectedLength { 120 return fmt.Errorf("map Length() does not match number of MapIterator() entries") 121 } 122 } else { 123 // Don't sort map, emit map contents (and recurse). 124 for itr := n.MapIterator(); !itr.Done(); { 125 k, v, err := itr.Next() 126 if err != nil { 127 return err 128 } 129 tk.Type = tok.TString 130 tk.Str, err = k.AsString() 131 if err != nil { 132 return err 133 } 134 if _, err := sink.Step(&tk); err != nil { 135 return err 136 } 137 if err := Marshal(v, sink, options); err != nil { 138 return err 139 } 140 } 141 } 142 // Emit map close. 143 tk.Type = tok.TMapClose 144 _, err := sink.Step(&tk) 145 return err 146 case datamodel.Kind_List: 147 // Emit start of list. 148 tk.Type = tok.TArrOpen 149 l := n.Length() 150 tk.Length = int(l) // TODO: overflow check 151 if _, err := sink.Step(&tk); err != nil { 152 return err 153 } 154 // Emit list contents (and recurse). 155 for i := int64(0); i < l; i++ { 156 v, err := n.LookupByIndex(i) 157 if err != nil { 158 return err 159 } 160 if err := Marshal(v, sink, options); err != nil { 161 return err 162 } 163 } 164 // Emit list close. 165 tk.Type = tok.TArrClose 166 _, err := sink.Step(&tk) 167 return err 168 case datamodel.Kind_Bool: 169 v, err := n.AsBool() 170 if err != nil { 171 return err 172 } 173 tk.Type = tok.TBool 174 tk.Bool = v 175 _, err = sink.Step(&tk) 176 return err 177 case datamodel.Kind_Int: 178 v, err := n.AsInt() 179 if err != nil { 180 return err 181 } 182 tk.Type = tok.TInt 183 tk.Int = int64(v) 184 _, err = sink.Step(&tk) 185 return err 186 case datamodel.Kind_Float: 187 v, err := n.AsFloat() 188 if err != nil { 189 return err 190 } 191 tk.Type = tok.TFloat64 192 tk.Float64 = v 193 _, err = sink.Step(&tk) 194 return err 195 case datamodel.Kind_String: 196 v, err := n.AsString() 197 if err != nil { 198 return err 199 } 200 tk.Type = tok.TString 201 tk.Str = v 202 _, err = sink.Step(&tk) 203 return err 204 case datamodel.Kind_Bytes: 205 if !options.EncodeBytes { 206 return fmt.Errorf("cannot marshal IPLD bytes to this codec") 207 } 208 v, err := n.AsBytes() 209 if err != nil { 210 return err 211 } 212 // Precisely seven tokens to emit: 213 tk.Type = tok.TMapOpen 214 tk.Length = 1 215 if _, err = sink.Step(&tk); err != nil { 216 return err 217 } 218 tk.Type = tok.TString 219 tk.Str = "/" 220 if _, err = sink.Step(&tk); err != nil { 221 return err 222 } 223 tk.Type = tok.TMapOpen 224 tk.Length = 1 225 if _, err = sink.Step(&tk); err != nil { 226 return err 227 } 228 tk.Type = tok.TString 229 tk.Str = "bytes" 230 if _, err = sink.Step(&tk); err != nil { 231 return err 232 } 233 tk.Str = base64.RawStdEncoding.EncodeToString(v) 234 if _, err = sink.Step(&tk); err != nil { 235 return err 236 } 237 tk.Type = tok.TMapClose 238 if _, err = sink.Step(&tk); err != nil { 239 return err 240 } 241 tk.Type = tok.TMapClose 242 if _, err = sink.Step(&tk); err != nil { 243 return err 244 } 245 return nil 246 case datamodel.Kind_Link: 247 if !options.EncodeLinks { 248 return fmt.Errorf("cannot marshal IPLD links to this codec") 249 } 250 v, err := n.AsLink() 251 if err != nil { 252 return err 253 } 254 switch lnk := v.(type) { 255 case cidlink.Link: 256 if !lnk.Cid.Defined() { 257 return fmt.Errorf("encoding undefined CIDs are not supported by this codec") 258 } 259 // Precisely four tokens to emit: 260 tk.Type = tok.TMapOpen 261 tk.Length = 1 262 if _, err = sink.Step(&tk); err != nil { 263 return err 264 } 265 tk.Type = tok.TString 266 tk.Str = "/" 267 if _, err = sink.Step(&tk); err != nil { 268 return err 269 } 270 tk.Str = lnk.Cid.String() 271 if _, err = sink.Step(&tk); err != nil { 272 return err 273 } 274 tk.Type = tok.TMapClose 275 if _, err = sink.Step(&tk); err != nil { 276 return err 277 } 278 return nil 279 default: 280 return fmt.Errorf("schemafree link emission only supported by this codec for CID type links; got type %T", lnk) 281 } 282 default: 283 panic("unreachable") 284 } 285 }