unmarshal.go 4.47 KB
Newer Older
Eric Myhre's avatar
Eric Myhre committed
1 2 3 4 5 6 7 8 9
package codec

import (
	"fmt"
	"math"

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

tavit ohanian's avatar
tavit ohanian committed
10
	ld "gitlab.dms3.io/ld/go-ld-prime"
Eric Myhre's avatar
Eric Myhre committed
11 12
)

tavit ohanian's avatar
tavit ohanian committed
13
// wishlist: if we could reconstruct the ld.Path of an error while
Eric Myhre's avatar
Eric Myhre committed
14 15 16 17
//  *unwinding* from that error... that'd be nice.
//   (trying to build it proactively would waste tons of allocs on the happy path.)
//  we can do this; it just requires well-typed errors and a bunch of work.

tavit ohanian's avatar
tavit ohanian committed
18
// Tests for all this are in the ld.Node impl tests!
Eric Myhre's avatar
Eric Myhre committed
19 20 21 22 23 24 25 26 27 28 29
//  They're effectively doing double duty: testing the builders, too.
//   (Is that sensible?  Should it be refactored?  Not sure; maybe!)

// Unmarshal provides a very general tokens-to-node unmarshalling feature.
// It can handle either cbor or json by being combined with a refmt TokenSink.
//
// The unmarshalled data is fed to the given NodeAssembler, which accumulates it;
// at the end, any error is returned from the Unmarshal method,
// and the user can pick up the finished Node from wherever their assembler has it.
// Typical usage might look like the following:
//
30
//		nb := basicnode.Prototype__Any{}.NewBuilder()
Eric Myhre's avatar
Eric Myhre committed
31 32 33 34 35 36 37 38
//		err := codec.Unmarshal(nb, json.Decoder(reader))
//		n := nb.Build()
//
// It is valid for all the data model types except links, which are only
// supported if the nodes are typed and provide additional information
// to clarify how links should be decoded through their type info.
// (The dag-cbor and dag-json formats can be used if links are of CID
// implementation and need to be decoded in a schemafree way.)
tavit ohanian's avatar
tavit ohanian committed
39
func Unmarshal(na ld.NodeAssembler, tokSrc shared.TokenSource) error {
Eric Myhre's avatar
Eric Myhre committed
40 41 42 43 44 45
	var tk tok.Token
	done, err := tokSrc.Step(&tk)
	if err != nil {
		return err
	}
	if done && !tk.Type.IsValue() {
46
		return fmt.Errorf("unexpected eof")
Eric Myhre's avatar
Eric Myhre committed
47 48 49 50 51 52
	}
	return unmarshal(na, tokSrc, &tk)
}

// starts with the first token already primed.  Necessary to get recursion
//  to flow right without a peek+unpeek system.
tavit ohanian's avatar
tavit ohanian committed
53
func unmarshal(na ld.NodeAssembler, tokSrc shared.TokenSource, tk *tok.Token) error {
54
	// FUTURE: check for schema.TypedNodeBuilder that's going to parse a Link (they can slurp any token kind they want).
Eric Myhre's avatar
Eric Myhre committed
55 56 57 58 59 60 61 62
	switch tk.Type {
	case tok.TMapOpen:
		expectLen := tk.Length
		allocLen := tk.Length
		if tk.Length == -1 {
			expectLen = math.MaxInt32
			allocLen = 0
		}
63
		ma, err := na.BeginMap(int64(allocLen))
Eric Myhre's avatar
Eric Myhre committed
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
		if err != nil {
			return err
		}
		observedLen := 0
		for {
			_, err := tokSrc.Step(tk)
			if err != nil {
				return err
			}
			switch tk.Type {
			case tok.TMapClose:
				if expectLen != math.MaxInt32 && observedLen != expectLen {
					return fmt.Errorf("unexpected mapClose before declared length")
				}
				return ma.Finish()
			case tok.TString:
				// continue
			default:
				return fmt.Errorf("unexpected %s token while expecting map key", tk.Type)
			}
			observedLen++
			if observedLen > expectLen {
				return fmt.Errorf("unexpected continuation of map elements beyond declared length")
			}
88
			mva, err := ma.AssembleEntry(tk.Str)
Eric Myhre's avatar
Eric Myhre committed
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
			if err != nil { // return in error if the key was rejected
				return err
			}
			err = Unmarshal(mva, tokSrc)
			if err != nil { // return in error if some part of the recursion errored
				return err
			}
		}
	case tok.TMapClose:
		return fmt.Errorf("unexpected mapClose token")
	case tok.TArrOpen:
		expectLen := tk.Length
		allocLen := tk.Length
		if tk.Length == -1 {
			expectLen = math.MaxInt32
			allocLen = 0
		}
106
		la, err := na.BeginList(int64(allocLen))
Eric Myhre's avatar
Eric Myhre committed
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 136 137 138 139 140 141 142 143
		if err != nil {
			return err
		}
		observedLen := 0
		for {
			_, err := tokSrc.Step(tk)
			if err != nil {
				return err
			}
			switch tk.Type {
			case tok.TArrClose:
				if expectLen != math.MaxInt32 && observedLen != expectLen {
					return fmt.Errorf("unexpected arrClose before declared length")
				}
				return la.Finish()
			default:
				observedLen++
				if observedLen > expectLen {
					return fmt.Errorf("unexpected continuation of array elements beyond declared length")
				}
				err := unmarshal(la.AssembleValue(), tokSrc, tk)
				if err != nil { // return in error if some part of the recursion errored
					return err
				}
			}
		}
	case tok.TArrClose:
		return fmt.Errorf("unexpected arrClose token")
	case tok.TNull:
		return na.AssignNull()
	case tok.TString:
		return na.AssignString(tk.Str)
	case tok.TBytes:
		return na.AssignBytes(tk.Bytes)
	case tok.TBool:
		return na.AssignBool(tk.Bool)
	case tok.TInt:
144
		return na.AssignInt(tk.Int)
Eric Myhre's avatar
Eric Myhre committed
145
	case tok.TUint:
146
		return na.AssignInt(int64(tk.Uint)) // FIXME overflow check
Eric Myhre's avatar
Eric Myhre committed
147 148 149 150 151 152
	case tok.TFloat64:
		return na.AssignFloat(tk.Float64)
	default:
		panic("unreachable")
	}
}