aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/zclconf/go-cty/cty/json/type_implied.go
blob: 1a973066e4af54513c2f57bf98a15467e9a53f16 (plain) (blame)
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
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
package json

import (
	"bytes"
	"encoding/json"
	"fmt"

	"github.com/zclconf/go-cty/cty"
)

// ImpliedType returns the cty Type implied by the structure of the given
// JSON-compliant buffer. This function implements the default type mapping
// behavior used when decoding arbitrary JSON without explicit cty Type
// information.
//
// The rules are as follows:
//
// JSON strings, numbers and bools map to their equivalent primitive type in
// cty.
//
// JSON objects map to cty object types, with the attributes defined by the
// object keys and the types of their values.
//
// JSON arrays map to cty tuple types, with the elements defined by the
// types of the array members.
//
// Any nulls are typed as DynamicPseudoType, so callers of this function
// must be prepared to deal with this. Callers that do not wish to deal with
// dynamic typing should not use this function and should instead describe
// their required types explicitly with a cty.Type instance when decoding.
//
// Any JSON syntax errors will be returned as an error, and the type will
// be the invalid value cty.NilType.
func ImpliedType(buf []byte) (cty.Type, error) {
	r := bytes.NewReader(buf)
	dec := json.NewDecoder(r)
	dec.UseNumber()

	ty, err := impliedType(dec)
	if err != nil {
		return cty.NilType, err
	}

	if dec.More() {
		return cty.NilType, fmt.Errorf("extraneous data after JSON object")
	}

	return ty, nil
}

func impliedType(dec *json.Decoder) (cty.Type, error) {
	tok, err := dec.Token()
	if err != nil {
		return cty.NilType, err
	}

	return impliedTypeForTok(tok, dec)
}

func impliedTypeForTok(tok json.Token, dec *json.Decoder) (cty.Type, error) {
	if tok == nil {
		return cty.DynamicPseudoType, nil
	}

	switch ttok := tok.(type) {
	case bool:
		return cty.Bool, nil

	case json.Number:
		return cty.Number, nil

	case string:
		return cty.String, nil

	case json.Delim:

		switch rune(ttok) {
		case '{':
			return impliedObjectType(dec)
		case '[':
			return impliedTupleType(dec)
		default:
			return cty.NilType, fmt.Errorf("unexpected token %q", ttok)
		}

	default:
		return cty.NilType, fmt.Errorf("unsupported JSON token %#v", tok)
	}
}

func impliedObjectType(dec *json.Decoder) (cty.Type, error) {
	// By the time we get in here, we've already consumed the { delimiter
	// and so our next token should be the first object key.

	var atys map[string]cty.Type

	for {
		// Read the object key first
		tok, err := dec.Token()
		if err != nil {
			return cty.NilType, err
		}

		if ttok, ok := tok.(json.Delim); ok {
			if rune(ttok) != '}' {
				return cty.NilType, fmt.Errorf("unexpected delimiter %q", ttok)
			}
			break
		}

		key, ok := tok.(string)
		if !ok {
			return cty.NilType, fmt.Errorf("expected string but found %T", tok)
		}

		// Now read the value
		tok, err = dec.Token()
		if err != nil {
			return cty.NilType, err
		}

		aty, err := impliedTypeForTok(tok, dec)
		if err != nil {
			return cty.NilType, err
		}

		if atys == nil {
			atys = make(map[string]cty.Type)
		}
		atys[key] = aty
	}

	if len(atys) == 0 {
		return cty.EmptyObject, nil
	}

	return cty.Object(atys), nil
}

func impliedTupleType(dec *json.Decoder) (cty.Type, error) {
	// By the time we get in here, we've already consumed the { delimiter
	// and so our next token should be the first value.

	var etys []cty.Type

	for {
		tok, err := dec.Token()
		if err != nil {
			return cty.NilType, err
		}

		if ttok, ok := tok.(json.Delim); ok {
			if rune(ttok) != ']' {
				return cty.NilType, fmt.Errorf("unexpected delimiter %q", ttok)
			}
			break
		}

		ety, err := impliedTypeForTok(tok, dec)
		if err != nil {
			return cty.NilType, err
		}
		etys = append(etys, ety)
	}

	if len(etys) == 0 {
		return cty.EmptyTuple, nil
	}

	return cty.Tuple(etys), nil
}