234 lines
5.4 KiB
Go
234 lines
5.4 KiB
Go
package tftypes
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// Type is an interface representing a Terraform type. It is only meant to be
|
|
// implemented by the tftypes package. Types define the shape and
|
|
// characteristics of data coming from or being sent to Terraform.
|
|
type Type interface {
|
|
// AttributePathStepper requires each Type to implement the
|
|
// ApplyTerraform5AttributePathStep method, so Type is compatible with
|
|
// WalkAttributePath. The method should return the Type found at that
|
|
// AttributePath within the Type or ErrInvalidStep.
|
|
AttributePathStepper
|
|
|
|
// Is is used to determine what type a Type implementation is. It is
|
|
// the recommended method for determining whether two types are
|
|
// equivalent or not.
|
|
|
|
// Is performs shallow type equality checks, in that the root type is
|
|
// compared, but underlying attribute/element types are not.
|
|
Is(Type) bool
|
|
|
|
// Equal performs deep type equality checks, including attribute/element
|
|
// types and whether attributes are optional or not.
|
|
Equal(Type) bool
|
|
|
|
// UsableAs performs type conformance checks. This primarily checks if the
|
|
// target implements DynamicPsuedoType in a compatible manner.
|
|
UsableAs(Type) bool
|
|
|
|
// String returns a string representation of the Type's name.
|
|
String() string
|
|
|
|
// MarshalJSON returns a JSON representation of the Type's signature.
|
|
// It is modeled based on Terraform's requirements for type signature
|
|
// JSON representations, and may change over time to match Terraform's
|
|
// formatting.
|
|
//
|
|
// Deprecated: this is not meant to be called by third-party code.
|
|
MarshalJSON() ([]byte, error)
|
|
|
|
// private is meant to keep this interface from being implemented by
|
|
// types from other packages.
|
|
private()
|
|
|
|
// supportedGoTypes returns a list of string representations of the Go
|
|
// types that the Type supports for its values.
|
|
supportedGoTypes() []string
|
|
}
|
|
|
|
// TypeFromElements returns the common type that the passed elements all have
|
|
// in common. An error will be returned if the passed elements are not of the
|
|
// same type.
|
|
func TypeFromElements(elements []Value) (Type, error) {
|
|
var typ Type
|
|
for _, el := range elements {
|
|
if typ == nil {
|
|
typ = el.Type()
|
|
continue
|
|
}
|
|
if !typ.Equal(el.Type()) {
|
|
return nil, errors.New("elements do not all have the same types")
|
|
}
|
|
}
|
|
if typ == nil {
|
|
return DynamicPseudoType, nil
|
|
}
|
|
return typ, nil
|
|
}
|
|
|
|
type jsonType struct {
|
|
t Type
|
|
}
|
|
|
|
// ParseJSONType returns a Type from its JSON representation. The JSON
|
|
// representation should come from Terraform or from MarshalJSON as the format
|
|
// is not part of this package's API guarantees.
|
|
//
|
|
// Deprecated: this is not meant to be called by third-party code.
|
|
func ParseJSONType(buf []byte) (Type, error) {
|
|
var t jsonType
|
|
err := json.Unmarshal(buf, &t)
|
|
return t.t, err
|
|
}
|
|
|
|
func (t *jsonType) UnmarshalJSON(buf []byte) error {
|
|
r := bytes.NewReader(buf)
|
|
dec := json.NewDecoder(r)
|
|
|
|
tok, err := dec.Token()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch v := tok.(type) {
|
|
case string:
|
|
switch v {
|
|
case "bool":
|
|
t.t = Bool
|
|
case "number":
|
|
t.t = Number
|
|
case "string":
|
|
t.t = String
|
|
case "dynamic":
|
|
t.t = DynamicPseudoType
|
|
default:
|
|
return fmt.Errorf("invalid primitive type name %q", v)
|
|
}
|
|
|
|
if dec.More() {
|
|
return fmt.Errorf("extraneous data after type description")
|
|
}
|
|
return nil
|
|
case json.Delim:
|
|
if rune(v) != '[' {
|
|
return fmt.Errorf("invalid complex type description")
|
|
}
|
|
|
|
tok, err = dec.Token()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
kind, ok := tok.(string)
|
|
if !ok {
|
|
return fmt.Errorf("invalid complex type kind name")
|
|
}
|
|
|
|
switch kind {
|
|
case "list":
|
|
var ety jsonType
|
|
err = dec.Decode(&ety)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
t.t = List{
|
|
ElementType: ety.t,
|
|
}
|
|
case "map":
|
|
var ety jsonType
|
|
err = dec.Decode(&ety)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
t.t = Map{
|
|
ElementType: ety.t,
|
|
}
|
|
case "set":
|
|
var ety jsonType
|
|
err = dec.Decode(&ety)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
t.t = Set{
|
|
ElementType: ety.t,
|
|
}
|
|
case "object":
|
|
var atys map[string]jsonType
|
|
err = dec.Decode(&atys)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
types := make(map[string]Type, len(atys))
|
|
for k, v := range atys {
|
|
types[k] = v.t
|
|
}
|
|
o := Object{
|
|
AttributeTypes: types,
|
|
OptionalAttributes: map[string]struct{}{},
|
|
}
|
|
if dec.More() {
|
|
var optionals []string
|
|
err = dec.Decode(&optionals)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, attr := range optionals {
|
|
o.OptionalAttributes[attr] = struct{}{}
|
|
}
|
|
}
|
|
t.t = o
|
|
case "tuple":
|
|
var etys []jsonType
|
|
err = dec.Decode(&etys)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
types := make([]Type, 0, len(etys))
|
|
for _, ty := range etys {
|
|
types = append(types, ty.t)
|
|
}
|
|
t.t = Tuple{
|
|
ElementTypes: types,
|
|
}
|
|
default:
|
|
return fmt.Errorf("invalid complex type kind name")
|
|
}
|
|
|
|
tok, err = dec.Token()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if delim, ok := tok.(json.Delim); !ok || rune(delim) != ']' || dec.More() {
|
|
return fmt.Errorf("unexpected extra data in type description")
|
|
}
|
|
|
|
return nil
|
|
|
|
default:
|
|
return fmt.Errorf("invalid type description")
|
|
}
|
|
}
|
|
|
|
func formattedSupportedGoTypes(t Type) string {
|
|
sgt := t.supportedGoTypes()
|
|
switch len(sgt) {
|
|
case 0:
|
|
return "no supported Go types"
|
|
case 1:
|
|
return sgt[0]
|
|
case 2:
|
|
return sgt[0] + " or " + sgt[1]
|
|
default:
|
|
sgt[len(sgt)-1] = "or " + sgt[len(sgt)-1]
|
|
return strings.Join(sgt, ", ")
|
|
}
|
|
}
|