terraform-provider-gitea/vendor/github.com/hashicorp/terraform-plugin-go/tftypes/value_json.go
Tobias Trabelsi e1266ebf64
Some checks reported errors
continuous-integration/drone/pr Build encountered an error
continuous-integration/drone/push Build encountered an error
updated GHA
Update to v2 SDK
updated dependencies
2022-08-06 16:21:18 +02:00

484 lines
13 KiB
Go

package tftypes
import (
"bytes"
"encoding/json"
"math/big"
"strings"
)
// ValueFromJSON returns a Value from the JSON-encoded bytes, using the
// provided Type to determine what shape the Value should be.
// DynamicPseudoTypes will be transparently parsed into the types they
// represent.
//
// Deprecated: this function is exported for internal use in
// terraform-plugin-go. Third parties should not use it, and its behavior is
// not covered under the API compatibility guarantees. Don't use this.
func ValueFromJSON(data []byte, typ Type) (Value, error) {
return jsonUnmarshal(data, typ, NewAttributePath())
}
func jsonByteDecoder(buf []byte) *json.Decoder {
r := bytes.NewReader(buf)
dec := json.NewDecoder(r)
dec.UseNumber()
return dec
}
func jsonUnmarshal(buf []byte, typ Type, p *AttributePath) (Value, error) {
dec := jsonByteDecoder(buf)
tok, err := dec.Token()
if err != nil {
return Value{}, p.NewErrorf("error reading token: %w", err)
}
if tok == nil {
return NewValue(typ, nil), nil
}
switch {
case typ.Is(String):
return jsonUnmarshalString(buf, typ, p)
case typ.Is(Number):
return jsonUnmarshalNumber(buf, typ, p)
case typ.Is(Bool):
return jsonUnmarshalBool(buf, typ, p)
case typ.Is(DynamicPseudoType):
return jsonUnmarshalDynamicPseudoType(buf, typ, p)
case typ.Is(List{}):
return jsonUnmarshalList(buf, typ.(List).ElementType, p)
case typ.Is(Set{}):
return jsonUnmarshalSet(buf, typ.(Set).ElementType, p)
case typ.Is(Map{}):
return jsonUnmarshalMap(buf, typ.(Map).ElementType, p)
case typ.Is(Tuple{}):
return jsonUnmarshalTuple(buf, typ.(Tuple).ElementTypes, p)
case typ.Is(Object{}):
return jsonUnmarshalObject(buf, typ.(Object).AttributeTypes, p)
}
return Value{}, p.NewErrorf("unknown type %s", typ)
}
func jsonUnmarshalString(buf []byte, _ Type, p *AttributePath) (Value, error) {
dec := jsonByteDecoder(buf)
tok, err := dec.Token()
if err != nil {
return Value{}, p.NewErrorf("error reading token: %w", err)
}
switch v := tok.(type) {
case string:
return NewValue(String, v), nil
case json.Number:
return NewValue(String, string(v)), nil
case bool:
if v {
return NewValue(String, "true"), nil
}
return NewValue(String, "false"), nil
}
return Value{}, p.NewErrorf("unsupported type %T sent as %s", tok, String)
}
func jsonUnmarshalNumber(buf []byte, typ Type, p *AttributePath) (Value, error) {
dec := jsonByteDecoder(buf)
tok, err := dec.Token()
if err != nil {
return Value{}, p.NewErrorf("error reading token: %w", err)
}
switch numTok := tok.(type) {
case json.Number:
f, _, err := big.ParseFloat(string(numTok), 10, 512, big.ToNearestEven)
if err != nil {
return Value{}, p.NewErrorf("error parsing number: %w", err)
}
return NewValue(typ, f), nil
case string:
f, _, err := big.ParseFloat(numTok, 10, 512, big.ToNearestEven)
if err != nil {
return Value{}, p.NewErrorf("error parsing number: %w", err)
}
return NewValue(typ, f), nil
}
return Value{}, p.NewErrorf("unsupported type %T sent as %s", tok, Number)
}
func jsonUnmarshalBool(buf []byte, _ Type, p *AttributePath) (Value, error) {
dec := jsonByteDecoder(buf)
tok, err := dec.Token()
if err != nil {
return Value{}, p.NewErrorf("error reading token: %w", err)
}
switch v := tok.(type) {
case bool:
return NewValue(Bool, v), nil
case string:
switch v {
case "true", "1":
return NewValue(Bool, true), nil
case "false", "0":
return NewValue(Bool, false), nil
}
switch strings.ToLower(v) {
case "true":
return Value{}, p.NewErrorf("to convert from string, use lowercase \"true\"")
case "false":
return Value{}, p.NewErrorf("to convert from string, use lowercase \"false\"")
}
case json.Number:
switch v {
case "1":
return NewValue(Bool, true), nil
case "0":
return NewValue(Bool, false), nil
}
}
return Value{}, p.NewErrorf("unsupported type %T sent as %s", tok, Bool)
}
func jsonUnmarshalDynamicPseudoType(buf []byte, _ Type, p *AttributePath) (Value, error) {
dec := jsonByteDecoder(buf)
tok, err := dec.Token()
if err != nil {
return Value{}, p.NewErrorf("error reading token: %w", err)
}
if tok != json.Delim('{') {
return Value{}, p.NewErrorf("invalid JSON, expected %q, got %q", json.Delim('{'), tok)
}
var t Type
var valBody []byte
for dec.More() {
tok, err = dec.Token()
if err != nil {
return Value{}, p.NewErrorf("error reading token: %w", err)
}
key, ok := tok.(string)
if !ok {
return Value{}, p.NewErrorf("expected key to be a string, got %T", tok)
}
var rawVal json.RawMessage
err = dec.Decode(&rawVal)
if err != nil {
return Value{}, p.NewErrorf("error decoding value: %w", err)
}
switch key {
case "type":
t, err = ParseJSONType(rawVal) //nolint:staticcheck
if err != nil {
return Value{}, p.NewErrorf("error decoding type information: %w", err)
}
case "value":
valBody = rawVal
default:
return Value{}, p.NewErrorf("invalid key %q in dynamically-typed value", key)
}
}
tok, err = dec.Token()
if err != nil {
return Value{}, p.NewErrorf("error reading token: %w", err)
}
if tok != json.Delim('}') {
return Value{}, p.NewErrorf("invalid JSON, expected %q, got %q", json.Delim('}'), tok)
}
if t == nil {
return Value{}, p.NewErrorf("missing type in dynamically-typed value")
}
if valBody == nil {
return Value{}, p.NewErrorf("missing value in dynamically-typed value")
}
return jsonUnmarshal(valBody, t, p)
}
func jsonUnmarshalList(buf []byte, elementType Type, p *AttributePath) (Value, error) {
dec := jsonByteDecoder(buf)
tok, err := dec.Token()
if err != nil {
return Value{}, p.NewErrorf("error reading token: %w", err)
}
if tok != json.Delim('[') {
return Value{}, p.NewErrorf("invalid JSON, expected %q, got %q", json.Delim('['), tok)
}
// we want to have a value for this always, even if there are no
// elements, because no elements is *technically* different than empty,
// and we want to preserve that distinction
//
// var vals []Value
// would evaluate as nil if the list is empty
//
// while generally in Go it's undesirable to treat empty and nil slices
// separately, in this case we're surfacing a non-Go-in-origin
// distinction, so we'll allow it.
vals := []Value{}
var idx int
for dec.More() {
innerPath := p.WithElementKeyInt(idx)
// update the index
idx++
var rawVal json.RawMessage
err = dec.Decode(&rawVal)
if err != nil {
return Value{}, innerPath.NewErrorf("error decoding value: %w", err)
}
val, err := jsonUnmarshal(rawVal, elementType, innerPath)
if err != nil {
return Value{}, err
}
vals = append(vals, val)
}
tok, err = dec.Token()
if err != nil {
return Value{}, p.NewErrorf("error reading token: %w", err)
}
if tok != json.Delim(']') {
return Value{}, p.NewErrorf("invalid JSON, expected %q, got %q", json.Delim(']'), tok)
}
elTyp := elementType
if elTyp.Is(DynamicPseudoType) {
elTyp, err = TypeFromElements(vals)
if err != nil {
return Value{}, p.NewErrorf("invalid elements for list: %w", err)
}
}
return NewValue(List{
ElementType: elTyp,
}, vals), nil
}
func jsonUnmarshalSet(buf []byte, elementType Type, p *AttributePath) (Value, error) {
dec := jsonByteDecoder(buf)
tok, err := dec.Token()
if err != nil {
return Value{}, p.NewErrorf("error reading token: %w", err)
}
if tok != json.Delim('[') {
return Value{}, p.NewErrorf("invalid JSON, expected %q, got %q", json.Delim('['), tok)
}
// we want to have a value for this always, even if there are no
// elements, because no elements is *technically* different than empty,
// and we want to preserve that distinction
//
// var vals []Value
// would evaluate as nil if the set is empty
//
// while generally in Go it's undesirable to treat empty and nil slices
// separately, in this case we're surfacing a non-Go-in-origin
// distinction, so we'll allow it.
vals := []Value{}
for dec.More() {
innerPath := p.WithElementKeyValue(NewValue(elementType, UnknownValue))
var rawVal json.RawMessage
err = dec.Decode(&rawVal)
if err != nil {
return Value{}, innerPath.NewErrorf("error decoding value: %w", err)
}
val, err := jsonUnmarshal(rawVal, elementType, innerPath)
if err != nil {
return Value{}, err
}
vals = append(vals, val)
}
tok, err = dec.Token()
if err != nil {
return Value{}, p.NewErrorf("error reading token: %w", err)
}
if tok != json.Delim(']') {
return Value{}, p.NewErrorf("invalid JSON, expected %q, got %q", json.Delim(']'), tok)
}
elTyp := elementType
if elTyp.Is(DynamicPseudoType) {
elTyp, err = TypeFromElements(vals)
if err != nil {
return Value{}, p.NewErrorf("invalid elements for list: %w", err)
}
}
return NewValue(Set{
ElementType: elTyp,
}, vals), nil
}
func jsonUnmarshalMap(buf []byte, attrType Type, p *AttributePath) (Value, error) {
dec := jsonByteDecoder(buf)
tok, err := dec.Token()
if err != nil {
return Value{}, p.NewErrorf("error reading token: %w", err)
}
if tok != json.Delim('{') {
return Value{}, p.NewErrorf("invalid JSON, expected %q, got %q", json.Delim('{'), tok)
}
vals := map[string]Value{}
for dec.More() {
innerPath := p.WithElementKeyValue(NewValue(attrType, UnknownValue))
tok, err := dec.Token()
if err != nil {
return Value{}, innerPath.NewErrorf("error reading token: %w", err)
}
key, ok := tok.(string)
if !ok {
return Value{}, innerPath.NewErrorf("expected map key to be a string, got %T", tok)
}
//fix the path value, we have an actual key now
innerPath = p.WithElementKeyString(key)
var rawVal json.RawMessage
err = dec.Decode(&rawVal)
if err != nil {
return Value{}, innerPath.NewErrorf("error decoding value: %w", err)
}
val, err := jsonUnmarshal(rawVal, attrType, innerPath)
if err != nil {
return Value{}, err
}
vals[key] = val
}
tok, err = dec.Token()
if err != nil {
return Value{}, p.NewErrorf("error reading token: %w", err)
}
if tok != json.Delim('}') {
return Value{}, p.NewErrorf("invalid JSON, expected %q, got %q", json.Delim('}'), tok)
}
return NewValue(Map{
ElementType: attrType,
}, vals), nil
}
func jsonUnmarshalTuple(buf []byte, elementTypes []Type, p *AttributePath) (Value, error) {
dec := jsonByteDecoder(buf)
tok, err := dec.Token()
if err != nil {
return Value{}, p.NewErrorf("error reading token: %w", err)
}
if tok != json.Delim('[') {
return Value{}, p.NewErrorf("invalid JSON, expected %q, got %q", json.Delim('['), tok)
}
// we want to have a value for this always, even if there are no
// elements, because no elements is *technically* different than empty,
// and we want to preserve that distinction
//
// var vals []Value
// would evaluate as nil if the tuple is empty
//
// while generally in Go it's undesirable to treat empty and nil slices
// separately, in this case we're surfacing a non-Go-in-origin
// distinction, so we'll allow it.
vals := []Value{}
var idx int
for dec.More() {
if idx >= len(elementTypes) {
return Value{}, p.NewErrorf("too many tuple elements (only have types for %d)", len(elementTypes))
}
innerPath := p.WithElementKeyInt(idx)
elementType := elementTypes[idx]
idx++
var rawVal json.RawMessage
err = dec.Decode(&rawVal)
if err != nil {
return Value{}, innerPath.NewErrorf("error decoding value: %w", err)
}
val, err := jsonUnmarshal(rawVal, elementType, innerPath)
if err != nil {
return Value{}, err
}
vals = append(vals, val)
}
tok, err = dec.Token()
if err != nil {
return Value{}, p.NewErrorf("error reading token: %w", err)
}
if tok != json.Delim(']') {
return Value{}, p.NewErrorf("invalid JSON, expected %q, got %q", json.Delim(']'), tok)
}
if len(vals) != len(elementTypes) {
return Value{}, p.NewErrorf("not enough tuple elements (only have %d, have types for %d)", len(vals), len(elementTypes))
}
return NewValue(Tuple{
ElementTypes: elementTypes,
}, vals), nil
}
func jsonUnmarshalObject(buf []byte, attrTypes map[string]Type, p *AttributePath) (Value, error) {
dec := jsonByteDecoder(buf)
tok, err := dec.Token()
if err != nil {
return Value{}, p.NewErrorf("error reading token: %w", err)
}
if tok != json.Delim('{') {
return Value{}, p.NewErrorf("invalid JSON, expected %q, got %q", json.Delim('{'), tok)
}
vals := map[string]Value{}
for dec.More() {
innerPath := p.WithElementKeyValue(NewValue(String, UnknownValue))
tok, err := dec.Token()
if err != nil {
return Value{}, innerPath.NewErrorf("error reading token: %w", err)
}
key, ok := tok.(string)
if !ok {
return Value{}, innerPath.NewErrorf("object attribute key was %T, not string", tok)
}
attrType, ok := attrTypes[key]
if !ok {
return Value{}, innerPath.NewErrorf("unsupported attribute %q", key)
}
innerPath = p.WithAttributeName(key)
var rawVal json.RawMessage
err = dec.Decode(&rawVal)
if err != nil {
return Value{}, innerPath.NewErrorf("error decoding value: %w", err)
}
val, err := jsonUnmarshal(rawVal, attrType, innerPath)
if err != nil {
return Value{}, err
}
vals[key] = val
}
tok, err = dec.Token()
if err != nil {
return Value{}, p.NewErrorf("error reading token: %w", err)
}
if tok != json.Delim('}') {
return Value{}, p.NewErrorf("invalid JSON, expected %q, got %q", json.Delim('}'), tok)
}
// make sure we have a value for every attribute
for k, typ := range attrTypes {
if _, ok := vals[k]; !ok {
vals[k] = NewValue(typ, nil)
}
}
return NewValue(Object{
AttributeTypes: attrTypes,
}, vals), nil
}