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  }