add vendor

This commit is contained in:
Malar Invention
2022-04-03 09:37:16 +05:30
parent f96ba5f172
commit 00ebcd295e
2339 changed files with 705854 additions and 0 deletions

128
vendor/github.com/zclconf/go-cty/cty/capsule.go generated vendored Normal file
View File

@ -0,0 +1,128 @@
package cty
import (
"fmt"
"reflect"
)
type capsuleType struct {
typeImplSigil
Name string
GoType reflect.Type
Ops *CapsuleOps
}
func (t *capsuleType) Equals(other Type) bool {
if otherP, ok := other.typeImpl.(*capsuleType); ok {
// capsule types compare by pointer identity
return otherP == t
}
return false
}
func (t *capsuleType) FriendlyName(mode friendlyTypeNameMode) string {
return t.Name
}
func (t *capsuleType) GoString() string {
impl := t.Ops.TypeGoString
if impl == nil {
// To get a useful representation of our native type requires some
// shenanigans.
victimVal := reflect.Zero(t.GoType)
if t.Ops == noCapsuleOps {
return fmt.Sprintf("cty.Capsule(%q, reflect.TypeOf(%#v))", t.Name, victimVal.Interface())
} else {
// Including the operations in the output will make this _very_ long,
// so in practice any capsule type with ops ought to provide a
// TypeGoString function to override this with something more
// reasonable.
return fmt.Sprintf("cty.CapsuleWithOps(%q, reflect.TypeOf(%#v), %#v)", t.Name, victimVal.Interface(), t.Ops)
}
}
return impl(t.GoType)
}
// Capsule creates a new Capsule type.
//
// A Capsule type is a special type that can be used to transport arbitrary
// Go native values of a given type through the cty type system. A language
// that uses cty as its type system might, for example, provide functions
// that return capsule-typed values and then other functions that operate
// on those values.
//
// From cty's perspective, Capsule types have a few interesting characteristics,
// described in the following paragraphs.
//
// Each capsule type has an associated Go native type that it is able to
// transport. Capsule types compare by identity, so each call to the
// Capsule function creates an entirely-distinct cty Type, even if two calls
// use the same native type.
//
// Each capsule-typed value contains a pointer to a value of the given native
// type. A capsule-typed value by default supports no operations except
// equality, and equality is implemented by pointer identity of the
// encapsulated pointer. A capsule type can optionally have its own
// implementations of certain operations if it is created with CapsuleWithOps
// instead of Capsule.
//
// The given name is used as the new type's "friendly name". This can be any
// string in principle, but will usually be a short, all-lowercase name aimed
// at users of the embedding language (i.e. not mention Go-specific details)
// and will ideally not create ambiguity with any predefined cty type.
//
// Capsule types are never introduced by any standard cty operation, so a
// calling application opts in to including them within its own type system
// by creating them and introducing them via its own functions. At that point,
// the application is responsible for dealing with any capsule-typed values
// that might be returned.
func Capsule(name string, nativeType reflect.Type) Type {
return Type{
&capsuleType{
Name: name,
GoType: nativeType,
Ops: noCapsuleOps,
},
}
}
// CapsuleWithOps is like Capsule except the caller may provide an object
// representing some overloaded operation implementations to associate with
// the given capsule type.
//
// All of the other caveats and restrictions for capsule types still apply, but
// overloaded operations can potentially help a capsule type participate better
// in cty operations.
func CapsuleWithOps(name string, nativeType reflect.Type, ops *CapsuleOps) Type {
// Copy the operations to make sure the caller can't modify them after
// we're constructed.
ourOps := *ops
ourOps.assertValid()
return Type{
&capsuleType{
Name: name,
GoType: nativeType,
Ops: &ourOps,
},
}
}
// IsCapsuleType returns true if this type is a capsule type, as created
// by cty.Capsule .
func (t Type) IsCapsuleType() bool {
_, ok := t.typeImpl.(*capsuleType)
return ok
}
// EncapsulatedType returns the encapsulated native type of a capsule type,
// or panics if the receiver is not a Capsule type.
//
// Is IsCapsuleType to determine if this method is safe to call.
func (t Type) EncapsulatedType() reflect.Type {
impl, ok := t.typeImpl.(*capsuleType)
if !ok {
panic("not a capsule type")
}
return impl.GoType
}

132
vendor/github.com/zclconf/go-cty/cty/capsule_ops.go generated vendored Normal file
View File

@ -0,0 +1,132 @@
package cty
import (
"reflect"
)
// CapsuleOps represents a set of overloaded operations for a capsule type.
//
// Each field is a reference to a function that can either be nil or can be
// set to an implementation of the corresponding operation. If an operation
// function is nil then it isn't supported for the given capsule type.
type CapsuleOps struct {
// GoString provides the GoString implementation for values of the
// corresponding type. Conventionally this should return a string
// representation of an expression that would produce an equivalent
// value.
GoString func(val interface{}) string
// TypeGoString provides the GoString implementation for the corresponding
// capsule type itself.
TypeGoString func(goTy reflect.Type) string
// Equals provides the implementation of the Equals operation. This is
// called only with known, non-null values of the corresponding type,
// but if the corresponding type is a compound type then it must be
// ready to detect and handle nested unknown or null values, usually
// by recursively calling Value.Equals on those nested values.
//
// The result value must always be of type cty.Bool, or the Equals
// operation will panic.
//
// If RawEquals is set without also setting Equals, the RawEquals
// implementation will be used as a fallback implementation. That fallback
// is appropriate only for leaf types that do not contain any nested
// cty.Value that would need to distinguish Equals vs. RawEquals for their
// own equality.
//
// If RawEquals is nil then Equals must also be nil, selecting the default
// pointer-identity comparison instead.
Equals func(a, b interface{}) Value
// RawEquals provides the implementation of the RawEquals operation.
// This is called only with known, non-null values of the corresponding
// type, but if the corresponding type is a compound type then it must be
// ready to detect and handle nested unknown or null values, usually
// by recursively calling Value.RawEquals on those nested values.
//
// If RawEquals is nil, values of the corresponding type are compared by
// pointer identity of the encapsulated value.
RawEquals func(a, b interface{}) bool
// ConversionFrom can provide conversions from the corresponding type to
// some other type when values of the corresponding type are used with
// the "convert" package. (The main cty package does not use this operation.)
//
// This function itself returns a function, allowing it to switch its
// behavior depending on the given source type. Return nil to indicate
// that no such conversion is available.
ConversionFrom func(src Type) func(interface{}, Path) (Value, error)
// ConversionTo can provide conversions to the corresponding type from
// some other type when values of the corresponding type are used with
// the "convert" package. (The main cty package does not use this operation.)
//
// This function itself returns a function, allowing it to switch its
// behavior depending on the given destination type. Return nil to indicate
// that no such conversion is available.
ConversionTo func(dst Type) func(Value, Path) (interface{}, error)
// ExtensionData is an extension point for applications that wish to
// create their own extension features using capsule types.
//
// The key argument is any value that can be compared with Go's ==
// operator, but should be of a named type in a package belonging to the
// application defining the key. An ExtensionData implementation must
// check to see if the given key is familar to it, and if so return a
// suitable value for the key.
//
// If the given key is unrecognized, the ExtensionData function must
// return a nil interface. (Importantly, not an interface containing a nil
// pointer of some other type.)
// The common implementation of ExtensionData is a single switch statement
// over "key" which has a default case returning nil.
//
// The meaning of any given key is entirely up to the application that
// defines it. Applications consuming ExtensionData from capsule types
// should do so defensively: if the result of ExtensionData is not valid,
// prefer to ignore it or gracefully produce an error rather than causing
// a panic.
ExtensionData func(key interface{}) interface{}
}
// noCapsuleOps is a pointer to a CapsuleOps with no functions set, which
// is used as the default operations value when a type is created using
// the Capsule function.
var noCapsuleOps = &CapsuleOps{}
func (ops *CapsuleOps) assertValid() {
if ops.RawEquals == nil && ops.Equals != nil {
panic("Equals cannot be set without RawEquals")
}
}
// CapsuleOps returns a pointer to the CapsuleOps value for a capsule type,
// or panics if the receiver is not a capsule type.
//
// The caller must not modify the CapsuleOps.
func (ty Type) CapsuleOps() *CapsuleOps {
if !ty.IsCapsuleType() {
panic("not a capsule-typed value")
}
return ty.typeImpl.(*capsuleType).Ops
}
// CapsuleExtensionData is a convenience interface to the ExtensionData
// function that can be optionally implemented for a capsule type. It will
// check to see if the underlying type implements ExtensionData and call it
// if so. If not, it will return nil to indicate that the given key is not
// supported.
//
// See the documentation for CapsuleOps.ExtensionData for more information
// on the purpose of and usage of this mechanism.
//
// If CapsuleExtensionData is called on a non-capsule type then it will panic.
func (ty Type) CapsuleExtensionData(key interface{}) interface{} {
ops := ty.CapsuleOps()
if ops.ExtensionData == nil {
return nil
}
return ops.ExtensionData(key)
}

34
vendor/github.com/zclconf/go-cty/cty/collection.go generated vendored Normal file
View File

@ -0,0 +1,34 @@
package cty
import (
"errors"
)
type collectionTypeImpl interface {
ElementType() Type
}
// IsCollectionType returns true if the given type supports the operations
// that are defined for all collection types.
func (t Type) IsCollectionType() bool {
_, ok := t.typeImpl.(collectionTypeImpl)
return ok
}
// ElementType returns the element type of the receiver if it is a collection
// type, or panics if it is not. Use IsCollectionType first to test whether
// this method will succeed.
func (t Type) ElementType() Type {
if ct, ok := t.typeImpl.(collectionTypeImpl); ok {
return ct.ElementType()
}
panic(errors.New("not a collection type"))
}
// ElementCallback is a callback type used for iterating over elements of
// collections and attributes of objects.
//
// The types of key and value depend on what type is being iterated over.
// Return true to stop iterating after the current element, or false to
// continue iterating.
type ElementCallback func(key Value, val Value) (stop bool)

View File

@ -0,0 +1,165 @@
package convert
import (
"github.com/zclconf/go-cty/cty"
)
// compareTypes implements a preference order for unification.
//
// The result of this method is not useful for anything other than unification
// preferences, since it assumes that the caller will verify that any suggested
// conversion is actually possible and it is thus able to to make certain
// optimistic assumptions.
func compareTypes(a cty.Type, b cty.Type) int {
// DynamicPseudoType always has lowest preference, because anything can
// convert to it (it acts as a placeholder for "any type") and we want
// to optimistically assume that any dynamics will converge on matching
// their neighbors.
if a == cty.DynamicPseudoType || b == cty.DynamicPseudoType {
if a != cty.DynamicPseudoType {
return -1
}
if b != cty.DynamicPseudoType {
return 1
}
return 0
}
if a.IsPrimitiveType() && b.IsPrimitiveType() {
// String is a supertype of all primitive types, because we can
// represent all primitive values as specially-formatted strings.
if a == cty.String || b == cty.String {
if a != cty.String {
return 1
}
if b != cty.String {
return -1
}
return 0
}
}
if a.IsListType() && b.IsListType() {
return compareTypes(a.ElementType(), b.ElementType())
}
if a.IsSetType() && b.IsSetType() {
return compareTypes(a.ElementType(), b.ElementType())
}
if a.IsMapType() && b.IsMapType() {
return compareTypes(a.ElementType(), b.ElementType())
}
// From this point on we may have swapped the two items in order to
// simplify our cases. Therefore any non-zero return after this point
// must be multiplied by "swap" to potentially invert the return value
// if needed.
swap := 1
switch {
case a.IsTupleType() && b.IsListType():
fallthrough
case a.IsObjectType() && b.IsMapType():
fallthrough
case a.IsSetType() && b.IsTupleType():
fallthrough
case a.IsSetType() && b.IsListType():
a, b = b, a
swap = -1
}
if b.IsSetType() && (a.IsTupleType() || a.IsListType()) {
// We'll just optimistically assume that the element types are
// unifyable/convertible, and let a second recursive pass
// figure out how to make that so.
return -1 * swap
}
if a.IsListType() && b.IsTupleType() {
// We'll just optimistically assume that the tuple's element types
// can be unified into something compatible with the list's element
// type.
return -1 * swap
}
if a.IsMapType() && b.IsObjectType() {
// We'll just optimistically assume that the object's attribute types
// can be unified into something compatible with the map's element
// type.
return -1 * swap
}
// For object and tuple types, comparing two types doesn't really tell
// the whole story because it may be possible to construct a new type C
// that is the supertype of both A and B by unifying each attribute/element
// separately. That possibility is handled by Unify as a follow-up if
// type sorting is insufficient to produce a valid result.
//
// Here we will take care of the simple possibilities where no new type
// is needed.
if a.IsObjectType() && b.IsObjectType() {
atysA := a.AttributeTypes()
atysB := b.AttributeTypes()
if len(atysA) != len(atysB) {
return 0
}
hasASuper := false
hasBSuper := false
for k := range atysA {
if _, has := atysB[k]; !has {
return 0
}
cmp := compareTypes(atysA[k], atysB[k])
if cmp < 0 {
hasASuper = true
} else if cmp > 0 {
hasBSuper = true
}
}
switch {
case hasASuper && hasBSuper:
return 0
case hasASuper:
return -1 * swap
case hasBSuper:
return 1 * swap
default:
return 0
}
}
if a.IsTupleType() && b.IsTupleType() {
etysA := a.TupleElementTypes()
etysB := b.TupleElementTypes()
if len(etysA) != len(etysB) {
return 0
}
hasASuper := false
hasBSuper := false
for i := range etysA {
cmp := compareTypes(etysA[i], etysB[i])
if cmp < 0 {
hasASuper = true
} else if cmp > 0 {
hasBSuper = true
}
}
switch {
case hasASuper && hasBSuper:
return 0
case hasASuper:
return -1 * swap
case hasBSuper:
return 1 * swap
default:
return 0
}
}
return 0
}

View File

@ -0,0 +1,201 @@
package convert
import (
"github.com/zclconf/go-cty/cty"
)
// conversion is an internal variant of Conversion that carries around
// a cty.Path to be used in error responses.
type conversion func(cty.Value, cty.Path) (cty.Value, error)
func getConversion(in cty.Type, out cty.Type, unsafe bool) conversion {
conv := getConversionKnown(in, out, unsafe)
if conv == nil {
return nil
}
// Wrap the conversion in some standard checks that we don't want to
// have to repeat in every conversion function.
var ret conversion
ret = func(in cty.Value, path cty.Path) (cty.Value, error) {
if in.IsMarked() {
// We must unmark during the conversion and then re-apply the
// same marks to the result.
in, inMarks := in.Unmark()
v, err := ret(in, path)
if v != cty.NilVal {
v = v.WithMarks(inMarks)
}
return v, err
}
if out == cty.DynamicPseudoType {
// Conversion to DynamicPseudoType always just passes through verbatim.
return in, nil
}
if isKnown, isNull := in.IsKnown(), in.IsNull(); !isKnown || isNull {
// Avoid constructing unknown or null values with types which
// include optional attributes. Known or non-null object values
// will be passed to a conversion function which drops the optional
// attributes from the type. Unknown and null pass through values
// must do the same to ensure that homogeneous collections have a
// single element type.
out = out.WithoutOptionalAttributesDeep()
if !isKnown {
return cty.UnknownVal(out), nil
}
if isNull {
// We'll pass through nulls, albeit type converted, and let
// the caller deal with whatever handling they want to do in
// case null values are considered valid in some applications.
return cty.NullVal(out), nil
}
}
return conv(in, path)
}
return ret
}
func getConversionKnown(in cty.Type, out cty.Type, unsafe bool) conversion {
switch {
case out == cty.DynamicPseudoType:
// Conversion *to* DynamicPseudoType means that the caller wishes
// to allow any type in this position, so we'll produce a do-nothing
// conversion that just passes through the value as-is.
return dynamicPassthrough
case unsafe && in == cty.DynamicPseudoType:
// Conversion *from* DynamicPseudoType means that we have a value
// whose type isn't yet known during type checking. For these we will
// assume that conversion will succeed and deal with any errors that
// result (which is why we can only do this when "unsafe" is set).
return dynamicFixup(out)
case in.IsPrimitiveType() && out.IsPrimitiveType():
conv := primitiveConversionsSafe[in][out]
if conv != nil {
return conv
}
if unsafe {
return primitiveConversionsUnsafe[in][out]
}
return nil
case out.IsObjectType() && in.IsObjectType():
return conversionObjectToObject(in, out, unsafe)
case out.IsTupleType() && in.IsTupleType():
return conversionTupleToTuple(in, out, unsafe)
case out.IsListType() && (in.IsListType() || in.IsSetType()):
inEty := in.ElementType()
outEty := out.ElementType()
if inEty.Equals(outEty) {
// This indicates that we're converting from list to set with
// the same element type, so we don't need an element converter.
return conversionCollectionToList(outEty, nil)
}
convEty := getConversion(inEty, outEty, unsafe)
if convEty == nil {
return nil
}
return conversionCollectionToList(outEty, convEty)
case out.IsSetType() && (in.IsListType() || in.IsSetType()):
if in.IsListType() && !unsafe {
// Conversion from list to map is unsafe because it will lose
// information: the ordering will not be preserved, and any
// duplicate elements will be conflated.
return nil
}
inEty := in.ElementType()
outEty := out.ElementType()
convEty := getConversion(inEty, outEty, unsafe)
if inEty.Equals(outEty) {
// This indicates that we're converting from set to list with
// the same element type, so we don't need an element converter.
return conversionCollectionToSet(outEty, nil)
}
if convEty == nil {
return nil
}
return conversionCollectionToSet(outEty, convEty)
case out.IsMapType() && in.IsMapType():
inEty := in.ElementType()
outEty := out.ElementType()
convEty := getConversion(inEty, outEty, unsafe)
if convEty == nil {
return nil
}
return conversionCollectionToMap(outEty, convEty)
case out.IsListType() && in.IsTupleType():
outEty := out.ElementType()
return conversionTupleToList(in, outEty, unsafe)
case out.IsSetType() && in.IsTupleType():
outEty := out.ElementType()
return conversionTupleToSet(in, outEty, unsafe)
case out.IsMapType() && in.IsObjectType():
outEty := out.ElementType()
return conversionObjectToMap(in, outEty, unsafe)
case out.IsObjectType() && in.IsMapType():
if !unsafe {
// Converting a map to an object is an "unsafe" conversion,
// because we don't know if all the map keys will correspond to
// object attributes.
return nil
}
return conversionMapToObject(in, out, unsafe)
case in.IsCapsuleType() || out.IsCapsuleType():
if !unsafe {
// Capsule types can only participate in "unsafe" conversions,
// because we don't know enough about their conversion behaviors
// to be sure that they will always be safe.
return nil
}
if in.Equals(out) {
// conversion to self is never allowed
return nil
}
if out.IsCapsuleType() {
if fn := out.CapsuleOps().ConversionTo; fn != nil {
return conversionToCapsule(in, out, fn)
}
}
if in.IsCapsuleType() {
if fn := in.CapsuleOps().ConversionFrom; fn != nil {
return conversionFromCapsule(in, out, fn)
}
}
// No conversion operation is available, then.
return nil
default:
return nil
}
}
// retConversion wraps a conversion (internal type) so it can be returned
// as a Conversion (public type).
func retConversion(conv conversion) Conversion {
if conv == nil {
return nil
}
return func(in cty.Value) (cty.Value, error) {
return conv(in, cty.Path(nil))
}
}

View File

@ -0,0 +1,31 @@
package convert
import (
"github.com/zclconf/go-cty/cty"
)
func conversionToCapsule(inTy, outTy cty.Type, fn func(inTy cty.Type) func(cty.Value, cty.Path) (interface{}, error)) conversion {
rawConv := fn(inTy)
if rawConv == nil {
return nil
}
return func(in cty.Value, path cty.Path) (cty.Value, error) {
rawV, err := rawConv(in, path)
if err != nil {
return cty.NilVal, err
}
return cty.CapsuleVal(outTy, rawV), nil
}
}
func conversionFromCapsule(inTy, outTy cty.Type, fn func(outTy cty.Type) func(interface{}, cty.Path) (cty.Value, error)) conversion {
rawConv := fn(outTy)
if rawConv == nil {
return nil
}
return func(in cty.Value, path cty.Path) (cty.Value, error) {
return rawConv(in.EncapsulatedValue(), path)
}
}

View File

@ -0,0 +1,568 @@
package convert
import (
"github.com/zclconf/go-cty/cty"
)
// conversionCollectionToList returns a conversion that will apply the given
// conversion to all of the elements of a collection (something that supports
// ForEachElement and LengthInt) and then returns the result as a list.
//
// "conv" can be nil if the elements are expected to already be of the
// correct type and just need to be re-wrapped into a list. (For example,
// if we're converting from a set into a list of the same element type.)
func conversionCollectionToList(ety cty.Type, conv conversion) conversion {
return func(val cty.Value, path cty.Path) (cty.Value, error) {
if !val.Length().IsKnown() {
// If the input collection has an unknown length (which is true
// for a set containing unknown values) then our result must be
// an unknown list, because we can't predict how many elements
// the resulting list should have.
return cty.UnknownVal(cty.List(val.Type().ElementType())), nil
}
elems := make([]cty.Value, 0, val.LengthInt())
i := int64(0)
elemPath := append(path.Copy(), nil)
it := val.ElementIterator()
for it.Next() {
_, val := it.Element()
var err error
elemPath[len(elemPath)-1] = cty.IndexStep{
Key: cty.NumberIntVal(i),
}
if conv != nil {
val, err = conv(val, elemPath)
if err != nil {
return cty.NilVal, err
}
}
elems = append(elems, val)
i++
}
if len(elems) == 0 {
// Prefer a concrete type over a dynamic type when returning an
// empty list
if ety == cty.DynamicPseudoType {
return cty.ListValEmpty(val.Type().ElementType()), nil
}
return cty.ListValEmpty(ety), nil
}
if !cty.CanListVal(elems) {
return cty.NilVal, path.NewErrorf("element types must all match for conversion to list")
}
return cty.ListVal(elems), nil
}
}
// conversionCollectionToSet returns a conversion that will apply the given
// conversion to all of the elements of a collection (something that supports
// ForEachElement and LengthInt) and then returns the result as a set.
//
// "conv" can be nil if the elements are expected to already be of the
// correct type and just need to be re-wrapped into a set. (For example,
// if we're converting from a list into a set of the same element type.)
func conversionCollectionToSet(ety cty.Type, conv conversion) conversion {
return func(val cty.Value, path cty.Path) (cty.Value, error) {
elems := make([]cty.Value, 0, val.LengthInt())
i := int64(0)
elemPath := append(path.Copy(), nil)
it := val.ElementIterator()
for it.Next() {
_, val := it.Element()
var err error
elemPath[len(elemPath)-1] = cty.IndexStep{
Key: cty.NumberIntVal(i),
}
if conv != nil {
val, err = conv(val, elemPath)
if err != nil {
return cty.NilVal, err
}
}
elems = append(elems, val)
i++
}
if len(elems) == 0 {
// Prefer a concrete type over a dynamic type when returning an
// empty set
if ety == cty.DynamicPseudoType {
return cty.SetValEmpty(val.Type().ElementType()), nil
}
return cty.SetValEmpty(ety), nil
}
if !cty.CanSetVal(elems) {
return cty.NilVal, path.NewErrorf("element types must all match for conversion to set")
}
return cty.SetVal(elems), nil
}
}
// conversionCollectionToMap returns a conversion that will apply the given
// conversion to all of the elements of a collection (something that supports
// ForEachElement and LengthInt) and then returns the result as a map.
//
// "conv" can be nil if the elements are expected to already be of the
// correct type and just need to be re-wrapped into a map.
func conversionCollectionToMap(ety cty.Type, conv conversion) conversion {
return func(val cty.Value, path cty.Path) (cty.Value, error) {
elems := make(map[string]cty.Value, 0)
elemPath := append(path.Copy(), nil)
it := val.ElementIterator()
for it.Next() {
key, val := it.Element()
var err error
elemPath[len(elemPath)-1] = cty.IndexStep{
Key: key,
}
keyStr, err := Convert(key, cty.String)
if err != nil {
// Should never happen, because keys can only be numbers or
// strings and both can convert to string.
return cty.DynamicVal, elemPath.NewErrorf("cannot convert key type %s to string for map", key.Type().FriendlyName())
}
if conv != nil {
val, err = conv(val, elemPath)
if err != nil {
return cty.NilVal, err
}
}
elems[keyStr.AsString()] = val
}
if len(elems) == 0 {
// Prefer a concrete type over a dynamic type when returning an
// empty map
if ety == cty.DynamicPseudoType {
return cty.MapValEmpty(val.Type().ElementType()), nil
}
return cty.MapValEmpty(ety), nil
}
if ety.IsCollectionType() || ety.IsObjectType() {
var err error
if elems, err = conversionUnifyCollectionElements(elems, path, false); err != nil {
return cty.NilVal, err
}
}
if !cty.CanMapVal(elems) {
return cty.NilVal, path.NewErrorf("element types must all match for conversion to map")
}
return cty.MapVal(elems), nil
}
}
// conversionTupleToSet returns a conversion that will take a value of the
// given tuple type and return a set of the given element type.
//
// Will panic if the given tupleType isn't actually a tuple type.
func conversionTupleToSet(tupleType cty.Type, setEty cty.Type, unsafe bool) conversion {
tupleEtys := tupleType.TupleElementTypes()
if len(tupleEtys) == 0 {
// Empty tuple short-circuit
return func(val cty.Value, path cty.Path) (cty.Value, error) {
return cty.SetValEmpty(setEty), nil
}
}
if setEty == cty.DynamicPseudoType {
// This is a special case where the caller wants us to find
// a suitable single type that all elements can convert to, if
// possible.
setEty, _ = unify(tupleEtys, unsafe)
if setEty == cty.NilType {
return nil
}
// If the set element type after unification is still the dynamic
// type, the only way this can result in a valid set is if all values
// are of dynamic type
if setEty == cty.DynamicPseudoType {
for _, tupleEty := range tupleEtys {
if !tupleEty.Equals(cty.DynamicPseudoType) {
return nil
}
}
}
}
elemConvs := make([]conversion, len(tupleEtys))
for i, tupleEty := range tupleEtys {
if tupleEty.Equals(setEty) {
// no conversion required
continue
}
elemConvs[i] = getConversion(tupleEty, setEty, unsafe)
if elemConvs[i] == nil {
// If any of our element conversions are impossible, then the our
// whole conversion is impossible.
return nil
}
}
// If we fall out here then a conversion is possible, using the
// element conversions in elemConvs
return func(val cty.Value, path cty.Path) (cty.Value, error) {
elems := make([]cty.Value, 0, len(elemConvs))
elemPath := append(path.Copy(), nil)
i := int64(0)
it := val.ElementIterator()
for it.Next() {
_, val := it.Element()
var err error
elemPath[len(elemPath)-1] = cty.IndexStep{
Key: cty.NumberIntVal(i),
}
conv := elemConvs[i]
if conv != nil {
val, err = conv(val, elemPath)
if err != nil {
return cty.NilVal, err
}
}
elems = append(elems, val)
i++
}
if !cty.CanSetVal(elems) {
return cty.NilVal, path.NewErrorf("element types must all match for conversion to set")
}
return cty.SetVal(elems), nil
}
}
// conversionTupleToList returns a conversion that will take a value of the
// given tuple type and return a list of the given element type.
//
// Will panic if the given tupleType isn't actually a tuple type.
func conversionTupleToList(tupleType cty.Type, listEty cty.Type, unsafe bool) conversion {
tupleEtys := tupleType.TupleElementTypes()
if len(tupleEtys) == 0 {
// Empty tuple short-circuit
return func(val cty.Value, path cty.Path) (cty.Value, error) {
return cty.ListValEmpty(listEty), nil
}
}
if listEty == cty.DynamicPseudoType {
// This is a special case where the caller wants us to find
// a suitable single type that all elements can convert to, if
// possible.
listEty, _ = unify(tupleEtys, unsafe)
if listEty == cty.NilType {
return nil
}
// If the list element type after unification is still the dynamic
// type, the only way this can result in a valid list is if all values
// are of dynamic type
if listEty == cty.DynamicPseudoType {
for _, tupleEty := range tupleEtys {
if !tupleEty.Equals(cty.DynamicPseudoType) {
return nil
}
}
}
}
elemConvs := make([]conversion, len(tupleEtys))
for i, tupleEty := range tupleEtys {
if tupleEty.Equals(listEty) {
// no conversion required
continue
}
elemConvs[i] = getConversion(tupleEty, listEty, unsafe)
if elemConvs[i] == nil {
// If any of our element conversions are impossible, then the our
// whole conversion is impossible.
return nil
}
}
// If we fall out here then a conversion is possible, using the
// element conversions in elemConvs
return func(val cty.Value, path cty.Path) (cty.Value, error) {
elems := make([]cty.Value, 0, len(elemConvs))
elemTys := make([]cty.Type, 0, len(elems))
elemPath := append(path.Copy(), nil)
i := int64(0)
it := val.ElementIterator()
for it.Next() {
_, val := it.Element()
var err error
elemPath[len(elemPath)-1] = cty.IndexStep{
Key: cty.NumberIntVal(i),
}
conv := elemConvs[i]
if conv != nil {
val, err = conv(val, elemPath)
if err != nil {
return cty.NilVal, err
}
}
elems = append(elems, val)
elemTys = append(elemTys, val.Type())
i++
}
elems, err := conversionUnifyListElements(elems, elemPath, unsafe)
if err != nil {
return cty.NilVal, err
}
if !cty.CanListVal(elems) {
return cty.NilVal, path.NewErrorf("element types must all match for conversion to list")
}
return cty.ListVal(elems), nil
}
}
// conversionObjectToMap returns a conversion that will take a value of the
// given object type and return a map of the given element type.
//
// Will panic if the given objectType isn't actually an object type.
func conversionObjectToMap(objectType cty.Type, mapEty cty.Type, unsafe bool) conversion {
objectAtys := objectType.AttributeTypes()
if len(objectAtys) == 0 {
// Empty object short-circuit
return func(val cty.Value, path cty.Path) (cty.Value, error) {
return cty.MapValEmpty(mapEty), nil
}
}
if mapEty == cty.DynamicPseudoType {
// This is a special case where the caller wants us to find
// a suitable single type that all elements can convert to, if
// possible.
objectAtysList := make([]cty.Type, 0, len(objectAtys))
for _, aty := range objectAtys {
objectAtysList = append(objectAtysList, aty)
}
mapEty, _ = unify(objectAtysList, unsafe)
if mapEty == cty.NilType {
return nil
}
}
elemConvs := make(map[string]conversion, len(objectAtys))
for name, objectAty := range objectAtys {
if objectAty.Equals(mapEty) {
// no conversion required
continue
}
elemConvs[name] = getConversion(objectAty, mapEty, unsafe)
if elemConvs[name] == nil {
// If any of our element conversions are impossible, then the our
// whole conversion is impossible.
return nil
}
}
// If we fall out here then a conversion is possible, using the
// element conversions in elemConvs
return func(val cty.Value, path cty.Path) (cty.Value, error) {
elems := make(map[string]cty.Value, len(elemConvs))
elemPath := append(path.Copy(), nil)
it := val.ElementIterator()
for it.Next() {
name, val := it.Element()
var err error
elemPath[len(elemPath)-1] = cty.IndexStep{
Key: name,
}
conv := elemConvs[name.AsString()]
if conv != nil {
val, err = conv(val, elemPath)
if err != nil {
return cty.NilVal, err
}
}
elems[name.AsString()] = val
}
if mapEty.IsCollectionType() || mapEty.IsObjectType() {
var err error
if elems, err = conversionUnifyCollectionElements(elems, path, unsafe); err != nil {
return cty.NilVal, err
}
}
if !cty.CanMapVal(elems) {
return cty.NilVal, path.NewErrorf("attribute types must all match for conversion to map")
}
return cty.MapVal(elems), nil
}
}
// conversionMapToObject returns a conversion that will take a value of the
// given map type and return an object of the given type. The object attribute
// types must all be compatible with the map element type.
//
// Will panic if the given mapType and objType are not maps and objects
// respectively.
func conversionMapToObject(mapType cty.Type, objType cty.Type, unsafe bool) conversion {
objectAtys := objType.AttributeTypes()
mapEty := mapType.ElementType()
elemConvs := make(map[string]conversion, len(objectAtys))
for name, objectAty := range objectAtys {
if objectAty.Equals(mapEty) {
// no conversion required
continue
}
elemConvs[name] = getConversion(mapEty, objectAty, unsafe)
if elemConvs[name] == nil {
// If any of our element conversions are impossible, then the our
// whole conversion is impossible.
return nil
}
}
// If we fall out here then a conversion is possible, using the
// element conversions in elemConvs
return func(val cty.Value, path cty.Path) (cty.Value, error) {
elems := make(map[string]cty.Value, len(elemConvs))
elemPath := append(path.Copy(), nil)
it := val.ElementIterator()
for it.Next() {
name, val := it.Element()
// if there is no corresponding attribute, we skip this key
if _, ok := objectAtys[name.AsString()]; !ok {
continue
}
var err error
elemPath[len(elemPath)-1] = cty.IndexStep{
Key: name,
}
conv := elemConvs[name.AsString()]
if conv != nil {
val, err = conv(val, elemPath)
if err != nil {
return cty.NilVal, err
}
}
elems[name.AsString()] = val
}
for name, aty := range objectAtys {
if _, exists := elems[name]; !exists {
if optional := objType.AttributeOptional(name); optional {
elems[name] = cty.NullVal(aty)
} else {
return cty.NilVal, path.NewErrorf("map has no element for required attribute %q", name)
}
}
}
return cty.ObjectVal(elems), nil
}
}
func conversionUnifyCollectionElements(elems map[string]cty.Value, path cty.Path, unsafe bool) (map[string]cty.Value, error) {
elemTypes := make([]cty.Type, 0, len(elems))
for _, elem := range elems {
elemTypes = append(elemTypes, elem.Type())
}
unifiedType, _ := unify(elemTypes, unsafe)
if unifiedType == cty.NilType {
return nil, path.NewErrorf("cannot find a common base type for all elements")
}
unifiedElems := make(map[string]cty.Value)
elemPath := append(path.Copy(), nil)
for name, elem := range elems {
if elem.Type().Equals(unifiedType) {
unifiedElems[name] = elem
continue
}
conv := getConversion(elem.Type(), unifiedType, unsafe)
if conv == nil {
}
elemPath[len(elemPath)-1] = cty.IndexStep{
Key: cty.StringVal(name),
}
val, err := conv(elem, elemPath)
if err != nil {
return nil, err
}
unifiedElems[name] = val
}
return unifiedElems, nil
}
func conversionUnifyListElements(elems []cty.Value, path cty.Path, unsafe bool) ([]cty.Value, error) {
elemTypes := make([]cty.Type, len(elems))
for i, elem := range elems {
elemTypes[i] = elem.Type()
}
unifiedType, _ := unify(elemTypes, unsafe)
if unifiedType == cty.NilType {
return nil, path.NewErrorf("cannot find a common base type for all elements")
}
ret := make([]cty.Value, len(elems))
elemPath := append(path.Copy(), nil)
for i, elem := range elems {
if elem.Type().Equals(unifiedType) {
ret[i] = elem
continue
}
conv := getConversion(elem.Type(), unifiedType, unsafe)
if conv == nil {
}
elemPath[len(elemPath)-1] = cty.IndexStep{
Key: cty.NumberIntVal(int64(i)),
}
val, err := conv(elem, elemPath)
if err != nil {
return nil, err
}
ret[i] = val
}
return ret, nil
}

View File

@ -0,0 +1,33 @@
package convert
import (
"github.com/zclconf/go-cty/cty"
)
// dynamicFixup deals with just-in-time conversions of values that were
// input-typed as cty.DynamicPseudoType during analysis, ensuring that
// we end up with the desired output type once the value is known, or
// failing with an error if that is not possible.
//
// This is in the spirit of the cty philosophy of optimistically assuming that
// DynamicPseudoType values will become the intended value eventually, and
// dealing with any inconsistencies during final evaluation.
func dynamicFixup(wantType cty.Type) conversion {
return func(in cty.Value, path cty.Path) (cty.Value, error) {
ret, err := Convert(in, wantType)
if err != nil {
// Re-wrap this error so that the returned path is relative
// to the caller's original value, rather than relative to our
// conversion value here.
return cty.NilVal, path.NewError(err)
}
return ret, nil
}
}
// dynamicPassthrough is an identity conversion that is used when the
// target type is DynamicPseudoType, indicating that the caller doesn't care
// which type is returned.
func dynamicPassthrough(in cty.Value, path cty.Path) (cty.Value, error) {
return in, nil
}

View File

@ -0,0 +1,95 @@
package convert
import (
"github.com/zclconf/go-cty/cty"
)
// conversionObjectToObject returns a conversion that will make the input
// object type conform to the output object type, if possible.
//
// Conversion is possible only if the output type is a subset of the input
// type, meaning that each attribute of the output type has a corresponding
// attribute in the input type where a recursive conversion is available.
//
// If the "out" type has any optional attributes, those attributes may be
// absent in the "in" type, in which case null values will be used in their
// place in the result.
//
// Shallow object conversions work the same for both safe and unsafe modes,
// but the safety flag is passed on to recursive conversions and may thus
// limit the above definition of "subset".
func conversionObjectToObject(in, out cty.Type, unsafe bool) conversion {
inAtys := in.AttributeTypes()
outAtys := out.AttributeTypes()
outOptionals := out.OptionalAttributes()
attrConvs := make(map[string]conversion)
for name, outAty := range outAtys {
inAty, exists := inAtys[name]
if !exists {
if _, optional := outOptionals[name]; optional {
// If it's optional then we'll skip inserting an
// attribute conversion and then deal with inserting
// the default value in our overall conversion logic
// later.
continue
}
// No conversion is available, then.
return nil
}
if inAty.Equals(outAty) {
// No conversion needed, but we'll still record the attribute
// in our map for later reference.
attrConvs[name] = nil
continue
}
attrConvs[name] = getConversion(inAty, outAty, unsafe)
if attrConvs[name] == nil {
// If a recursive conversion isn't available, then our top-level
// configuration is impossible too.
return nil
}
}
// If we get here then a conversion is possible, using the attribute
// conversions given in attrConvs.
return func(val cty.Value, path cty.Path) (cty.Value, error) {
attrVals := make(map[string]cty.Value, len(attrConvs))
path = append(path, nil)
pathStep := &path[len(path)-1]
for it := val.ElementIterator(); it.Next(); {
nameVal, val := it.Element()
var err error
name := nameVal.AsString()
*pathStep = cty.GetAttrStep{
Name: name,
}
conv, exists := attrConvs[name]
if !exists {
continue
}
if conv != nil {
val, err = conv(val, path)
if err != nil {
return cty.NilVal, err
}
}
attrVals[name] = val
}
for name := range outOptionals {
if _, exists := attrVals[name]; !exists {
wantTy := outAtys[name]
attrVals[name] = cty.NullVal(wantTy)
}
}
return cty.ObjectVal(attrVals), nil
}
}

View File

@ -0,0 +1,57 @@
package convert
import (
"strings"
"github.com/zclconf/go-cty/cty"
)
var stringTrue = cty.StringVal("true")
var stringFalse = cty.StringVal("false")
var primitiveConversionsSafe = map[cty.Type]map[cty.Type]conversion{
cty.Number: {
cty.String: func(val cty.Value, path cty.Path) (cty.Value, error) {
f := val.AsBigFloat()
return cty.StringVal(f.Text('f', -1)), nil
},
},
cty.Bool: {
cty.String: func(val cty.Value, path cty.Path) (cty.Value, error) {
if val.True() {
return stringTrue, nil
} else {
return stringFalse, nil
}
},
},
}
var primitiveConversionsUnsafe = map[cty.Type]map[cty.Type]conversion{
cty.String: {
cty.Number: func(val cty.Value, path cty.Path) (cty.Value, error) {
v, err := cty.ParseNumberVal(val.AsString())
if err != nil {
return cty.NilVal, path.NewErrorf("a number is required")
}
return v, nil
},
cty.Bool: func(val cty.Value, path cty.Path) (cty.Value, error) {
switch val.AsString() {
case "true", "1":
return cty.True, nil
case "false", "0":
return cty.False, nil
default:
switch strings.ToLower(val.AsString()) {
case "true":
return cty.NilVal, path.NewErrorf("a bool is required; to convert from string, use lowercase \"true\"")
case "false":
return cty.NilVal, path.NewErrorf("a bool is required; to convert from string, use lowercase \"false\"")
default:
return cty.NilVal, path.NewErrorf("a bool is required")
}
}
},
},
}

View File

@ -0,0 +1,71 @@
package convert
import (
"github.com/zclconf/go-cty/cty"
)
// conversionTupleToTuple returns a conversion that will make the input
// tuple type conform to the output tuple type, if possible.
//
// Conversion is possible only if the two tuple types have the same number
// of elements and the corresponding elements by index can be converted.
//
// Shallow tuple conversions work the same for both safe and unsafe modes,
// but the safety flag is passed on to recursive conversions and may thus
// limit which element type conversions are possible.
func conversionTupleToTuple(in, out cty.Type, unsafe bool) conversion {
inEtys := in.TupleElementTypes()
outEtys := out.TupleElementTypes()
if len(inEtys) != len(outEtys) {
return nil // no conversion is possible
}
elemConvs := make([]conversion, len(inEtys))
for i, outEty := range outEtys {
inEty := inEtys[i]
if inEty.Equals(outEty) {
// No conversion needed, so we can leave this one nil.
continue
}
elemConvs[i] = getConversion(inEty, outEty, unsafe)
if elemConvs[i] == nil {
// If a recursive conversion isn't available, then our top-level
// configuration is impossible too.
return nil
}
}
// If we get here then a conversion is possible, using the element
// conversions given in elemConvs.
return func(val cty.Value, path cty.Path) (cty.Value, error) {
elemVals := make([]cty.Value, len(elemConvs))
path = append(path, nil)
pathStep := &path[len(path)-1]
i := 0
for it := val.ElementIterator(); it.Next(); i++ {
_, val := it.Element()
var err error
*pathStep = cty.IndexStep{
Key: cty.NumberIntVal(int64(i)),
}
conv := elemConvs[i]
if conv != nil {
val, err = conv(val, path)
if err != nil {
return cty.NilVal, err
}
}
elemVals[i] = val
}
return cty.TupleVal(elemVals), nil
}
}

15
vendor/github.com/zclconf/go-cty/cty/convert/doc.go generated vendored Normal file
View File

@ -0,0 +1,15 @@
// Package convert contains some routines for converting between cty types.
// The intent of providing this package is to encourage applications using
// cty to have consistent type conversion behavior for maximal interoperability
// when Values pass from one application to another.
//
// The conversions are categorized into two categories. "Safe" conversions are
// ones that are guaranteed to succeed if given a non-null value of the
// appropriate source type. "Unsafe" conversions, on the other hand, are valid
// for only a subset of input values, and thus may fail with an error when
// called for values outside of that valid subset.
//
// The functions whose names end in Unsafe support all of the conversions that
// are supported by the corresponding functions whose names do not have that
// suffix, and then additional unsafe conversions as well.
package convert

View File

@ -0,0 +1,226 @@
package convert
import (
"bytes"
"fmt"
"sort"
"github.com/zclconf/go-cty/cty"
)
// MismatchMessage is a helper to return an English-language description of
// the differences between got and want, phrased as a reason why got does
// not conform to want.
//
// This function does not itself attempt conversion, and so it should generally
// be used only after a conversion has failed, to report the conversion failure
// to an English-speaking user. The result will be confusing got is actually
// conforming to or convertable to want.
//
// The shorthand helper function Convert uses this function internally to
// produce its error messages, so callers of that function do not need to
// also use MismatchMessage.
//
// This function is similar to Type.TestConformance, but it is tailored to
// describing conversion failures and so the messages it generates relate
// specifically to the conversion rules implemented in this package.
func MismatchMessage(got, want cty.Type) string {
switch {
case got.IsObjectType() && want.IsObjectType():
// If both types are object types then we may be able to say something
// about their respective attributes.
return mismatchMessageObjects(got, want)
case got.IsTupleType() && want.IsListType() && want.ElementType() == cty.DynamicPseudoType:
// If conversion from tuple to list failed then it's because we couldn't
// find a common type to convert all of the tuple elements to.
return "all list elements must have the same type"
case got.IsTupleType() && want.IsSetType() && want.ElementType() == cty.DynamicPseudoType:
// If conversion from tuple to set failed then it's because we couldn't
// find a common type to convert all of the tuple elements to.
return "all set elements must have the same type"
case got.IsObjectType() && want.IsMapType() && want.ElementType() == cty.DynamicPseudoType:
// If conversion from object to map failed then it's because we couldn't
// find a common type to convert all of the object attributes to.
return "all map elements must have the same type"
case (got.IsTupleType() || got.IsObjectType()) && want.IsCollectionType():
return mismatchMessageCollectionsFromStructural(got, want)
case got.IsCollectionType() && want.IsCollectionType():
return mismatchMessageCollectionsFromCollections(got, want)
default:
// If we have nothing better to say, we'll just state what was required.
return want.FriendlyNameForConstraint() + " required"
}
}
func mismatchMessageObjects(got, want cty.Type) string {
// Per our conversion rules, "got" is allowed to be a superset of "want",
// and so we'll produce error messages here under that assumption.
gotAtys := got.AttributeTypes()
wantAtys := want.AttributeTypes()
// If we find missing attributes then we'll report those in preference,
// but if not then we will report a maximum of one non-conforming
// attribute, just to keep our messages relatively terse.
// We'll also prefer to report a recursive type error from an _unsafe_
// conversion over a safe one, because these are subjectively more
// "serious".
var missingAttrs []string
var unsafeMismatchAttr string
var safeMismatchAttr string
for name, wantAty := range wantAtys {
gotAty, exists := gotAtys[name]
if !exists {
if !want.AttributeOptional(name) {
missingAttrs = append(missingAttrs, name)
}
continue
}
if gotAty.Equals(wantAty) {
continue // exact match, so no problem
}
// We'll now try to convert these attributes in isolation and
// see if we have a nested conversion error to report.
// We'll try an unsafe conversion first, and then fall back on
// safe if unsafe is possible.
// If we already have an unsafe mismatch attr error then we won't bother
// hunting for another one.
if unsafeMismatchAttr != "" {
continue
}
if conv := GetConversionUnsafe(gotAty, wantAty); conv == nil {
unsafeMismatchAttr = fmt.Sprintf("attribute %q: %s", name, MismatchMessage(gotAty, wantAty))
}
// If we already have a safe mismatch attr error then we won't bother
// hunting for another one.
if safeMismatchAttr != "" {
continue
}
if conv := GetConversion(gotAty, wantAty); conv == nil {
safeMismatchAttr = fmt.Sprintf("attribute %q: %s", name, MismatchMessage(gotAty, wantAty))
}
}
// We should now have collected at least one problem. If we have more than
// one then we'll use our preference order to decide what is most important
// to report.
switch {
case len(missingAttrs) != 0:
sort.Strings(missingAttrs)
switch len(missingAttrs) {
case 1:
return fmt.Sprintf("attribute %q is required", missingAttrs[0])
case 2:
return fmt.Sprintf("attributes %q and %q are required", missingAttrs[0], missingAttrs[1])
default:
sort.Strings(missingAttrs)
var buf bytes.Buffer
for _, name := range missingAttrs[:len(missingAttrs)-1] {
fmt.Fprintf(&buf, "%q, ", name)
}
fmt.Fprintf(&buf, "and %q", missingAttrs[len(missingAttrs)-1])
return fmt.Sprintf("attributes %s are required", buf.Bytes())
}
case unsafeMismatchAttr != "":
return unsafeMismatchAttr
case safeMismatchAttr != "":
return safeMismatchAttr
default:
// We should never get here, but if we do then we'll return
// just a generic message.
return "incorrect object attributes"
}
}
func mismatchMessageCollectionsFromStructural(got, want cty.Type) string {
// First some straightforward cases where the kind is just altogether wrong.
switch {
case want.IsListType() && !got.IsTupleType():
return want.FriendlyNameForConstraint() + " required"
case want.IsSetType() && !got.IsTupleType():
return want.FriendlyNameForConstraint() + " required"
case want.IsMapType() && !got.IsObjectType():
return want.FriendlyNameForConstraint() + " required"
}
// If the kinds are matched well enough then we'll move on to checking
// individual elements.
wantEty := want.ElementType()
switch {
case got.IsTupleType():
for i, gotEty := range got.TupleElementTypes() {
if gotEty.Equals(wantEty) {
continue // exact match, so no problem
}
if conv := getConversion(gotEty, wantEty, true); conv != nil {
continue // conversion is available, so no problem
}
return fmt.Sprintf("element %d: %s", i, MismatchMessage(gotEty, wantEty))
}
// If we get down here then something weird is going on but we'll
// return a reasonable fallback message anyway.
return fmt.Sprintf("all elements must be %s", wantEty.FriendlyNameForConstraint())
case got.IsObjectType():
for name, gotAty := range got.AttributeTypes() {
if gotAty.Equals(wantEty) {
continue // exact match, so no problem
}
if conv := getConversion(gotAty, wantEty, true); conv != nil {
continue // conversion is available, so no problem
}
return fmt.Sprintf("element %q: %s", name, MismatchMessage(gotAty, wantEty))
}
// If we get down here then something weird is going on but we'll
// return a reasonable fallback message anyway.
return fmt.Sprintf("all elements must be %s", wantEty.FriendlyNameForConstraint())
default:
// Should not be possible to get here since we only call this function
// with got as structural types, but...
return want.FriendlyNameForConstraint() + " required"
}
}
func mismatchMessageCollectionsFromCollections(got, want cty.Type) string {
// First some straightforward cases where the kind is just altogether wrong.
switch {
case want.IsListType() && !(got.IsListType() || got.IsSetType()):
return want.FriendlyNameForConstraint() + " required"
case want.IsSetType() && !(got.IsListType() || got.IsSetType()):
return want.FriendlyNameForConstraint() + " required"
case want.IsMapType() && !got.IsMapType():
return want.FriendlyNameForConstraint() + " required"
}
// If the kinds are matched well enough then we'll check the element types.
gotEty := got.ElementType()
wantEty := want.ElementType()
noun := "element type"
switch {
case want.IsListType():
noun = "list element type"
case want.IsSetType():
noun = "set element type"
case want.IsMapType():
noun = "map element type"
}
return fmt.Sprintf("incorrect %s: %s", noun, MismatchMessage(gotEty, wantEty))
}

83
vendor/github.com/zclconf/go-cty/cty/convert/public.go generated vendored Normal file
View File

@ -0,0 +1,83 @@
package convert
import (
"errors"
"github.com/zclconf/go-cty/cty"
)
// This file contains the public interface of this package, which is intended
// to be a small, convenient interface designed for easy integration into
// a hypothetical language type checker and interpreter.
// Conversion is a named function type representing a conversion from a
// value of one type to a value of another type.
//
// The source type for a conversion is always the source type given to
// the function that returned the Conversion, but there is no way to recover
// that from a Conversion value itself. If a Conversion is given a value
// that is not of its expected type (with the exception of DynamicPseudoType,
// which is always supported) then the function may panic or produce undefined
// results.
type Conversion func(in cty.Value) (out cty.Value, err error)
// GetConversion returns a Conversion between the given in and out Types if
// a safe one is available, or returns nil otherwise.
func GetConversion(in cty.Type, out cty.Type) Conversion {
return retConversion(getConversion(in, out, false))
}
// GetConversionUnsafe returns a Conversion between the given in and out Types
// if either a safe or unsafe one is available, or returns nil otherwise.
func GetConversionUnsafe(in cty.Type, out cty.Type) Conversion {
return retConversion(getConversion(in, out, true))
}
// Convert returns the result of converting the given value to the given type
// if an safe or unsafe conversion is available, or returns an error if such a
// conversion is impossible.
//
// This is a convenience wrapper around calling GetConversionUnsafe and then
// immediately passing the given value to the resulting function.
func Convert(in cty.Value, want cty.Type) (cty.Value, error) {
if in.Type().Equals(want) {
return in, nil
}
conv := GetConversionUnsafe(in.Type(), want)
if conv == nil {
return cty.NilVal, errors.New(MismatchMessage(in.Type(), want))
}
return conv(in)
}
// Unify attempts to find the most general type that can be converted from
// all of the given types. If this is possible, that type is returned along
// with a slice of necessary conversions for some of the given types.
//
// If no common supertype can be found, this function returns cty.NilType and
// a nil slice.
//
// If a common supertype *can* be found, the returned slice will always be
// non-nil and will contain a non-nil conversion for each given type that
// needs to be converted, with indices corresponding to the input slice.
// Any given type that does *not* need conversion (because it is already of
// the appropriate type) will have a nil Conversion.
//
// cty.DynamicPseudoType is, as usual, a special case. If the given type list
// contains a mixture of dynamic and non-dynamic types, the dynamic types are
// disregarded for type selection and a conversion is returned for them that
// will attempt a late conversion of the given value to the target type,
// failing with a conversion error if the eventual concrete type is not
// compatible. If *all* given types are DynamicPseudoType, or in the
// degenerate case of an empty slice of types, the returned type is itself
// cty.DynamicPseudoType and no conversions are attempted.
func Unify(types []cty.Type) (cty.Type, []Conversion) {
return unify(types, false)
}
// UnifyUnsafe is the same as Unify except that it may return unsafe
// conversions in situations where a safe conversion isn't also available.
func UnifyUnsafe(types []cty.Type) (cty.Type, []Conversion) {
return unify(types, true)
}

View File

@ -0,0 +1,69 @@
package convert
import (
"github.com/zclconf/go-cty/cty"
)
// sortTypes produces an ordering of the given types that serves as a
// preference order for the result of unification of the given types.
// The return value is a slice of indices into the given slice, and will
// thus always be the same length as the given slice.
//
// The goal is that the most general of the given types will appear first
// in the ordering. If there are uncomparable pairs of types in the list
// then they will appear in an undefined order, and the unification pass
// will presumably then fail.
func sortTypes(tys []cty.Type) []int {
l := len(tys)
// First we build a graph whose edges represent "more general than",
// which we will then do a topological sort of.
edges := make([][]int, l)
for i := 0; i < (l - 1); i++ {
for j := i + 1; j < l; j++ {
cmp := compareTypes(tys[i], tys[j])
switch {
case cmp < 0:
edges[i] = append(edges[i], j)
case cmp > 0:
edges[j] = append(edges[j], i)
}
}
}
// Compute the in-degree of each node
inDegree := make([]int, l)
for _, outs := range edges {
for _, j := range outs {
inDegree[j]++
}
}
// The array backing our result will double as our queue for visiting
// the nodes, with the queue slice moving along this array until it
// is empty and positioned at the end of the array. Thus our visiting
// order is also our result order.
result := make([]int, l)
queue := result[0:0]
// Initialize the queue with any item of in-degree 0, preserving
// their relative order.
for i, n := range inDegree {
if n == 0 {
queue = append(queue, i)
}
}
for len(queue) != 0 {
i := queue[0]
queue = queue[1:]
for _, j := range edges[i] {
inDegree[j]--
if inDegree[j] == 0 {
queue = append(queue, j)
}
}
}
return result
}

501
vendor/github.com/zclconf/go-cty/cty/convert/unify.go generated vendored Normal file
View File

@ -0,0 +1,501 @@
package convert
import (
"github.com/zclconf/go-cty/cty"
)
// The current unify implementation is somewhat inefficient, but we accept this
// under the assumption that it will generally be used with small numbers of
// types and with types of reasonable complexity. However, it does have a
// "happy path" where all of the given types are equal.
//
// This function is likely to have poor performance in cases where any given
// types are very complex (lots of deeply-nested structures) or if the list
// of types itself is very large. In particular, it will walk the nested type
// structure under the given types several times, especially when given a
// list of types for which unification is not possible, since each permutation
// will be tried to determine that result.
func unify(types []cty.Type, unsafe bool) (cty.Type, []Conversion) {
if len(types) == 0 {
// Degenerate case
return cty.NilType, nil
}
// If all of the given types are of the same structural kind, we may be
// able to construct a new type that they can all be unified to, even if
// that is not one of the given types. We must try this before the general
// behavior below because in unsafe mode we can convert an object type to
// a subset of that type, which would be a much less useful conversion for
// unification purposes.
{
mapCt := 0
listCt := 0
setCt := 0
objectCt := 0
tupleCt := 0
dynamicCt := 0
for _, ty := range types {
switch {
case ty.IsMapType():
mapCt++
case ty.IsListType():
listCt++
case ty.IsSetType():
setCt++
case ty.IsObjectType():
objectCt++
case ty.IsTupleType():
tupleCt++
case ty == cty.DynamicPseudoType:
dynamicCt++
default:
break
}
}
switch {
case mapCt > 0 && (mapCt+dynamicCt) == len(types):
return unifyCollectionTypes(cty.Map, types, unsafe, dynamicCt > 0)
case mapCt > 0 && (mapCt+objectCt+dynamicCt) == len(types):
// Objects often contain map data, but are not directly typed as
// such due to language constructs or function types. Try to unify
// them as maps first before falling back to heterogeneous type
// conversion.
ty, convs := unifyObjectsAsMaps(types, unsafe)
// If we got a map back, we know the unification was successful.
if ty.IsMapType() {
return ty, convs
}
case listCt > 0 && (listCt+dynamicCt) == len(types):
return unifyCollectionTypes(cty.List, types, unsafe, dynamicCt > 0)
case listCt > 0 && (listCt+tupleCt+dynamicCt) == len(types):
// Tuples are often lists in disguise, and we may be able to
// unify them as such.
ty, convs := unifyTuplesAsList(types, unsafe)
// if we got a list back, we know the unification was successful.
// Otherwise we will fall back to the heterogeneous type codepath.
if ty.IsListType() {
return ty, convs
}
case setCt > 0 && (setCt+dynamicCt) == len(types):
return unifyCollectionTypes(cty.Set, types, unsafe, dynamicCt > 0)
case objectCt > 0 && (objectCt+dynamicCt) == len(types):
return unifyObjectTypes(types, unsafe, dynamicCt > 0)
case tupleCt > 0 && (tupleCt+dynamicCt) == len(types):
return unifyTupleTypes(types, unsafe, dynamicCt > 0)
case objectCt > 0 && tupleCt > 0:
// Can never unify object and tuple types since they have incompatible kinds
return cty.NilType, nil
}
}
prefOrder := sortTypes(types)
// sortTypes gives us an order where earlier items are preferable as
// our result type. We'll now walk through these and choose the first
// one we encounter for which conversions exist for all source types.
conversions := make([]Conversion, len(types))
Preferences:
for _, wantTypeIdx := range prefOrder {
wantType := types[wantTypeIdx]
for i, tryType := range types {
if i == wantTypeIdx {
// Don't need to convert our wanted type to itself
conversions[i] = nil
continue
}
if tryType.Equals(wantType) {
conversions[i] = nil
continue
}
if unsafe {
conversions[i] = GetConversionUnsafe(tryType, wantType)
} else {
conversions[i] = GetConversion(tryType, wantType)
}
if conversions[i] == nil {
// wantType is not a suitable unification type, so we'll
// try the next one in our preference order.
continue Preferences
}
}
return wantType, conversions
}
// If we fall out here, no unification is possible
return cty.NilType, nil
}
// unifyTuplesAsList attempts to first see if the tuples unify as lists, then
// re-unifies the given types with the list in place of the tuples.
func unifyTuplesAsList(types []cty.Type, unsafe bool) (cty.Type, []Conversion) {
var tuples []cty.Type
var tupleIdxs []int
for i, t := range types {
if t.IsTupleType() {
tuples = append(tuples, t)
tupleIdxs = append(tupleIdxs, i)
}
}
ty, tupleConvs := unifyTupleTypesToList(tuples, unsafe)
if !ty.IsListType() {
return cty.NilType, nil
}
// the tuples themselves unified as a list, get the overall
// unification with this list type instead of the tuple.
// make a copy of the types, so we can fallback to the standard
// codepath if something went wrong
listed := make([]cty.Type, len(types))
copy(listed, types)
for _, idx := range tupleIdxs {
listed[idx] = ty
}
newTy, convs := unify(listed, unsafe)
if !newTy.IsListType() {
return cty.NilType, nil
}
// we have a good conversion, wrap the nested tuple conversions.
// We know the tuple conversion is not nil, because we went from tuple to
// list
for i, idx := range tupleIdxs {
listConv := convs[idx]
tupleConv := tupleConvs[i]
if listConv == nil {
convs[idx] = tupleConv
continue
}
convs[idx] = func(in cty.Value) (out cty.Value, err error) {
out, err = tupleConv(in)
if err != nil {
return out, err
}
return listConv(in)
}
}
return newTy, convs
}
// unifyObjectsAsMaps attempts to first see if the objects unify as maps, then
// re-unifies the given types with the map in place of the objects.
func unifyObjectsAsMaps(types []cty.Type, unsafe bool) (cty.Type, []Conversion) {
var objs []cty.Type
var objIdxs []int
for i, t := range types {
if t.IsObjectType() {
objs = append(objs, t)
objIdxs = append(objIdxs, i)
}
}
ty, objConvs := unifyObjectTypesToMap(objs, unsafe)
if !ty.IsMapType() {
return cty.NilType, nil
}
// the objects themselves unified as a map, get the overall
// unification with this map type instead of the object.
// Make a copy of the types, so we can fallback to the standard codepath if
// something went wrong without changing the original types.
mapped := make([]cty.Type, len(types))
copy(mapped, types)
for _, idx := range objIdxs {
mapped[idx] = ty
}
newTy, convs := unify(mapped, unsafe)
if !newTy.IsMapType() {
return cty.NilType, nil
}
// we have a good conversion, so wrap the nested object conversions.
// We know the object conversion is not nil, because we went from object to
// map.
for i, idx := range objIdxs {
mapConv := convs[idx]
objConv := objConvs[i]
if mapConv == nil {
convs[idx] = objConv
continue
}
convs[idx] = func(in cty.Value) (out cty.Value, err error) {
out, err = objConv(in)
if err != nil {
return out, err
}
return mapConv(in)
}
}
return newTy, convs
}
func unifyCollectionTypes(collectionType func(cty.Type) cty.Type, types []cty.Type, unsafe bool, hasDynamic bool) (cty.Type, []Conversion) {
// If we had any dynamic types in the input here then we can't predict
// what path we'll take through here once these become known types, so
// we'll conservatively produce DynamicVal for these.
if hasDynamic {
return unifyAllAsDynamic(types)
}
elemTypes := make([]cty.Type, 0, len(types))
for _, ty := range types {
elemTypes = append(elemTypes, ty.ElementType())
}
retElemType, _ := unify(elemTypes, unsafe)
if retElemType == cty.NilType {
return cty.NilType, nil
}
retTy := collectionType(retElemType)
conversions := make([]Conversion, len(types))
for i, ty := range types {
if ty.Equals(retTy) {
continue
}
if unsafe {
conversions[i] = GetConversionUnsafe(ty, retTy)
} else {
conversions[i] = GetConversion(ty, retTy)
}
if conversions[i] == nil {
// Shouldn't be reachable, since we were able to unify
return cty.NilType, nil
}
}
return retTy, conversions
}
func unifyObjectTypes(types []cty.Type, unsafe bool, hasDynamic bool) (cty.Type, []Conversion) {
// If we had any dynamic types in the input here then we can't predict
// what path we'll take through here once these become known types, so
// we'll conservatively produce DynamicVal for these.
if hasDynamic {
return unifyAllAsDynamic(types)
}
// There are two different ways we can succeed here:
// - If all of the given object types have the same set of attribute names
// and the corresponding types are all unifyable, then we construct that
// type.
// - If the given object types have different attribute names or their
// corresponding types are not unifyable, we'll instead try to unify
// all of the attribute types together to produce a map type.
//
// Our unification behavior is intentionally stricter than our conversion
// behavior for subset object types because user intent is different with
// unification use-cases: it makes sense to allow {"foo":true} to convert
// to emptyobjectval, but unifying an object with an attribute with the
// empty object type should be an error because unifying to the empty
// object type would be suprising and useless.
firstAttrs := types[0].AttributeTypes()
for _, ty := range types[1:] {
thisAttrs := ty.AttributeTypes()
if len(thisAttrs) != len(firstAttrs) {
// If number of attributes is different then there can be no
// object type in common.
return unifyObjectTypesToMap(types, unsafe)
}
for name := range thisAttrs {
if _, ok := firstAttrs[name]; !ok {
// If attribute names don't exactly match then there can be
// no object type in common.
return unifyObjectTypesToMap(types, unsafe)
}
}
}
// If we get here then we've proven that all of the given object types
// have exactly the same set of attribute names, though the types may
// differ.
retAtys := make(map[string]cty.Type)
atysAcross := make([]cty.Type, len(types))
for name := range firstAttrs {
for i, ty := range types {
atysAcross[i] = ty.AttributeType(name)
}
retAtys[name], _ = unify(atysAcross, unsafe)
if retAtys[name] == cty.NilType {
// Cannot unify this attribute alone, which means that unification
// of everything down to a map type can't be possible either.
return cty.NilType, nil
}
}
retTy := cty.Object(retAtys)
conversions := make([]Conversion, len(types))
for i, ty := range types {
if ty.Equals(retTy) {
continue
}
if unsafe {
conversions[i] = GetConversionUnsafe(ty, retTy)
} else {
conversions[i] = GetConversion(ty, retTy)
}
if conversions[i] == nil {
// Shouldn't be reachable, since we were able to unify
return unifyObjectTypesToMap(types, unsafe)
}
}
return retTy, conversions
}
func unifyObjectTypesToMap(types []cty.Type, unsafe bool) (cty.Type, []Conversion) {
// This is our fallback case for unifyObjectTypes, where we see if we can
// construct a map type that can accept all of the attribute types.
var atys []cty.Type
for _, ty := range types {
for _, aty := range ty.AttributeTypes() {
atys = append(atys, aty)
}
}
ety, _ := unify(atys, unsafe)
if ety == cty.NilType {
return cty.NilType, nil
}
retTy := cty.Map(ety)
conversions := make([]Conversion, len(types))
for i, ty := range types {
if ty.Equals(retTy) {
continue
}
if unsafe {
conversions[i] = GetConversionUnsafe(ty, retTy)
} else {
conversions[i] = GetConversion(ty, retTy)
}
if conversions[i] == nil {
return cty.NilType, nil
}
}
return retTy, conversions
}
func unifyTupleTypes(types []cty.Type, unsafe bool, hasDynamic bool) (cty.Type, []Conversion) {
// If we had any dynamic types in the input here then we can't predict
// what path we'll take through here once these become known types, so
// we'll conservatively produce DynamicVal for these.
if hasDynamic {
return unifyAllAsDynamic(types)
}
// There are two different ways we can succeed here:
// - If all of the given tuple types have the same sequence of element types
// and the corresponding types are all unifyable, then we construct that
// type.
// - If the given tuple types have different element types or their
// corresponding types are not unifyable, we'll instead try to unify
// all of the elements types together to produce a list type.
firstEtys := types[0].TupleElementTypes()
for _, ty := range types[1:] {
thisEtys := ty.TupleElementTypes()
if len(thisEtys) != len(firstEtys) {
// If number of elements is different then there can be no
// tuple type in common.
return unifyTupleTypesToList(types, unsafe)
}
}
// If we get here then we've proven that all of the given tuple types
// have the same number of elements, though the types may differ.
retEtys := make([]cty.Type, len(firstEtys))
atysAcross := make([]cty.Type, len(types))
for idx := range firstEtys {
for tyI, ty := range types {
atysAcross[tyI] = ty.TupleElementTypes()[idx]
}
retEtys[idx], _ = unify(atysAcross, unsafe)
if retEtys[idx] == cty.NilType {
// Cannot unify this element alone, which means that unification
// of everything down to a map type can't be possible either.
return cty.NilType, nil
}
}
retTy := cty.Tuple(retEtys)
conversions := make([]Conversion, len(types))
for i, ty := range types {
if ty.Equals(retTy) {
continue
}
if unsafe {
conversions[i] = GetConversionUnsafe(ty, retTy)
} else {
conversions[i] = GetConversion(ty, retTy)
}
if conversions[i] == nil {
// Shouldn't be reachable, since we were able to unify
return unifyTupleTypesToList(types, unsafe)
}
}
return retTy, conversions
}
func unifyTupleTypesToList(types []cty.Type, unsafe bool) (cty.Type, []Conversion) {
// This is our fallback case for unifyTupleTypes, where we see if we can
// construct a list type that can accept all of the element types.
var etys []cty.Type
for _, ty := range types {
for _, ety := range ty.TupleElementTypes() {
etys = append(etys, ety)
}
}
ety, _ := unify(etys, unsafe)
if ety == cty.NilType {
return cty.NilType, nil
}
retTy := cty.List(ety)
conversions := make([]Conversion, len(types))
for i, ty := range types {
if ty.Equals(retTy) {
continue
}
if unsafe {
conversions[i] = GetConversionUnsafe(ty, retTy)
} else {
conversions[i] = GetConversion(ty, retTy)
}
if conversions[i] == nil {
// Shouldn't be reachable, since we were able to unify
return unifyObjectTypesToMap(types, unsafe)
}
}
return retTy, conversions
}
func unifyAllAsDynamic(types []cty.Type) (cty.Type, []Conversion) {
conversions := make([]Conversion, len(types))
for i := range conversions {
conversions[i] = func(cty.Value) (cty.Value, error) {
return cty.DynamicVal, nil
}
}
return cty.DynamicPseudoType, conversions
}

18
vendor/github.com/zclconf/go-cty/cty/doc.go generated vendored Normal file
View File

@ -0,0 +1,18 @@
// Package cty (pronounced see-tie) provides some infrastructure for a type
// system that might be useful for applications that need to represent
// configuration values provided by the user whose types are not known
// at compile time, particularly if the calling application also allows
// such values to be used in expressions.
//
// The type system consists of primitive types Number, String and Bool, as
// well as List and Map collection types and Object types that can have
// arbitrarily-typed sets of attributes.
//
// A set of operations is defined on these types, which is accessible via
// the wrapper struct Value, which annotates the raw, internal representation
// of a value with its corresponding type.
//
// This package is oriented towards being a building block for configuration
// languages used to bootstrap an application. It is not optimized for use
// in tight loops where CPU time or memory pressure are a concern.
package cty

View File

@ -0,0 +1,194 @@
package cty
import (
"sort"
"github.com/zclconf/go-cty/cty/set"
)
// ElementIterator is the interface type returned by Value.ElementIterator to
// allow the caller to iterate over elements of a collection-typed value.
//
// Its usage pattern is as follows:
//
// it := val.ElementIterator()
// for it.Next() {
// key, val := it.Element()
// // ...
// }
type ElementIterator interface {
Next() bool
Element() (key Value, value Value)
}
func canElementIterator(val Value) bool {
switch {
case val.IsMarked():
return false
case val.ty.IsListType():
return true
case val.ty.IsMapType():
return true
case val.ty.IsSetType():
return true
case val.ty.IsTupleType():
return true
case val.ty.IsObjectType():
return true
default:
return false
}
}
func elementIterator(val Value) ElementIterator {
val.assertUnmarked()
switch {
case val.ty.IsListType():
return &listElementIterator{
ety: val.ty.ElementType(),
vals: val.v.([]interface{}),
idx: -1,
}
case val.ty.IsMapType():
// We iterate the keys in a predictable lexicographical order so
// that results will always be stable given the same input map.
rawMap := val.v.(map[string]interface{})
keys := make([]string, 0, len(rawMap))
for key := range rawMap {
keys = append(keys, key)
}
sort.Strings(keys)
return &mapElementIterator{
ety: val.ty.ElementType(),
vals: rawMap,
keys: keys,
idx: -1,
}
case val.ty.IsSetType():
rawSet := val.v.(set.Set)
return &setElementIterator{
ety: val.ty.ElementType(),
setIt: rawSet.Iterator(),
}
case val.ty.IsTupleType():
return &tupleElementIterator{
etys: val.ty.TupleElementTypes(),
vals: val.v.([]interface{}),
idx: -1,
}
case val.ty.IsObjectType():
// We iterate the keys in a predictable lexicographical order so
// that results will always be stable given the same object type.
atys := val.ty.AttributeTypes()
keys := make([]string, 0, len(atys))
for key := range atys {
keys = append(keys, key)
}
sort.Strings(keys)
return &objectElementIterator{
atys: atys,
vals: val.v.(map[string]interface{}),
attrNames: keys,
idx: -1,
}
default:
panic("attempt to iterate on non-collection, non-tuple type")
}
}
type listElementIterator struct {
ety Type
vals []interface{}
idx int
}
func (it *listElementIterator) Element() (Value, Value) {
i := it.idx
return NumberIntVal(int64(i)), Value{
ty: it.ety,
v: it.vals[i],
}
}
func (it *listElementIterator) Next() bool {
it.idx++
return it.idx < len(it.vals)
}
type mapElementIterator struct {
ety Type
vals map[string]interface{}
keys []string
idx int
}
func (it *mapElementIterator) Element() (Value, Value) {
key := it.keys[it.idx]
return StringVal(key), Value{
ty: it.ety,
v: it.vals[key],
}
}
func (it *mapElementIterator) Next() bool {
it.idx++
return it.idx < len(it.keys)
}
type setElementIterator struct {
ety Type
setIt *set.Iterator
}
func (it *setElementIterator) Element() (Value, Value) {
val := Value{
ty: it.ety,
v: it.setIt.Value(),
}
return val, val
}
func (it *setElementIterator) Next() bool {
return it.setIt.Next()
}
type tupleElementIterator struct {
etys []Type
vals []interface{}
idx int
}
func (it *tupleElementIterator) Element() (Value, Value) {
i := it.idx
return NumberIntVal(int64(i)), Value{
ty: it.etys[i],
v: it.vals[i],
}
}
func (it *tupleElementIterator) Next() bool {
it.idx++
return it.idx < len(it.vals)
}
type objectElementIterator struct {
atys map[string]Type
vals map[string]interface{}
attrNames []string
idx int
}
func (it *objectElementIterator) Element() (Value, Value) {
key := it.attrNames[it.idx]
return StringVal(key), Value{
ty: it.atys[key],
v: it.vals[key],
}
}
func (it *objectElementIterator) Next() bool {
it.idx++
return it.idx < len(it.attrNames)
}

55
vendor/github.com/zclconf/go-cty/cty/error.go generated vendored Normal file
View File

@ -0,0 +1,55 @@
package cty
import (
"fmt"
)
// PathError is a specialization of error that represents where in a
// potentially-deep data structure an error occured, using a Path.
type PathError struct {
error
Path Path
}
func errorf(path Path, f string, args ...interface{}) error {
// We need to copy the Path because often our caller builds it by
// continually mutating the same underlying buffer.
sPath := make(Path, len(path))
copy(sPath, path)
return PathError{
error: fmt.Errorf(f, args...),
Path: sPath,
}
}
// NewErrorf creates a new PathError for the current path by passing the
// given format and arguments to fmt.Errorf and then wrapping the result
// similarly to NewError.
func (p Path) NewErrorf(f string, args ...interface{}) error {
return errorf(p, f, args...)
}
// NewError creates a new PathError for the current path, wrapping the given
// error.
func (p Path) NewError(err error) error {
// if we're being asked to wrap an existing PathError then our new
// PathError will be the concatenation of the two paths, ensuring
// that we still get a single flat PathError that's thus easier for
// callers to deal with.
perr, wrappingPath := err.(PathError)
pathLen := len(p)
if wrappingPath {
pathLen = pathLen + len(perr.Path)
}
sPath := make(Path, pathLen)
copy(sPath, p)
if wrappingPath {
copy(sPath[len(p):], perr.Path)
}
return PathError{
error: err,
Path: sPath,
}
}

View File

@ -0,0 +1,70 @@
package function
import (
"github.com/zclconf/go-cty/cty"
)
// Parameter represents a parameter to a function.
type Parameter struct {
// Name is an optional name for the argument. This package ignores this
// value, but callers may use it for documentation, etc.
Name string
// A type that any argument for this parameter must conform to.
// cty.DynamicPseudoType can be used, either at top-level or nested
// in a parameterized type, to indicate that any type should be
// permitted, to allow the definition of type-generic functions.
Type cty.Type
// If AllowNull is set then null values may be passed into this
// argument's slot in both the type-check function and the implementation
// function. If not set, such values are rejected by the built-in
// checking rules.
AllowNull bool
// If AllowUnknown is set then unknown values may be passed into this
// argument's slot in the implementation function. If not set, any
// unknown values will cause the function to immediately return
// an unkonwn value without calling the implementation function, thus
// freeing the function implementer from dealing with this case.
AllowUnknown bool
// If AllowDynamicType is set then DynamicVal may be passed into this
// argument's slot in the implementation function. If not set, any
// dynamic values will cause the function to immediately return
// DynamicVal value without calling the implementation function, thus
// freeing the function implementer from dealing with this case.
//
// Note that DynamicVal is also unknown, so in order to receive dynamic
// *values* it is also necessary to set AllowUnknown.
//
// However, it is valid to set AllowDynamicType without AllowUnknown, in
// which case a dynamic value may be passed to the type checking function
// but will not make it to the *implementation* function. Instead, an
// unknown value of the type returned by the type-check function will be
// returned. This is suggested for functions that have a static return
// type since it allows the return value to be typed even if the input
// values are not, thus improving the type-check accuracy of derived
// values.
AllowDynamicType bool
// If AllowMarked is set then marked values may be passed into this
// argument's slot in the implementation function. If not set, any
// marked value will be unmarked before calling and then the markings
// from that value will be applied automatically to the function result,
// ensuring that the marks get propagated in a simplistic way even if
// a function is unable to handle them.
//
// For any argument whose parameter has AllowMarked set, it's the
// function implementation's responsibility to Unmark the given value
// and propagate the marks appropriatedly to the result in order to
// avoid losing the marks. Application-specific functions might use
// special rules to selectively propagate particular marks.
//
// The automatic unmarking of values applies only to the main
// implementation function. In an application that uses marked values,
// the Type implementation for a function must always be prepared to accept
// marked values, which is easy to achieve by consulting only the type
// and ignoring the value itself.
AllowMarked bool
}

6
vendor/github.com/zclconf/go-cty/cty/function/doc.go generated vendored Normal file
View File

@ -0,0 +1,6 @@
// Package function builds on the functionality of cty by modeling functions
// that operate on cty Values.
//
// Functions are, at their core, Go anonymous functions. However, this package
// wraps around them utility functions for parameter type checking, etc.
package function

50
vendor/github.com/zclconf/go-cty/cty/function/error.go generated vendored Normal file
View File

@ -0,0 +1,50 @@
package function
import (
"fmt"
"runtime/debug"
)
// ArgError represents an error with one of the arguments in a call. The
// attribute Index represents the zero-based index of the argument in question.
//
// Its error *may* be a cty.PathError, in which case the error actually
// pertains to a nested value within the data structure passed as the argument.
type ArgError struct {
error
Index int
}
func NewArgErrorf(i int, f string, args ...interface{}) error {
return ArgError{
error: fmt.Errorf(f, args...),
Index: i,
}
}
func NewArgError(i int, err error) error {
return ArgError{
error: err,
Index: i,
}
}
// PanicError indicates that a panic occurred while executing either a
// function's type or implementation function. This is captured and wrapped
// into a normal error so that callers (expected to be language runtimes)
// are freed from having to deal with panics in buggy functions.
type PanicError struct {
Value interface{}
Stack []byte
}
func errorForPanic(val interface{}) error {
return PanicError{
Value: val,
Stack: debug.Stack(),
}
}
func (e PanicError) Error() string {
return fmt.Sprintf("panic in function implementation: %s\n%s", e.Value, e.Stack)
}

View File

@ -0,0 +1,346 @@
package function
import (
"fmt"
"github.com/zclconf/go-cty/cty"
)
// Function represents a function. This is the main type in this package.
type Function struct {
spec *Spec
}
// Spec is the specification of a function, used to instantiate
// a new Function.
type Spec struct {
// Params is a description of the positional parameters for the function.
// The standard checking logic rejects any calls that do not provide
// arguments conforming to this definition, freeing the function
// implementer from dealing with such inconsistencies.
Params []Parameter
// VarParam is an optional specification of additional "varargs" the
// function accepts. If this is non-nil then callers may provide an
// arbitrary number of additional arguments (after those matching with
// the fixed parameters in Params) that conform to the given specification,
// which will appear as additional values in the slices of values
// provided to the type and implementation functions.
VarParam *Parameter
// Type is the TypeFunc that decides the return type of the function
// given its arguments, which may be Unknown. See the documentation
// of TypeFunc for more information.
//
// Use StaticReturnType if the function's return type does not vary
// depending on its arguments.
Type TypeFunc
// Impl is the ImplFunc that implements the function's behavior.
//
// Functions are expected to behave as pure functions, and not create
// any visible side-effects.
//
// If a TypeFunc is also provided, the value returned from Impl *must*
// conform to the type it returns, or a call to the function will panic.
Impl ImplFunc
}
// New creates a new function with the given specification.
//
// After passing a Spec to this function, the caller must no longer read from
// or mutate it.
func New(spec *Spec) Function {
f := Function{
spec: spec,
}
return f
}
// TypeFunc is a callback type for determining the return type of a function
// given its arguments.
//
// Any of the values passed to this function may be unknown, even if the
// parameters are not configured to accept unknowns.
//
// If any of the given values are *not* unknown, the TypeFunc may use the
// values for pre-validation and for choosing the return type. For example,
// a hypothetical JSON-unmarshalling function could return
// cty.DynamicPseudoType if the given JSON string is unknown, but return
// a concrete type based on the JSON structure if the JSON string is already
// known.
type TypeFunc func(args []cty.Value) (cty.Type, error)
// ImplFunc is a callback type for the main implementation of a function.
//
// "args" are the values for the arguments, and this slice will always be at
// least as long as the argument definition slice for the function.
//
// "retType" is the type returned from the Type callback, included as a
// convenience to avoid the need to re-compute the return type for generic
// functions whose return type is a function of the arguments.
type ImplFunc func(args []cty.Value, retType cty.Type) (cty.Value, error)
// StaticReturnType returns a TypeFunc that always returns the given type.
//
// This is provided as a convenience for defining a function whose return
// type does not depend on the argument types.
func StaticReturnType(ty cty.Type) TypeFunc {
return func([]cty.Value) (cty.Type, error) {
return ty, nil
}
}
// ReturnType returns the return type of a function given a set of candidate
// argument types, or returns an error if the given types are unacceptable.
//
// If the caller already knows values for at least some of the arguments
// it can be better to call ReturnTypeForValues, since certain functions may
// determine their return types from their values and return DynamicVal if
// the values are unknown.
func (f Function) ReturnType(argTypes []cty.Type) (cty.Type, error) {
vals := make([]cty.Value, len(argTypes))
for i, ty := range argTypes {
vals[i] = cty.UnknownVal(ty)
}
return f.ReturnTypeForValues(vals)
}
// ReturnTypeForValues is similar to ReturnType but can be used if the caller
// already knows the values of some or all of the arguments, in which case
// the function may be able to determine a more definite result if its
// return type depends on the argument *values*.
//
// For any arguments whose values are not known, pass an Unknown value of
// the appropriate type.
func (f Function) ReturnTypeForValues(args []cty.Value) (ty cty.Type, err error) {
var posArgs []cty.Value
var varArgs []cty.Value
if f.spec.VarParam == nil {
if len(args) != len(f.spec.Params) {
return cty.Type{}, fmt.Errorf(
"wrong number of arguments (%d required; %d given)",
len(f.spec.Params), len(args),
)
}
posArgs = args
varArgs = nil
} else {
if len(args) < len(f.spec.Params) {
return cty.Type{}, fmt.Errorf(
"wrong number of arguments (at least %d required; %d given)",
len(f.spec.Params), len(args),
)
}
posArgs = args[0:len(f.spec.Params)]
varArgs = args[len(f.spec.Params):]
}
for i, spec := range f.spec.Params {
val := posArgs[i]
if val.ContainsMarked() && !spec.AllowMarked {
// During type checking we just unmark values and discard their
// marks, under the assumption that during actual execution of
// the function we'll do similarly and then re-apply the marks
// afterwards. Note that this does mean that a function that
// inspects values (rather than just types) in its Type
// implementation can potentially fail to take into account marks,
// unless it specifically opts in to seeing them.
unmarked, _ := val.UnmarkDeep()
newArgs := make([]cty.Value, len(args))
copy(newArgs, args)
newArgs[i] = unmarked
args = newArgs
}
if val.IsNull() && !spec.AllowNull {
return cty.Type{}, NewArgErrorf(i, "argument must not be null")
}
// AllowUnknown is ignored for type-checking, since we expect to be
// able to type check with unknown values. We *do* still need to deal
// with DynamicPseudoType here though, since the Type function might
// not be ready to deal with that.
if val.Type() == cty.DynamicPseudoType {
if !spec.AllowDynamicType {
return cty.DynamicPseudoType, nil
}
} else if errs := val.Type().TestConformance(spec.Type); errs != nil {
// For now we'll just return the first error in the set, since
// we don't have a good way to return the whole list here.
// Would be good to do something better at some point...
return cty.Type{}, NewArgError(i, errs[0])
}
}
if varArgs != nil {
spec := f.spec.VarParam
for i, val := range varArgs {
realI := i + len(posArgs)
if val.ContainsMarked() && !spec.AllowMarked {
// See the similar block in the loop above for what's going on here.
unmarked, _ := val.UnmarkDeep()
newArgs := make([]cty.Value, len(args))
copy(newArgs, args)
newArgs[realI] = unmarked
args = newArgs
}
if val.IsNull() && !spec.AllowNull {
return cty.Type{}, NewArgErrorf(realI, "argument must not be null")
}
if val.Type() == cty.DynamicPseudoType {
if !spec.AllowDynamicType {
return cty.DynamicPseudoType, nil
}
} else if errs := val.Type().TestConformance(spec.Type); errs != nil {
// For now we'll just return the first error in the set, since
// we don't have a good way to return the whole list here.
// Would be good to do something better at some point...
return cty.Type{}, NewArgError(i, errs[0])
}
}
}
// Intercept any panics from the function and return them as normal errors,
// so a calling language runtime doesn't need to deal with panics.
defer func() {
if r := recover(); r != nil {
ty = cty.NilType
err = errorForPanic(r)
}
}()
return f.spec.Type(args)
}
// Call actually calls the function with the given arguments, which must
// conform to the function's parameter specification or an error will be
// returned.
func (f Function) Call(args []cty.Value) (val cty.Value, err error) {
expectedType, err := f.ReturnTypeForValues(args)
if err != nil {
return cty.NilVal, err
}
// Type checking already dealt with most situations relating to our
// parameter specification, but we still need to deal with unknown
// values and marked values.
posArgs := args[:len(f.spec.Params)]
varArgs := args[len(f.spec.Params):]
var resultMarks []cty.ValueMarks
for i, spec := range f.spec.Params {
val := posArgs[i]
if !val.IsKnown() && !spec.AllowUnknown {
return cty.UnknownVal(expectedType), nil
}
if !spec.AllowMarked {
unwrappedVal, marks := val.UnmarkDeep()
if len(marks) > 0 {
// In order to avoid additional overhead on applications that
// are not using marked values, we copy the given args only
// if we encounter a marked value we need to unmark. However,
// as a consequence we end up doing redundant copying if multiple
// marked values need to be unwrapped. That seems okay because
// argument lists are generally small.
newArgs := make([]cty.Value, len(args))
copy(newArgs, args)
newArgs[i] = unwrappedVal
resultMarks = append(resultMarks, marks)
args = newArgs
}
}
}
if f.spec.VarParam != nil {
spec := f.spec.VarParam
for i, val := range varArgs {
if !val.IsKnown() && !spec.AllowUnknown {
return cty.UnknownVal(expectedType), nil
}
if !spec.AllowMarked {
unwrappedVal, marks := val.UnmarkDeep()
if len(marks) > 0 {
newArgs := make([]cty.Value, len(args))
copy(newArgs, args)
newArgs[len(posArgs)+i] = unwrappedVal
resultMarks = append(resultMarks, marks)
args = newArgs
}
}
}
}
var retVal cty.Value
{
// Intercept any panics from the function and return them as normal errors,
// so a calling language runtime doesn't need to deal with panics.
defer func() {
if r := recover(); r != nil {
val = cty.NilVal
err = errorForPanic(r)
}
}()
retVal, err = f.spec.Impl(args, expectedType)
if err != nil {
return cty.NilVal, err
}
if len(resultMarks) > 0 {
retVal = retVal.WithMarks(resultMarks...)
}
}
// Returned value must conform to what the Type function expected, to
// protect callers from having to deal with inconsistencies.
if errs := retVal.Type().TestConformance(expectedType); errs != nil {
panic(fmt.Errorf(
"returned value %#v does not conform to expected return type %#v: %s",
retVal, expectedType, errs[0],
))
}
return retVal, nil
}
// ProxyFunc the type returned by the method Function.Proxy.
type ProxyFunc func(args ...cty.Value) (cty.Value, error)
// Proxy returns a function that can be called with cty.Value arguments
// to run the function. This is provided as a convenience for when using
// a function directly within Go code.
func (f Function) Proxy() ProxyFunc {
return func(args ...cty.Value) (cty.Value, error) {
return f.Call(args)
}
}
// Params returns information about the function's fixed positional parameters.
// This does not include information about any variadic arguments accepted;
// for that, call VarParam.
func (f Function) Params() []Parameter {
new := make([]Parameter, len(f.spec.Params))
copy(new, f.spec.Params)
return new
}
// VarParam returns information about the variadic arguments the function
// expects, or nil if the function is not variadic.
func (f Function) VarParam() *Parameter {
if f.spec.VarParam == nil {
return nil
}
ret := *f.spec.VarParam
return &ret
}

View File

@ -0,0 +1,78 @@
package stdlib
import (
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
)
var NotFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "val",
Type: cty.Bool,
AllowDynamicType: true,
AllowMarked: true,
},
},
Type: function.StaticReturnType(cty.Bool),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
return args[0].Not(), nil
},
})
var AndFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "a",
Type: cty.Bool,
AllowDynamicType: true,
AllowMarked: true,
},
{
Name: "b",
Type: cty.Bool,
AllowDynamicType: true,
AllowMarked: true,
},
},
Type: function.StaticReturnType(cty.Bool),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
return args[0].And(args[1]), nil
},
})
var OrFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "a",
Type: cty.Bool,
AllowDynamicType: true,
AllowMarked: true,
},
{
Name: "b",
Type: cty.Bool,
AllowDynamicType: true,
AllowMarked: true,
},
},
Type: function.StaticReturnType(cty.Bool),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
return args[0].Or(args[1]), nil
},
})
// Not returns the logical complement of the given boolean value.
func Not(num cty.Value) (cty.Value, error) {
return NotFunc.Call([]cty.Value{num})
}
// And returns true if and only if both of the given boolean values are true.
func And(a, b cty.Value) (cty.Value, error) {
return AndFunc.Call([]cty.Value{a, b})
}
// Or returns true if either of the given boolean values are true.
func Or(a, b cty.Value) (cty.Value, error) {
return OrFunc.Call([]cty.Value{a, b})
}

View File

@ -0,0 +1,112 @@
package stdlib
import (
"fmt"
"reflect"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
"github.com/zclconf/go-cty/cty/gocty"
)
// Bytes is a capsule type that can be used with the binary functions to
// support applications that need to support raw buffers in addition to
// UTF-8 strings.
var Bytes = cty.Capsule("bytes", reflect.TypeOf([]byte(nil)))
// BytesVal creates a new Bytes value from the given buffer, which must be
// non-nil or this function will panic.
//
// Once a byte slice has been wrapped in a Bytes capsule, its underlying array
// must be considered immutable.
func BytesVal(buf []byte) cty.Value {
if buf == nil {
panic("can't make Bytes value from nil slice")
}
return cty.CapsuleVal(Bytes, &buf)
}
// BytesLen is a Function that returns the length of the buffer encapsulated
// in a Bytes value.
var BytesLenFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "buf",
Type: Bytes,
AllowDynamicType: true,
},
},
Type: function.StaticReturnType(cty.Number),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
bufPtr := args[0].EncapsulatedValue().(*[]byte)
return cty.NumberIntVal(int64(len(*bufPtr))), nil
},
})
// BytesSlice is a Function that returns a slice of the given Bytes value.
var BytesSliceFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "buf",
Type: Bytes,
AllowDynamicType: true,
},
{
Name: "offset",
Type: cty.Number,
AllowDynamicType: true,
},
{
Name: "length",
Type: cty.Number,
AllowDynamicType: true,
},
},
Type: function.StaticReturnType(Bytes),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
bufPtr := args[0].EncapsulatedValue().(*[]byte)
var offset, length int
var err error
err = gocty.FromCtyValue(args[1], &offset)
if err != nil {
return cty.NilVal, err
}
err = gocty.FromCtyValue(args[2], &length)
if err != nil {
return cty.NilVal, err
}
if offset < 0 || length < 0 {
return cty.NilVal, fmt.Errorf("offset and length must be non-negative")
}
if offset > len(*bufPtr) {
return cty.NilVal, fmt.Errorf(
"offset %d is greater than total buffer length %d",
offset, len(*bufPtr),
)
}
end := offset + length
if end > len(*bufPtr) {
return cty.NilVal, fmt.Errorf(
"offset %d + length %d is greater than total buffer length %d",
offset, length, len(*bufPtr),
)
}
return BytesVal((*bufPtr)[offset:end]), nil
},
})
func BytesLen(buf cty.Value) (cty.Value, error) {
return BytesLenFunc.Call([]cty.Value{buf})
}
func BytesSlice(buf cty.Value, offset cty.Value, length cty.Value) (cty.Value, error) {
return BytesSliceFunc.Call([]cty.Value{buf, offset, length})
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,87 @@
package stdlib
import (
"strconv"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert"
"github.com/zclconf/go-cty/cty/function"
)
// MakeToFunc constructs a "to..." function, like "tostring", which converts
// its argument to a specific type or type kind.
//
// The given type wantTy can be any type constraint that cty's "convert" package
// would accept. In particular, this means that you can pass
// cty.List(cty.DynamicPseudoType) to mean "list of any single type", which
// will then cause cty to attempt to unify all of the element types when given
// a tuple.
func MakeToFunc(wantTy cty.Type) function.Function {
return function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "v",
// We use DynamicPseudoType rather than wantTy here so that
// all values will pass through the function API verbatim and
// we can handle the conversion logic within the Type and
// Impl functions. This allows us to customize the error
// messages to be more appropriate for an explicit type
// conversion, whereas the cty function system produces
// messages aimed at _implicit_ type conversions.
Type: cty.DynamicPseudoType,
AllowNull: true,
},
},
Type: func(args []cty.Value) (cty.Type, error) {
gotTy := args[0].Type()
if gotTy.Equals(wantTy) {
return wantTy, nil
}
conv := convert.GetConversionUnsafe(args[0].Type(), wantTy)
if conv == nil {
// We'll use some specialized errors for some trickier cases,
// but most we can handle in a simple way.
switch {
case gotTy.IsTupleType() && wantTy.IsTupleType():
return cty.NilType, function.NewArgErrorf(0, "incompatible tuple type for conversion: %s", convert.MismatchMessage(gotTy, wantTy))
case gotTy.IsObjectType() && wantTy.IsObjectType():
return cty.NilType, function.NewArgErrorf(0, "incompatible object type for conversion: %s", convert.MismatchMessage(gotTy, wantTy))
default:
return cty.NilType, function.NewArgErrorf(0, "cannot convert %s to %s", gotTy.FriendlyName(), wantTy.FriendlyNameForConstraint())
}
}
// If a conversion is available then everything is fine.
return wantTy, nil
},
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
// We didn't set "AllowUnknown" on our argument, so it is guaranteed
// to be known here but may still be null.
ret, err := convert.Convert(args[0], retType)
if err != nil {
// Because we used GetConversionUnsafe above, conversion can
// still potentially fail in here. For example, if the user
// asks to convert the string "a" to bool then we'll
// optimistically permit it during type checking but fail here
// once we note that the value isn't either "true" or "false".
gotTy := args[0].Type()
switch {
case gotTy == cty.String && wantTy == cty.Bool:
what := "string"
if !args[0].IsNull() {
what = strconv.Quote(args[0].AsString())
}
return cty.NilVal, function.NewArgErrorf(0, `cannot convert %s to bool; only the strings "true" or "false" are allowed`, what)
case gotTy == cty.String && wantTy == cty.Number:
what := "string"
if !args[0].IsNull() {
what = strconv.Quote(args[0].AsString())
}
return cty.NilVal, function.NewArgErrorf(0, `cannot convert %s to number; given string must be a decimal representation of a number`, what)
default:
return cty.NilVal, function.NewArgErrorf(0, "cannot convert %s to %s", gotTy.FriendlyName(), wantTy.FriendlyNameForConstraint())
}
}
return ret, nil
},
})
}

View File

@ -0,0 +1,102 @@
package stdlib
import (
"encoding/csv"
"fmt"
"io"
"strings"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
)
var CSVDecodeFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "str",
Type: cty.String,
},
},
Type: func(args []cty.Value) (cty.Type, error) {
str := args[0]
if !str.IsKnown() {
return cty.DynamicPseudoType, nil
}
r := strings.NewReader(str.AsString())
cr := csv.NewReader(r)
headers, err := cr.Read()
if err == io.EOF {
return cty.DynamicPseudoType, fmt.Errorf("missing header line")
}
if err != nil {
return cty.DynamicPseudoType, csvError(err)
}
atys := make(map[string]cty.Type, len(headers))
for _, name := range headers {
if _, exists := atys[name]; exists {
return cty.DynamicPseudoType, fmt.Errorf("duplicate column name %q", name)
}
atys[name] = cty.String
}
return cty.List(cty.Object(atys)), nil
},
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
ety := retType.ElementType()
atys := ety.AttributeTypes()
str := args[0]
r := strings.NewReader(str.AsString())
cr := csv.NewReader(r)
cr.FieldsPerRecord = len(atys)
// Read the header row first, since that'll tell us which indices
// map to which attribute names.
headers, err := cr.Read()
if err != nil {
return cty.DynamicVal, err
}
var rows []cty.Value
for {
cols, err := cr.Read()
if err == io.EOF {
break
}
if err != nil {
return cty.DynamicVal, csvError(err)
}
vals := make(map[string]cty.Value, len(cols))
for i, str := range cols {
name := headers[i]
vals[name] = cty.StringVal(str)
}
rows = append(rows, cty.ObjectVal(vals))
}
if len(rows) == 0 {
return cty.ListValEmpty(ety), nil
}
return cty.ListVal(rows), nil
},
})
// CSVDecode parses the given CSV (RFC 4180) string and, if it is valid,
// returns a list of objects representing the rows.
//
// The result is always a list of some object type. The first row of the
// input is used to determine the object attributes, and subsequent rows
// determine the values of those attributes.
func CSVDecode(str cty.Value) (cty.Value, error) {
return CSVDecodeFunc.Call([]cty.Value{str})
}
func csvError(err error) error {
switch err := err.(type) {
case *csv.ParseError:
return fmt.Errorf("CSV parse error on line %d: %w", err.Line, err.Err)
default:
return err
}
}

View File

@ -0,0 +1,434 @@
package stdlib
import (
"bufio"
"bytes"
"fmt"
"strings"
"time"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
)
var FormatDateFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "format",
Type: cty.String,
},
{
Name: "time",
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
formatStr := args[0].AsString()
timeStr := args[1].AsString()
t, err := parseTimestamp(timeStr)
if err != nil {
return cty.DynamicVal, function.NewArgError(1, err)
}
var buf bytes.Buffer
sc := bufio.NewScanner(strings.NewReader(formatStr))
sc.Split(splitDateFormat)
const esc = '\''
for sc.Scan() {
tok := sc.Bytes()
// The leading byte signals the token type
switch {
case tok[0] == esc:
if tok[len(tok)-1] != esc || len(tok) == 1 {
return cty.DynamicVal, function.NewArgErrorf(0, "unterminated literal '")
}
if len(tok) == 2 {
// Must be a single escaped quote, ''
buf.WriteByte(esc)
} else {
// The content (until a closing esc) is printed out verbatim
// except that we must un-double any double-esc escapes in
// the middle of the string.
raw := tok[1 : len(tok)-1]
for i := 0; i < len(raw); i++ {
buf.WriteByte(raw[i])
if raw[i] == esc {
i++ // skip the escaped quote
}
}
}
case startsDateFormatVerb(tok[0]):
switch tok[0] {
case 'Y':
y := t.Year()
switch len(tok) {
case 2:
fmt.Fprintf(&buf, "%02d", y%100)
case 4:
fmt.Fprintf(&buf, "%04d", y)
default:
return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q: year must either be \"YY\" or \"YYYY\"", tok)
}
case 'M':
m := t.Month()
switch len(tok) {
case 1:
fmt.Fprintf(&buf, "%d", m)
case 2:
fmt.Fprintf(&buf, "%02d", m)
case 3:
buf.WriteString(m.String()[:3])
case 4:
buf.WriteString(m.String())
default:
return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q: month must be \"M\", \"MM\", \"MMM\", or \"MMMM\"", tok)
}
case 'D':
d := t.Day()
switch len(tok) {
case 1:
fmt.Fprintf(&buf, "%d", d)
case 2:
fmt.Fprintf(&buf, "%02d", d)
default:
return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q: day of month must either be \"D\" or \"DD\"", tok)
}
case 'E':
d := t.Weekday()
switch len(tok) {
case 3:
buf.WriteString(d.String()[:3])
case 4:
buf.WriteString(d.String())
default:
return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q: day of week must either be \"EEE\" or \"EEEE\"", tok)
}
case 'h':
h := t.Hour()
switch len(tok) {
case 1:
fmt.Fprintf(&buf, "%d", h)
case 2:
fmt.Fprintf(&buf, "%02d", h)
default:
return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q: 24-hour must either be \"h\" or \"hh\"", tok)
}
case 'H':
h := t.Hour() % 12
if h == 0 {
h = 12
}
switch len(tok) {
case 1:
fmt.Fprintf(&buf, "%d", h)
case 2:
fmt.Fprintf(&buf, "%02d", h)
default:
return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q: 12-hour must either be \"H\" or \"HH\"", tok)
}
case 'A', 'a':
if len(tok) != 2 {
return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q: must be \"%s%s\"", tok, tok[0:1], tok[0:1])
}
upper := tok[0] == 'A'
switch t.Hour() / 12 {
case 0:
if upper {
buf.WriteString("AM")
} else {
buf.WriteString("am")
}
case 1:
if upper {
buf.WriteString("PM")
} else {
buf.WriteString("pm")
}
}
case 'm':
m := t.Minute()
switch len(tok) {
case 1:
fmt.Fprintf(&buf, "%d", m)
case 2:
fmt.Fprintf(&buf, "%02d", m)
default:
return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q: minute must either be \"m\" or \"mm\"", tok)
}
case 's':
s := t.Second()
switch len(tok) {
case 1:
fmt.Fprintf(&buf, "%d", s)
case 2:
fmt.Fprintf(&buf, "%02d", s)
default:
return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q: second must either be \"s\" or \"ss\"", tok)
}
case 'Z':
// We'll just lean on Go's own formatter for this one, since
// the necessary information is unexported.
switch len(tok) {
case 1:
buf.WriteString(t.Format("Z07:00"))
case 3:
str := t.Format("-0700")
switch str {
case "+0000":
buf.WriteString("UTC")
default:
buf.WriteString(str)
}
case 4:
buf.WriteString(t.Format("-0700"))
case 5:
buf.WriteString(t.Format("-07:00"))
default:
return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q: timezone must be Z, ZZZZ, or ZZZZZ", tok)
}
default:
return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q", tok)
}
default:
// Any other starting character indicates a literal sequence
buf.Write(tok)
}
}
return cty.StringVal(buf.String()), nil
},
})
// TimeAddFunc is a function that adds a duration to a timestamp, returning a new timestamp.
var TimeAddFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "timestamp",
Type: cty.String,
},
{
Name: "duration",
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
ts, err := parseTimestamp(args[0].AsString())
if err != nil {
return cty.UnknownVal(cty.String), err
}
duration, err := time.ParseDuration(args[1].AsString())
if err != nil {
return cty.UnknownVal(cty.String), err
}
return cty.StringVal(ts.Add(duration).Format(time.RFC3339)), nil
},
})
// FormatDate reformats a timestamp given in RFC3339 syntax into another time
// syntax defined by a given format string.
//
// The format string uses letter mnemonics to represent portions of the
// timestamp, with repetition signifying length variants of each portion.
// Single quote characters ' can be used to quote sequences of literal letters
// that should not be interpreted as formatting mnemonics.
//
// The full set of supported mnemonic sequences is listed below:
//
// YY Year modulo 100 zero-padded to two digits, like "06".
// YYYY Four (or more) digit year, like "2006".
// M Month number, like "1" for January.
// MM Month number zero-padded to two digits, like "01".
// MMM English month name abbreviated to three letters, like "Jan".
// MMMM English month name unabbreviated, like "January".
// D Day of month number, like "2".
// DD Day of month number zero-padded to two digits, like "02".
// EEE English day of week name abbreviated to three letters, like "Mon".
// EEEE English day of week name unabbreviated, like "Monday".
// h 24-hour number, like "2".
// hh 24-hour number zero-padded to two digits, like "02".
// H 12-hour number, like "2".
// HH 12-hour number zero-padded to two digits, like "02".
// AA Hour AM/PM marker in uppercase, like "AM".
// aa Hour AM/PM marker in lowercase, like "am".
// m Minute within hour, like "5".
// mm Minute within hour zero-padded to two digits, like "05".
// s Second within minute, like "9".
// ss Second within minute zero-padded to two digits, like "09".
// ZZZZ Timezone offset with just sign and digit, like "-0800".
// ZZZZZ Timezone offset with colon separating hours and minutes, like "-08:00".
// Z Like ZZZZZ but with a special case "Z" for UTC.
// ZZZ Like ZZZZ but with a special case "UTC" for UTC.
//
// The format syntax is optimized mainly for generating machine-oriented
// timestamps rather than human-oriented timestamps; the English language
// portions of the output reflect the use of English names in a number of
// machine-readable date formatting standards. For presentation to humans,
// a locale-aware time formatter (not included in this package) is a better
// choice.
//
// The format syntax is not compatible with that of any other language, but
// is optimized so that patterns for common standard date formats can be
// recognized quickly even by a reader unfamiliar with the format syntax.
func FormatDate(format cty.Value, timestamp cty.Value) (cty.Value, error) {
return FormatDateFunc.Call([]cty.Value{format, timestamp})
}
func parseTimestamp(ts string) (time.Time, error) {
t, err := time.Parse(time.RFC3339, ts)
if err != nil {
switch err := err.(type) {
case *time.ParseError:
// If err is s time.ParseError then its string representation is not
// appropriate since it relies on details of Go's strange date format
// representation, which a caller of our functions is not expected
// to be familiar with.
//
// Therefore we do some light transformation to get a more suitable
// error that should make more sense to our callers. These are
// still not awesome error messages, but at least they refer to
// the timestamp portions by name rather than by Go's example
// values.
if err.LayoutElem == "" && err.ValueElem == "" && err.Message != "" {
// For some reason err.Message is populated with a ": " prefix
// by the time package.
return time.Time{}, fmt.Errorf("not a valid RFC3339 timestamp%s", err.Message)
}
var what string
switch err.LayoutElem {
case "2006":
what = "year"
case "01":
what = "month"
case "02":
what = "day of month"
case "15":
what = "hour"
case "04":
what = "minute"
case "05":
what = "second"
case "Z07:00":
what = "UTC offset"
case "T":
return time.Time{}, fmt.Errorf("not a valid RFC3339 timestamp: missing required time introducer 'T'")
case ":", "-":
if err.ValueElem == "" {
return time.Time{}, fmt.Errorf("not a valid RFC3339 timestamp: end of string where %q is expected", err.LayoutElem)
} else {
return time.Time{}, fmt.Errorf("not a valid RFC3339 timestamp: found %q where %q is expected", err.ValueElem, err.LayoutElem)
}
default:
// Should never get here, because time.RFC3339 includes only the
// above portions, but since that might change in future we'll
// be robust here.
what = "timestamp segment"
}
if err.ValueElem == "" {
return time.Time{}, fmt.Errorf("not a valid RFC3339 timestamp: end of string before %s", what)
} else {
return time.Time{}, fmt.Errorf("not a valid RFC3339 timestamp: cannot use %q as %s", err.ValueElem, what)
}
}
return time.Time{}, err
}
return t, nil
}
// splitDataFormat is a bufio.SplitFunc used to tokenize a date format.
func splitDateFormat(data []byte, atEOF bool) (advance int, token []byte, err error) {
if len(data) == 0 {
return 0, nil, nil
}
const esc = '\''
switch {
case data[0] == esc:
// If we have another quote immediately after then this is a single
// escaped escape.
if len(data) > 1 && data[1] == esc {
return 2, data[:2], nil
}
// Beginning of quoted sequence, so we will seek forward until we find
// the closing quote, ignoring escaped quotes along the way.
for i := 1; i < len(data); i++ {
if data[i] == esc {
if (i + 1) == len(data) {
if atEOF {
// We have a closing quote and are at the end of our input
return len(data), data, nil
} else {
// We need at least one more byte to decide if this is an
// escape or a terminator.
return 0, nil, nil
}
}
if data[i+1] == esc {
i++ // doubled-up quotes are an escape sequence
continue
}
// We've found the closing quote
return i + 1, data[:i+1], nil
}
}
// If we fall out here then we need more bytes to find the end,
// unless we're already at the end with an unclosed quote.
if atEOF {
return len(data), data, nil
}
return 0, nil, nil
case startsDateFormatVerb(data[0]):
rep := data[0]
for i := 1; i < len(data); i++ {
if data[i] != rep {
return i, data[:i], nil
}
}
if atEOF {
return len(data), data, nil
}
// We need more data to decide if we've found the end
return 0, nil, nil
default:
for i := 1; i < len(data); i++ {
if data[i] == esc || startsDateFormatVerb(data[i]) {
return i, data[:i], nil
}
}
// We might not actually be at the end of a literal sequence,
// but that doesn't matter since we'll concat them back together
// anyway.
return len(data), data, nil
}
}
func startsDateFormatVerb(b byte) bool {
return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z')
}
// TimeAdd adds a duration to a timestamp, returning a new timestamp.
//
// In the HCL language, timestamps are conventionally represented as
// strings using RFC 3339 "Date and Time format" syntax. Timeadd requires
// the timestamp argument to be a string conforming to this syntax.
//
// `duration` is a string representation of a time difference, consisting of
// sequences of number and unit pairs, like `"1.5h"` or `1h30m`. The accepted
// units are `ns`, `us` (or `µs`), `"ms"`, `"s"`, `"m"`, and `"h"`. The first
// number may be negative to indicate a negative duration, like `"-2h5m"`.
//
// The result is a string, also in RFC 3339 format, representing the result
// of adding the given direction to the given timestamp.
func TimeAdd(timestamp cty.Value, duration cty.Value) (cty.Value, error) {
return TimeAddFunc.Call([]cty.Value{timestamp, duration})
}

View File

@ -0,0 +1,13 @@
// Package stdlib is a collection of cty functions that are expected to be
// generally useful, and are thus factored out into this shared library in
// the hope that cty-using applications will have consistent behavior when
// using these functions.
//
// See the parent package "function" for more information on the purpose
// and usage of cty functions.
//
// This package contains both Go functions, which provide convenient access
// to call the functions from Go code, and the Function objects themselves.
// The latter follow the naming scheme of appending "Func" to the end of
// the function name.
package stdlib

View File

@ -0,0 +1,519 @@
package stdlib
import (
"bytes"
"fmt"
"math/big"
"strings"
"github.com/apparentlymart/go-textseg/v13/textseg"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert"
"github.com/zclconf/go-cty/cty/function"
"github.com/zclconf/go-cty/cty/json"
)
//go:generate ragel -Z format_fsm.rl
//go:generate gofmt -w format_fsm.go
var FormatFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "format",
Type: cty.String,
},
},
VarParam: &function.Parameter{
Name: "args",
Type: cty.DynamicPseudoType,
AllowNull: true,
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
for _, arg := range args[1:] {
if !arg.IsWhollyKnown() {
// We require all nested values to be known because the only
// thing we can do for a collection/structural type is print
// it as JSON and that requires it to be wholly known.
return cty.UnknownVal(cty.String), nil
}
}
str, err := formatFSM(args[0].AsString(), args[1:])
return cty.StringVal(str), err
},
})
var FormatListFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "format",
Type: cty.String,
},
},
VarParam: &function.Parameter{
Name: "args",
Type: cty.DynamicPseudoType,
AllowNull: true,
AllowUnknown: true,
},
Type: function.StaticReturnType(cty.List(cty.String)),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
fmtVal := args[0]
args = args[1:]
if len(args) == 0 {
// With no arguments, this function is equivalent to Format, but
// returning a single-element list result.
result, err := Format(fmtVal, args...)
return cty.ListVal([]cty.Value{result}), err
}
fmtStr := fmtVal.AsString()
// Each of our arguments will be dealt with either as an iterator
// or as a single value. Iterators are used for sequence-type values
// (lists, sets, tuples) while everything else is treated as a
// single value. The sequences we iterate over are required to be
// all the same length.
iterLen := -1
lenChooser := -1
iterators := make([]cty.ElementIterator, len(args))
singleVals := make([]cty.Value, len(args))
unknowns := make([]bool, len(args))
for i, arg := range args {
argTy := arg.Type()
switch {
case (argTy.IsListType() || argTy.IsSetType() || argTy.IsTupleType()) && !arg.IsNull():
if !argTy.IsTupleType() && !(arg.IsKnown() && arg.Length().IsKnown()) {
// We can't iterate this one at all yet then, so we can't
// yet produce a result.
unknowns[i] = true
continue
}
thisLen := arg.LengthInt()
if iterLen == -1 {
iterLen = thisLen
lenChooser = i
} else {
if thisLen != iterLen {
return cty.NullVal(cty.List(cty.String)), function.NewArgErrorf(
i+1,
"argument %d has length %d, which is inconsistent with argument %d of length %d",
i+1, thisLen,
lenChooser+1, iterLen,
)
}
}
if !arg.IsKnown() {
// We allowed an unknown tuple value to fall through in
// our initial check above so that we'd be able to run
// the above error checks against it, but we still can't
// iterate it if the checks pass.
unknowns[i] = true
continue
}
iterators[i] = arg.ElementIterator()
case arg == cty.DynamicVal:
unknowns[i] = true
default:
singleVals[i] = arg
}
}
for _, isUnk := range unknowns {
if isUnk {
return cty.UnknownVal(retType), nil
}
}
if iterLen == 0 {
// If our sequences are all empty then our result must be empty.
return cty.ListValEmpty(cty.String), nil
}
if iterLen == -1 {
// If we didn't encounter any iterables at all then we're going
// to just do one iteration with items from singleVals.
iterLen = 1
}
ret := make([]cty.Value, 0, iterLen)
fmtArgs := make([]cty.Value, len(iterators))
Results:
for iterIdx := 0; iterIdx < iterLen; iterIdx++ {
// Construct our arguments for a single format call
for i := range fmtArgs {
switch {
case iterators[i] != nil:
iterator := iterators[i]
iterator.Next()
_, val := iterator.Element()
fmtArgs[i] = val
default:
fmtArgs[i] = singleVals[i]
}
// If any of the arguments to this call would be unknown then
// this particular result is unknown, but we'll keep going
// to see if any other iterations can produce known values.
if !fmtArgs[i].IsWhollyKnown() {
// We require all nested values to be known because the only
// thing we can do for a collection/structural type is print
// it as JSON and that requires it to be wholly known.
ret = append(ret, cty.UnknownVal(cty.String))
continue Results
}
}
str, err := formatFSM(fmtStr, fmtArgs)
if err != nil {
return cty.NullVal(cty.List(cty.String)), fmt.Errorf(
"error on format iteration %d: %s", iterIdx, err,
)
}
ret = append(ret, cty.StringVal(str))
}
return cty.ListVal(ret), nil
},
})
// Format produces a string representation of zero or more values using a
// format string similar to the "printf" function in C.
//
// It supports the following "verbs":
//
// %% Literal percent sign, consuming no value
// %v A default formatting of the value based on type, as described below.
// %#v JSON serialization of the value
// %t Converts to boolean and then produces "true" or "false"
// %b Converts to number, requires integer, produces binary representation
// %d Converts to number, requires integer, produces decimal representation
// %o Converts to number, requires integer, produces octal representation
// %x Converts to number, requires integer, produces hexadecimal representation
// with lowercase letters
// %X Like %x but with uppercase letters
// %e Converts to number, produces scientific notation like -1.234456e+78
// %E Like %e but with an uppercase "E" representing the exponent
// %f Converts to number, produces decimal representation with fractional
// part but no exponent, like 123.456
// %g %e for large exponents or %f otherwise
// %G %E for large exponents or %f otherwise
// %s Converts to string and produces the string's characters
// %q Converts to string and produces JSON-quoted string representation,
// like %v.
//
// The default format selections made by %v are:
//
// string %s
// number %g
// bool %t
// other %#v
//
// Null values produce the literal keyword "null" for %v and %#v, and produce
// an error otherwise.
//
// Width is specified by an optional decimal number immediately preceding the
// verb letter. If absent, the width is whatever is necessary to represent the
// value. Precision is specified after the (optional) width by a period
// followed by a decimal number. If no period is present, a default precision
// is used. A period with no following number is invalid.
// For examples:
//
// %f default width, default precision
// %9f width 9, default precision
// %.2f default width, precision 2
// %9.2f width 9, precision 2
//
// Width and precision are measured in unicode characters (grapheme clusters).
//
// For most values, width is the minimum number of characters to output,
// padding the formatted form with spaces if necessary.
//
// For strings, precision limits the length of the input to be formatted (not
// the size of the output), truncating if necessary.
//
// For numbers, width sets the minimum width of the field and precision sets
// the number of places after the decimal, if appropriate, except that for
// %g/%G precision sets the total number of significant digits.
//
// The following additional symbols can be used immediately after the percent
// introducer as flags:
//
// (a space) leave a space where the sign would be if number is positive
// + Include a sign for a number even if it is positive (numeric only)
// - Pad with spaces on the left rather than the right
// 0 Pad with zeros rather than spaces.
//
// Flag characters are ignored for verbs that do not support them.
//
// By default, % sequences consume successive arguments starting with the first.
// Introducing a [n] sequence immediately before the verb letter, where n is a
// decimal integer, explicitly chooses a particular value argument by its
// one-based index. Subsequent calls without an explicit index will then
// proceed with n+1, n+2, etc.
//
// An error is produced if the format string calls for an impossible conversion
// or accesses more values than are given. An error is produced also for
// an unsupported format verb.
func Format(format cty.Value, vals ...cty.Value) (cty.Value, error) {
args := make([]cty.Value, 0, len(vals)+1)
args = append(args, format)
args = append(args, vals...)
return FormatFunc.Call(args)
}
// FormatList applies the same formatting behavior as Format, but accepts
// a mixture of list and non-list values as arguments. Any list arguments
// passed must have the same length, which dictates the length of the
// resulting list.
//
// Any non-list arguments are used repeatedly for each iteration over the
// list arguments. The list arguments are iterated in order by key, so
// corresponding items are formatted together.
func FormatList(format cty.Value, vals ...cty.Value) (cty.Value, error) {
args := make([]cty.Value, 0, len(vals)+1)
args = append(args, format)
args = append(args, vals...)
return FormatListFunc.Call(args)
}
type formatVerb struct {
Raw string
Offset int
ArgNum int
Mode rune
Zero bool
Sharp bool
Plus bool
Minus bool
Space bool
HasPrec bool
Prec int
HasWidth bool
Width int
}
// formatAppend is called by formatFSM (generated by format_fsm.rl) for each
// formatting sequence that is encountered.
func formatAppend(verb *formatVerb, buf *bytes.Buffer, args []cty.Value) error {
argIdx := verb.ArgNum - 1
if argIdx >= len(args) {
return fmt.Errorf(
"not enough arguments for %q at %d: need index %d but have %d total",
verb.Raw, verb.Offset,
verb.ArgNum, len(args),
)
}
arg := args[argIdx]
if verb.Mode != 'v' && arg.IsNull() {
return fmt.Errorf("unsupported value for %q at %d: null value cannot be formatted", verb.Raw, verb.Offset)
}
// Normalize to make some things easier for downstream formatters
if !verb.HasWidth {
verb.Width = -1
}
if !verb.HasPrec {
verb.Prec = -1
}
// For our first pass we'll ensure the verb is supported and then fan
// out to other functions based on what conversion is needed.
switch verb.Mode {
case 'v':
return formatAppendAsIs(verb, buf, arg)
case 't':
return formatAppendBool(verb, buf, arg)
case 'b', 'd', 'o', 'x', 'X', 'e', 'E', 'f', 'g', 'G':
return formatAppendNumber(verb, buf, arg)
case 's', 'q':
return formatAppendString(verb, buf, arg)
default:
return fmt.Errorf("unsupported format verb %q in %q at offset %d", verb.Mode, verb.Raw, verb.Offset)
}
}
func formatAppendAsIs(verb *formatVerb, buf *bytes.Buffer, arg cty.Value) error {
if !verb.Sharp && !arg.IsNull() {
// Unless the caller overrode it with the sharp flag, we'll try some
// specialized formats before we fall back on JSON.
switch arg.Type() {
case cty.String:
fmted := arg.AsString()
fmted = formatPadWidth(verb, fmted)
buf.WriteString(fmted)
return nil
case cty.Number:
bf := arg.AsBigFloat()
fmted := bf.Text('g', -1)
fmted = formatPadWidth(verb, fmted)
buf.WriteString(fmted)
return nil
}
}
jb, err := json.Marshal(arg, arg.Type())
if err != nil {
return fmt.Errorf("unsupported value for %q at %d: %s", verb.Raw, verb.Offset, err)
}
fmted := formatPadWidth(verb, string(jb))
buf.WriteString(fmted)
return nil
}
func formatAppendBool(verb *formatVerb, buf *bytes.Buffer, arg cty.Value) error {
var err error
arg, err = convert.Convert(arg, cty.Bool)
if err != nil {
return fmt.Errorf("unsupported value for %q at %d: %s", verb.Raw, verb.Offset, err)
}
if arg.True() {
buf.WriteString("true")
} else {
buf.WriteString("false")
}
return nil
}
func formatAppendNumber(verb *formatVerb, buf *bytes.Buffer, arg cty.Value) error {
var err error
arg, err = convert.Convert(arg, cty.Number)
if err != nil {
return fmt.Errorf("unsupported value for %q at %d: %s", verb.Raw, verb.Offset, err)
}
switch verb.Mode {
case 'b', 'd', 'o', 'x', 'X':
return formatAppendInteger(verb, buf, arg)
default:
bf := arg.AsBigFloat()
// For floats our format syntax is a subset of Go's, so it's
// safe for us to just lean on the existing Go implementation.
fmtstr := formatStripIndexSegment(verb.Raw)
fmted := fmt.Sprintf(fmtstr, bf)
buf.WriteString(fmted)
return nil
}
}
func formatAppendInteger(verb *formatVerb, buf *bytes.Buffer, arg cty.Value) error {
bf := arg.AsBigFloat()
bi, acc := bf.Int(nil)
if acc != big.Exact {
return fmt.Errorf("unsupported value for %q at %d: an integer is required", verb.Raw, verb.Offset)
}
// For integers our format syntax is a subset of Go's, so it's
// safe for us to just lean on the existing Go implementation.
fmtstr := formatStripIndexSegment(verb.Raw)
fmted := fmt.Sprintf(fmtstr, bi)
buf.WriteString(fmted)
return nil
}
func formatAppendString(verb *formatVerb, buf *bytes.Buffer, arg cty.Value) error {
var err error
arg, err = convert.Convert(arg, cty.String)
if err != nil {
return fmt.Errorf("unsupported value for %q at %d: %s", verb.Raw, verb.Offset, err)
}
// We _cannot_ directly use the Go fmt.Sprintf implementation for strings
// because it measures widths and precisions in runes rather than grapheme
// clusters.
str := arg.AsString()
if verb.Prec > 0 {
strB := []byte(str)
pos := 0
wanted := verb.Prec
for i := 0; i < wanted; i++ {
next := strB[pos:]
if len(next) == 0 {
// ran out of characters before we hit our max width
break
}
d, _, _ := textseg.ScanGraphemeClusters(strB[pos:], true)
pos += d
}
str = str[:pos]
}
switch verb.Mode {
case 's':
fmted := formatPadWidth(verb, str)
buf.WriteString(fmted)
case 'q':
jb, err := json.Marshal(cty.StringVal(str), cty.String)
if err != nil {
// Should never happen, since we know this is a known, non-null string
panic(fmt.Errorf("failed to marshal %#v as JSON: %s", arg, err))
}
fmted := formatPadWidth(verb, string(jb))
buf.WriteString(fmted)
default:
// Should never happen because formatAppend should've already validated
panic(fmt.Errorf("invalid string formatting mode %q", verb.Mode))
}
return nil
}
func formatPadWidth(verb *formatVerb, fmted string) string {
if verb.Width < 0 {
return fmted
}
// Safe to ignore errors because ScanGraphemeClusters cannot produce errors
givenLen, _ := textseg.TokenCount([]byte(fmted), textseg.ScanGraphemeClusters)
wantLen := verb.Width
if givenLen >= wantLen {
return fmted
}
padLen := wantLen - givenLen
padChar := " "
if verb.Zero {
padChar = "0"
}
pads := strings.Repeat(padChar, padLen)
if verb.Minus {
return fmted + pads
}
return pads + fmted
}
// formatStripIndexSegment strips out any [nnn] segment present in a verb
// string so that we can pass it through to Go's fmt.Sprintf with a single
// argument. This is used in cases where we're just leaning on Go's formatter
// because it's a superset of ours.
func formatStripIndexSegment(rawVerb string) string {
// We assume the string has already been validated here, since we should
// only be using this function with strings that were accepted by our
// scanner in formatFSM.
start := strings.Index(rawVerb, "[")
end := strings.Index(rawVerb, "]")
if start == -1 || end == -1 {
return rawVerb
}
return rawVerb[:start] + rawVerb[end+1:]
}

View File

@ -0,0 +1,374 @@
// line 1 "format_fsm.rl"
// This file is generated from format_fsm.rl. DO NOT EDIT.
// line 5 "format_fsm.rl"
package stdlib
import (
"bytes"
"fmt"
"unicode/utf8"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
)
// line 21 "format_fsm.go"
var _formatfsm_actions []byte = []byte{
0, 1, 0, 1, 1, 1, 2, 1, 4,
1, 5, 1, 6, 1, 7, 1, 8,
1, 9, 1, 10, 1, 11, 1, 14,
1, 16, 1, 17, 1, 18, 2, 3,
4, 2, 12, 10, 2, 12, 16, 2,
12, 18, 2, 13, 14, 2, 15, 10,
2, 15, 18,
}
var _formatfsm_key_offsets []byte = []byte{
0, 0, 14, 27, 34, 36, 39, 43,
51,
}
var _formatfsm_trans_keys []byte = []byte{
32, 35, 37, 43, 45, 46, 48, 91,
49, 57, 65, 90, 97, 122, 32, 35,
43, 45, 46, 48, 91, 49, 57, 65,
90, 97, 122, 91, 48, 57, 65, 90,
97, 122, 49, 57, 93, 48, 57, 65,
90, 97, 122, 46, 91, 48, 57, 65,
90, 97, 122, 37,
}
var _formatfsm_single_lengths []byte = []byte{
0, 8, 7, 1, 0, 1, 0, 2,
1,
}
var _formatfsm_range_lengths []byte = []byte{
0, 3, 3, 3, 1, 1, 2, 3,
0,
}
var _formatfsm_index_offsets []byte = []byte{
0, 0, 12, 23, 28, 30, 33, 36,
42,
}
var _formatfsm_indicies []byte = []byte{
1, 2, 3, 4, 5, 6, 7, 10,
8, 9, 9, 0, 1, 2, 4, 5,
6, 7, 10, 8, 9, 9, 0, 13,
11, 12, 12, 0, 14, 0, 15, 14,
0, 9, 9, 0, 16, 19, 17, 18,
18, 0, 20, 3,
}
var _formatfsm_trans_targs []byte = []byte{
0, 2, 2, 8, 2, 2, 3, 2,
7, 8, 4, 3, 8, 4, 5, 6,
3, 7, 8, 4, 1,
}
var _formatfsm_trans_actions []byte = []byte{
7, 17, 9, 3, 15, 13, 25, 11,
43, 29, 19, 27, 49, 46, 21, 0,
37, 23, 40, 34, 1,
}
var _formatfsm_eof_actions []byte = []byte{
0, 31, 31, 31, 31, 31, 31, 31,
5,
}
const formatfsm_start int = 8
const formatfsm_first_final int = 8
const formatfsm_error int = 0
const formatfsm_en_main int = 8
// line 20 "format_fsm.rl"
func formatFSM(format string, a []cty.Value) (string, error) {
var buf bytes.Buffer
data := format
nextArg := 1 // arg numbers are 1-based
var verb formatVerb
highestArgIdx := 0 // zero means "none", since arg numbers are 1-based
// line 159 "format_fsm.rl"
// Ragel state
p := 0 // "Pointer" into data
pe := len(data) // End-of-data "pointer"
cs := 0 // current state (will be initialized by ragel-generated code)
ts := 0
te := 0
eof := pe
// Keep Go compiler happy even if generated code doesn't use these
_ = ts
_ = te
_ = eof
// line 123 "format_fsm.go"
{
cs = formatfsm_start
}
// line 128 "format_fsm.go"
{
var _klen int
var _trans int
var _acts int
var _nacts uint
var _keys int
if p == pe {
goto _test_eof
}
if cs == 0 {
goto _out
}
_resume:
_keys = int(_formatfsm_key_offsets[cs])
_trans = int(_formatfsm_index_offsets[cs])
_klen = int(_formatfsm_single_lengths[cs])
if _klen > 0 {
_lower := int(_keys)
var _mid int
_upper := int(_keys + _klen - 1)
for {
if _upper < _lower {
break
}
_mid = _lower + ((_upper - _lower) >> 1)
switch {
case data[p] < _formatfsm_trans_keys[_mid]:
_upper = _mid - 1
case data[p] > _formatfsm_trans_keys[_mid]:
_lower = _mid + 1
default:
_trans += int(_mid - int(_keys))
goto _match
}
}
_keys += _klen
_trans += _klen
}
_klen = int(_formatfsm_range_lengths[cs])
if _klen > 0 {
_lower := int(_keys)
var _mid int
_upper := int(_keys + (_klen << 1) - 2)
for {
if _upper < _lower {
break
}
_mid = _lower + (((_upper - _lower) >> 1) & ^1)
switch {
case data[p] < _formatfsm_trans_keys[_mid]:
_upper = _mid - 2
case data[p] > _formatfsm_trans_keys[_mid+1]:
_lower = _mid + 2
default:
_trans += int((_mid - int(_keys)) >> 1)
goto _match
}
}
_trans += _klen
}
_match:
_trans = int(_formatfsm_indicies[_trans])
cs = int(_formatfsm_trans_targs[_trans])
if _formatfsm_trans_actions[_trans] == 0 {
goto _again
}
_acts = int(_formatfsm_trans_actions[_trans])
_nacts = uint(_formatfsm_actions[_acts])
_acts++
for ; _nacts > 0; _nacts-- {
_acts++
switch _formatfsm_actions[_acts-1] {
case 0:
// line 31 "format_fsm.rl"
verb = formatVerb{
ArgNum: nextArg,
Prec: -1,
Width: -1,
}
ts = p
case 1:
// line 40 "format_fsm.rl"
buf.WriteByte(data[p])
case 4:
// line 51 "format_fsm.rl"
// We'll try to slurp a whole UTF-8 sequence here, to give the user
// better feedback.
r, _ := utf8.DecodeRuneInString(data[p:])
return buf.String(), fmt.Errorf("unrecognized format character %q at offset %d", r, p)
case 5:
// line 58 "format_fsm.rl"
verb.Sharp = true
case 6:
// line 61 "format_fsm.rl"
verb.Zero = true
case 7:
// line 64 "format_fsm.rl"
verb.Minus = true
case 8:
// line 67 "format_fsm.rl"
verb.Plus = true
case 9:
// line 70 "format_fsm.rl"
verb.Space = true
case 10:
// line 74 "format_fsm.rl"
verb.ArgNum = 0
case 11:
// line 77 "format_fsm.rl"
verb.ArgNum = (10 * verb.ArgNum) + (int(data[p]) - '0')
case 12:
// line 81 "format_fsm.rl"
verb.HasWidth = true
case 13:
// line 84 "format_fsm.rl"
verb.Width = 0
case 14:
// line 87 "format_fsm.rl"
verb.Width = (10 * verb.Width) + (int(data[p]) - '0')
case 15:
// line 91 "format_fsm.rl"
verb.HasPrec = true
case 16:
// line 94 "format_fsm.rl"
verb.Prec = 0
case 17:
// line 97 "format_fsm.rl"
verb.Prec = (10 * verb.Prec) + (int(data[p]) - '0')
case 18:
// line 101 "format_fsm.rl"
verb.Mode = rune(data[p])
te = p + 1
verb.Raw = data[ts:te]
verb.Offset = ts
if verb.ArgNum > highestArgIdx {
highestArgIdx = verb.ArgNum
}
err := formatAppend(&verb, &buf, a)
if err != nil {
return buf.String(), err
}
nextArg = verb.ArgNum + 1
// line 330 "format_fsm.go"
}
}
_again:
if cs == 0 {
goto _out
}
p++
if p != pe {
goto _resume
}
_test_eof:
{
}
if p == eof {
__acts := _formatfsm_eof_actions[cs]
__nacts := uint(_formatfsm_actions[__acts])
__acts++
for ; __nacts > 0; __nacts-- {
__acts++
switch _formatfsm_actions[__acts-1] {
case 2:
// line 44 "format_fsm.rl"
case 3:
// line 47 "format_fsm.rl"
return buf.String(), fmt.Errorf("invalid format string starting at offset %d", p)
case 4:
// line 51 "format_fsm.rl"
// We'll try to slurp a whole UTF-8 sequence here, to give the user
// better feedback.
r, _ := utf8.DecodeRuneInString(data[p:])
return buf.String(), fmt.Errorf("unrecognized format character %q at offset %d", r, p)
// line 369 "format_fsm.go"
}
}
}
_out:
{
}
}
// line 177 "format_fsm.rl"
// If we fall out here without being in a final state then we've
// encountered something that the scanner can't match, which should
// be impossible (the scanner matches all bytes _somehow_) but we'll
// flag it anyway rather than just losing data from the end.
if cs < formatfsm_first_final {
return buf.String(), fmt.Errorf("extraneous characters beginning at offset %d", p)
}
if highestArgIdx < len(a) {
// Extraneous args are an error, to more easily detect mistakes
firstBad := highestArgIdx + 1
if highestArgIdx == 0 {
// Custom error message for this case
return buf.String(), function.NewArgErrorf(firstBad, "too many arguments; no verbs in format string")
}
return buf.String(), function.NewArgErrorf(firstBad, "too many arguments; only %d used by format string", highestArgIdx)
}
return buf.String(), nil
}

View File

@ -0,0 +1,198 @@
// This file is generated from format_fsm.rl. DO NOT EDIT.
%%{
# (except you are actually in scan_tokens.rl here, so edit away!)
machine formatfsm;
}%%
package stdlib
import (
"bytes"
"fmt"
"unicode/utf8"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
)
%%{
write data;
}%%
func formatFSM(format string, a []cty.Value) (string, error) {
var buf bytes.Buffer
data := format
nextArg := 1 // arg numbers are 1-based
var verb formatVerb
highestArgIdx := 0 // zero means "none", since arg numbers are 1-based
%%{
action begin {
verb = formatVerb{
ArgNum: nextArg,
Prec: -1,
Width: -1,
}
ts = p
}
action emit {
buf.WriteByte(fc);
}
action finish_ok {
}
action finish_err {
return buf.String(), fmt.Errorf("invalid format string starting at offset %d", p)
}
action err_char {
// We'll try to slurp a whole UTF-8 sequence here, to give the user
// better feedback.
r, _ := utf8.DecodeRuneInString(data[p:])
return buf.String(), fmt.Errorf("unrecognized format character %q at offset %d", r, p)
}
action flag_sharp {
verb.Sharp = true
}
action flag_zero {
verb.Zero = true
}
action flag_minus {
verb.Minus = true
}
action flag_plus {
verb.Plus = true
}
action flag_space {
verb.Space = true
}
action argidx_reset {
verb.ArgNum = 0
}
action argidx_num {
verb.ArgNum = (10 * verb.ArgNum) + (int(fc) - '0')
}
action has_width {
verb.HasWidth = true
}
action width_reset {
verb.Width = 0
}
action width_num {
verb.Width = (10 * verb.Width) + (int(fc) - '0')
}
action has_prec {
verb.HasPrec = true
}
action prec_reset {
verb.Prec = 0
}
action prec_num {
verb.Prec = (10 * verb.Prec) + (int(fc) - '0')
}
action mode {
verb.Mode = rune(fc)
te = p+1
verb.Raw = data[ts:te]
verb.Offset = ts
if verb.ArgNum > highestArgIdx {
highestArgIdx = verb.ArgNum
}
err := formatAppend(&verb, &buf, a)
if err != nil {
return buf.String(), err
}
nextArg = verb.ArgNum + 1
}
# a number that isn't zero and doesn't have a leading zero
num = [1-9] [0-9]*;
flags = (
'0' @flag_zero |
'#' @flag_sharp |
'-' @flag_minus |
'+' @flag_plus |
' ' @flag_space
)*;
argidx = ((
'[' (num $argidx_num) ']'
) >argidx_reset)?;
width = (
( num $width_num ) >width_reset %has_width
)?;
precision = (
('.' ( digit* $prec_num )) >prec_reset %has_prec
)?;
# We accept any letter here, but will be more picky in formatAppend
mode = ('a'..'z' | 'A'..'Z') @mode;
fmt_verb = (
'%' @begin
flags
width
precision
argidx
mode
);
main := (
[^%] @emit |
'%%' @emit |
fmt_verb
)* @/finish_err %/finish_ok $!err_char;
}%%
// Ragel state
p := 0 // "Pointer" into data
pe := len(data) // End-of-data "pointer"
cs := 0 // current state (will be initialized by ragel-generated code)
ts := 0
te := 0
eof := pe
// Keep Go compiler happy even if generated code doesn't use these
_ = ts
_ = te
_ = eof
%%{
write init;
write exec;
}%%
// If we fall out here without being in a final state then we've
// encountered something that the scanner can't match, which should
// be impossible (the scanner matches all bytes _somehow_) but we'll
// flag it anyway rather than just losing data from the end.
if cs < formatfsm_first_final {
return buf.String(), fmt.Errorf("extraneous characters beginning at offset %d", p)
}
if highestArgIdx < len(a) {
// Extraneous args are an error, to more easily detect mistakes
firstBad := highestArgIdx+1
if highestArgIdx == 0 {
// Custom error message for this case
return buf.String(), function.NewArgErrorf(firstBad, "too many arguments; no verbs in format string")
}
return buf.String(), function.NewArgErrorf(firstBad, "too many arguments; only %d used by format string", highestArgIdx)
}
return buf.String(), nil
}

View File

@ -0,0 +1,107 @@
package stdlib
import (
"fmt"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert"
"github.com/zclconf/go-cty/cty/function"
)
var EqualFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "a",
Type: cty.DynamicPseudoType,
AllowUnknown: true,
AllowDynamicType: true,
AllowNull: true,
},
{
Name: "b",
Type: cty.DynamicPseudoType,
AllowUnknown: true,
AllowDynamicType: true,
AllowNull: true,
},
},
Type: function.StaticReturnType(cty.Bool),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
return args[0].Equals(args[1]), nil
},
})
var NotEqualFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "a",
Type: cty.DynamicPseudoType,
AllowUnknown: true,
AllowDynamicType: true,
AllowNull: true,
},
{
Name: "b",
Type: cty.DynamicPseudoType,
AllowUnknown: true,
AllowDynamicType: true,
AllowNull: true,
},
},
Type: function.StaticReturnType(cty.Bool),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
return args[0].Equals(args[1]).Not(), nil
},
})
var CoalesceFunc = function.New(&function.Spec{
Params: []function.Parameter{},
VarParam: &function.Parameter{
Name: "vals",
Type: cty.DynamicPseudoType,
AllowUnknown: true,
AllowDynamicType: true,
AllowNull: true,
},
Type: func(args []cty.Value) (ret cty.Type, err error) {
argTypes := make([]cty.Type, len(args))
for i, val := range args {
argTypes[i] = val.Type()
}
retType, _ := convert.UnifyUnsafe(argTypes)
if retType == cty.NilType {
return cty.NilType, fmt.Errorf("all arguments must have the same type")
}
return retType, nil
},
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
for _, argVal := range args {
if !argVal.IsKnown() {
return cty.UnknownVal(retType), nil
}
if argVal.IsNull() {
continue
}
return convert.Convert(argVal, retType)
}
return cty.NilVal, fmt.Errorf("no non-null arguments")
},
})
// Equal determines whether the two given values are equal, returning a
// bool value.
func Equal(a cty.Value, b cty.Value) (cty.Value, error) {
return EqualFunc.Call([]cty.Value{a, b})
}
// NotEqual is the opposite of Equal.
func NotEqual(a cty.Value, b cty.Value) (cty.Value, error) {
return NotEqualFunc.Call([]cty.Value{a, b})
}
// Coalesce returns the first of the given arguments that is not null. If
// all arguments are null, an error is produced.
func Coalesce(vals ...cty.Value) (cty.Value, error) {
return CoalesceFunc.Call(vals)
}

View File

@ -0,0 +1,77 @@
package stdlib
import (
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
"github.com/zclconf/go-cty/cty/json"
)
var JSONEncodeFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "val",
Type: cty.DynamicPseudoType,
AllowDynamicType: true,
AllowNull: true,
},
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
val := args[0]
if !val.IsWhollyKnown() {
// We can't serialize unknowns, so if the value is unknown or
// contains any _nested_ unknowns then our result must be
// unknown.
return cty.UnknownVal(retType), nil
}
if val.IsNull() {
return cty.StringVal("null"), nil
}
buf, err := json.Marshal(val, val.Type())
if err != nil {
return cty.NilVal, err
}
return cty.StringVal(string(buf)), nil
},
})
var JSONDecodeFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "str",
Type: cty.String,
},
},
Type: func(args []cty.Value) (cty.Type, error) {
str := args[0]
if !str.IsKnown() {
return cty.DynamicPseudoType, nil
}
buf := []byte(str.AsString())
return json.ImpliedType(buf)
},
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
buf := []byte(args[0].AsString())
return json.Unmarshal(buf, retType)
},
})
// JSONEncode returns a JSON serialization of the given value.
func JSONEncode(val cty.Value) (cty.Value, error) {
return JSONEncodeFunc.Call([]cty.Value{val})
}
// JSONDecode parses the given JSON string and, if it is valid, returns the
// value it represents.
//
// Note that applying JSONDecode to the result of JSONEncode may not produce
// an identically-typed result, since JSON encoding is lossy for cty Types.
// The resulting value will consist only of primitive types, object types, and
// tuple types.
func JSONDecode(str cty.Value) (cty.Value, error) {
return JSONDecodeFunc.Call([]cty.Value{str})
}

View File

@ -0,0 +1,667 @@
package stdlib
import (
"fmt"
"math"
"math/big"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
"github.com/zclconf/go-cty/cty/gocty"
)
var AbsoluteFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "num",
Type: cty.Number,
AllowDynamicType: true,
AllowMarked: true,
},
},
Type: function.StaticReturnType(cty.Number),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
return args[0].Absolute(), nil
},
})
var AddFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "a",
Type: cty.Number,
AllowDynamicType: true,
},
{
Name: "b",
Type: cty.Number,
AllowDynamicType: true,
},
},
Type: function.StaticReturnType(cty.Number),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
// big.Float.Add can panic if the input values are opposing infinities,
// so we must catch that here in order to remain within
// the cty Function abstraction.
defer func() {
if r := recover(); r != nil {
if _, ok := r.(big.ErrNaN); ok {
ret = cty.NilVal
err = fmt.Errorf("can't compute sum of opposing infinities")
} else {
// not a panic we recognize
panic(r)
}
}
}()
return args[0].Add(args[1]), nil
},
})
var SubtractFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "a",
Type: cty.Number,
AllowDynamicType: true,
},
{
Name: "b",
Type: cty.Number,
AllowDynamicType: true,
},
},
Type: function.StaticReturnType(cty.Number),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
// big.Float.Sub can panic if the input values are infinities,
// so we must catch that here in order to remain within
// the cty Function abstraction.
defer func() {
if r := recover(); r != nil {
if _, ok := r.(big.ErrNaN); ok {
ret = cty.NilVal
err = fmt.Errorf("can't subtract infinity from itself")
} else {
// not a panic we recognize
panic(r)
}
}
}()
return args[0].Subtract(args[1]), nil
},
})
var MultiplyFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "a",
Type: cty.Number,
AllowDynamicType: true,
},
{
Name: "b",
Type: cty.Number,
AllowDynamicType: true,
},
},
Type: function.StaticReturnType(cty.Number),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
// big.Float.Mul can panic if the input values are both zero or both
// infinity, so we must catch that here in order to remain within
// the cty Function abstraction.
defer func() {
if r := recover(); r != nil {
if _, ok := r.(big.ErrNaN); ok {
ret = cty.NilVal
err = fmt.Errorf("can't multiply zero by infinity")
} else {
// not a panic we recognize
panic(r)
}
}
}()
return args[0].Multiply(args[1]), nil
},
})
var DivideFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "a",
Type: cty.Number,
AllowDynamicType: true,
},
{
Name: "b",
Type: cty.Number,
AllowDynamicType: true,
},
},
Type: function.StaticReturnType(cty.Number),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
// big.Float.Quo can panic if the input values are both zero or both
// infinity, so we must catch that here in order to remain within
// the cty Function abstraction.
defer func() {
if r := recover(); r != nil {
if _, ok := r.(big.ErrNaN); ok {
ret = cty.NilVal
err = fmt.Errorf("can't divide zero by zero or infinity by infinity")
} else {
// not a panic we recognize
panic(r)
}
}
}()
return args[0].Divide(args[1]), nil
},
})
var ModuloFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "a",
Type: cty.Number,
AllowDynamicType: true,
},
{
Name: "b",
Type: cty.Number,
AllowDynamicType: true,
},
},
Type: function.StaticReturnType(cty.Number),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
// big.Float.Mul can panic if the input values are both zero or both
// infinity, so we must catch that here in order to remain within
// the cty Function abstraction.
defer func() {
if r := recover(); r != nil {
if _, ok := r.(big.ErrNaN); ok {
ret = cty.NilVal
err = fmt.Errorf("can't use modulo with zero and infinity")
} else {
// not a panic we recognize
panic(r)
}
}
}()
return args[0].Modulo(args[1]), nil
},
})
var GreaterThanFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "a",
Type: cty.Number,
AllowDynamicType: true,
AllowMarked: true,
},
{
Name: "b",
Type: cty.Number,
AllowDynamicType: true,
AllowMarked: true,
},
},
Type: function.StaticReturnType(cty.Bool),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
return args[0].GreaterThan(args[1]), nil
},
})
var GreaterThanOrEqualToFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "a",
Type: cty.Number,
AllowDynamicType: true,
AllowMarked: true,
},
{
Name: "b",
Type: cty.Number,
AllowDynamicType: true,
AllowMarked: true,
},
},
Type: function.StaticReturnType(cty.Bool),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
return args[0].GreaterThanOrEqualTo(args[1]), nil
},
})
var LessThanFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "a",
Type: cty.Number,
AllowDynamicType: true,
AllowMarked: true,
},
{
Name: "b",
Type: cty.Number,
AllowDynamicType: true,
AllowMarked: true,
},
},
Type: function.StaticReturnType(cty.Bool),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
return args[0].LessThan(args[1]), nil
},
})
var LessThanOrEqualToFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "a",
Type: cty.Number,
AllowDynamicType: true,
AllowMarked: true,
},
{
Name: "b",
Type: cty.Number,
AllowDynamicType: true,
AllowMarked: true,
},
},
Type: function.StaticReturnType(cty.Bool),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
return args[0].LessThanOrEqualTo(args[1]), nil
},
})
var NegateFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "num",
Type: cty.Number,
AllowDynamicType: true,
AllowMarked: true,
},
},
Type: function.StaticReturnType(cty.Number),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
return args[0].Negate(), nil
},
})
var MinFunc = function.New(&function.Spec{
Params: []function.Parameter{},
VarParam: &function.Parameter{
Name: "numbers",
Type: cty.Number,
AllowDynamicType: true,
},
Type: function.StaticReturnType(cty.Number),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
if len(args) == 0 {
return cty.NilVal, fmt.Errorf("must pass at least one number")
}
min := cty.PositiveInfinity
for _, num := range args {
if num.LessThan(min).True() {
min = num
}
}
return min, nil
},
})
var MaxFunc = function.New(&function.Spec{
Params: []function.Parameter{},
VarParam: &function.Parameter{
Name: "numbers",
Type: cty.Number,
AllowDynamicType: true,
},
Type: function.StaticReturnType(cty.Number),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
if len(args) == 0 {
return cty.NilVal, fmt.Errorf("must pass at least one number")
}
max := cty.NegativeInfinity
for _, num := range args {
if num.GreaterThan(max).True() {
max = num
}
}
return max, nil
},
})
var IntFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "num",
Type: cty.Number,
AllowDynamicType: true,
},
},
Type: function.StaticReturnType(cty.Number),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
bf := args[0].AsBigFloat()
if bf.IsInt() {
return args[0], nil
}
bi, _ := bf.Int(nil)
bf = (&big.Float{}).SetInt(bi)
return cty.NumberVal(bf), nil
},
})
// CeilFunc is a function that returns the closest whole number greater
// than or equal to the given value.
var CeilFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "num",
Type: cty.Number,
},
},
Type: function.StaticReturnType(cty.Number),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
f := args[0].AsBigFloat()
if f.IsInf() {
return cty.NumberVal(f), nil
}
i, acc := f.Int(nil)
switch acc {
case big.Exact, big.Above:
// Done.
case big.Below:
i.Add(i, big.NewInt(1))
}
return cty.NumberVal(f.SetInt(i)), nil
},
})
// FloorFunc is a function that returns the closest whole number lesser
// than or equal to the given value.
var FloorFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "num",
Type: cty.Number,
},
},
Type: function.StaticReturnType(cty.Number),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
f := args[0].AsBigFloat()
if f.IsInf() {
return cty.NumberVal(f), nil
}
i, acc := f.Int(nil)
switch acc {
case big.Exact, big.Below:
// Done.
case big.Above:
i.Sub(i, big.NewInt(1))
}
return cty.NumberVal(f.SetInt(i)), nil
},
})
// LogFunc is a function that returns the logarithm of a given number in a given base.
var LogFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "num",
Type: cty.Number,
},
{
Name: "base",
Type: cty.Number,
},
},
Type: function.StaticReturnType(cty.Number),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
var num float64
if err := gocty.FromCtyValue(args[0], &num); err != nil {
return cty.UnknownVal(cty.String), err
}
var base float64
if err := gocty.FromCtyValue(args[1], &base); err != nil {
return cty.UnknownVal(cty.String), err
}
return cty.NumberFloatVal(math.Log(num) / math.Log(base)), nil
},
})
// PowFunc is a function that returns the logarithm of a given number in a given base.
var PowFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "num",
Type: cty.Number,
},
{
Name: "power",
Type: cty.Number,
},
},
Type: function.StaticReturnType(cty.Number),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
var num float64
if err := gocty.FromCtyValue(args[0], &num); err != nil {
return cty.UnknownVal(cty.String), err
}
var power float64
if err := gocty.FromCtyValue(args[1], &power); err != nil {
return cty.UnknownVal(cty.String), err
}
return cty.NumberFloatVal(math.Pow(num, power)), nil
},
})
// SignumFunc is a function that determines the sign of a number, returning a
// number between -1 and 1 to represent the sign..
var SignumFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "num",
Type: cty.Number,
},
},
Type: function.StaticReturnType(cty.Number),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
var num int
if err := gocty.FromCtyValue(args[0], &num); err != nil {
return cty.UnknownVal(cty.String), err
}
switch {
case num < 0:
return cty.NumberIntVal(-1), nil
case num > 0:
return cty.NumberIntVal(+1), nil
default:
return cty.NumberIntVal(0), nil
}
},
})
// ParseIntFunc is a function that parses a string argument and returns an integer of the specified base.
var ParseIntFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "number",
Type: cty.DynamicPseudoType,
},
{
Name: "base",
Type: cty.Number,
},
},
Type: func(args []cty.Value) (cty.Type, error) {
if !args[0].Type().Equals(cty.String) {
return cty.Number, function.NewArgErrorf(0, "first argument must be a string, not %s", args[0].Type().FriendlyName())
}
return cty.Number, nil
},
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
var numstr string
var base int
var err error
if err = gocty.FromCtyValue(args[0], &numstr); err != nil {
return cty.UnknownVal(cty.String), function.NewArgError(0, err)
}
if err = gocty.FromCtyValue(args[1], &base); err != nil {
return cty.UnknownVal(cty.Number), function.NewArgError(1, err)
}
if base < 2 || base > 62 {
return cty.UnknownVal(cty.Number), function.NewArgErrorf(
1,
"base must be a whole number between 2 and 62 inclusive",
)
}
num, ok := (&big.Int{}).SetString(numstr, base)
if !ok {
return cty.UnknownVal(cty.Number), function.NewArgErrorf(
0,
"cannot parse %q as a base %d integer",
numstr,
base,
)
}
parsedNum := cty.NumberVal((&big.Float{}).SetInt(num))
return parsedNum, nil
},
})
// Absolute returns the magnitude of the given number, without its sign.
// That is, it turns negative values into positive values.
func Absolute(num cty.Value) (cty.Value, error) {
return AbsoluteFunc.Call([]cty.Value{num})
}
// Add returns the sum of the two given numbers.
func Add(a cty.Value, b cty.Value) (cty.Value, error) {
return AddFunc.Call([]cty.Value{a, b})
}
// Subtract returns the difference between the two given numbers.
func Subtract(a cty.Value, b cty.Value) (cty.Value, error) {
return SubtractFunc.Call([]cty.Value{a, b})
}
// Multiply returns the product of the two given numbers.
func Multiply(a cty.Value, b cty.Value) (cty.Value, error) {
return MultiplyFunc.Call([]cty.Value{a, b})
}
// Divide returns a divided by b, where both a and b are numbers.
func Divide(a cty.Value, b cty.Value) (cty.Value, error) {
return DivideFunc.Call([]cty.Value{a, b})
}
// Negate returns the given number multipled by -1.
func Negate(num cty.Value) (cty.Value, error) {
return NegateFunc.Call([]cty.Value{num})
}
// LessThan returns true if a is less than b.
func LessThan(a cty.Value, b cty.Value) (cty.Value, error) {
return LessThanFunc.Call([]cty.Value{a, b})
}
// LessThanOrEqualTo returns true if a is less than b.
func LessThanOrEqualTo(a cty.Value, b cty.Value) (cty.Value, error) {
return LessThanOrEqualToFunc.Call([]cty.Value{a, b})
}
// GreaterThan returns true if a is less than b.
func GreaterThan(a cty.Value, b cty.Value) (cty.Value, error) {
return GreaterThanFunc.Call([]cty.Value{a, b})
}
// GreaterThanOrEqualTo returns true if a is less than b.
func GreaterThanOrEqualTo(a cty.Value, b cty.Value) (cty.Value, error) {
return GreaterThanOrEqualToFunc.Call([]cty.Value{a, b})
}
// Modulo returns the remainder of a divided by b under integer division,
// where both a and b are numbers.
func Modulo(a cty.Value, b cty.Value) (cty.Value, error) {
return ModuloFunc.Call([]cty.Value{a, b})
}
// Min returns the minimum number from the given numbers.
func Min(numbers ...cty.Value) (cty.Value, error) {
return MinFunc.Call(numbers)
}
// Max returns the maximum number from the given numbers.
func Max(numbers ...cty.Value) (cty.Value, error) {
return MaxFunc.Call(numbers)
}
// Int removes the fractional component of the given number returning an
// integer representing the whole number component, rounding towards zero.
// For example, -1.5 becomes -1.
//
// If an infinity is passed to Int, an error is returned.
func Int(num cty.Value) (cty.Value, error) {
if num == cty.PositiveInfinity || num == cty.NegativeInfinity {
return cty.NilVal, fmt.Errorf("can't truncate infinity to an integer")
}
return IntFunc.Call([]cty.Value{num})
}
// Ceil returns the closest whole number greater than or equal to the given value.
func Ceil(num cty.Value) (cty.Value, error) {
return CeilFunc.Call([]cty.Value{num})
}
// Floor returns the closest whole number lesser than or equal to the given value.
func Floor(num cty.Value) (cty.Value, error) {
return FloorFunc.Call([]cty.Value{num})
}
// Log returns returns the logarithm of a given number in a given base.
func Log(num, base cty.Value) (cty.Value, error) {
return LogFunc.Call([]cty.Value{num, base})
}
// Pow returns the logarithm of a given number in a given base.
func Pow(num, power cty.Value) (cty.Value, error) {
return PowFunc.Call([]cty.Value{num, power})
}
// Signum determines the sign of a number, returning a number between -1 and
// 1 to represent the sign.
func Signum(num cty.Value) (cty.Value, error) {
return SignumFunc.Call([]cty.Value{num})
}
// ParseInt parses a string argument and returns an integer of the specified base.
func ParseInt(num cty.Value, base cty.Value) (cty.Value, error) {
return ParseIntFunc.Call([]cty.Value{num, base})
}

View File

@ -0,0 +1,233 @@
package stdlib
import (
"fmt"
"regexp"
resyntax "regexp/syntax"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
)
var RegexFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "pattern",
Type: cty.String,
},
{
Name: "string",
Type: cty.String,
},
},
Type: func(args []cty.Value) (cty.Type, error) {
if !args[0].IsKnown() {
// We can't predict our type without seeing our pattern
return cty.DynamicPseudoType, nil
}
retTy, err := regexPatternResultType(args[0].AsString())
if err != nil {
err = function.NewArgError(0, err)
}
return retTy, err
},
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
if retType == cty.DynamicPseudoType {
return cty.DynamicVal, nil
}
re, err := regexp.Compile(args[0].AsString())
if err != nil {
// Should never happen, since we checked this in the Type function above.
return cty.NilVal, function.NewArgErrorf(0, "error parsing pattern: %s", err)
}
str := args[1].AsString()
captureIdxs := re.FindStringSubmatchIndex(str)
if captureIdxs == nil {
return cty.NilVal, fmt.Errorf("pattern did not match any part of the given string")
}
return regexPatternResult(re, str, captureIdxs, retType), nil
},
})
var RegexAllFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "pattern",
Type: cty.String,
},
{
Name: "string",
Type: cty.String,
},
},
Type: func(args []cty.Value) (cty.Type, error) {
if !args[0].IsKnown() {
// We can't predict our type without seeing our pattern,
// but we do know it'll always be a list of something.
return cty.List(cty.DynamicPseudoType), nil
}
retTy, err := regexPatternResultType(args[0].AsString())
if err != nil {
err = function.NewArgError(0, err)
}
return cty.List(retTy), err
},
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
ety := retType.ElementType()
if ety == cty.DynamicPseudoType {
return cty.DynamicVal, nil
}
re, err := regexp.Compile(args[0].AsString())
if err != nil {
// Should never happen, since we checked this in the Type function above.
return cty.NilVal, function.NewArgErrorf(0, "error parsing pattern: %s", err)
}
str := args[1].AsString()
captureIdxsEach := re.FindAllStringSubmatchIndex(str, -1)
if len(captureIdxsEach) == 0 {
return cty.ListValEmpty(ety), nil
}
elems := make([]cty.Value, len(captureIdxsEach))
for i, captureIdxs := range captureIdxsEach {
elems[i] = regexPatternResult(re, str, captureIdxs, ety)
}
return cty.ListVal(elems), nil
},
})
// Regex is a function that extracts one or more substrings from a given
// string by applying a regular expression pattern, describing the first
// match.
//
// The return type depends on the composition of the capture groups (if any)
// in the pattern:
//
// - If there are no capture groups at all, the result is a single string
// representing the entire matched pattern.
// - If all of the capture groups are named, the result is an object whose
// keys are the named groups and whose values are their sub-matches, or
// null if a particular sub-group was inside another group that didn't
// match.
// - If none of the capture groups are named, the result is a tuple whose
// elements are the sub-groups in order and whose values are their
// sub-matches, or null if a particular sub-group was inside another group
// that didn't match.
// - It is invalid to use both named and un-named capture groups together in
// the same pattern.
//
// If the pattern doesn't match, this function returns an error. To test for
// a match, call RegexAll and check if the length of the result is greater
// than zero.
func Regex(pattern, str cty.Value) (cty.Value, error) {
return RegexFunc.Call([]cty.Value{pattern, str})
}
// RegexAll is similar to Regex but it finds all of the non-overlapping matches
// in the given string and returns a list of them.
//
// The result type is always a list, whose element type is deduced from the
// pattern in the same way as the return type for Regex is decided.
//
// If the pattern doesn't match at all, this function returns an empty list.
func RegexAll(pattern, str cty.Value) (cty.Value, error) {
return RegexAllFunc.Call([]cty.Value{pattern, str})
}
// regexPatternResultType parses the given regular expression pattern and
// returns the structural type that would be returned to represent its
// capture groups.
//
// Returns an error if parsing fails or if the pattern uses a mixture of
// named and unnamed capture groups, which is not permitted.
func regexPatternResultType(pattern string) (cty.Type, error) {
re, rawErr := regexp.Compile(pattern)
switch err := rawErr.(type) {
case *resyntax.Error:
return cty.NilType, fmt.Errorf("invalid regexp pattern: %s in %s", err.Code, err.Expr)
case error:
// Should never happen, since all regexp compile errors should
// be resyntax.Error, but just in case...
return cty.NilType, fmt.Errorf("error parsing pattern: %s", err)
}
allNames := re.SubexpNames()[1:]
var names []string
unnamed := 0
for _, name := range allNames {
if name == "" {
unnamed++
} else {
if names == nil {
names = make([]string, 0, len(allNames))
}
names = append(names, name)
}
}
switch {
case unnamed == 0 && len(names) == 0:
// If there are no capture groups at all then we'll return just a
// single string for the whole match.
return cty.String, nil
case unnamed > 0 && len(names) > 0:
return cty.NilType, fmt.Errorf("invalid regexp pattern: cannot mix both named and unnamed capture groups")
case unnamed > 0:
// For unnamed captures, we return a tuple of them all in order.
etys := make([]cty.Type, unnamed)
for i := range etys {
etys[i] = cty.String
}
return cty.Tuple(etys), nil
default:
// For named captures, we return an object using the capture names
// as keys.
atys := make(map[string]cty.Type, len(names))
for _, name := range names {
atys[name] = cty.String
}
return cty.Object(atys), nil
}
}
func regexPatternResult(re *regexp.Regexp, str string, captureIdxs []int, retType cty.Type) cty.Value {
switch {
case retType == cty.String:
start, end := captureIdxs[0], captureIdxs[1]
return cty.StringVal(str[start:end])
case retType.IsTupleType():
captureIdxs = captureIdxs[2:] // index 0 is the whole pattern span, which we ignore by skipping one pair
vals := make([]cty.Value, len(captureIdxs)/2)
for i := range vals {
start, end := captureIdxs[i*2], captureIdxs[i*2+1]
if start < 0 || end < 0 {
vals[i] = cty.NullVal(cty.String) // Did not match anything because containing group didn't match
continue
}
vals[i] = cty.StringVal(str[start:end])
}
return cty.TupleVal(vals)
case retType.IsObjectType():
captureIdxs = captureIdxs[2:] // index 0 is the whole pattern span, which we ignore by skipping one pair
vals := make(map[string]cty.Value, len(captureIdxs)/2)
names := re.SubexpNames()[1:]
for i, name := range names {
start, end := captureIdxs[i*2], captureIdxs[i*2+1]
if start < 0 || end < 0 {
vals[name] = cty.NullVal(cty.String) // Did not match anything because containing group didn't match
continue
}
vals[name] = cty.StringVal(str[start:end])
}
return cty.ObjectVal(vals)
default:
// Should never happen
panic(fmt.Sprintf("invalid return type %#v", retType))
}
}

View File

@ -0,0 +1,235 @@
package stdlib
import (
"fmt"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert"
"github.com/zclconf/go-cty/cty/function"
)
var ConcatFunc = function.New(&function.Spec{
Params: []function.Parameter{},
VarParam: &function.Parameter{
Name: "seqs",
Type: cty.DynamicPseudoType,
AllowMarked: true,
},
Type: func(args []cty.Value) (ret cty.Type, err error) {
if len(args) == 0 {
return cty.NilType, fmt.Errorf("at least one argument is required")
}
if args[0].Type().IsListType() {
// Possibly we're going to return a list, if all of our other
// args are also lists and we can find a common element type.
tys := make([]cty.Type, len(args))
for i, val := range args {
ty := val.Type()
if !ty.IsListType() {
tys = nil
break
}
tys[i] = ty
}
if tys != nil {
commonType, _ := convert.UnifyUnsafe(tys)
if commonType != cty.NilType {
return commonType, nil
}
}
}
etys := make([]cty.Type, 0, len(args))
for i, val := range args {
// Discard marks for nested values, as we only need to handle types
// and lengths.
val, _ := val.UnmarkDeep()
ety := val.Type()
switch {
case ety.IsTupleType():
etys = append(etys, ety.TupleElementTypes()...)
case ety.IsListType():
if !val.IsKnown() {
// We need to know the list to count its elements to
// build our tuple type, so any concat of an unknown
// list can't be typed yet.
return cty.DynamicPseudoType, nil
}
l := val.LengthInt()
subEty := ety.ElementType()
for j := 0; j < l; j++ {
etys = append(etys, subEty)
}
default:
return cty.NilType, function.NewArgErrorf(
i, "all arguments must be lists or tuples; got %s",
ety.FriendlyName(),
)
}
}
return cty.Tuple(etys), nil
},
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
switch {
case retType.IsListType():
// If retType is a list type then we know that all of the
// given values will be lists and that they will either be of
// retType or of something we can convert to retType.
vals := make([]cty.Value, 0, len(args))
var markses []cty.ValueMarks // remember any marked lists we find
for i, list := range args {
list, err = convert.Convert(list, retType)
if err != nil {
// Conversion might fail because we used UnifyUnsafe
// to choose our return type.
return cty.NilVal, function.NewArgError(i, err)
}
list, listMarks := list.Unmark()
if len(listMarks) > 0 {
markses = append(markses, listMarks)
}
it := list.ElementIterator()
for it.Next() {
_, v := it.Element()
vals = append(vals, v)
}
}
if len(vals) == 0 {
return cty.ListValEmpty(retType.ElementType()).WithMarks(markses...), nil
}
return cty.ListVal(vals).WithMarks(markses...), nil
case retType.IsTupleType():
// If retType is a tuple type then we could have a mixture of
// lists and tuples but we know they all have known values
// (because our params don't AllowUnknown) and we know that
// concatenating them all together will produce a tuple of
// retType because of the work we did in the Type function above.
vals := make([]cty.Value, 0, len(args))
var markses []cty.ValueMarks // remember any marked seqs we find
for _, seq := range args {
seq, seqMarks := seq.Unmark()
if len(seqMarks) > 0 {
markses = append(markses, seqMarks)
}
// Both lists and tuples support ElementIterator, so this is easy.
it := seq.ElementIterator()
for it.Next() {
_, v := it.Element()
vals = append(vals, v)
}
}
return cty.TupleVal(vals).WithMarks(markses...), nil
default:
// should never happen if Type is working correctly above
panic("unsupported return type")
}
},
})
var RangeFunc = function.New(&function.Spec{
VarParam: &function.Parameter{
Name: "params",
Type: cty.Number,
},
Type: function.StaticReturnType(cty.List(cty.Number)),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
var start, end, step cty.Value
switch len(args) {
case 1:
if args[0].LessThan(cty.Zero).True() {
start, end, step = cty.Zero, args[0], cty.NumberIntVal(-1)
} else {
start, end, step = cty.Zero, args[0], cty.NumberIntVal(1)
}
case 2:
if args[1].LessThan(args[0]).True() {
start, end, step = args[0], args[1], cty.NumberIntVal(-1)
} else {
start, end, step = args[0], args[1], cty.NumberIntVal(1)
}
case 3:
start, end, step = args[0], args[1], args[2]
default:
return cty.NilVal, fmt.Errorf("must have one, two, or three arguments")
}
var vals []cty.Value
if step == cty.Zero {
return cty.NilVal, function.NewArgErrorf(2, "step must not be zero")
}
down := step.LessThan(cty.Zero).True()
if down {
if end.GreaterThan(start).True() {
return cty.NilVal, function.NewArgErrorf(1, "end must be less than start when step is negative")
}
} else {
if end.LessThan(start).True() {
return cty.NilVal, function.NewArgErrorf(1, "end must be greater than start when step is positive")
}
}
num := start
for {
if down {
if num.LessThanOrEqualTo(end).True() {
break
}
} else {
if num.GreaterThanOrEqualTo(end).True() {
break
}
}
if len(vals) >= 1024 {
// Artificial limit to prevent bad arguments from consuming huge amounts of memory
return cty.NilVal, fmt.Errorf("more than 1024 values were generated; either decrease the difference between start and end or use a smaller step")
}
vals = append(vals, num)
num = num.Add(step)
}
if len(vals) == 0 {
return cty.ListValEmpty(cty.Number), nil
}
return cty.ListVal(vals), nil
},
})
// Concat takes one or more sequences (lists or tuples) and returns the single
// sequence that results from concatenating them together in order.
//
// If all of the given sequences are lists of the same element type then the
// result is a list of that type. Otherwise, the result is a of a tuple type
// constructed from the given sequence types.
func Concat(seqs ...cty.Value) (cty.Value, error) {
return ConcatFunc.Call(seqs)
}
// Range creates a list of numbers by starting from the given starting value,
// then adding the given step value until the result is greater than or
// equal to the given stopping value. Each intermediate result becomes an
// element in the resulting list.
//
// When all three parameters are set, the order is (start, end, step). If
// only two parameters are set, they are the start and end respectively and
// step defaults to 1. If only one argument is set, it gives the end value
// with start defaulting to 0 and step defaulting to 1.
//
// Because the resulting list must be fully buffered in memory, there is an
// artificial cap of 1024 elements, after which this function will return
// an error to avoid consuming unbounded amounts of memory. The Range function
// is primarily intended for creating small lists of indices to iterate over,
// so there should be no reason to generate huge lists with it.
func Range(params ...cty.Value) (cty.Value, error) {
return RangeFunc.Call(params)
}

View File

@ -0,0 +1,222 @@
package stdlib
import (
"fmt"
"github.com/zclconf/go-cty/cty/convert"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
)
var SetHasElementFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "set",
Type: cty.Set(cty.DynamicPseudoType),
AllowDynamicType: true,
},
{
Name: "elem",
Type: cty.DynamicPseudoType,
AllowDynamicType: true,
},
},
Type: function.StaticReturnType(cty.Bool),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
return args[0].HasElement(args[1]), nil
},
})
var SetUnionFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "first_set",
Type: cty.Set(cty.DynamicPseudoType),
AllowDynamicType: true,
},
},
VarParam: &function.Parameter{
Name: "other_sets",
Type: cty.Set(cty.DynamicPseudoType),
AllowDynamicType: true,
},
Type: setOperationReturnType,
Impl: setOperationImpl(func(s1, s2 cty.ValueSet) cty.ValueSet {
return s1.Union(s2)
}, true),
})
var SetIntersectionFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "first_set",
Type: cty.Set(cty.DynamicPseudoType),
AllowDynamicType: true,
},
},
VarParam: &function.Parameter{
Name: "other_sets",
Type: cty.Set(cty.DynamicPseudoType),
AllowDynamicType: true,
},
Type: setOperationReturnType,
Impl: setOperationImpl(func(s1, s2 cty.ValueSet) cty.ValueSet {
return s1.Intersection(s2)
}, false),
})
var SetSubtractFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "a",
Type: cty.Set(cty.DynamicPseudoType),
AllowDynamicType: true,
},
{
Name: "b",
Type: cty.Set(cty.DynamicPseudoType),
AllowDynamicType: true,
},
},
Type: setOperationReturnType,
Impl: setOperationImpl(func(s1, s2 cty.ValueSet) cty.ValueSet {
return s1.Subtract(s2)
}, false),
})
var SetSymmetricDifferenceFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "first_set",
Type: cty.Set(cty.DynamicPseudoType),
AllowDynamicType: true,
},
},
VarParam: &function.Parameter{
Name: "other_sets",
Type: cty.Set(cty.DynamicPseudoType),
AllowDynamicType: true,
},
Type: setOperationReturnType,
Impl: setOperationImpl(func(s1, s2 cty.ValueSet) cty.ValueSet {
return s1.SymmetricDifference(s2)
}, false),
})
// SetHasElement determines whether the given set contains the given value as an
// element.
func SetHasElement(set cty.Value, elem cty.Value) (cty.Value, error) {
return SetHasElementFunc.Call([]cty.Value{set, elem})
}
// SetUnion returns a new set containing all of the elements from the given
// sets, which must have element types that can all be converted to some
// common type using the standard type unification rules. If conversion
// is not possible, an error is returned.
//
// The union operation is performed after type conversion, which may result
// in some previously-distinct values being conflated.
//
// At least one set must be provided.
func SetUnion(sets ...cty.Value) (cty.Value, error) {
return SetUnionFunc.Call(sets)
}
// Intersection returns a new set containing the elements that exist
// in all of the given sets, which must have element types that can all be
// converted to some common type using the standard type unification rules.
// If conversion is not possible, an error is returned.
//
// The intersection operation is performed after type conversion, which may
// result in some previously-distinct values being conflated.
//
// At least one set must be provided.
func SetIntersection(sets ...cty.Value) (cty.Value, error) {
return SetIntersectionFunc.Call(sets)
}
// SetSubtract returns a new set containing the elements from the
// first set that are not present in the second set. The sets must have
// element types that can both be converted to some common type using the
// standard type unification rules. If conversion is not possible, an error
// is returned.
//
// The subtract operation is performed after type conversion, which may
// result in some previously-distinct values being conflated.
func SetSubtract(a, b cty.Value) (cty.Value, error) {
return SetSubtractFunc.Call([]cty.Value{a, b})
}
// SetSymmetricDifference returns a new set containing elements that appear
// in any of the given sets but not multiple. The sets must have
// element types that can all be converted to some common type using the
// standard type unification rules. If conversion is not possible, an error
// is returned.
//
// The difference operation is performed after type conversion, which may
// result in some previously-distinct values being conflated.
func SetSymmetricDifference(sets ...cty.Value) (cty.Value, error) {
return SetSymmetricDifferenceFunc.Call(sets)
}
func setOperationReturnType(args []cty.Value) (ret cty.Type, err error) {
var etys []cty.Type
for _, arg := range args {
ty := arg.Type().ElementType()
// Do not unify types for empty dynamic pseudo typed collections. These
// will always convert to any other concrete type.
if arg.IsKnown() && arg.LengthInt() == 0 && ty.Equals(cty.DynamicPseudoType) {
continue
}
etys = append(etys, ty)
}
// If all element types were skipped (due to being empty dynamic collections),
// the return type should also be a set of dynamic pseudo type.
if len(etys) == 0 {
return cty.Set(cty.DynamicPseudoType), nil
}
newEty, _ := convert.UnifyUnsafe(etys)
if newEty == cty.NilType {
return cty.NilType, fmt.Errorf("given sets must all have compatible element types")
}
return cty.Set(newEty), nil
}
func setOperationImpl(f func(s1, s2 cty.ValueSet) cty.ValueSet, allowUnknowns bool) function.ImplFunc {
return func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
first := args[0]
first, err = convert.Convert(first, retType)
if err != nil {
return cty.NilVal, function.NewArgError(0, err)
}
if !allowUnknowns && !first.IsWhollyKnown() {
// This set function can produce a correct result only when all
// elements are known, because eventually knowing the unknown
// values may cause the result to have fewer known elements, or
// might cause a result with no unknown elements at all to become
// one with a different length.
return cty.UnknownVal(retType), nil
}
set := first.AsValueSet()
for i, arg := range args[1:] {
arg, err := convert.Convert(arg, retType)
if err != nil {
return cty.NilVal, function.NewArgError(i+1, err)
}
if !allowUnknowns && !arg.IsWhollyKnown() {
// (For the same reason as we did this check for "first" above.)
return cty.UnknownVal(retType), nil
}
argSet := arg.AsValueSet()
set = f(set, argSet)
}
return cty.SetValFromValueSet(set), nil
}
}

View File

@ -0,0 +1,546 @@
package stdlib
import (
"fmt"
"regexp"
"sort"
"strings"
"github.com/apparentlymart/go-textseg/v13/textseg"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
"github.com/zclconf/go-cty/cty/gocty"
)
var UpperFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "str",
Type: cty.String,
AllowDynamicType: true,
},
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
in := args[0].AsString()
out := strings.ToUpper(in)
return cty.StringVal(out), nil
},
})
var LowerFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "str",
Type: cty.String,
AllowDynamicType: true,
},
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
in := args[0].AsString()
out := strings.ToLower(in)
return cty.StringVal(out), nil
},
})
var ReverseFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "str",
Type: cty.String,
AllowDynamicType: true,
},
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
in := []byte(args[0].AsString())
out := make([]byte, len(in))
pos := len(out)
inB := []byte(in)
for i := 0; i < len(in); {
d, _, _ := textseg.ScanGraphemeClusters(inB[i:], true)
cluster := in[i : i+d]
pos -= len(cluster)
copy(out[pos:], cluster)
i += d
}
return cty.StringVal(string(out)), nil
},
})
var StrlenFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "str",
Type: cty.String,
AllowDynamicType: true,
},
},
Type: function.StaticReturnType(cty.Number),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
in := args[0].AsString()
l := 0
inB := []byte(in)
for i := 0; i < len(in); {
d, _, _ := textseg.ScanGraphemeClusters(inB[i:], true)
l++
i += d
}
return cty.NumberIntVal(int64(l)), nil
},
})
var SubstrFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "str",
Type: cty.String,
AllowDynamicType: true,
},
{
Name: "offset",
Type: cty.Number,
AllowDynamicType: true,
},
{
Name: "length",
Type: cty.Number,
AllowDynamicType: true,
},
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
in := []byte(args[0].AsString())
var offset, length int
var err error
err = gocty.FromCtyValue(args[1], &offset)
if err != nil {
return cty.NilVal, err
}
err = gocty.FromCtyValue(args[2], &length)
if err != nil {
return cty.NilVal, err
}
if offset < 0 {
totalLenNum, err := Strlen(args[0])
if err != nil {
// should never happen
panic("Stdlen returned an error")
}
var totalLen int
err = gocty.FromCtyValue(totalLenNum, &totalLen)
if err != nil {
// should never happen
panic("Stdlen returned a non-int number")
}
offset += totalLen
} else if length == 0 {
// Short circuit here, after error checks, because if a
// string of length 0 has been requested it will always
// be the empty string
return cty.StringVal(""), nil
}
sub := in
pos := 0
var i int
// First we'll seek forward to our offset
if offset > 0 {
for i = 0; i < len(sub); {
d, _, _ := textseg.ScanGraphemeClusters(sub[i:], true)
i += d
pos++
if pos == offset {
break
}
if i >= len(in) {
return cty.StringVal(""), nil
}
}
sub = sub[i:]
}
if length < 0 {
// Taking the remainder of the string is a fast path since
// we can just return the rest of the buffer verbatim.
return cty.StringVal(string(sub)), nil
}
// Otherwise we need to start seeking forward again until we
// reach the length we want.
pos = 0
for i = 0; i < len(sub); {
d, _, _ := textseg.ScanGraphemeClusters(sub[i:], true)
i += d
pos++
if pos == length {
break
}
}
sub = sub[:i]
return cty.StringVal(string(sub)), nil
},
})
var JoinFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "separator",
Type: cty.String,
},
},
VarParam: &function.Parameter{
Name: "lists",
Type: cty.List(cty.String),
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
sep := args[0].AsString()
listVals := args[1:]
if len(listVals) < 1 {
return cty.UnknownVal(cty.String), fmt.Errorf("at least one list is required")
}
l := 0
for _, list := range listVals {
if !list.IsWhollyKnown() {
return cty.UnknownVal(cty.String), nil
}
l += list.LengthInt()
}
items := make([]string, 0, l)
for ai, list := range listVals {
ei := 0
for it := list.ElementIterator(); it.Next(); {
_, val := it.Element()
if val.IsNull() {
if len(listVals) > 1 {
return cty.UnknownVal(cty.String), function.NewArgErrorf(ai+1, "element %d of list %d is null; cannot concatenate null values", ei, ai+1)
}
return cty.UnknownVal(cty.String), function.NewArgErrorf(ai+1, "element %d is null; cannot concatenate null values", ei)
}
items = append(items, val.AsString())
ei++
}
}
return cty.StringVal(strings.Join(items, sep)), nil
},
})
var SortFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "list",
Type: cty.List(cty.String),
},
},
Type: function.StaticReturnType(cty.List(cty.String)),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
listVal := args[0]
if !listVal.IsWhollyKnown() {
// If some of the element values aren't known yet then we
// can't yet predict the order of the result.
return cty.UnknownVal(retType), nil
}
if listVal.LengthInt() == 0 { // Easy path
return listVal, nil
}
list := make([]string, 0, listVal.LengthInt())
for it := listVal.ElementIterator(); it.Next(); {
iv, v := it.Element()
if v.IsNull() {
return cty.UnknownVal(retType), fmt.Errorf("given list element %s is null; a null string cannot be sorted", iv.AsBigFloat().String())
}
list = append(list, v.AsString())
}
sort.Strings(list)
retVals := make([]cty.Value, len(list))
for i, s := range list {
retVals[i] = cty.StringVal(s)
}
return cty.ListVal(retVals), nil
},
})
var SplitFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "separator",
Type: cty.String,
},
{
Name: "str",
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.List(cty.String)),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
sep := args[0].AsString()
str := args[1].AsString()
elems := strings.Split(str, sep)
elemVals := make([]cty.Value, len(elems))
for i, s := range elems {
elemVals[i] = cty.StringVal(s)
}
if len(elemVals) == 0 {
return cty.ListValEmpty(cty.String), nil
}
return cty.ListVal(elemVals), nil
},
})
// ChompFunc is a function that removes newline characters at the end of a
// string.
var ChompFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "str",
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
newlines := regexp.MustCompile(`(?:\r\n?|\n)*\z`)
return cty.StringVal(newlines.ReplaceAllString(args[0].AsString(), "")), nil
},
})
// IndentFunc is a function that adds a given number of spaces to the
// beginnings of all but the first line in a given multi-line string.
var IndentFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "spaces",
Type: cty.Number,
},
{
Name: "str",
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
var spaces int
if err := gocty.FromCtyValue(args[0], &spaces); err != nil {
return cty.UnknownVal(cty.String), err
}
data := args[1].AsString()
pad := strings.Repeat(" ", spaces)
return cty.StringVal(strings.Replace(data, "\n", "\n"+pad, -1)), nil
},
})
// TitleFunc is a function that converts the first letter of each word in the
// given string to uppercase.
var TitleFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "str",
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
return cty.StringVal(strings.Title(args[0].AsString())), nil
},
})
// TrimSpaceFunc is a function that removes any space characters from the start
// and end of the given string.
var TrimSpaceFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "str",
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
return cty.StringVal(strings.TrimSpace(args[0].AsString())), nil
},
})
// TrimFunc is a function that removes the specified characters from the start
// and end of the given string.
var TrimFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "str",
Type: cty.String,
},
{
Name: "cutset",
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
str := args[0].AsString()
cutset := args[1].AsString()
return cty.StringVal(strings.Trim(str, cutset)), nil
},
})
// TrimPrefixFunc is a function that removes the specified characters from the
// start the given string.
var TrimPrefixFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "str",
Type: cty.String,
},
{
Name: "prefix",
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
str := args[0].AsString()
prefix := args[1].AsString()
return cty.StringVal(strings.TrimPrefix(str, prefix)), nil
},
})
// TrimSuffixFunc is a function that removes the specified characters from the
// end of the given string.
var TrimSuffixFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "str",
Type: cty.String,
},
{
Name: "suffix",
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
str := args[0].AsString()
cutset := args[1].AsString()
return cty.StringVal(strings.TrimSuffix(str, cutset)), nil
},
})
// Upper is a Function that converts a given string to uppercase.
func Upper(str cty.Value) (cty.Value, error) {
return UpperFunc.Call([]cty.Value{str})
}
// Lower is a Function that converts a given string to lowercase.
func Lower(str cty.Value) (cty.Value, error) {
return LowerFunc.Call([]cty.Value{str})
}
// Reverse is a Function that reverses the order of the characters in the
// given string.
//
// As usual, "character" for the sake of this function is a grapheme cluster,
// so combining diacritics (for example) will be considered together as a
// single character.
func Reverse(str cty.Value) (cty.Value, error) {
return ReverseFunc.Call([]cty.Value{str})
}
// Strlen is a Function that returns the length of the given string in
// characters.
//
// As usual, "character" for the sake of this function is a grapheme cluster,
// so combining diacritics (for example) will be considered together as a
// single character.
func Strlen(str cty.Value) (cty.Value, error) {
return StrlenFunc.Call([]cty.Value{str})
}
// Substr is a Function that extracts a sequence of characters from another
// string and creates a new string.
//
// As usual, "character" for the sake of this function is a grapheme cluster,
// so combining diacritics (for example) will be considered together as a
// single character.
//
// The "offset" index may be negative, in which case it is relative to the
// end of the given string.
//
// The "length" may be -1, in which case the remainder of the string after
// the given offset will be returned.
func Substr(str cty.Value, offset cty.Value, length cty.Value) (cty.Value, error) {
return SubstrFunc.Call([]cty.Value{str, offset, length})
}
// Join concatenates together the string elements of one or more lists with a
// given separator.
func Join(sep cty.Value, lists ...cty.Value) (cty.Value, error) {
args := make([]cty.Value, len(lists)+1)
args[0] = sep
copy(args[1:], lists)
return JoinFunc.Call(args)
}
// Sort re-orders the elements of a given list of strings so that they are
// in ascending lexicographical order.
func Sort(list cty.Value) (cty.Value, error) {
return SortFunc.Call([]cty.Value{list})
}
// Split divides a given string by a given separator, returning a list of
// strings containing the characters between the separator sequences.
func Split(sep, str cty.Value) (cty.Value, error) {
return SplitFunc.Call([]cty.Value{sep, str})
}
// Chomp removes newline characters at the end of a string.
func Chomp(str cty.Value) (cty.Value, error) {
return ChompFunc.Call([]cty.Value{str})
}
// Indent adds a given number of spaces to the beginnings of all but the first
// line in a given multi-line string.
func Indent(spaces, str cty.Value) (cty.Value, error) {
return IndentFunc.Call([]cty.Value{spaces, str})
}
// Title converts the first letter of each word in the given string to uppercase.
func Title(str cty.Value) (cty.Value, error) {
return TitleFunc.Call([]cty.Value{str})
}
// TrimSpace removes any space characters from the start and end of the given string.
func TrimSpace(str cty.Value) (cty.Value, error) {
return TrimSpaceFunc.Call([]cty.Value{str})
}
// Trim removes the specified characters from the start and end of the given string.
func Trim(str, cutset cty.Value) (cty.Value, error) {
return TrimFunc.Call([]cty.Value{str, cutset})
}
// TrimPrefix removes the specified prefix from the start of the given string.
func TrimPrefix(str, prefix cty.Value) (cty.Value, error) {
return TrimPrefixFunc.Call([]cty.Value{str, prefix})
}
// TrimSuffix removes the specified suffix from the end of the given string.
func TrimSuffix(str, suffix cty.Value) (cty.Value, error) {
return TrimSuffixFunc.Call([]cty.Value{str, suffix})
}

View File

@ -0,0 +1,80 @@
package stdlib
import (
"regexp"
"strings"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
)
// ReplaceFunc is a function that searches a given string for another given
// substring, and replaces each occurence with a given replacement string.
// The substr argument is a simple string.
var ReplaceFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "str",
Type: cty.String,
},
{
Name: "substr",
Type: cty.String,
},
{
Name: "replace",
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
str := args[0].AsString()
substr := args[1].AsString()
replace := args[2].AsString()
return cty.StringVal(strings.Replace(str, substr, replace, -1)), nil
},
})
// RegexReplaceFunc is a function that searches a given string for another
// given substring, and replaces each occurence with a given replacement
// string. The substr argument must be a valid regular expression.
var RegexReplaceFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "str",
Type: cty.String,
},
{
Name: "substr",
Type: cty.String,
},
{
Name: "replace",
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
str := args[0].AsString()
substr := args[1].AsString()
replace := args[2].AsString()
re, err := regexp.Compile(substr)
if err != nil {
return cty.UnknownVal(cty.String), err
}
return cty.StringVal(re.ReplaceAllString(str, replace)), nil
},
})
// Replace searches a given string for another given substring,
// and replaces all occurrences with a given replacement string.
func Replace(str, substr, replace cty.Value) (cty.Value, error) {
return ReplaceFunc.Call([]cty.Value{str, substr, replace})
}
func RegexReplace(str, substr, replace cty.Value) (cty.Value, error) {
return RegexReplaceFunc.Call([]cty.Value{str, substr, replace})
}

View File

@ -0,0 +1,31 @@
package function
import (
"github.com/zclconf/go-cty/cty"
)
// Unpredictable wraps a given function such that it retains the same arguments
// and type checking behavior but will return an unknown value when called.
//
// It is recommended that most functions be "pure", which is to say that they
// will always produce the same value given particular input. However,
// sometimes it is necessary to offer functions whose behavior depends on
// some external state, such as reading a file or determining the current time.
// In such cases, an unpredictable wrapper might be used to stand in for
// the function during some sort of prior "checking" phase in order to delay
// the actual effect until later.
//
// While Unpredictable can support a function that isn't pure in its
// implementation, it still expects a function to be pure in its type checking
// behavior, except for the special case of returning cty.DynamicPseudoType
// if it is not yet able to predict its return value based on current argument
// information.
func Unpredictable(f Function) Function {
newSpec := *f.spec // shallow copy
newSpec.Impl = unpredictableImpl
return New(&newSpec)
}
func unpredictableImpl(args []cty.Value, retType cty.Type) (cty.Value, error) {
return cty.UnknownVal(retType), nil
}

204
vendor/github.com/zclconf/go-cty/cty/gob.go generated vendored Normal file
View File

@ -0,0 +1,204 @@
package cty
import (
"bytes"
"encoding/gob"
"errors"
"fmt"
"math/big"
"github.com/zclconf/go-cty/cty/set"
)
// GobEncode is an implementation of the gob.GobEncoder interface, which
// allows Values to be included in structures encoded with encoding/gob.
//
// Currently it is not possible to represent values of capsule types in gob,
// because the types themselves cannot be represented.
func (val Value) GobEncode() ([]byte, error) {
if val.IsMarked() {
return nil, errors.New("value is marked")
}
buf := &bytes.Buffer{}
enc := gob.NewEncoder(buf)
gv := gobValue{
Version: 0,
Ty: val.ty,
V: val.v,
}
err := enc.Encode(gv)
if err != nil {
return nil, fmt.Errorf("error encoding cty.Value: %s", err)
}
return buf.Bytes(), nil
}
// GobDecode is an implementation of the gob.GobDecoder interface, which
// inverts the operation performed by GobEncode. See the documentation of
// GobEncode for considerations when using cty.Value instances with gob.
func (val *Value) GobDecode(buf []byte) error {
r := bytes.NewReader(buf)
dec := gob.NewDecoder(r)
var gv gobValue
err := dec.Decode(&gv)
if err != nil {
return fmt.Errorf("error decoding cty.Value: %s", err)
}
if gv.Version != 0 {
return fmt.Errorf("unsupported cty.Value encoding version %d; only 0 is supported", gv.Version)
}
// Because big.Float.GobEncode is implemented with a pointer reciever,
// gob encoding of an interface{} containing a *big.Float value does not
// round-trip correctly, emerging instead as a non-pointer big.Float.
// The rest of cty expects all number values to be represented by
// *big.Float, so we'll fix that up here.
gv.V = gobDecodeFixNumberPtr(gv.V, gv.Ty)
val.ty = gv.Ty
val.v = gv.V
return nil
}
// GobEncode is an implementation of the gob.GobEncoder interface, which
// allows Types to be included in structures encoded with encoding/gob.
//
// Currently it is not possible to represent capsule types in gob.
func (t Type) GobEncode() ([]byte, error) {
buf := &bytes.Buffer{}
enc := gob.NewEncoder(buf)
gt := gobType{
Version: 0,
Impl: t.typeImpl,
}
err := enc.Encode(gt)
if err != nil {
return nil, fmt.Errorf("error encoding cty.Type: %s", err)
}
return buf.Bytes(), nil
}
// GobDecode is an implementatino of the gob.GobDecoder interface, which
// reverses the encoding performed by GobEncode to allow types to be recovered
// from gob buffers.
func (t *Type) GobDecode(buf []byte) error {
r := bytes.NewReader(buf)
dec := gob.NewDecoder(r)
var gt gobType
err := dec.Decode(&gt)
if err != nil {
return fmt.Errorf("error decoding cty.Type: %s", err)
}
if gt.Version != 0 {
return fmt.Errorf("unsupported cty.Type encoding version %d; only 0 is supported", gt.Version)
}
t.typeImpl = gt.Impl
return nil
}
// Capsule types cannot currently be gob-encoded, because they rely on pointer
// equality and we have no way to recover the original pointer on decode.
func (t *capsuleType) GobEncode() ([]byte, error) {
return nil, fmt.Errorf("cannot gob-encode capsule type %q", t.FriendlyName(friendlyTypeName))
}
func (t *capsuleType) GobDecode() ([]byte, error) {
return nil, fmt.Errorf("cannot gob-decode capsule type %q", t.FriendlyName(friendlyTypeName))
}
type gobValue struct {
Version int
Ty Type
V interface{}
}
type gobType struct {
Version int
Impl typeImpl
}
type gobCapsuleTypeImpl struct {
}
// goDecodeFixNumberPtr fixes an unfortunate quirk of round-tripping cty.Number
// values through gob: the big.Float.GobEncode method is implemented on a
// pointer receiver, and so it loses the "pointer-ness" of the value on
// encode, causing the values to emerge the other end as big.Float rather than
// *big.Float as we expect elsewhere in cty.
//
// The implementation of gobDecodeFixNumberPtr mutates the given raw value
// during its work, and may either return the same value mutated or a new
// value. Callers must no longer use whatever value they pass as "raw" after
// this function is called.
func gobDecodeFixNumberPtr(raw interface{}, ty Type) interface{} {
// Unfortunately we need to work recursively here because number values
// might be embedded in structural or collection type values.
switch {
case ty.Equals(Number):
if bf, ok := raw.(big.Float); ok {
return &bf // wrap in pointer
}
case ty.IsMapType() && ty.ElementType().Equals(Number):
if m, ok := raw.(map[string]interface{}); ok {
for k, v := range m {
m[k] = gobDecodeFixNumberPtr(v, ty.ElementType())
}
}
case ty.IsListType() && ty.ElementType().Equals(Number):
if s, ok := raw.([]interface{}); ok {
for i, v := range s {
s[i] = gobDecodeFixNumberPtr(v, ty.ElementType())
}
}
case ty.IsSetType() && ty.ElementType().Equals(Number):
if s, ok := raw.(set.Set); ok {
newS := set.NewSet(s.Rules())
for it := s.Iterator(); it.Next(); {
newV := gobDecodeFixNumberPtr(it.Value(), ty.ElementType())
newS.Add(newV)
}
return newS
}
case ty.IsObjectType():
if m, ok := raw.(map[string]interface{}); ok {
for k, v := range m {
aty := ty.AttributeType(k)
m[k] = gobDecodeFixNumberPtr(v, aty)
}
}
case ty.IsTupleType():
if s, ok := raw.([]interface{}); ok {
for i, v := range s {
ety := ty.TupleElementType(i)
s[i] = gobDecodeFixNumberPtr(v, ety)
}
}
}
return raw
}
// gobDecodeFixNumberPtrVal is a helper wrapper around gobDecodeFixNumberPtr
// that works with already-constructed values. This is primarily for testing,
// to fix up intentionally-invalid number values for the parts of the test
// code that need them to be valid, such as calling GoString on them.
func gobDecodeFixNumberPtrVal(v Value) Value {
raw := gobDecodeFixNumberPtr(v.v, v.ty)
return Value{
v: raw,
ty: v.ty,
}
}

7
vendor/github.com/zclconf/go-cty/cty/gocty/doc.go generated vendored Normal file
View File

@ -0,0 +1,7 @@
// Package gocty deals with converting between cty Values and native go
// values.
//
// It operates under a similar principle to the encoding/json and
// encoding/xml packages in the standard library, using reflection to
// populate native Go data structures from cty values and vice-versa.
package gocty

43
vendor/github.com/zclconf/go-cty/cty/gocty/helpers.go generated vendored Normal file
View File

@ -0,0 +1,43 @@
package gocty
import (
"math/big"
"reflect"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/set"
)
var valueType = reflect.TypeOf(cty.Value{})
var typeType = reflect.TypeOf(cty.Type{})
var setType = reflect.TypeOf(set.Set{})
var bigFloatType = reflect.TypeOf(big.Float{})
var bigIntType = reflect.TypeOf(big.Int{})
var emptyInterfaceType = reflect.TypeOf(interface{}(nil))
var stringType = reflect.TypeOf("")
// structTagIndices interrogates the fields of the given type (which must
// be a struct type, or we'll panic) and returns a map from the cty
// attribute names declared via struct tags to the indices of the
// fields holding those tags.
//
// This function will panic if two fields within the struct are tagged with
// the same cty attribute name.
func structTagIndices(st reflect.Type) map[string]int {
ct := st.NumField()
ret := make(map[string]int, ct)
for i := 0; i < ct; i++ {
field := st.Field(i)
attrName := field.Tag.Get("cty")
if attrName != "" {
ret[attrName] = i
}
}
return ret
}

548
vendor/github.com/zclconf/go-cty/cty/gocty/in.go generated vendored Normal file
View File

@ -0,0 +1,548 @@
package gocty
import (
"math/big"
"reflect"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert"
"github.com/zclconf/go-cty/cty/set"
)
// ToCtyValue produces a cty.Value from a Go value. The result will conform
// to the given type, or an error will be returned if this is not possible.
//
// The target type serves as a hint to resolve ambiguities in the mapping.
// For example, the Go type set.Set tells us that the value is a set but
// does not describe the set's element type. This also allows for convenient
// conversions, such as populating a set from a slice rather than having to
// first explicitly instantiate a set.Set.
//
// The audience of this function is assumed to be the developers of Go code
// that is integrating with cty, and thus the error messages it returns are
// presented from Go's perspective. These messages are thus not appropriate
// for display to end-users. An error returned from ToCtyValue represents a
// bug in the calling program, not user error.
func ToCtyValue(val interface{}, ty cty.Type) (cty.Value, error) {
// 'path' starts off as empty but will grow for each level of recursive
// call we make, so by the time toCtyValue returns it is likely to have
// unused capacity on the end of it, depending on how deeply-recursive
// the given Type is.
path := make(cty.Path, 0)
return toCtyValue(reflect.ValueOf(val), ty, path)
}
func toCtyValue(val reflect.Value, ty cty.Type, path cty.Path) (cty.Value, error) {
if val != (reflect.Value{}) && val.Type().AssignableTo(valueType) {
// If the source value is a cty.Value then we'll try to just pass
// through to the target type directly.
return toCtyPassthrough(val, ty, path)
}
switch ty {
case cty.Bool:
return toCtyBool(val, path)
case cty.Number:
return toCtyNumber(val, path)
case cty.String:
return toCtyString(val, path)
case cty.DynamicPseudoType:
return toCtyDynamic(val, path)
}
switch {
case ty.IsListType():
return toCtyList(val, ty.ElementType(), path)
case ty.IsMapType():
return toCtyMap(val, ty.ElementType(), path)
case ty.IsSetType():
return toCtySet(val, ty.ElementType(), path)
case ty.IsObjectType():
return toCtyObject(val, ty.AttributeTypes(), path)
case ty.IsTupleType():
return toCtyTuple(val, ty.TupleElementTypes(), path)
case ty.IsCapsuleType():
return toCtyCapsule(val, ty, path)
}
// We should never fall out here
return cty.NilVal, path.NewErrorf("unsupported target type %#v", ty)
}
func toCtyBool(val reflect.Value, path cty.Path) (cty.Value, error) {
if val = toCtyUnwrapPointer(val); !val.IsValid() {
return cty.NullVal(cty.Bool), nil
}
switch val.Kind() {
case reflect.Bool:
return cty.BoolVal(val.Bool()), nil
default:
return cty.NilVal, path.NewErrorf("can't convert Go %s to bool", val.Kind())
}
}
func toCtyNumber(val reflect.Value, path cty.Path) (cty.Value, error) {
if val = toCtyUnwrapPointer(val); !val.IsValid() {
return cty.NullVal(cty.Number), nil
}
switch val.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return cty.NumberIntVal(val.Int()), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return cty.NumberUIntVal(val.Uint()), nil
case reflect.Float32, reflect.Float64:
return cty.NumberFloatVal(val.Float()), nil
case reflect.Struct:
if val.Type().AssignableTo(bigIntType) {
bigInt := val.Interface().(big.Int)
bigFloat := (&big.Float{}).SetInt(&bigInt)
val = reflect.ValueOf(*bigFloat)
}
if val.Type().AssignableTo(bigFloatType) {
bigFloat := val.Interface().(big.Float)
return cty.NumberVal(&bigFloat), nil
}
fallthrough
default:
return cty.NilVal, path.NewErrorf("can't convert Go %s to number", val.Kind())
}
}
func toCtyString(val reflect.Value, path cty.Path) (cty.Value, error) {
if val = toCtyUnwrapPointer(val); !val.IsValid() {
return cty.NullVal(cty.String), nil
}
switch val.Kind() {
case reflect.String:
return cty.StringVal(val.String()), nil
default:
return cty.NilVal, path.NewErrorf("can't convert Go %s to string", val.Kind())
}
}
func toCtyList(val reflect.Value, ety cty.Type, path cty.Path) (cty.Value, error) {
if val = toCtyUnwrapPointer(val); !val.IsValid() {
return cty.NullVal(cty.List(ety)), nil
}
switch val.Kind() {
case reflect.Slice:
if val.IsNil() {
return cty.NullVal(cty.List(ety)), nil
}
fallthrough
case reflect.Array:
if val.Len() == 0 {
return cty.ListValEmpty(ety), nil
}
// While we work on our elements we'll temporarily grow
// path to give us a place to put our index step.
path = append(path, cty.PathStep(nil))
vals := make([]cty.Value, val.Len())
for i := range vals {
var err error
path[len(path)-1] = cty.IndexStep{
Key: cty.NumberIntVal(int64(i)),
}
vals[i], err = toCtyValue(val.Index(i), ety, path)
if err != nil {
return cty.NilVal, err
}
}
// Discard our extra path segment, retaining it as extra capacity
// for future appending to the path.
path = path[:len(path)-1]
return cty.ListVal(vals), nil
default:
return cty.NilVal, path.NewErrorf("can't convert Go %s to %#v", val.Kind(), cty.List(ety))
}
}
func toCtyMap(val reflect.Value, ety cty.Type, path cty.Path) (cty.Value, error) {
if val = toCtyUnwrapPointer(val); !val.IsValid() {
return cty.NullVal(cty.Map(ety)), nil
}
switch val.Kind() {
case reflect.Map:
if val.IsNil() {
return cty.NullVal(cty.Map(ety)), nil
}
if val.Len() == 0 {
return cty.MapValEmpty(ety), nil
}
keyType := val.Type().Key()
if keyType.Kind() != reflect.String {
return cty.NilVal, path.NewErrorf("can't convert Go map with key type %s; key type must be string", keyType)
}
// While we work on our elements we'll temporarily grow
// path to give us a place to put our index step.
path = append(path, cty.PathStep(nil))
vals := make(map[string]cty.Value, val.Len())
for _, kv := range val.MapKeys() {
k := kv.String()
var err error
path[len(path)-1] = cty.IndexStep{
Key: cty.StringVal(k),
}
vals[k], err = toCtyValue(val.MapIndex(reflect.ValueOf(k)), ety, path)
if err != nil {
return cty.NilVal, err
}
}
// Discard our extra path segment, retaining it as extra capacity
// for future appending to the path.
path = path[:len(path)-1]
return cty.MapVal(vals), nil
default:
return cty.NilVal, path.NewErrorf("can't convert Go %s to %#v", val.Kind(), cty.Map(ety))
}
}
func toCtySet(val reflect.Value, ety cty.Type, path cty.Path) (cty.Value, error) {
if val = toCtyUnwrapPointer(val); !val.IsValid() {
return cty.NullVal(cty.Set(ety)), nil
}
var vals []cty.Value
switch val.Kind() {
case reflect.Slice:
if val.IsNil() {
return cty.NullVal(cty.Set(ety)), nil
}
fallthrough
case reflect.Array:
if val.Len() == 0 {
return cty.SetValEmpty(ety), nil
}
vals = make([]cty.Value, val.Len())
for i := range vals {
var err error
vals[i], err = toCtyValue(val.Index(i), ety, path)
if err != nil {
return cty.NilVal, err
}
}
case reflect.Struct:
if !val.Type().AssignableTo(setType) {
return cty.NilVal, path.NewErrorf("can't convert Go %s to %#v", val.Type(), cty.Set(ety))
}
rawSet := val.Interface().(set.Set)
inVals := rawSet.Values()
if len(inVals) == 0 {
return cty.SetValEmpty(ety), nil
}
vals = make([]cty.Value, len(inVals))
for i := range inVals {
var err error
vals[i], err = toCtyValue(reflect.ValueOf(inVals[i]), ety, path)
if err != nil {
return cty.NilVal, err
}
}
default:
return cty.NilVal, path.NewErrorf("can't convert Go %s to %#v", val.Kind(), cty.Set(ety))
}
return cty.SetVal(vals), nil
}
func toCtyObject(val reflect.Value, attrTypes map[string]cty.Type, path cty.Path) (cty.Value, error) {
if val = toCtyUnwrapPointer(val); !val.IsValid() {
return cty.NullVal(cty.Object(attrTypes)), nil
}
switch val.Kind() {
case reflect.Map:
if val.IsNil() {
return cty.NullVal(cty.Object(attrTypes)), nil
}
keyType := val.Type().Key()
if keyType.Kind() != reflect.String {
return cty.NilVal, path.NewErrorf("can't convert Go map with key type %s; key type must be string", keyType)
}
if len(attrTypes) == 0 {
return cty.EmptyObjectVal, nil
}
// While we work on our elements we'll temporarily grow
// path to give us a place to put our GetAttr step.
path = append(path, cty.PathStep(nil))
haveKeys := make(map[string]struct{}, val.Len())
for _, kv := range val.MapKeys() {
haveKeys[kv.String()] = struct{}{}
}
vals := make(map[string]cty.Value, len(attrTypes))
for k, at := range attrTypes {
var err error
path[len(path)-1] = cty.GetAttrStep{
Name: k,
}
if _, have := haveKeys[k]; !have {
vals[k] = cty.NullVal(at)
continue
}
vals[k], err = toCtyValue(val.MapIndex(reflect.ValueOf(k)), at, path)
if err != nil {
return cty.NilVal, err
}
}
// Discard our extra path segment, retaining it as extra capacity
// for future appending to the path.
path = path[:len(path)-1]
return cty.ObjectVal(vals), nil
case reflect.Struct:
if len(attrTypes) == 0 {
return cty.EmptyObjectVal, nil
}
// While we work on our elements we'll temporarily grow
// path to give us a place to put our GetAttr step.
path = append(path, cty.PathStep(nil))
attrFields := structTagIndices(val.Type())
vals := make(map[string]cty.Value, len(attrTypes))
for k, at := range attrTypes {
path[len(path)-1] = cty.GetAttrStep{
Name: k,
}
if fieldIdx, have := attrFields[k]; have {
var err error
vals[k], err = toCtyValue(val.Field(fieldIdx), at, path)
if err != nil {
return cty.NilVal, err
}
} else {
vals[k] = cty.NullVal(at)
}
}
// Discard our extra path segment, retaining it as extra capacity
// for future appending to the path.
path = path[:len(path)-1]
return cty.ObjectVal(vals), nil
default:
return cty.NilVal, path.NewErrorf("can't convert Go %s to %#v", val.Kind(), cty.Object(attrTypes))
}
}
func toCtyTuple(val reflect.Value, elemTypes []cty.Type, path cty.Path) (cty.Value, error) {
if val = toCtyUnwrapPointer(val); !val.IsValid() {
return cty.NullVal(cty.Tuple(elemTypes)), nil
}
switch val.Kind() {
case reflect.Slice:
if val.IsNil() {
return cty.NullVal(cty.Tuple(elemTypes)), nil
}
if val.Len() != len(elemTypes) {
return cty.NilVal, path.NewErrorf("wrong number of elements %d; need %d", val.Len(), len(elemTypes))
}
if len(elemTypes) == 0 {
return cty.EmptyTupleVal, nil
}
// While we work on our elements we'll temporarily grow
// path to give us a place to put our Index step.
path = append(path, cty.PathStep(nil))
vals := make([]cty.Value, len(elemTypes))
for i, ety := range elemTypes {
var err error
path[len(path)-1] = cty.IndexStep{
Key: cty.NumberIntVal(int64(i)),
}
vals[i], err = toCtyValue(val.Index(i), ety, path)
if err != nil {
return cty.NilVal, err
}
}
// Discard our extra path segment, retaining it as extra capacity
// for future appending to the path.
path = path[:len(path)-1]
return cty.TupleVal(vals), nil
case reflect.Struct:
fieldCount := val.Type().NumField()
if fieldCount != len(elemTypes) {
return cty.NilVal, path.NewErrorf("wrong number of struct fields %d; need %d", fieldCount, len(elemTypes))
}
if len(elemTypes) == 0 {
return cty.EmptyTupleVal, nil
}
// While we work on our elements we'll temporarily grow
// path to give us a place to put our Index step.
path = append(path, cty.PathStep(nil))
vals := make([]cty.Value, len(elemTypes))
for i, ety := range elemTypes {
var err error
path[len(path)-1] = cty.IndexStep{
Key: cty.NumberIntVal(int64(i)),
}
vals[i], err = toCtyValue(val.Field(i), ety, path)
if err != nil {
return cty.NilVal, err
}
}
// Discard our extra path segment, retaining it as extra capacity
// for future appending to the path.
path = path[:len(path)-1]
return cty.TupleVal(vals), nil
default:
return cty.NilVal, path.NewErrorf("can't convert Go %s to %#v", val.Kind(), cty.Tuple(elemTypes))
}
}
func toCtyCapsule(val reflect.Value, capsuleType cty.Type, path cty.Path) (cty.Value, error) {
if val = toCtyUnwrapPointer(val); !val.IsValid() {
return cty.NullVal(capsuleType), nil
}
if val.Kind() != reflect.Ptr {
if !val.CanAddr() {
return cty.NilVal, path.NewErrorf("source value for capsule %#v must be addressable", capsuleType)
}
val = val.Addr()
}
if !val.Type().Elem().AssignableTo(capsuleType.EncapsulatedType()) {
return cty.NilVal, path.NewErrorf("value of type %T not compatible with capsule %#v", val.Interface(), capsuleType)
}
return cty.CapsuleVal(capsuleType, val.Interface()), nil
}
func toCtyDynamic(val reflect.Value, path cty.Path) (cty.Value, error) {
if val = toCtyUnwrapPointer(val); !val.IsValid() {
return cty.NullVal(cty.DynamicPseudoType), nil
}
switch val.Kind() {
case reflect.Struct:
if !val.Type().AssignableTo(valueType) {
return cty.NilVal, path.NewErrorf("can't convert Go %s dynamically; only cty.Value allowed", val.Type())
}
return val.Interface().(cty.Value), nil
default:
return cty.NilVal, path.NewErrorf("can't convert Go %s dynamically; only cty.Value allowed", val.Kind())
}
}
func toCtyPassthrough(wrappedVal reflect.Value, wantTy cty.Type, path cty.Path) (cty.Value, error) {
if wrappedVal = toCtyUnwrapPointer(wrappedVal); !wrappedVal.IsValid() {
return cty.NullVal(wantTy), nil
}
givenVal := wrappedVal.Interface().(cty.Value)
val, err := convert.Convert(givenVal, wantTy)
if err != nil {
return cty.NilVal, path.NewErrorf("unsuitable value: %s", err)
}
return val, nil
}
// toCtyUnwrapPointer is a helper for dealing with Go pointers. It has three
// possible outcomes:
//
// - Given value isn't a pointer, so it's just returned as-is.
// - Given value is a non-nil pointer, in which case it is dereferenced
// and the result returned.
// - Given value is a nil pointer, in which case an invalid value is returned.
//
// For nested pointer types, like **int, they are all dereferenced in turn
// until a non-pointer value is found, or until a nil pointer is encountered.
func toCtyUnwrapPointer(val reflect.Value) reflect.Value {
for val.Kind() == reflect.Ptr || val.Kind() == reflect.Interface {
if val.IsNil() {
return reflect.Value{}
}
val = val.Elem()
}
return val
}

686
vendor/github.com/zclconf/go-cty/cty/gocty/out.go generated vendored Normal file
View File

@ -0,0 +1,686 @@
package gocty
import (
"math"
"math/big"
"reflect"
"github.com/zclconf/go-cty/cty"
)
// FromCtyValue assigns a cty.Value to a reflect.Value, which must be a pointer,
// using a fixed set of conversion rules.
//
// This function considers its audience to be the creator of the cty Value
// given, and thus the error messages it generates are (unlike with ToCtyValue)
// presented in cty terminology that is generally appropriate to return to
// end-users in applications where cty data structures are built from
// user-provided configuration. In particular this means that if incorrect
// target types are provided by the calling application the resulting error
// messages are likely to be confusing, since we assume that the given target
// type is correct and the cty.Value is where the error lies.
//
// If an error is returned, the target data structure may have been partially
// populated, but the degree to which this is true is an implementation
// detail that the calling application should not rely on.
//
// The function will panic if given a non-pointer as the Go value target,
// since that is considered to be a bug in the calling program.
func FromCtyValue(val cty.Value, target interface{}) error {
tVal := reflect.ValueOf(target)
if tVal.Kind() != reflect.Ptr {
panic("target value is not a pointer")
}
if tVal.IsNil() {
panic("target value is nil pointer")
}
// 'path' starts off as empty but will grow for each level of recursive
// call we make, so by the time fromCtyValue returns it is likely to have
// unused capacity on the end of it, depending on how deeply-recursive
// the given cty.Value is.
path := make(cty.Path, 0)
return fromCtyValue(val, tVal, path)
}
func fromCtyValue(val cty.Value, target reflect.Value, path cty.Path) error {
ty := val.Type()
deepTarget := fromCtyPopulatePtr(target, false)
// If we're decoding into a cty.Value then we just pass through the
// value as-is, to enable partial decoding. This is the only situation
// where unknown values are permitted.
if deepTarget.Kind() == reflect.Struct && deepTarget.Type().AssignableTo(valueType) {
deepTarget.Set(reflect.ValueOf(val))
return nil
}
// Lists and maps can be nil without indirection, but everything else
// requires a pointer and we set it immediately to nil.
// We also make an exception for capsule types because we want to handle
// pointers specially for these.
// (fromCtyList and fromCtyMap must therefore deal with val.IsNull, while
// other types can assume no nulls after this point.)
if val.IsNull() && !val.Type().IsListType() && !val.Type().IsMapType() && !val.Type().IsCapsuleType() {
target = fromCtyPopulatePtr(target, true)
if target.Kind() != reflect.Ptr {
return path.NewErrorf("null value is not allowed")
}
target.Set(reflect.Zero(target.Type()))
return nil
}
target = deepTarget
if !val.IsKnown() {
return path.NewErrorf("value must be known")
}
switch ty {
case cty.Bool:
return fromCtyBool(val, target, path)
case cty.Number:
return fromCtyNumber(val, target, path)
case cty.String:
return fromCtyString(val, target, path)
}
switch {
case ty.IsListType():
return fromCtyList(val, target, path)
case ty.IsMapType():
return fromCtyMap(val, target, path)
case ty.IsSetType():
return fromCtySet(val, target, path)
case ty.IsObjectType():
return fromCtyObject(val, target, path)
case ty.IsTupleType():
return fromCtyTuple(val, target, path)
case ty.IsCapsuleType():
return fromCtyCapsule(val, target, path)
}
// We should never fall out here; reaching here indicates a bug in this
// function.
return path.NewErrorf("unsupported source type %#v", ty)
}
func fromCtyBool(val cty.Value, target reflect.Value, path cty.Path) error {
switch target.Kind() {
case reflect.Bool:
target.SetBool(val.True())
return nil
default:
return likelyRequiredTypesError(path, target)
}
}
func fromCtyNumber(val cty.Value, target reflect.Value, path cty.Path) error {
bf := val.AsBigFloat()
switch target.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return fromCtyNumberInt(bf, target, path)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return fromCtyNumberUInt(bf, target, path)
case reflect.Float32, reflect.Float64:
return fromCtyNumberFloat(bf, target, path)
case reflect.Struct:
return fromCtyNumberBig(bf, target, path)
default:
return likelyRequiredTypesError(path, target)
}
}
func fromCtyNumberInt(bf *big.Float, target reflect.Value, path cty.Path) error {
// Doing this with switch rather than << arithmetic because << with
// result >32-bits is not portable to 32-bit systems.
var min int64
var max int64
switch target.Type().Bits() {
case 8:
min = math.MinInt8
max = math.MaxInt8
case 16:
min = math.MinInt16
max = math.MaxInt16
case 32:
min = math.MinInt32
max = math.MaxInt32
case 64:
min = math.MinInt64
max = math.MaxInt64
default:
panic("weird number of bits in target int")
}
iv, accuracy := bf.Int64()
if accuracy != big.Exact || iv < min || iv > max {
return path.NewErrorf("value must be a whole number, between %d and %d", min, max)
}
target.SetInt(iv)
return nil
}
func fromCtyNumberUInt(bf *big.Float, target reflect.Value, path cty.Path) error {
// Doing this with switch rather than << arithmetic because << with
// result >32-bits is not portable to 32-bit systems.
var max uint64
switch target.Type().Bits() {
case 8:
max = math.MaxUint8
case 16:
max = math.MaxUint16
case 32:
max = math.MaxUint32
case 64:
max = math.MaxUint64
default:
panic("weird number of bits in target uint")
}
iv, accuracy := bf.Uint64()
if accuracy != big.Exact || iv > max {
return path.NewErrorf("value must be a whole number, between 0 and %d inclusive", max)
}
target.SetUint(iv)
return nil
}
func fromCtyNumberFloat(bf *big.Float, target reflect.Value, path cty.Path) error {
switch target.Kind() {
case reflect.Float32, reflect.Float64:
fv, accuracy := bf.Float64()
if accuracy != big.Exact {
// We allow the precision to be truncated as part of our conversion,
// but we don't want to silently introduce infinities.
if math.IsInf(fv, 0) {
return path.NewErrorf("value must be between %f and %f inclusive", -math.MaxFloat64, math.MaxFloat64)
}
}
target.SetFloat(fv)
return nil
default:
panic("unsupported kind of float")
}
}
func fromCtyNumberBig(bf *big.Float, target reflect.Value, path cty.Path) error {
switch {
case bigFloatType.ConvertibleTo(target.Type()):
// Easy!
target.Set(reflect.ValueOf(bf).Elem().Convert(target.Type()))
return nil
case bigIntType.ConvertibleTo(target.Type()):
bi, accuracy := bf.Int(nil)
if accuracy != big.Exact {
return path.NewErrorf("value must be a whole number")
}
target.Set(reflect.ValueOf(bi).Elem().Convert(target.Type()))
return nil
default:
return likelyRequiredTypesError(path, target)
}
}
func fromCtyString(val cty.Value, target reflect.Value, path cty.Path) error {
switch target.Kind() {
case reflect.String:
target.SetString(val.AsString())
return nil
default:
return likelyRequiredTypesError(path, target)
}
}
func fromCtyList(val cty.Value, target reflect.Value, path cty.Path) error {
switch target.Kind() {
case reflect.Slice:
if val.IsNull() {
target.Set(reflect.Zero(target.Type()))
return nil
}
length := val.LengthInt()
tv := reflect.MakeSlice(target.Type(), length, length)
path = append(path, nil)
i := 0
var err error
val.ForEachElement(func(key cty.Value, val cty.Value) bool {
path[len(path)-1] = cty.IndexStep{
Key: cty.NumberIntVal(int64(i)),
}
targetElem := tv.Index(i)
err = fromCtyValue(val, targetElem, path)
if err != nil {
return true
}
i++
return false
})
if err != nil {
return err
}
path = path[:len(path)-1]
target.Set(tv)
return nil
case reflect.Array:
if val.IsNull() {
return path.NewErrorf("null value is not allowed")
}
length := val.LengthInt()
if length != target.Len() {
return path.NewErrorf("must be a list of length %d", target.Len())
}
path = append(path, nil)
i := 0
var err error
val.ForEachElement(func(key cty.Value, val cty.Value) bool {
path[len(path)-1] = cty.IndexStep{
Key: cty.NumberIntVal(int64(i)),
}
targetElem := target.Index(i)
err = fromCtyValue(val, targetElem, path)
if err != nil {
return true
}
i++
return false
})
if err != nil {
return err
}
path = path[:len(path)-1]
return nil
default:
return likelyRequiredTypesError(path, target)
}
}
func fromCtyMap(val cty.Value, target reflect.Value, path cty.Path) error {
switch target.Kind() {
case reflect.Map:
if val.IsNull() {
target.Set(reflect.Zero(target.Type()))
return nil
}
tv := reflect.MakeMap(target.Type())
et := target.Type().Elem()
path = append(path, nil)
var err error
val.ForEachElement(func(key cty.Value, val cty.Value) bool {
path[len(path)-1] = cty.IndexStep{
Key: key,
}
ks := key.AsString()
targetElem := reflect.New(et)
err = fromCtyValue(val, targetElem, path)
tv.SetMapIndex(reflect.ValueOf(ks), targetElem.Elem())
return err != nil
})
if err != nil {
return err
}
path = path[:len(path)-1]
target.Set(tv)
return nil
default:
return likelyRequiredTypesError(path, target)
}
}
func fromCtySet(val cty.Value, target reflect.Value, path cty.Path) error {
switch target.Kind() {
case reflect.Slice:
if val.IsNull() {
target.Set(reflect.Zero(target.Type()))
return nil
}
length := val.LengthInt()
tv := reflect.MakeSlice(target.Type(), length, length)
i := 0
var err error
val.ForEachElement(func(key cty.Value, val cty.Value) bool {
targetElem := tv.Index(i)
err = fromCtyValue(val, targetElem, path)
if err != nil {
return true
}
i++
return false
})
if err != nil {
return err
}
target.Set(tv)
return nil
case reflect.Array:
if val.IsNull() {
return path.NewErrorf("null value is not allowed")
}
length := val.LengthInt()
if length != target.Len() {
return path.NewErrorf("must be a set of length %d", target.Len())
}
i := 0
var err error
val.ForEachElement(func(key cty.Value, val cty.Value) bool {
targetElem := target.Index(i)
err = fromCtyValue(val, targetElem, path)
if err != nil {
return true
}
i++
return false
})
if err != nil {
return err
}
return nil
// TODO: decode into set.Set instance
default:
return likelyRequiredTypesError(path, target)
}
}
func fromCtyObject(val cty.Value, target reflect.Value, path cty.Path) error {
switch target.Kind() {
case reflect.Struct:
attrTypes := val.Type().AttributeTypes()
targetFields := structTagIndices(target.Type())
path = append(path, nil)
for k, i := range targetFields {
if _, exists := attrTypes[k]; !exists {
// If the field in question isn't able to represent nil,
// that's an error.
fk := target.Field(i).Kind()
switch fk {
case reflect.Ptr, reflect.Slice, reflect.Map, reflect.Interface:
// okay
default:
return path.NewErrorf("missing required attribute %q", k)
}
}
}
for k := range attrTypes {
path[len(path)-1] = cty.GetAttrStep{
Name: k,
}
fieldIdx, exists := targetFields[k]
if !exists {
return path.NewErrorf("unsupported attribute %q", k)
}
ev := val.GetAttr(k)
targetField := target.Field(fieldIdx)
err := fromCtyValue(ev, targetField, path)
if err != nil {
return err
}
}
path = path[:len(path)-1]
return nil
default:
return likelyRequiredTypesError(path, target)
}
}
func fromCtyTuple(val cty.Value, target reflect.Value, path cty.Path) error {
switch target.Kind() {
case reflect.Struct:
elemTypes := val.Type().TupleElementTypes()
fieldCount := target.Type().NumField()
if fieldCount != len(elemTypes) {
return path.NewErrorf("a tuple of %d elements is required", fieldCount)
}
path = append(path, nil)
for i := range elemTypes {
path[len(path)-1] = cty.IndexStep{
Key: cty.NumberIntVal(int64(i)),
}
ev := val.Index(cty.NumberIntVal(int64(i)))
targetField := target.Field(i)
err := fromCtyValue(ev, targetField, path)
if err != nil {
return err
}
}
path = path[:len(path)-1]
return nil
default:
return likelyRequiredTypesError(path, target)
}
}
func fromCtyCapsule(val cty.Value, target reflect.Value, path cty.Path) error {
if target.Kind() == reflect.Ptr {
// Walk through indirection until we get to the last pointer,
// which we might set to null below.
target = fromCtyPopulatePtr(target, true)
if val.IsNull() {
target.Set(reflect.Zero(target.Type()))
return nil
}
// Since a capsule contains a pointer to an object, we'll preserve
// that pointer on the way out and thus allow the caller to recover
// the original object, rather than a copy of it.
eType := val.Type().EncapsulatedType()
if !eType.AssignableTo(target.Elem().Type()) {
// Our interface contract promises that we won't expose Go
// implementation details in error messages, so we need to keep
// this vague. This can only arise if a calling application has
// more than one capsule type in play and a user mixes them up.
return path.NewErrorf("incorrect type %s", val.Type().FriendlyName())
}
target.Set(reflect.ValueOf(val.EncapsulatedValue()))
return nil
} else {
if val.IsNull() {
return path.NewErrorf("null value is not allowed")
}
// If our target isn't a pointer then we will attempt to copy
// the encapsulated value into it.
eType := val.Type().EncapsulatedType()
if !eType.AssignableTo(target.Type()) {
// Our interface contract promises that we won't expose Go
// implementation details in error messages, so we need to keep
// this vague. This can only arise if a calling application has
// more than one capsule type in play and a user mixes them up.
return path.NewErrorf("incorrect type %s", val.Type().FriendlyName())
}
// We know that EncapsulatedValue is always a pointer, so we
// can safely call .Elem on its reflect.Value.
target.Set(reflect.ValueOf(val.EncapsulatedValue()).Elem())
return nil
}
}
// fromCtyPopulatePtr recognizes when target is a pointer type and allocates
// a value to assign to that pointer, which it returns.
//
// If the given value has multiple levels of indirection, like **int, these
// will be processed in turn so that the return value is guaranteed to be
// a non-pointer.
//
// As an exception, if decodingNull is true then the returned value will be
// the final level of pointer, if any, so that the caller can assign it
// as nil to represent a null value. If the given target value is not a pointer
// at all then the returned value will be just the given target, so the caller
// must test if the returned value is a pointer before trying to assign nil
// to it.
func fromCtyPopulatePtr(target reflect.Value, decodingNull bool) reflect.Value {
for {
if target.Kind() == reflect.Interface && !target.IsNil() {
e := target.Elem()
if e.Kind() == reflect.Ptr && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Ptr) {
target = e
}
}
if target.Kind() != reflect.Ptr {
break
}
// Stop early if we're decodingNull and we've found our last indirection
if target.Elem().Kind() != reflect.Ptr && decodingNull && target.CanSet() {
break
}
if target.IsNil() {
target.Set(reflect.New(target.Type().Elem()))
}
target = target.Elem()
}
return target
}
// likelyRequiredTypesError returns an error that states which types are
// acceptable by making some assumptions about what types we support for
// each target Go kind. It's not a precise science but it allows us to return
// an error message that is cty-user-oriented rather than Go-oriented.
//
// Generally these error messages should be a matter of last resort, since
// the calling application should be validating user-provided value types
// before decoding anyway.
func likelyRequiredTypesError(path cty.Path, target reflect.Value) error {
switch target.Kind() {
case reflect.Bool:
return path.NewErrorf("bool value is required")
case reflect.String:
return path.NewErrorf("string value is required")
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
fallthrough
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
fallthrough
case reflect.Float32, reflect.Float64:
return path.NewErrorf("number value is required")
case reflect.Slice, reflect.Array:
return path.NewErrorf("list or set value is required")
case reflect.Map:
return path.NewErrorf("map or object value is required")
case reflect.Struct:
switch {
case target.Type().AssignableTo(bigFloatType) || target.Type().AssignableTo(bigIntType):
return path.NewErrorf("number value is required")
case target.Type().AssignableTo(setType):
return path.NewErrorf("set or list value is required")
default:
return path.NewErrorf("object or tuple value is required")
}
default:
// We should avoid getting into this path, since this error
// message is rather useless.
return path.NewErrorf("incorrect type")
}
}

View File

@ -0,0 +1,108 @@
package gocty
import (
"reflect"
"github.com/zclconf/go-cty/cty"
)
// ImpliedType takes an arbitrary Go value (as an interface{}) and attempts
// to find a suitable cty.Type instance that could be used for a conversion
// with ToCtyValue.
//
// This allows -- for simple situations at least -- types to be defined just
// once in Go and the cty types derived from the Go types, but in the process
// it makes some assumptions that may be undesirable so applications are
// encouraged to build their cty types directly if exacting control is
// required.
//
// Not all Go types can be represented as cty types, so an error may be
// returned which is usually considered to be a bug in the calling program.
// In particular, ImpliedType will never use capsule types in its returned
// type, because it cannot know the capsule types supported by the calling
// program.
func ImpliedType(gv interface{}) (cty.Type, error) {
rt := reflect.TypeOf(gv)
var path cty.Path
return impliedType(rt, path)
}
func impliedType(rt reflect.Type, path cty.Path) (cty.Type, error) {
switch rt.Kind() {
case reflect.Ptr:
return impliedType(rt.Elem(), path)
// Primitive types
case reflect.Bool:
return cty.Bool, nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return cty.Number, nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return cty.Number, nil
case reflect.Float32, reflect.Float64:
return cty.Number, nil
case reflect.String:
return cty.String, nil
// Collection types
case reflect.Slice:
path := append(path, cty.IndexStep{Key: cty.UnknownVal(cty.Number)})
ety, err := impliedType(rt.Elem(), path)
if err != nil {
return cty.NilType, err
}
return cty.List(ety), nil
case reflect.Map:
if !stringType.AssignableTo(rt.Key()) {
return cty.NilType, path.NewErrorf("no cty.Type for %s (must have string keys)", rt)
}
path := append(path, cty.IndexStep{Key: cty.UnknownVal(cty.String)})
ety, err := impliedType(rt.Elem(), path)
if err != nil {
return cty.NilType, err
}
return cty.Map(ety), nil
// Structural types
case reflect.Struct:
return impliedStructType(rt, path)
default:
return cty.NilType, path.NewErrorf("no cty.Type for %s", rt)
}
}
func impliedStructType(rt reflect.Type, path cty.Path) (cty.Type, error) {
if valueType.AssignableTo(rt) {
// Special case: cty.Value represents cty.DynamicPseudoType, for
// type conformance checking.
return cty.DynamicPseudoType, nil
}
fieldIdxs := structTagIndices(rt)
if len(fieldIdxs) == 0 {
return cty.NilType, path.NewErrorf("no cty.Type for %s (no cty field tags)", rt)
}
atys := make(map[string]cty.Type, len(fieldIdxs))
{
// Temporary extension of path for attributes
path := append(path, nil)
for k, fi := range fieldIdxs {
path[len(path)-1] = cty.GetAttrStep{Name: k}
ft := rt.Field(fi).Type
aty, err := impliedType(ft, path)
if err != nil {
return cty.NilType, err
}
atys[k] = aty
}
}
return cty.Object(atys), nil
}

99
vendor/github.com/zclconf/go-cty/cty/helper.go generated vendored Normal file
View File

@ -0,0 +1,99 @@
package cty
import (
"fmt"
)
// anyUnknown is a helper to easily check if a set of values contains any
// unknowns, for operations that short-circuit to return unknown in that case.
func anyUnknown(values ...Value) bool {
for _, val := range values {
if val.v == unknown {
return true
}
}
return false
}
// typeCheck tests whether all of the given values belong to the given type.
// If the given types are a mixture of the given type and the dynamic
// pseudo-type then a short-circuit dynamic value is returned. If the given
// values are all of the correct type but at least one is unknown then
// a short-circuit unknown value is returned. If any other types appear then
// an error is returned. Otherwise (finally!) the result is nil, nil.
func typeCheck(required Type, ret Type, values ...Value) (shortCircuit *Value, err error) {
hasDynamic := false
hasUnknown := false
for i, val := range values {
if val.ty == DynamicPseudoType {
hasDynamic = true
continue
}
if !val.Type().Equals(required) {
return nil, fmt.Errorf(
"type mismatch: want %s but value %d is %s",
required.FriendlyName(),
i, val.ty.FriendlyName(),
)
}
if val.v == unknown {
hasUnknown = true
}
}
if hasDynamic {
return &DynamicVal, nil
}
if hasUnknown {
ret := UnknownVal(ret)
return &ret, nil
}
return nil, nil
}
// mustTypeCheck is a wrapper around typeCheck that immediately panics if
// any error is returned.
func mustTypeCheck(required Type, ret Type, values ...Value) *Value {
shortCircuit, err := typeCheck(required, ret, values...)
if err != nil {
panic(err)
}
return shortCircuit
}
// shortCircuitForceType takes the return value from mustTypeCheck and
// replaces it with an unknown of the given type if the original value was
// DynamicVal.
//
// This is useful for operations that are specified to always return a
// particular type, since then a dynamic result can safely be "upgrade" to
// a strongly-typed unknown, which then allows subsequent operations to
// be actually type-checked.
//
// It is safe to use this only if the operation in question is defined as
// returning either a value of the given type or panicking, since we know
// then that subsequent operations won't run if the operation panics.
//
// If the given short-circuit value is *not* DynamicVal then it must be
// of the given type, or this function will panic.
func forceShortCircuitType(shortCircuit *Value, ty Type) *Value {
if shortCircuit == nil {
return nil
}
if shortCircuit.ty == DynamicPseudoType {
ret := UnknownVal(ty)
return &ret
}
if !shortCircuit.ty.Equals(ty) {
panic("forceShortCircuitType got value of wrong type")
}
return shortCircuit
}

199
vendor/github.com/zclconf/go-cty/cty/json.go generated vendored Normal file
View File

@ -0,0 +1,199 @@
package cty
import (
"bytes"
"encoding/json"
"fmt"
"sort"
)
// MarshalJSON is an implementation of json.Marshaler that allows Type
// instances to be serialized as JSON.
//
// All standard types can be serialized, but capsule types cannot since there
// is no way to automatically recover the original pointer and capsule types
// compare by equality.
func (t Type) MarshalJSON() ([]byte, error) {
switch impl := t.typeImpl.(type) {
case primitiveType:
switch impl.Kind {
case primitiveTypeBool:
return []byte{'"', 'b', 'o', 'o', 'l', '"'}, nil
case primitiveTypeNumber:
return []byte{'"', 'n', 'u', 'm', 'b', 'e', 'r', '"'}, nil
case primitiveTypeString:
return []byte{'"', 's', 't', 'r', 'i', 'n', 'g', '"'}, nil
default:
panic("unknown primitive type kind")
}
case typeList, typeMap, typeSet:
buf := &bytes.Buffer{}
etyJSON, err := t.ElementType().MarshalJSON()
if err != nil {
return nil, err
}
buf.WriteRune('[')
switch impl.(type) {
case typeList:
buf.WriteString(`"list"`)
case typeMap:
buf.WriteString(`"map"`)
case typeSet:
buf.WriteString(`"set"`)
}
buf.WriteRune(',')
buf.Write(etyJSON)
buf.WriteRune(']')
return buf.Bytes(), nil
case typeObject:
buf := &bytes.Buffer{}
atysJSON, err := json.Marshal(t.AttributeTypes())
if err != nil {
return nil, err
}
buf.WriteString(`["object",`)
buf.Write(atysJSON)
if optionals := t.OptionalAttributes(); len(optionals) > 0 {
buf.WriteByte(',')
optionalNames := make([]string, 0, len(optionals))
for k := range optionals {
optionalNames = append(optionalNames, k)
}
sort.Strings(optionalNames)
optionalsJSON, err := json.Marshal(optionalNames)
if err != nil {
return nil, err
}
buf.Write(optionalsJSON)
}
buf.WriteRune(']')
return buf.Bytes(), nil
case typeTuple:
buf := &bytes.Buffer{}
etysJSON, err := json.Marshal(t.TupleElementTypes())
if err != nil {
return nil, err
}
buf.WriteString(`["tuple",`)
buf.Write(etysJSON)
buf.WriteRune(']')
return buf.Bytes(), nil
case pseudoTypeDynamic:
return []byte{'"', 'd', 'y', 'n', 'a', 'm', 'i', 'c', '"'}, nil
case *capsuleType:
return nil, fmt.Errorf("type not allowed: %s", t.FriendlyName())
default:
// should never happen
panic("unknown type implementation")
}
}
// UnmarshalJSON is the opposite of MarshalJSON. See the documentation of
// MarshalJSON for information on the limitations of JSON serialization of
// types.
func (t *Type) 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 = Bool
case "number":
*t = Number
case "string":
*t = String
case "dynamic":
*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 Type
err = dec.Decode(&ety)
if err != nil {
return err
}
*t = List(ety)
case "map":
var ety Type
err = dec.Decode(&ety)
if err != nil {
return err
}
*t = Map(ety)
case "set":
var ety Type
err = dec.Decode(&ety)
if err != nil {
return err
}
*t = Set(ety)
case "object":
var atys map[string]Type
err = dec.Decode(&atys)
if err != nil {
return err
}
if dec.More() {
var optionals []string
err = dec.Decode(&optionals)
if err != nil {
return err
}
*t = ObjectWithOptionalAttrs(atys, optionals)
} else {
*t = Object(atys)
}
case "tuple":
var etys []Type
err = dec.Decode(&etys)
if err != nil {
return err
}
*t = Tuple(etys)
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")
}
}

11
vendor/github.com/zclconf/go-cty/cty/json/doc.go generated vendored Normal file
View File

@ -0,0 +1,11 @@
// Package json provides functions for serializing cty types and values in
// JSON format, and for decoding them again.
//
// Since the cty type system is a superset of the JSON type system,
// round-tripping through JSON is lossy unless type information is provided
// both at encoding time and decoding time. Callers of this package are
// therefore suggested to define their expected structure as a cty.Type
// and pass it in consistently both when encoding and when decoding, though
// default (type-lossy) behavior is provided for situations where the precise
// representation of the data is not significant.
package json

193
vendor/github.com/zclconf/go-cty/cty/json/marshal.go generated vendored Normal file
View File

@ -0,0 +1,193 @@
package json
import (
"bytes"
"encoding/json"
"sort"
"github.com/zclconf/go-cty/cty"
)
func marshal(val cty.Value, t cty.Type, path cty.Path, b *bytes.Buffer) error {
if val.IsMarked() {
return path.NewErrorf("value has marks, so it cannot be serialized as JSON")
}
// If we're going to decode as DynamicPseudoType then we need to save
// dynamic type information to recover the real type.
if t == cty.DynamicPseudoType && val.Type() != cty.DynamicPseudoType {
return marshalDynamic(val, path, b)
}
if val.IsNull() {
b.WriteString("null")
return nil
}
if !val.IsKnown() {
return path.NewErrorf("value is not known")
}
// The caller should've guaranteed that the given val is conformant with
// the given type t, so we'll proceed under that assumption here.
switch {
case t.IsPrimitiveType():
switch t {
case cty.String:
json, err := json.Marshal(val.AsString())
if err != nil {
return path.NewErrorf("failed to serialize value: %s", err)
}
b.Write(json)
return nil
case cty.Number:
if val.RawEquals(cty.PositiveInfinity) || val.RawEquals(cty.NegativeInfinity) {
return path.NewErrorf("cannot serialize infinity as JSON")
}
b.WriteString(val.AsBigFloat().Text('f', -1))
return nil
case cty.Bool:
if val.True() {
b.WriteString("true")
} else {
b.WriteString("false")
}
return nil
default:
panic("unsupported primitive type")
}
case t.IsListType(), t.IsSetType():
b.WriteRune('[')
first := true
ety := t.ElementType()
it := val.ElementIterator()
path := append(path, nil) // local override of 'path' with extra element
for it.Next() {
if !first {
b.WriteRune(',')
}
ek, ev := it.Element()
path[len(path)-1] = cty.IndexStep{
Key: ek,
}
err := marshal(ev, ety, path, b)
if err != nil {
return err
}
first = false
}
b.WriteRune(']')
return nil
case t.IsMapType():
b.WriteRune('{')
first := true
ety := t.ElementType()
it := val.ElementIterator()
path := append(path, nil) // local override of 'path' with extra element
for it.Next() {
if !first {
b.WriteRune(',')
}
ek, ev := it.Element()
path[len(path)-1] = cty.IndexStep{
Key: ek,
}
var err error
err = marshal(ek, ek.Type(), path, b)
if err != nil {
return err
}
b.WriteRune(':')
err = marshal(ev, ety, path, b)
if err != nil {
return err
}
first = false
}
b.WriteRune('}')
return nil
case t.IsTupleType():
b.WriteRune('[')
etys := t.TupleElementTypes()
it := val.ElementIterator()
path := append(path, nil) // local override of 'path' with extra element
i := 0
for it.Next() {
if i > 0 {
b.WriteRune(',')
}
ety := etys[i]
ek, ev := it.Element()
path[len(path)-1] = cty.IndexStep{
Key: ek,
}
err := marshal(ev, ety, path, b)
if err != nil {
return err
}
i++
}
b.WriteRune(']')
return nil
case t.IsObjectType():
b.WriteRune('{')
atys := t.AttributeTypes()
path := append(path, nil) // local override of 'path' with extra element
names := make([]string, 0, len(atys))
for k := range atys {
names = append(names, k)
}
sort.Strings(names)
for i, k := range names {
aty := atys[k]
if i > 0 {
b.WriteRune(',')
}
av := val.GetAttr(k)
path[len(path)-1] = cty.GetAttrStep{
Name: k,
}
var err error
err = marshal(cty.StringVal(k), cty.String, path, b)
if err != nil {
return err
}
b.WriteRune(':')
err = marshal(av, aty, path, b)
if err != nil {
return err
}
}
b.WriteRune('}')
return nil
case t.IsCapsuleType():
rawVal := val.EncapsulatedValue()
jsonVal, err := json.Marshal(rawVal)
if err != nil {
return path.NewError(err)
}
b.Write(jsonVal)
return nil
default:
// should never happen
return path.NewErrorf("cannot JSON-serialize %s", t.FriendlyName())
}
}
// marshalDynamic adds an extra wrapping object containing dynamic type
// information for the given value.
func marshalDynamic(val cty.Value, path cty.Path, b *bytes.Buffer) error {
typeJSON, err := MarshalType(val.Type())
if err != nil {
return path.NewErrorf("failed to serialize type: %s", err)
}
b.WriteString(`{"value":`)
marshal(val, val.Type(), path, b)
b.WriteString(`,"type":`)
b.Write(typeJSON)
b.WriteRune('}')
return nil
}

41
vendor/github.com/zclconf/go-cty/cty/json/simple.go generated vendored Normal file
View File

@ -0,0 +1,41 @@
package json
import (
"github.com/zclconf/go-cty/cty"
)
// SimpleJSONValue is a wrapper around cty.Value that adds implementations of
// json.Marshaler and json.Unmarshaler for simple-but-type-lossy automatic
// encoding and decoding of values.
//
// The couplet Marshal and Unmarshal both take extra type information to
// inform the encoding and decoding process so that all of the cty types
// can be represented even though JSON's type system is a subset.
//
// SimpleJSONValue instead takes the approach of discarding the value's type
// information and then deriving a new type from the stored structure when
// decoding. This results in the same data being returned but not necessarily
// with exactly the same type.
//
// For information on how types are inferred when decoding, see the
// documentation of the function ImpliedType.
type SimpleJSONValue struct {
cty.Value
}
// MarshalJSON is an implementation of json.Marshaler. See the documentation
// of SimpleJSONValue for more information.
func (v SimpleJSONValue) MarshalJSON() ([]byte, error) {
return Marshal(v.Value, v.Type())
}
// UnmarshalJSON is an implementation of json.Unmarshaler. See the
// documentation of SimpleJSONValue for more information.
func (v *SimpleJSONValue) UnmarshalJSON(buf []byte) error {
t, err := ImpliedType(buf)
if err != nil {
return err
}
v.Value, err = Unmarshal(buf, t)
return err
}

23
vendor/github.com/zclconf/go-cty/cty/json/type.go generated vendored Normal file
View File

@ -0,0 +1,23 @@
package json
import (
"github.com/zclconf/go-cty/cty"
)
// MarshalType returns a JSON serialization of the given type.
//
// This is just a thin wrapper around t.MarshalJSON, for symmetry with
// UnmarshalType.
func MarshalType(t cty.Type) ([]byte, error) {
return t.MarshalJSON()
}
// UnmarshalType decodes a JSON serialization of the given type as produced
// by either Type.MarshalJSON or MarshalType.
//
// This is a convenience wrapper around Type.UnmarshalJSON.
func UnmarshalType(buf []byte) (cty.Type, error) {
var t cty.Type
err := t.UnmarshalJSON(buf)
return t, err
}

View File

@ -0,0 +1,170 @@
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) == ']' {
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
}

459
vendor/github.com/zclconf/go-cty/cty/json/unmarshal.go generated vendored Normal file
View File

@ -0,0 +1,459 @@
package json
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert"
)
func unmarshal(buf []byte, t cty.Type, path cty.Path) (cty.Value, error) {
dec := bufDecoder(buf)
tok, err := dec.Token()
if err != nil {
return cty.NilVal, path.NewError(err)
}
if tok == nil {
return cty.NullVal(t), nil
}
if t == cty.DynamicPseudoType {
return unmarshalDynamic(buf, path)
}
switch {
case t.IsPrimitiveType():
val, err := unmarshalPrimitive(tok, t, path)
if err != nil {
return cty.NilVal, err
}
return val, nil
case t.IsListType():
return unmarshalList(buf, t.ElementType(), path)
case t.IsSetType():
return unmarshalSet(buf, t.ElementType(), path)
case t.IsMapType():
return unmarshalMap(buf, t.ElementType(), path)
case t.IsTupleType():
return unmarshalTuple(buf, t.TupleElementTypes(), path)
case t.IsObjectType():
return unmarshalObject(buf, t.AttributeTypes(), path)
case t.IsCapsuleType():
return unmarshalCapsule(buf, t, path)
default:
return cty.NilVal, path.NewErrorf("unsupported type %s", t.FriendlyName())
}
}
func unmarshalPrimitive(tok json.Token, t cty.Type, path cty.Path) (cty.Value, error) {
switch t {
case cty.Bool:
switch v := tok.(type) {
case bool:
return cty.BoolVal(v), nil
case string:
val, err := convert.Convert(cty.StringVal(v), t)
if err != nil {
return cty.NilVal, path.NewError(err)
}
return val, nil
default:
return cty.NilVal, path.NewErrorf("bool is required")
}
case cty.Number:
if v, ok := tok.(json.Number); ok {
tok = string(v)
}
switch v := tok.(type) {
case string:
val, err := cty.ParseNumberVal(v)
if err != nil {
return cty.NilVal, path.NewError(err)
}
return val, nil
default:
return cty.NilVal, path.NewErrorf("number is required")
}
case cty.String:
switch v := tok.(type) {
case string:
return cty.StringVal(v), nil
case json.Number:
return cty.StringVal(string(v)), nil
case bool:
val, err := convert.Convert(cty.BoolVal(v), t)
if err != nil {
return cty.NilVal, path.NewError(err)
}
return val, nil
default:
return cty.NilVal, path.NewErrorf("string is required")
}
default:
// should never happen
panic("unsupported primitive type")
}
}
func unmarshalList(buf []byte, ety cty.Type, path cty.Path) (cty.Value, error) {
dec := bufDecoder(buf)
if err := requireDelim(dec, '['); err != nil {
return cty.NilVal, path.NewError(err)
}
var vals []cty.Value
{
path := append(path, nil)
var idx int64
for dec.More() {
path[len(path)-1] = cty.IndexStep{
Key: cty.NumberIntVal(idx),
}
idx++
rawVal, err := readRawValue(dec)
if err != nil {
return cty.NilVal, path.NewErrorf("failed to read list value: %s", err)
}
el, err := unmarshal(rawVal, ety, path)
if err != nil {
return cty.NilVal, err
}
vals = append(vals, el)
}
}
if err := requireDelim(dec, ']'); err != nil {
return cty.NilVal, path.NewError(err)
}
if len(vals) == 0 {
return cty.ListValEmpty(ety), nil
}
return cty.ListVal(vals), nil
}
func unmarshalSet(buf []byte, ety cty.Type, path cty.Path) (cty.Value, error) {
dec := bufDecoder(buf)
if err := requireDelim(dec, '['); err != nil {
return cty.NilVal, path.NewError(err)
}
var vals []cty.Value
{
path := append(path, nil)
for dec.More() {
path[len(path)-1] = cty.IndexStep{
Key: cty.UnknownVal(ety),
}
rawVal, err := readRawValue(dec)
if err != nil {
return cty.NilVal, path.NewErrorf("failed to read set value: %s", err)
}
el, err := unmarshal(rawVal, ety, path)
if err != nil {
return cty.NilVal, err
}
vals = append(vals, el)
}
}
if err := requireDelim(dec, ']'); err != nil {
return cty.NilVal, path.NewError(err)
}
if len(vals) == 0 {
return cty.SetValEmpty(ety), nil
}
return cty.SetVal(vals), nil
}
func unmarshalMap(buf []byte, ety cty.Type, path cty.Path) (cty.Value, error) {
dec := bufDecoder(buf)
if err := requireDelim(dec, '{'); err != nil {
return cty.NilVal, path.NewError(err)
}
vals := make(map[string]cty.Value)
{
path := append(path, nil)
for dec.More() {
path[len(path)-1] = cty.IndexStep{
Key: cty.UnknownVal(cty.String),
}
var err error
k, err := requireObjectKey(dec)
if err != nil {
return cty.NilVal, path.NewErrorf("failed to read map key: %s", err)
}
path[len(path)-1] = cty.IndexStep{
Key: cty.StringVal(k),
}
rawVal, err := readRawValue(dec)
if err != nil {
return cty.NilVal, path.NewErrorf("failed to read map value: %s", err)
}
el, err := unmarshal(rawVal, ety, path)
if err != nil {
return cty.NilVal, err
}
vals[k] = el
}
}
if err := requireDelim(dec, '}'); err != nil {
return cty.NilVal, path.NewError(err)
}
if len(vals) == 0 {
return cty.MapValEmpty(ety), nil
}
return cty.MapVal(vals), nil
}
func unmarshalTuple(buf []byte, etys []cty.Type, path cty.Path) (cty.Value, error) {
dec := bufDecoder(buf)
if err := requireDelim(dec, '['); err != nil {
return cty.NilVal, path.NewError(err)
}
var vals []cty.Value
{
path := append(path, nil)
var idx int
for dec.More() {
if idx >= len(etys) {
return cty.NilVal, path[:len(path)-1].NewErrorf("too many tuple elements (need %d)", len(etys))
}
path[len(path)-1] = cty.IndexStep{
Key: cty.NumberIntVal(int64(idx)),
}
ety := etys[idx]
idx++
rawVal, err := readRawValue(dec)
if err != nil {
return cty.NilVal, path.NewErrorf("failed to read tuple value: %s", err)
}
el, err := unmarshal(rawVal, ety, path)
if err != nil {
return cty.NilVal, err
}
vals = append(vals, el)
}
}
if err := requireDelim(dec, ']'); err != nil {
return cty.NilVal, path.NewError(err)
}
if len(vals) != len(etys) {
return cty.NilVal, path[:len(path)-1].NewErrorf("not enough tuple elements (need %d)", len(etys))
}
if len(vals) == 0 {
return cty.EmptyTupleVal, nil
}
return cty.TupleVal(vals), nil
}
func unmarshalObject(buf []byte, atys map[string]cty.Type, path cty.Path) (cty.Value, error) {
dec := bufDecoder(buf)
if err := requireDelim(dec, '{'); err != nil {
return cty.NilVal, path.NewError(err)
}
vals := make(map[string]cty.Value)
{
objPath := path // some errors report from the object's perspective
path := append(path, nil) // path to a specific attribute
for dec.More() {
var err error
k, err := requireObjectKey(dec)
if err != nil {
return cty.NilVal, path.NewErrorf("failed to read object key: %s", err)
}
aty, ok := atys[k]
if !ok {
return cty.NilVal, objPath.NewErrorf("unsupported attribute %q", k)
}
path[len(path)-1] = cty.GetAttrStep{
Name: k,
}
rawVal, err := readRawValue(dec)
if err != nil {
return cty.NilVal, path.NewErrorf("failed to read object value: %s", err)
}
el, err := unmarshal(rawVal, aty, path)
if err != nil {
return cty.NilVal, err
}
vals[k] = el
}
}
if err := requireDelim(dec, '}'); err != nil {
return cty.NilVal, path.NewError(err)
}
// Make sure we have a value for every attribute
for k, aty := range atys {
if _, exists := vals[k]; !exists {
vals[k] = cty.NullVal(aty)
}
}
if len(vals) == 0 {
return cty.EmptyObjectVal, nil
}
return cty.ObjectVal(vals), nil
}
func unmarshalCapsule(buf []byte, t cty.Type, path cty.Path) (cty.Value, error) {
rawType := t.EncapsulatedType()
ptrPtr := reflect.New(reflect.PtrTo(rawType))
ptrPtr.Elem().Set(reflect.New(rawType))
ptr := ptrPtr.Elem().Interface()
err := json.Unmarshal(buf, ptr)
if err != nil {
return cty.NilVal, path.NewError(err)
}
return cty.CapsuleVal(t, ptr), nil
}
func unmarshalDynamic(buf []byte, path cty.Path) (cty.Value, error) {
dec := bufDecoder(buf)
if err := requireDelim(dec, '{'); err != nil {
return cty.NilVal, path.NewError(err)
}
var t cty.Type
var valBody []byte // defer actual decoding until we know the type
for dec.More() {
var err error
key, err := requireObjectKey(dec)
if err != nil {
return cty.NilVal, path.NewErrorf("failed to read dynamic type descriptor key: %s", err)
}
rawVal, err := readRawValue(dec)
if err != nil {
return cty.NilVal, path.NewErrorf("failed to read dynamic type descriptor value: %s", err)
}
switch key {
case "type":
err := json.Unmarshal(rawVal, &t)
if err != nil {
return cty.NilVal, path.NewErrorf("failed to decode type for dynamic value: %s", err)
}
case "value":
valBody = rawVal
default:
return cty.NilVal, path.NewErrorf("invalid key %q in dynamically-typed value", key)
}
}
if err := requireDelim(dec, '}'); err != nil {
return cty.NilVal, path.NewError(err)
}
if t == cty.NilType {
return cty.NilVal, path.NewErrorf("missing type in dynamically-typed value")
}
if valBody == nil {
return cty.NilVal, path.NewErrorf("missing value in dynamically-typed value")
}
val, err := Unmarshal([]byte(valBody), t)
if err != nil {
return cty.NilVal, path.NewError(err)
}
return val, nil
}
func requireDelim(dec *json.Decoder, d rune) error {
tok, err := dec.Token()
if err != nil {
return err
}
if tok != json.Delim(d) {
return fmt.Errorf("missing expected %c", d)
}
return nil
}
func requireObjectKey(dec *json.Decoder) (string, error) {
tok, err := dec.Token()
if err != nil {
return "", err
}
if s, ok := tok.(string); ok {
return s, nil
}
return "", fmt.Errorf("missing expected object key")
}
func readRawValue(dec *json.Decoder) ([]byte, error) {
var rawVal json.RawMessage
err := dec.Decode(&rawVal)
if err != nil {
return nil, err
}
return []byte(rawVal), nil
}
func bufDecoder(buf []byte) *json.Decoder {
r := bytes.NewReader(buf)
dec := json.NewDecoder(r)
dec.UseNumber()
return dec
}

65
vendor/github.com/zclconf/go-cty/cty/json/value.go generated vendored Normal file
View File

@ -0,0 +1,65 @@
package json
import (
"bytes"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert"
)
// Marshal produces a JSON representation of the given value that can later
// be decoded into a value of the given type.
//
// A type is specified separately to allow for the given type to include
// cty.DynamicPseudoType to represent situations where any type is permitted
// and so type information must be included to allow recovery of the stored
// structure when decoding.
//
// The given type will also be used to attempt automatic conversions of any
// non-conformant types in the given value, although this will not always
// be possible. If the value cannot be made to be conformant then an error is
// returned, which may be a cty.PathError.
//
// Capsule-typed values can be marshalled, but with some caveats. Since
// capsule values are compared by pointer equality, it is impossible to recover
// a value that will compare equal to the original value. Additionally,
// it's not possible to JSON-serialize the capsule type itself, so it's not
// valid to use capsule types within parts of the value that are conformed to
// cty.DynamicPseudoType. Otherwise, a capsule value can be used as long as
// the encapsulated type itself is serializable with the Marshal function
// in encoding/json.
func Marshal(val cty.Value, t cty.Type) ([]byte, error) {
errs := val.Type().TestConformance(t)
if errs != nil {
// Attempt a conversion
var err error
val, err = convert.Convert(val, t)
if err != nil {
return nil, err
}
}
// From this point onward, val can be assumed to be conforming to t.
buf := &bytes.Buffer{}
var path cty.Path
err := marshal(val, t, path, buf)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// Unmarshal decodes a JSON representation of the given value into a cty Value
// conforming to the given type.
//
// While decoding, type conversions will be done where possible to make
// the result conformant even if the types given in JSON are not exactly
// correct. If conversion isn't possible then an error is returned, which
// may be a cty.PathError.
func Unmarshal(buf []byte, t cty.Type) (cty.Value, error) {
var path cty.Path
return unmarshal(buf, t, path)
}

74
vendor/github.com/zclconf/go-cty/cty/list_type.go generated vendored Normal file
View File

@ -0,0 +1,74 @@
package cty
import (
"fmt"
)
// TypeList instances represent specific list types. Each distinct ElementType
// creates a distinct, non-equal list type.
type typeList struct {
typeImplSigil
ElementTypeT Type
}
// List creates a map type with the given element Type.
//
// List types are CollectionType implementations.
func List(elem Type) Type {
return Type{
typeList{
ElementTypeT: elem,
},
}
}
// Equals returns true if the other Type is a list whose element type is
// equal to that of the receiver.
func (t typeList) Equals(other Type) bool {
ot, isList := other.typeImpl.(typeList)
if !isList {
return false
}
return t.ElementTypeT.Equals(ot.ElementTypeT)
}
func (t typeList) FriendlyName(mode friendlyTypeNameMode) string {
elemName := t.ElementTypeT.friendlyNameMode(mode)
if mode == friendlyTypeConstraintName {
if t.ElementTypeT == DynamicPseudoType {
elemName = "any single type"
}
}
return "list of " + elemName
}
func (t typeList) ElementType() Type {
return t.ElementTypeT
}
func (t typeList) GoString() string {
return fmt.Sprintf("cty.List(%#v)", t.ElementTypeT)
}
// IsListType returns true if the given type is a list type, regardless of its
// element type.
func (t Type) IsListType() bool {
_, ok := t.typeImpl.(typeList)
return ok
}
// ListElementType is a convenience method that checks if the given type is
// a list type, returning a pointer to its element type if so and nil
// otherwise. This is intended to allow convenient conditional branches,
// like so:
//
// if et := t.ListElementType(); et != nil {
// // Do something with *et
// }
func (t Type) ListElementType() *Type {
if lt, ok := t.typeImpl.(typeList); ok {
return &lt.ElementTypeT
}
return nil
}

74
vendor/github.com/zclconf/go-cty/cty/map_type.go generated vendored Normal file
View File

@ -0,0 +1,74 @@
package cty
import (
"fmt"
)
// TypeList instances represent specific list types. Each distinct ElementType
// creates a distinct, non-equal list type.
type typeMap struct {
typeImplSigil
ElementTypeT Type
}
// Map creates a map type with the given element Type.
//
// Map types are CollectionType implementations.
func Map(elem Type) Type {
return Type{
typeMap{
ElementTypeT: elem,
},
}
}
// Equals returns true if the other Type is a map whose element type is
// equal to that of the receiver.
func (t typeMap) Equals(other Type) bool {
ot, isMap := other.typeImpl.(typeMap)
if !isMap {
return false
}
return t.ElementTypeT.Equals(ot.ElementTypeT)
}
func (t typeMap) FriendlyName(mode friendlyTypeNameMode) string {
elemName := t.ElementTypeT.friendlyNameMode(mode)
if mode == friendlyTypeConstraintName {
if t.ElementTypeT == DynamicPseudoType {
elemName = "any single type"
}
}
return "map of " + elemName
}
func (t typeMap) ElementType() Type {
return t.ElementTypeT
}
func (t typeMap) GoString() string {
return fmt.Sprintf("cty.Map(%#v)", t.ElementTypeT)
}
// IsMapType returns true if the given type is a list type, regardless of its
// element type.
func (t Type) IsMapType() bool {
_, ok := t.typeImpl.(typeMap)
return ok
}
// MapElementType is a convenience method that checks if the given type is
// a map type, returning a pointer to its element type if so and nil
// otherwise. This is intended to allow convenient conditional branches,
// like so:
//
// if et := t.MapElementType(); et != nil {
// // Do something with *et
// }
func (t Type) MapElementType() *Type {
if lt, ok := t.typeImpl.(typeMap); ok {
return &lt.ElementTypeT
}
return nil
}

389
vendor/github.com/zclconf/go-cty/cty/marks.go generated vendored Normal file
View File

@ -0,0 +1,389 @@
package cty
import (
"fmt"
"strings"
)
// marker is an internal wrapper type used to add special "marks" to values.
//
// A "mark" is an annotation that can be used to represent additional
// characteristics of values that propagate through operation methods to
// result values. However, a marked value cannot be used with integration
// methods normally associated with its type, in order to ensure that
// calling applications don't inadvertently drop marks as they round-trip
// values out of cty and back in again.
//
// Marked values are created only explicitly by the calling application, so
// an application that never marks a value does not need to worry about
// encountering marked values.
type marker struct {
realV interface{}
marks ValueMarks
}
// ValueMarks is a map, representing a set, of "mark" values associated with
// a Value. See Value.Mark for more information on the usage of mark values.
type ValueMarks map[interface{}]struct{}
// NewValueMarks constructs a new ValueMarks set with the given mark values.
//
// If any of the arguments are already ValueMarks values then they'll be merged
// into the result, rather than used directly as individual marks.
func NewValueMarks(marks ...interface{}) ValueMarks {
if len(marks) == 0 {
return nil
}
ret := make(ValueMarks, len(marks))
for _, v := range marks {
if vm, ok := v.(ValueMarks); ok {
// Constructing a new ValueMarks with an existing ValueMarks
// implements a merge operation. (This can cause our result to
// have a larger size than we expected, but that's okay.)
for v := range vm {
ret[v] = struct{}{}
}
continue
}
ret[v] = struct{}{}
}
if len(ret) == 0 {
// If we were merging ValueMarks values together and they were all
// empty then we'll avoid returning a zero-length map and return a
// nil instead, as is conventional.
return nil
}
return ret
}
// Equal returns true if the receiver and the given ValueMarks both contain
// the same marks.
func (m ValueMarks) Equal(o ValueMarks) bool {
if len(m) != len(o) {
return false
}
for v := range m {
if _, ok := o[v]; !ok {
return false
}
}
return true
}
func (m ValueMarks) GoString() string {
var s strings.Builder
s.WriteString("cty.NewValueMarks(")
i := 0
for mv := range m {
if i != 0 {
s.WriteString(", ")
}
s.WriteString(fmt.Sprintf("%#v", mv))
i++
}
s.WriteString(")")
return s.String()
}
// PathValueMarks is a structure that enables tracking marks
// and the paths where they are located in one type
type PathValueMarks struct {
Path Path
Marks ValueMarks
}
func (p PathValueMarks) Equal(o PathValueMarks) bool {
if !p.Path.Equals(o.Path) {
return false
}
if !p.Marks.Equal(o.Marks) {
return false
}
return true
}
// IsMarked returns true if and only if the receiving value carries at least
// one mark. A marked value cannot be used directly with integration methods
// without explicitly unmarking it (and retrieving the markings) first.
func (val Value) IsMarked() bool {
_, ok := val.v.(marker)
return ok
}
// HasMark returns true if and only if the receiving value has the given mark.
func (val Value) HasMark(mark interface{}) bool {
if mr, ok := val.v.(marker); ok {
_, ok := mr.marks[mark]
return ok
}
return false
}
// ContainsMarked returns true if the receiving value or any value within it
// is marked.
//
// This operation is relatively expensive. If you only need a shallow result,
// use IsMarked instead.
func (val Value) ContainsMarked() bool {
ret := false
Walk(val, func(_ Path, v Value) (bool, error) {
if v.IsMarked() {
ret = true
return false, nil
}
return true, nil
})
return ret
}
func (val Value) assertUnmarked() {
if val.IsMarked() {
panic("value is marked, so must be unmarked first")
}
}
// Marks returns a map (representing a set) of all of the mark values
// associated with the receiving value, without changing the marks. Returns nil
// if the value is not marked at all.
func (val Value) Marks() ValueMarks {
if mr, ok := val.v.(marker); ok {
// copy so that the caller can't mutate our internals
ret := make(ValueMarks, len(mr.marks))
for k, v := range mr.marks {
ret[k] = v
}
return ret
}
return nil
}
// HasSameMarks returns true if an only if the receiver and the given other
// value have identical marks.
func (val Value) HasSameMarks(other Value) bool {
vm, vmOK := val.v.(marker)
om, omOK := other.v.(marker)
if vmOK != omOK {
return false
}
if vmOK {
return vm.marks.Equal(om.marks)
}
return true
}
// Mark returns a new value that as the same type and underlying value as
// the receiver but that also carries the given value as a "mark".
//
// Marks are used to carry additional application-specific characteristics
// associated with values. A marked value can be used with operation methods,
// in which case the marks are propagated to the operation results. A marked
// value _cannot_ be used with integration methods, so callers of those
// must derive an unmarked value using Unmark (and thus explicitly handle
// the markings) before calling the integration methods.
//
// The mark value can be any value that would be valid to use as a map key.
// The mark value should be of a named type in order to use the type itself
// as a namespace for markings. That type can be unexported if desired, in
// order to ensure that the mark can only be handled through the defining
// package's own functions.
//
// An application that never calls this method does not need to worry about
// handling marked values.
func (val Value) Mark(mark interface{}) Value {
var newMarker marker
newMarker.realV = val.v
if mr, ok := val.v.(marker); ok {
// It's already a marker, so we'll retain existing marks.
newMarker.marks = make(ValueMarks, len(mr.marks)+1)
for k, v := range mr.marks {
newMarker.marks[k] = v
}
// unwrap the inner marked value, so we don't get multiple layers
// of marking.
newMarker.realV = mr.realV
} else {
// It's not a marker yet, so we're creating the first mark.
newMarker.marks = make(ValueMarks, 1)
}
newMarker.marks[mark] = struct{}{}
return Value{
ty: val.ty,
v: newMarker,
}
}
type applyPathValueMarksTransformer struct {
pvm []PathValueMarks
}
func (t *applyPathValueMarksTransformer) Enter(p Path, v Value) (Value, error) {
return v, nil
}
func (t *applyPathValueMarksTransformer) Exit(p Path, v Value) (Value, error) {
for _, path := range t.pvm {
if p.Equals(path.Path) {
return v.WithMarks(path.Marks), nil
}
}
return v, nil
}
// MarkWithPaths accepts a slice of PathValueMarks to apply
// markers to particular paths and returns the marked
// Value.
func (val Value) MarkWithPaths(pvm []PathValueMarks) Value {
ret, _ := TransformWithTransformer(val, &applyPathValueMarksTransformer{pvm})
return ret
}
// Unmark separates the marks of the receiving value from the value itself,
// removing a new unmarked value and a map (representing a set) of the marks.
//
// If the receiver isn't marked, Unmark returns it verbatim along with a nil
// map of marks.
func (val Value) Unmark() (Value, ValueMarks) {
if !val.IsMarked() {
return val, nil
}
mr := val.v.(marker)
marks := val.Marks() // copy so that the caller can't mutate our internals
return Value{
ty: val.ty,
v: mr.realV,
}, marks
}
type unmarkTransformer struct {
pvm []PathValueMarks
}
func (t *unmarkTransformer) Enter(p Path, v Value) (Value, error) {
unmarkedVal, marks := v.Unmark()
if len(marks) > 0 {
path := make(Path, len(p), len(p)+1)
copy(path, p)
t.pvm = append(t.pvm, PathValueMarks{path, marks})
}
return unmarkedVal, nil
}
func (t *unmarkTransformer) Exit(p Path, v Value) (Value, error) {
return v, nil
}
// UnmarkDeep is similar to Unmark, but it works with an entire nested structure
// rather than just the given value directly.
//
// The result is guaranteed to contain no nested values that are marked, and
// the returned marks set includes the superset of all of the marks encountered
// during the operation.
func (val Value) UnmarkDeep() (Value, ValueMarks) {
t := unmarkTransformer{}
ret, _ := TransformWithTransformer(val, &t)
marks := make(ValueMarks)
for _, pvm := range t.pvm {
for m, s := range pvm.Marks {
marks[m] = s
}
}
return ret, marks
}
// UnmarkDeepWithPaths is like UnmarkDeep, except it returns a slice
// of PathValueMarks rather than a superset of all marks. This allows
// a caller to know which marks are associated with which paths
// in the Value.
func (val Value) UnmarkDeepWithPaths() (Value, []PathValueMarks) {
t := unmarkTransformer{}
ret, _ := TransformWithTransformer(val, &t)
return ret, t.pvm
}
func (val Value) unmarkForce() Value {
unw, _ := val.Unmark()
return unw
}
// WithMarks returns a new value that has the same type and underlying value
// as the receiver and also has the marks from the given maps (representing
// sets).
func (val Value) WithMarks(marks ...ValueMarks) Value {
if len(marks) == 0 {
return val
}
ownMarks := val.Marks()
markCount := len(ownMarks)
for _, s := range marks {
markCount += len(s)
}
if markCount == 0 {
return val
}
newMarks := make(ValueMarks, markCount)
for m := range ownMarks {
newMarks[m] = struct{}{}
}
for _, s := range marks {
for m := range s {
newMarks[m] = struct{}{}
}
}
v := val.v
if mr, ok := v.(marker); ok {
v = mr.realV
}
return Value{
ty: val.ty,
v: marker{
realV: v,
marks: newMarks,
},
}
}
// WithSameMarks returns a new value that has the same type and underlying
// value as the receiver and also has the marks from the given source values.
//
// Use this if you are implementing your own higher-level operations against
// cty using the integration methods, to re-introduce the marks from the
// source values of the operation.
func (val Value) WithSameMarks(srcs ...Value) Value {
if len(srcs) == 0 {
return val
}
ownMarks := val.Marks()
markCount := len(ownMarks)
for _, sv := range srcs {
if mr, ok := sv.v.(marker); ok {
markCount += len(mr.marks)
}
}
if markCount == 0 {
return val
}
newMarks := make(ValueMarks, markCount)
for m := range ownMarks {
newMarks[m] = struct{}{}
}
for _, sv := range srcs {
if mr, ok := sv.v.(marker); ok {
for m := range mr.marks {
newMarks[m] = struct{}{}
}
}
}
v := val.v
if mr, ok := v.(marker); ok {
v = mr.realV
}
return Value{
ty: val.ty,
v: marker{
realV: v,
marks: newMarks,
},
}
}

14
vendor/github.com/zclconf/go-cty/cty/msgpack/doc.go generated vendored Normal file
View File

@ -0,0 +1,14 @@
// Package msgpack provides functions for serializing cty values in the
// msgpack encoding, and decoding them again.
//
// If the same type information is provided both at encoding and decoding time
// then values can be round-tripped without loss, except for capsule types
// which are not currently supported.
//
// If any unknown values are passed to Marshal then they will be represented
// using a msgpack extension with type code zero, which is understood by
// the Unmarshal function within this package but will not be understood by
// a generic (non-cty-aware) msgpack decoder. Ensure that no unknown values
// are used if interoperability with other msgpack implementations is
// required.
package msgpack

View File

@ -0,0 +1,31 @@
package msgpack
import (
"bytes"
"github.com/vmihailenco/msgpack/v4"
"github.com/zclconf/go-cty/cty"
)
type dynamicVal struct {
Value cty.Value
Path cty.Path
}
func (dv *dynamicVal) MarshalMsgpack() ([]byte, error) {
// Rather than defining a msgpack-specific serialization of types,
// instead we use the existing JSON serialization.
typeJSON, err := dv.Value.Type().MarshalJSON()
if err != nil {
return nil, dv.Path.NewErrorf("failed to serialize type: %s", err)
}
var buf bytes.Buffer
enc := msgpack.NewEncoder(&buf)
enc.EncodeArrayLen(2)
enc.EncodeBytes(typeJSON)
err = marshal(dv.Value, dv.Value.Type(), dv.Path, enc)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}

View File

@ -0,0 +1,8 @@
package msgpack
import (
"math"
)
var negativeInfinity = math.Inf(-1)
var positiveInfinity = math.Inf(1)

212
vendor/github.com/zclconf/go-cty/cty/msgpack/marshal.go generated vendored Normal file
View File

@ -0,0 +1,212 @@
package msgpack
import (
"bytes"
"math/big"
"sort"
"github.com/vmihailenco/msgpack/v4"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert"
)
// Marshal produces a msgpack serialization of the given value that
// can be decoded into the given type later using Unmarshal.
//
// The given value must conform to the given type, or an error will
// be returned.
func Marshal(val cty.Value, ty cty.Type) ([]byte, error) {
errs := val.Type().TestConformance(ty)
if errs != nil {
// Attempt a conversion
var err error
val, err = convert.Convert(val, ty)
if err != nil {
return nil, err
}
}
// From this point onward, val can be assumed to be conforming to t.
var path cty.Path
var buf bytes.Buffer
enc := msgpack.NewEncoder(&buf)
enc.UseCompactEncoding(true)
err := marshal(val, ty, path, enc)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func marshal(val cty.Value, ty cty.Type, path cty.Path, enc *msgpack.Encoder) error {
if val.IsMarked() {
return path.NewErrorf("value has marks, so it cannot be serialized")
}
// If we're going to decode as DynamicPseudoType then we need to save
// dynamic type information to recover the real type.
if ty == cty.DynamicPseudoType && val.Type() != cty.DynamicPseudoType {
return marshalDynamic(val, path, enc)
}
if !val.IsKnown() {
err := enc.Encode(unknownVal)
if err != nil {
return path.NewError(err)
}
return nil
}
if val.IsNull() {
err := enc.EncodeNil()
if err != nil {
return path.NewError(err)
}
return nil
}
// The caller should've guaranteed that the given val is conformant with
// the given type ty, so we'll proceed under that assumption here.
switch {
case ty.IsPrimitiveType():
switch ty {
case cty.String:
err := enc.EncodeString(val.AsString())
if err != nil {
return path.NewError(err)
}
return nil
case cty.Number:
var err error
switch {
case val.RawEquals(cty.PositiveInfinity):
err = enc.EncodeFloat64(positiveInfinity)
case val.RawEquals(cty.NegativeInfinity):
err = enc.EncodeFloat64(negativeInfinity)
default:
bf := val.AsBigFloat()
if iv, acc := bf.Int64(); acc == big.Exact {
err = enc.EncodeInt(iv)
} else if fv, acc := bf.Float64(); acc == big.Exact {
err = enc.EncodeFloat64(fv)
} else {
err = enc.EncodeString(bf.Text('f', -1))
}
}
if err != nil {
return path.NewError(err)
}
return nil
case cty.Bool:
err := enc.EncodeBool(val.True())
if err != nil {
return path.NewError(err)
}
return nil
default:
panic("unsupported primitive type")
}
case ty.IsListType(), ty.IsSetType():
enc.EncodeArrayLen(val.LengthInt())
ety := ty.ElementType()
it := val.ElementIterator()
path := append(path, nil) // local override of 'path' with extra element
for it.Next() {
ek, ev := it.Element()
path[len(path)-1] = cty.IndexStep{
Key: ek,
}
err := marshal(ev, ety, path, enc)
if err != nil {
return err
}
}
return nil
case ty.IsMapType():
enc.EncodeMapLen(val.LengthInt())
ety := ty.ElementType()
it := val.ElementIterator()
path := append(path, nil) // local override of 'path' with extra element
for it.Next() {
ek, ev := it.Element()
path[len(path)-1] = cty.IndexStep{
Key: ek,
}
var err error
err = marshal(ek, ek.Type(), path, enc)
if err != nil {
return err
}
err = marshal(ev, ety, path, enc)
if err != nil {
return err
}
}
return nil
case ty.IsTupleType():
etys := ty.TupleElementTypes()
it := val.ElementIterator()
path := append(path, nil) // local override of 'path' with extra element
i := 0
enc.EncodeArrayLen(len(etys))
for it.Next() {
ety := etys[i]
ek, ev := it.Element()
path[len(path)-1] = cty.IndexStep{
Key: ek,
}
err := marshal(ev, ety, path, enc)
if err != nil {
return err
}
i++
}
return nil
case ty.IsObjectType():
atys := ty.AttributeTypes()
path := append(path, nil) // local override of 'path' with extra element
names := make([]string, 0, len(atys))
for k := range atys {
names = append(names, k)
}
sort.Strings(names)
enc.EncodeMapLen(len(names))
for _, k := range names {
aty := atys[k]
av := val.GetAttr(k)
path[len(path)-1] = cty.GetAttrStep{
Name: k,
}
var err error
err = marshal(cty.StringVal(k), cty.String, path, enc)
if err != nil {
return err
}
err = marshal(av, aty, path, enc)
if err != nil {
return err
}
}
return nil
case ty.IsCapsuleType():
return path.NewErrorf("capsule types not supported for msgpack encoding")
default:
// should never happen
return path.NewErrorf("cannot msgpack-serialize %s", ty.FriendlyName())
}
}
// marshalDynamic adds an extra wrapping object containing dynamic type
// information for the given value.
func marshalDynamic(val cty.Value, path cty.Path, enc *msgpack.Encoder) error {
dv := dynamicVal{
Value: val,
Path: path,
}
return enc.Encode(&dv)
}

View File

@ -0,0 +1,167 @@
package msgpack
import (
"bytes"
"fmt"
"io"
"github.com/vmihailenco/msgpack/v4"
msgpackcodes "github.com/vmihailenco/msgpack/v4/codes"
"github.com/zclconf/go-cty/cty"
)
// ImpliedType returns the cty Type implied by the structure of the given
// msgpack-compliant buffer. This function implements the default type mapping
// behavior used when decoding arbitrary msgpack without explicit cty Type
// information.
//
// The rules are as follows:
//
// msgpack strings, numbers and bools map to their equivalent primitive type in
// cty.
//
// msgpack maps become cty object types, with the attributes defined by the
// map keys and the types of their values.
//
// msgpack arrays become 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 unknown values are similarly typed as DynamicPseudoType, because these
// do not carry type information on the wire.
//
// Any parse 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 := msgpack.NewDecoder(r)
ty, err := impliedType(dec)
if err != nil {
return cty.NilType, err
}
// We must now be at the end of the buffer
err = dec.Skip()
if err != io.EOF {
return ty, fmt.Errorf("extra bytes after msgpack value")
}
return ty, nil
}
func impliedType(dec *msgpack.Decoder) (cty.Type, error) {
// If this function returns with a nil error then it must have already
// consumed the next value from the decoder, since when called recursively
// the caller will be expecting to find a following value here.
code, err := dec.PeekCode()
if err != nil {
return cty.NilType, err
}
switch {
case code == msgpackcodes.Nil || msgpackcodes.IsExt(code):
err := dec.Skip()
return cty.DynamicPseudoType, err
case code == msgpackcodes.True || code == msgpackcodes.False:
_, err := dec.DecodeBool()
return cty.Bool, err
case msgpackcodes.IsFixedNum(code):
_, err := dec.DecodeInt64()
return cty.Number, err
case code == msgpackcodes.Int8 || code == msgpackcodes.Int16 || code == msgpackcodes.Int32 || code == msgpackcodes.Int64:
_, err := dec.DecodeInt64()
return cty.Number, err
case code == msgpackcodes.Uint8 || code == msgpackcodes.Uint16 || code == msgpackcodes.Uint32 || code == msgpackcodes.Uint64:
_, err := dec.DecodeUint64()
return cty.Number, err
case code == msgpackcodes.Float || code == msgpackcodes.Double:
_, err := dec.DecodeFloat64()
return cty.Number, err
case msgpackcodes.IsString(code):
_, err := dec.DecodeString()
return cty.String, err
case msgpackcodes.IsFixedMap(code) || code == msgpackcodes.Map16 || code == msgpackcodes.Map32:
return impliedObjectType(dec)
case msgpackcodes.IsFixedArray(code) || code == msgpackcodes.Array16 || code == msgpackcodes.Array32:
return impliedTupleType(dec)
default:
return cty.NilType, fmt.Errorf("unsupported msgpack code %#v", code)
}
}
func impliedObjectType(dec *msgpack.Decoder) (cty.Type, error) {
// If we get in here then we've already peeked the next code and know
// it's some sort of map.
l, err := dec.DecodeMapLen()
if err != nil {
return cty.DynamicPseudoType, nil
}
var atys map[string]cty.Type
for i := 0; i < l; i++ {
// Read the map key first. We require maps to be strings, but msgpack
// doesn't so we're prepared to error here if not.
k, err := dec.DecodeString()
if err != nil {
return cty.DynamicPseudoType, err
}
aty, err := impliedType(dec)
if err != nil {
return cty.DynamicPseudoType, err
}
if atys == nil {
atys = make(map[string]cty.Type)
}
atys[k] = aty
}
if len(atys) == 0 {
return cty.EmptyObject, nil
}
return cty.Object(atys), nil
}
func impliedTupleType(dec *msgpack.Decoder) (cty.Type, error) {
// If we get in here then we've already peeked the next code and know
// it's some sort of array.
l, err := dec.DecodeArrayLen()
if err != nil {
return cty.DynamicPseudoType, nil
}
if l == 0 {
return cty.EmptyTuple, nil
}
etys := make([]cty.Type, l)
for i := 0; i < l; i++ {
ety, err := impliedType(dec)
if err != nil {
return cty.DynamicPseudoType, err
}
etys[i] = ety
}
return cty.Tuple(etys), nil
}

View File

@ -0,0 +1,16 @@
package msgpack
type unknownType struct{}
var unknownVal = unknownType{}
// unknownValBytes is the raw bytes of the msgpack fixext1 value we
// write to represent an unknown value. It's an extension value of
// type zero whose value is irrelevant. Since it's irrelevant, we
// set it to a single byte whose value is also zero, since that's
// the most compact possible representation.
var unknownValBytes = []byte{0xd4, 0, 0}
func (uv unknownType) MarshalMsgpack() ([]byte, error) {
return unknownValBytes, nil
}

View File

@ -0,0 +1,334 @@
package msgpack
import (
"bytes"
"github.com/vmihailenco/msgpack/v4"
msgpackCodes "github.com/vmihailenco/msgpack/v4/codes"
"github.com/zclconf/go-cty/cty"
)
// Unmarshal interprets the given bytes as a msgpack-encoded cty Value of
// the given type, returning the result.
//
// If an error is returned, the error is written with a hypothetical
// end-user that wrote the msgpack file as its audience, using cty type
// system concepts rather than Go type system concepts.
func Unmarshal(b []byte, ty cty.Type) (cty.Value, error) {
r := bytes.NewReader(b)
dec := msgpack.NewDecoder(r)
var path cty.Path
return unmarshal(dec, ty, path)
}
func unmarshal(dec *msgpack.Decoder, ty cty.Type, path cty.Path) (cty.Value, error) {
peek, err := dec.PeekCode()
if err != nil {
return cty.DynamicVal, path.NewError(err)
}
if msgpackCodes.IsExt(peek) {
// We just assume _all_ extensions are unknown values,
// since we don't have any other extensions.
dec.Skip() // skip what we've peeked
return cty.UnknownVal(ty), nil
}
if ty == cty.DynamicPseudoType {
return unmarshalDynamic(dec, path)
}
if peek == msgpackCodes.Nil {
dec.Skip() // skip what we've peeked
return cty.NullVal(ty), nil
}
switch {
case ty.IsPrimitiveType():
val, err := unmarshalPrimitive(dec, ty, path)
if err != nil {
return cty.NilVal, err
}
return val, nil
case ty.IsListType():
return unmarshalList(dec, ty.ElementType(), path)
case ty.IsSetType():
return unmarshalSet(dec, ty.ElementType(), path)
case ty.IsMapType():
return unmarshalMap(dec, ty.ElementType(), path)
case ty.IsTupleType():
return unmarshalTuple(dec, ty.TupleElementTypes(), path)
case ty.IsObjectType():
return unmarshalObject(dec, ty.AttributeTypes(), path)
default:
return cty.NilVal, path.NewErrorf("unsupported type %s", ty.FriendlyName())
}
}
func unmarshalPrimitive(dec *msgpack.Decoder, ty cty.Type, path cty.Path) (cty.Value, error) {
switch ty {
case cty.Bool:
rv, err := dec.DecodeBool()
if err != nil {
return cty.DynamicVal, path.NewErrorf("bool is required")
}
return cty.BoolVal(rv), nil
case cty.Number:
// Marshal will try int and float first, if the value can be
// losslessly represented in these encodings, and then fall
// back on a string if the number is too large or too precise.
peek, err := dec.PeekCode()
if err != nil {
return cty.DynamicVal, path.NewErrorf("number is required")
}
if msgpackCodes.IsFixedNum(peek) {
rv, err := dec.DecodeInt64()
if err != nil {
return cty.DynamicVal, path.NewErrorf("number is required")
}
return cty.NumberIntVal(rv), nil
}
switch peek {
case msgpackCodes.Int8, msgpackCodes.Int16, msgpackCodes.Int32, msgpackCodes.Int64:
rv, err := dec.DecodeInt64()
if err != nil {
return cty.DynamicVal, path.NewErrorf("number is required")
}
return cty.NumberIntVal(rv), nil
case msgpackCodes.Uint8, msgpackCodes.Uint16, msgpackCodes.Uint32, msgpackCodes.Uint64:
rv, err := dec.DecodeUint64()
if err != nil {
return cty.DynamicVal, path.NewErrorf("number is required")
}
return cty.NumberUIntVal(rv), nil
case msgpackCodes.Float, msgpackCodes.Double:
rv, err := dec.DecodeFloat64()
if err != nil {
return cty.DynamicVal, path.NewErrorf("number is required")
}
return cty.NumberFloatVal(rv), nil
default:
rv, err := dec.DecodeString()
if err != nil {
return cty.DynamicVal, path.NewErrorf("number is required")
}
v, err := cty.ParseNumberVal(rv)
if err != nil {
return cty.DynamicVal, path.NewErrorf("number is required")
}
return v, nil
}
case cty.String:
rv, err := dec.DecodeString()
if err != nil {
return cty.DynamicVal, path.NewErrorf("string is required")
}
return cty.StringVal(rv), nil
default:
// should never happen
panic("unsupported primitive type")
}
}
func unmarshalList(dec *msgpack.Decoder, ety cty.Type, path cty.Path) (cty.Value, error) {
length, err := dec.DecodeArrayLen()
if err != nil {
return cty.DynamicVal, path.NewErrorf("a list is required")
}
switch {
case length < 0:
return cty.NullVal(cty.List(ety)), nil
case length == 0:
return cty.ListValEmpty(ety), nil
}
vals := make([]cty.Value, 0, length)
path = append(path, nil)
for i := 0; i < length; i++ {
path[len(path)-1] = cty.IndexStep{
Key: cty.NumberIntVal(int64(i)),
}
val, err := unmarshal(dec, ety, path)
if err != nil {
return cty.DynamicVal, err
}
vals = append(vals, val)
}
return cty.ListVal(vals), nil
}
func unmarshalSet(dec *msgpack.Decoder, ety cty.Type, path cty.Path) (cty.Value, error) {
length, err := dec.DecodeArrayLen()
if err != nil {
return cty.DynamicVal, path.NewErrorf("a set is required")
}
switch {
case length < 0:
return cty.NullVal(cty.Set(ety)), nil
case length == 0:
return cty.SetValEmpty(ety), nil
}
vals := make([]cty.Value, 0, length)
path = append(path, nil)
for i := 0; i < length; i++ {
path[len(path)-1] = cty.IndexStep{
Key: cty.NumberIntVal(int64(i)),
}
val, err := unmarshal(dec, ety, path)
if err != nil {
return cty.DynamicVal, err
}
vals = append(vals, val)
}
return cty.SetVal(vals), nil
}
func unmarshalMap(dec *msgpack.Decoder, ety cty.Type, path cty.Path) (cty.Value, error) {
length, err := dec.DecodeMapLen()
if err != nil {
return cty.DynamicVal, path.NewErrorf("a map is required")
}
switch {
case length < 0:
return cty.NullVal(cty.Map(ety)), nil
case length == 0:
return cty.MapValEmpty(ety), nil
}
vals := make(map[string]cty.Value, length)
path = append(path, nil)
for i := 0; i < length; i++ {
key, err := dec.DecodeString()
if err != nil {
path[:len(path)-1].NewErrorf("non-string key in map")
}
path[len(path)-1] = cty.IndexStep{
Key: cty.StringVal(key),
}
val, err := unmarshal(dec, ety, path)
if err != nil {
return cty.DynamicVal, err
}
vals[key] = val
}
return cty.MapVal(vals), nil
}
func unmarshalTuple(dec *msgpack.Decoder, etys []cty.Type, path cty.Path) (cty.Value, error) {
length, err := dec.DecodeArrayLen()
if err != nil {
return cty.DynamicVal, path.NewErrorf("a tuple is required")
}
switch {
case length < 0:
return cty.NullVal(cty.Tuple(etys)), nil
case length == 0:
return cty.TupleVal(nil), nil
case length != len(etys):
return cty.DynamicVal, path.NewErrorf("a tuple of length %d is required", len(etys))
}
vals := make([]cty.Value, 0, length)
path = append(path, nil)
for i := 0; i < length; i++ {
path[len(path)-1] = cty.IndexStep{
Key: cty.NumberIntVal(int64(i)),
}
ety := etys[i]
val, err := unmarshal(dec, ety, path)
if err != nil {
return cty.DynamicVal, err
}
vals = append(vals, val)
}
return cty.TupleVal(vals), nil
}
func unmarshalObject(dec *msgpack.Decoder, atys map[string]cty.Type, path cty.Path) (cty.Value, error) {
length, err := dec.DecodeMapLen()
if err != nil {
return cty.DynamicVal, path.NewErrorf("an object is required")
}
switch {
case length < 0:
return cty.NullVal(cty.Object(atys)), nil
case length == 0:
return cty.ObjectVal(nil), nil
case length != len(atys):
return cty.DynamicVal, path.NewErrorf("an object with %d attributes is required (%d given)",
len(atys), length)
}
vals := make(map[string]cty.Value, length)
path = append(path, nil)
for i := 0; i < length; i++ {
key, err := dec.DecodeString()
if err != nil {
return cty.DynamicVal, path[:len(path)-1].NewErrorf("all keys must be strings")
}
path[len(path)-1] = cty.IndexStep{
Key: cty.StringVal(key),
}
aty, exists := atys[key]
if !exists {
return cty.DynamicVal, path.NewErrorf("unsupported attribute")
}
val, err := unmarshal(dec, aty, path)
if err != nil {
return cty.DynamicVal, err
}
vals[key] = val
}
return cty.ObjectVal(vals), nil
}
func unmarshalDynamic(dec *msgpack.Decoder, path cty.Path) (cty.Value, error) {
length, err := dec.DecodeArrayLen()
if err != nil {
return cty.DynamicVal, path.NewError(err)
}
switch {
case length == -1:
return cty.NullVal(cty.DynamicPseudoType), nil
case length != 2:
return cty.DynamicVal, path.NewErrorf(
"dynamic value array must have exactly two elements",
)
}
typeJSON, err := dec.DecodeBytes()
if err != nil {
return cty.DynamicVal, path.NewError(err)
}
var ty cty.Type
err = (&ty).UnmarshalJSON(typeJSON)
if err != nil {
return cty.DynamicVal, path.NewError(err)
}
return unmarshal(dec, ty, path)
}

14
vendor/github.com/zclconf/go-cty/cty/null.go generated vendored Normal file
View File

@ -0,0 +1,14 @@
package cty
// NullVal returns a null value of the given type. A null can be created of any
// type, but operations on such values will always panic. Calling applications
// are encouraged to use nulls only sparingly, particularly when user-provided
// expressions are to be evaluated, since the precence of nulls creates a
// much higher chance of evaluation errors that can't be caught by a type
// checker.
func NullVal(t Type) Value {
return Value{
ty: t,
v: nil,
}
}

220
vendor/github.com/zclconf/go-cty/cty/object_type.go generated vendored Normal file
View File

@ -0,0 +1,220 @@
package cty
import (
"fmt"
"sort"
)
type typeObject struct {
typeImplSigil
AttrTypes map[string]Type
AttrOptional map[string]struct{}
}
// Object creates an object type with the given attribute types.
//
// After a map is passed to this function the caller must no longer access it,
// since ownership is transferred to this library.
func Object(attrTypes map[string]Type) Type {
return ObjectWithOptionalAttrs(attrTypes, nil)
}
// ObjectWithOptionalAttrs creates an object type where some of its attributes
// are optional.
//
// This function is EXPERIMENTAL. The behavior of the function or of any other
// functions working either directly or indirectly with a type created by
// this function is not currently considered as a compatibility constraint, and
// is subject to change even in minor-version releases of this module. Other
// modules that work with cty types and values may or may not support object
// types with optional attributes; if they do not, their behavior when
// receiving one may be non-ideal.
//
// Optional attributes are significant only when an object type is being used
// as a target type for conversion in the "convert" package. A value of an
// object type always has a value for each of the attributes in the attribute
// types table, with optional values replaced with null during conversion.
//
// All keys in the optional slice must also exist in the attrTypes map. If not,
// this function will panic.
//
// After a map or array is passed to this function the caller must no longer
// access it, since ownership is transferred to this library.
func ObjectWithOptionalAttrs(attrTypes map[string]Type, optional []string) Type {
attrTypesNorm := make(map[string]Type, len(attrTypes))
for k, v := range attrTypes {
attrTypesNorm[NormalizeString(k)] = v
}
var optionalSet map[string]struct{}
if len(optional) > 0 {
optionalSet = make(map[string]struct{}, len(optional))
for _, k := range optional {
k = NormalizeString(k)
if _, exists := attrTypesNorm[k]; !exists {
panic(fmt.Sprintf("optional contains undeclared attribute %q", k))
}
optionalSet[k] = struct{}{}
}
}
return Type{
typeObject{
AttrTypes: attrTypesNorm,
AttrOptional: optionalSet,
},
}
}
func (t typeObject) Equals(other Type) bool {
if ot, ok := other.typeImpl.(typeObject); ok {
if len(t.AttrTypes) != len(ot.AttrTypes) {
// Fast path: if we don't have the same number of attributes
// then we can't possibly be equal. This also avoids the need
// to test attributes in both directions below, since we know
// there can't be extras in "other".
return false
}
for attr, ty := range t.AttrTypes {
oty, ok := ot.AttrTypes[attr]
if !ok {
return false
}
if !oty.Equals(ty) {
return false
}
_, opt := t.AttrOptional[attr]
_, oopt := ot.AttrOptional[attr]
if opt != oopt {
return false
}
}
return true
}
return false
}
func (t typeObject) FriendlyName(mode friendlyTypeNameMode) string {
// There isn't really a friendly way to write an object type due to its
// complexity, so we'll just do something English-ish. Callers will
// probably want to make some extra effort to avoid ever printing out
// an object type FriendlyName in its entirety. For example, could
// produce an error message by diffing two object types and saying
// something like "Expected attribute foo to be string, but got number".
// TODO: Finish this
return "object"
}
func (t typeObject) GoString() string {
if len(t.AttrTypes) == 0 {
return "cty.EmptyObject"
}
if len(t.AttrOptional) > 0 {
var opt []string
for k := range t.AttrOptional {
opt = append(opt, k)
}
sort.Strings(opt)
return fmt.Sprintf("cty.ObjectWithOptionalAttrs(%#v, %#v)", t.AttrTypes, opt)
}
return fmt.Sprintf("cty.Object(%#v)", t.AttrTypes)
}
// EmptyObject is a shorthand for Object(map[string]Type{}), to more
// easily talk about the empty object type.
var EmptyObject Type
// EmptyObjectVal is the only possible non-null, non-unknown value of type
// EmptyObject.
var EmptyObjectVal Value
func init() {
EmptyObject = Object(map[string]Type{})
EmptyObjectVal = Value{
ty: EmptyObject,
v: map[string]interface{}{},
}
}
// IsObjectType returns true if the given type is an object type, regardless
// of its element type.
func (t Type) IsObjectType() bool {
_, ok := t.typeImpl.(typeObject)
return ok
}
// HasAttribute returns true if the receiver has an attribute with the given
// name, regardless of its type. Will panic if the reciever isn't an object
// type; use IsObjectType to determine whether this operation will succeed.
func (t Type) HasAttribute(name string) bool {
name = NormalizeString(name)
if ot, ok := t.typeImpl.(typeObject); ok {
_, hasAttr := ot.AttrTypes[name]
return hasAttr
}
panic("HasAttribute on non-object Type")
}
// AttributeType returns the type of the attribute with the given name. Will
// panic if the receiver is not an object type (use IsObjectType to confirm)
// or if the object type has no such attribute (use HasAttribute to confirm).
func (t Type) AttributeType(name string) Type {
name = NormalizeString(name)
if ot, ok := t.typeImpl.(typeObject); ok {
aty, hasAttr := ot.AttrTypes[name]
if !hasAttr {
panic("no such attribute")
}
return aty
}
panic("AttributeType on non-object Type")
}
// AttributeTypes returns a map from attribute names to their associated
// types. Will panic if the receiver is not an object type (use IsObjectType
// to confirm).
//
// The returned map is part of the internal state of the type, and is provided
// for read access only. It is forbidden for any caller to modify the returned
// map. For many purposes the attribute-related methods of Value are more
// appropriate and more convenient to use.
func (t Type) AttributeTypes() map[string]Type {
if ot, ok := t.typeImpl.(typeObject); ok {
return ot.AttrTypes
}
panic("AttributeTypes on non-object Type")
}
// OptionalAttributes returns a map representing the set of attributes
// that are optional. Will panic if the receiver is not an object type
// (use IsObjectType to confirm).
//
// The returned map is part of the internal state of the type, and is provided
// for read access only. It is forbidden for any caller to modify the returned
// map.
func (t Type) OptionalAttributes() map[string]struct{} {
if ot, ok := t.typeImpl.(typeObject); ok {
return ot.AttrOptional
}
panic("OptionalAttributes on non-object Type")
}
// AttributeOptional returns true if the attribute of the given name is
// optional.
//
// Will panic if the receiver is not an object type (use IsObjectType to
// confirm) or if the object type has no such attribute (use HasAttribute to
// confirm).
func (t Type) AttributeOptional(name string) bool {
name = NormalizeString(name)
if ot, ok := t.typeImpl.(typeObject); ok {
if _, hasAttr := ot.AttrTypes[name]; !hasAttr {
panic("no such attribute")
}
_, exists := ot.AttrOptional[name]
return exists
}
panic("AttributeDefaultValue on non-object Type")
}

270
vendor/github.com/zclconf/go-cty/cty/path.go generated vendored Normal file
View File

@ -0,0 +1,270 @@
package cty
import (
"errors"
"fmt"
)
// A Path is a sequence of operations to locate a nested value within a
// data structure.
//
// The empty Path represents the given item. Any PathSteps within represent
// taking a single step down into a data structure.
//
// Path has some convenience methods for gradually constructing a path,
// but callers can also feel free to just produce a slice of PathStep manually
// and convert to this type, which may be more appropriate in environments
// where memory pressure is a concern.
//
// Although a Path is technically mutable, by convention callers should not
// mutate a path once it has been built and passed to some other subsystem.
// Instead, use Copy and then mutate the copy before using it.
type Path []PathStep
// PathStep represents a single step down into a data structure, as part
// of a Path. PathStep is a closed interface, meaning that the only
// permitted implementations are those within this package.
type PathStep interface {
pathStepSigil() pathStepImpl
Apply(Value) (Value, error)
}
// embed pathImpl into a struct to declare it a PathStep implementation
type pathStepImpl struct{}
func (p pathStepImpl) pathStepSigil() pathStepImpl {
return p
}
// Index returns a new Path that is the reciever with an IndexStep appended
// to the end.
//
// This is provided as a convenient way to construct paths, but each call
// will create garbage so it should not be used where memory pressure is a
// concern.
func (p Path) Index(v Value) Path {
ret := make(Path, len(p)+1)
copy(ret, p)
ret[len(p)] = IndexStep{
Key: v,
}
return ret
}
// IndexInt is a typed convenience method for Index.
func (p Path) IndexInt(v int) Path {
return p.Index(NumberIntVal(int64(v)))
}
// IndexString is a typed convenience method for Index.
func (p Path) IndexString(v string) Path {
return p.Index(StringVal(v))
}
// IndexPath is a convenience method to start a new Path with an IndexStep.
func IndexPath(v Value) Path {
return Path{}.Index(v)
}
// IndexIntPath is a typed convenience method for IndexPath.
func IndexIntPath(v int) Path {
return IndexPath(NumberIntVal(int64(v)))
}
// IndexStringPath is a typed convenience method for IndexPath.
func IndexStringPath(v string) Path {
return IndexPath(StringVal(v))
}
// GetAttr returns a new Path that is the reciever with a GetAttrStep appended
// to the end.
//
// This is provided as a convenient way to construct paths, but each call
// will create garbage so it should not be used where memory pressure is a
// concern.
func (p Path) GetAttr(name string) Path {
ret := make(Path, len(p)+1)
copy(ret, p)
ret[len(p)] = GetAttrStep{
Name: name,
}
return ret
}
// Equals compares 2 Paths for exact equality.
func (p Path) Equals(other Path) bool {
if len(p) != len(other) {
return false
}
for i := range p {
pv := p[i]
switch pv := pv.(type) {
case GetAttrStep:
ov, ok := other[i].(GetAttrStep)
if !ok || pv != ov {
return false
}
case IndexStep:
ov, ok := other[i].(IndexStep)
if !ok {
return false
}
if !pv.Key.RawEquals(ov.Key) {
return false
}
default:
// Any invalid steps default to evaluating false.
return false
}
}
return true
}
// HasPrefix determines if the path p contains the provided prefix.
func (p Path) HasPrefix(prefix Path) bool {
if len(prefix) > len(p) {
return false
}
return p[:len(prefix)].Equals(prefix)
}
// GetAttrPath is a convenience method to start a new Path with a GetAttrStep.
func GetAttrPath(name string) Path {
return Path{}.GetAttr(name)
}
// Apply applies each of the steps in turn to successive values starting with
// the given value, and returns the result. If any step returns an error,
// the whole operation returns an error.
func (p Path) Apply(val Value) (Value, error) {
var err error
for i, step := range p {
val, err = step.Apply(val)
if err != nil {
return NilVal, fmt.Errorf("at step %d: %s", i, err)
}
}
return val, nil
}
// LastStep applies the given path up to the last step and then returns
// the resulting value and the final step.
//
// This is useful when dealing with assignment operations, since in that
// case the *value* of the last step is not important (and may not, in fact,
// present at all) and we care only about its location.
//
// Since LastStep applies all steps except the last, it will return errors
// for those steps in the same way as Apply does.
//
// If the path has *no* steps then the returned PathStep will be nil,
// representing that any operation should be applied directly to the
// given value.
func (p Path) LastStep(val Value) (Value, PathStep, error) {
var err error
if len(p) == 0 {
return val, nil, nil
}
journey := p[:len(p)-1]
val, err = journey.Apply(val)
if err != nil {
return NilVal, nil, err
}
return val, p[len(p)-1], nil
}
// Copy makes a shallow copy of the receiver. Often when paths are passed to
// caller code they come with the constraint that they are valid only until
// the caller returns, due to how they are constructed internally. Callers
// can use Copy to conveniently produce a copy of the value that _they_ control
// the validity of.
func (p Path) Copy() Path {
ret := make(Path, len(p))
copy(ret, p)
return ret
}
// IndexStep is a Step implementation representing applying the index operation
// to a value, which must be of either a list, map, or set type.
//
// When describing a path through a *type* rather than a concrete value,
// the Key may be an unknown value, indicating that the step applies to
// *any* key of the given type.
//
// When indexing into a set, the Key is actually the element being accessed
// itself, since in sets elements are their own identity.
type IndexStep struct {
pathStepImpl
Key Value
}
// Apply returns the value resulting from indexing the given value with
// our key value.
func (s IndexStep) Apply(val Value) (Value, error) {
if val == NilVal || val.IsNull() {
return NilVal, errors.New("cannot index a null value")
}
switch s.Key.Type() {
case Number:
if !(val.Type().IsListType() || val.Type().IsTupleType()) {
return NilVal, errors.New("not a list type")
}
case String:
if !val.Type().IsMapType() {
return NilVal, errors.New("not a map type")
}
default:
return NilVal, errors.New("key value not number or string")
}
has := val.HasIndex(s.Key)
if !has.IsKnown() {
return UnknownVal(val.Type().ElementType()), nil
}
if !has.True() {
return NilVal, errors.New("value does not have given index key")
}
return val.Index(s.Key), nil
}
func (s IndexStep) GoString() string {
return fmt.Sprintf("cty.IndexStep{Key:%#v}", s.Key)
}
// GetAttrStep is a Step implementation representing retrieving an attribute
// from a value, which must be of an object type.
type GetAttrStep struct {
pathStepImpl
Name string
}
// Apply returns the value of our named attribute from the given value, which
// must be of an object type that has a value of that name.
func (s GetAttrStep) Apply(val Value) (Value, error) {
if val == NilVal || val.IsNull() {
return NilVal, errors.New("cannot access attributes on a null value")
}
if !val.Type().IsObjectType() {
return NilVal, errors.New("not an object type")
}
if !val.Type().HasAttribute(s.Name) {
return NilVal, fmt.Errorf("object has no attribute %q", s.Name)
}
return val.GetAttr(s.Name), nil
}
func (s GetAttrStep) GoString() string {
return fmt.Sprintf("cty.GetAttrStep{Name:%q}", s.Name)
}

204
vendor/github.com/zclconf/go-cty/cty/path_set.go generated vendored Normal file
View File

@ -0,0 +1,204 @@
package cty
import (
"fmt"
"hash/crc64"
"github.com/zclconf/go-cty/cty/set"
)
// PathSet represents a set of Path objects. This can be used, for example,
// to talk about a subset of paths within a value that meet some criteria,
// without directly modifying the values at those paths.
type PathSet struct {
set set.Set
}
// NewPathSet creates and returns a PathSet, with initial contents optionally
// set by the given arguments.
func NewPathSet(paths ...Path) PathSet {
ret := PathSet{
set: set.NewSet(pathSetRules{}),
}
for _, path := range paths {
ret.Add(path)
}
return ret
}
// Add inserts a single given path into the set.
//
// Paths are immutable after construction by convention. It is particularly
// important not to mutate a path after it has been placed into a PathSet.
// If a Path is mutated while in a set, behavior is undefined.
func (s PathSet) Add(path Path) {
s.set.Add(path)
}
// AddAllSteps is like Add but it also adds all of the steps leading to
// the given path.
//
// For example, if given a path representing "foo.bar", it will add both
// "foo" and "bar".
func (s PathSet) AddAllSteps(path Path) {
for i := 1; i <= len(path); i++ {
s.Add(path[:i])
}
}
// Has returns true if the given path is in the receiving set.
func (s PathSet) Has(path Path) bool {
return s.set.Has(path)
}
// List makes and returns a slice of all of the paths in the receiving set,
// in an undefined but consistent order.
func (s PathSet) List() []Path {
if s.Empty() {
return nil
}
ret := make([]Path, 0, s.set.Length())
for it := s.set.Iterator(); it.Next(); {
ret = append(ret, it.Value().(Path))
}
return ret
}
// Remove modifies the receving set to no longer include the given path.
// If the given path was already absent, this is a no-op.
func (s PathSet) Remove(path Path) {
s.set.Remove(path)
}
// Empty returns true if the length of the receiving set is zero.
func (s PathSet) Empty() bool {
return s.set.Length() == 0
}
// Union returns a new set whose contents are the union of the receiver and
// the given other set.
func (s PathSet) Union(other PathSet) PathSet {
return PathSet{
set: s.set.Union(other.set),
}
}
// Intersection returns a new set whose contents are the intersection of the
// receiver and the given other set.
func (s PathSet) Intersection(other PathSet) PathSet {
return PathSet{
set: s.set.Intersection(other.set),
}
}
// Subtract returns a new set whose contents are those from the receiver with
// any elements of the other given set subtracted.
func (s PathSet) Subtract(other PathSet) PathSet {
return PathSet{
set: s.set.Subtract(other.set),
}
}
// SymmetricDifference returns a new set whose contents are the symmetric
// difference of the receiver and the given other set.
func (s PathSet) SymmetricDifference(other PathSet) PathSet {
return PathSet{
set: s.set.SymmetricDifference(other.set),
}
}
// Equal returns true if and only if both the receiver and the given other
// set contain exactly the same paths.
func (s PathSet) Equal(other PathSet) bool {
if s.set.Length() != other.set.Length() {
return false
}
// Now we know the lengths are the same we only need to test in one
// direction whether everything in one is in the other.
for it := s.set.Iterator(); it.Next(); {
if !other.set.Has(it.Value()) {
return false
}
}
return true
}
var crc64Table = crc64.MakeTable(crc64.ISO)
var indexStepPlaceholder = []byte("#")
// pathSetRules is an implementation of set.Rules from the set package,
// used internally within PathSet.
type pathSetRules struct {
}
func (r pathSetRules) Hash(v interface{}) int {
path := v.(Path)
hash := crc64.New(crc64Table)
for _, rawStep := range path {
switch step := rawStep.(type) {
case GetAttrStep:
// (this creates some garbage converting the string name to a
// []byte, but that's okay since cty is not designed to be
// used in tight loops under memory pressure.)
hash.Write([]byte(step.Name))
default:
// For any other step type we just append a predefined value,
// which means that e.g. all indexes into a given collection will
// hash to the same value but we assume that collections are
// small and thus this won't hurt too much.
hash.Write(indexStepPlaceholder)
}
}
// We discard half of the hash on 32-bit platforms; collisions just make
// our lookups take marginally longer, so not a big deal.
return int(hash.Sum64())
}
func (r pathSetRules) Equivalent(a, b interface{}) bool {
aPath := a.(Path)
bPath := b.(Path)
if len(aPath) != len(bPath) {
return false
}
for i := range aPath {
switch aStep := aPath[i].(type) {
case GetAttrStep:
bStep, ok := bPath[i].(GetAttrStep)
if !ok {
return false
}
if aStep.Name != bStep.Name {
return false
}
case IndexStep:
bStep, ok := bPath[i].(IndexStep)
if !ok {
return false
}
eq := aStep.Key.Equals(bStep.Key)
if !eq.IsKnown() || eq.False() {
return false
}
default:
// Should never happen, since we document PathStep as a closed type.
panic(fmt.Errorf("unsupported step type %T", aStep))
}
}
return true
}
// SameRules is true if both Rules instances are pathSetRules structs.
func (r pathSetRules) SameRules(other set.Rules) bool {
_, ok := other.(pathSetRules)
return ok
}

167
vendor/github.com/zclconf/go-cty/cty/primitive_type.go generated vendored Normal file
View File

@ -0,0 +1,167 @@
package cty
import "math/big"
// primitiveType is the hidden implementation of the various primitive types
// that are exposed as variables in this package.
type primitiveType struct {
typeImplSigil
Kind primitiveTypeKind
}
type primitiveTypeKind byte
const (
primitiveTypeBool primitiveTypeKind = 'B'
primitiveTypeNumber primitiveTypeKind = 'N'
primitiveTypeString primitiveTypeKind = 'S'
)
func (t primitiveType) Equals(other Type) bool {
if otherP, ok := other.typeImpl.(primitiveType); ok {
return otherP.Kind == t.Kind
}
return false
}
func (t primitiveType) FriendlyName(mode friendlyTypeNameMode) string {
switch t.Kind {
case primitiveTypeBool:
return "bool"
case primitiveTypeNumber:
return "number"
case primitiveTypeString:
return "string"
default:
// should never happen
panic("invalid primitive type")
}
}
func (t primitiveType) GoString() string {
switch t.Kind {
case primitiveTypeBool:
return "cty.Bool"
case primitiveTypeNumber:
return "cty.Number"
case primitiveTypeString:
return "cty.String"
default:
// should never happen
panic("invalid primitive type")
}
}
// rawNumberEqual is our cty-specific definition of whether two big floats
// underlying cty.Number are "equal" for the purposes of the Value.Equals and
// Value.RawEquals methods.
//
// The built-in equality for big.Float is a direct comparison of the mantissa
// bits and the exponent, but that's too precise a check for cty because we
// routinely send numbers through decimal approximations and back and so
// we only promise to accurately represent the subset of binary floating point
// numbers that can be derived from a decimal string representation.
//
// In respect of the fact that cty only tries to preserve numbers that can
// reasonably be written in JSON documents, we use the string representation of
// a decimal approximation of the number as our comparison, relying on the
// big.Float type's heuristic for discarding extraneous mantissa bits that seem
// likely to only be there as a result of an earlier decimal-to-binary
// approximation during parsing, e.g. in ParseNumberVal.
func rawNumberEqual(a, b *big.Float) bool {
switch {
case (a == nil) != (b == nil):
return false
case a == nil: // b == nil too then, due to previous case
return true
default:
// This format and precision matches that used by cty/json.Marshal,
// and thus achieves our definition of "two numbers are equal if
// we'd use the same JSON serialization for both of them".
const format = 'f'
const prec = -1
aStr := a.Text(format, prec)
bStr := b.Text(format, prec)
// The one exception to our rule about equality-by-stringification is
// negative zero, because we want -0 to always be equal to +0.
const posZero = "0"
const negZero = "-0"
if aStr == negZero {
aStr = posZero
}
if bStr == negZero {
bStr = posZero
}
return aStr == bStr
}
}
// Number is the numeric type. Number values are arbitrary-precision
// decimal numbers, which can then be converted into Go's various numeric
// types only if they are in the appropriate range.
var Number Type
// String is the string type. String values are sequences of unicode codepoints
// encoded internally as UTF-8.
var String Type
// Bool is the boolean type. The two values of this type are True and False.
var Bool Type
// True is the truthy value of type Bool
var True Value
// False is the falsey value of type Bool
var False Value
// Zero is a number value representing exactly zero.
var Zero Value
// PositiveInfinity is a Number value representing positive infinity
var PositiveInfinity Value
// NegativeInfinity is a Number value representing negative infinity
var NegativeInfinity Value
func init() {
Number = Type{
primitiveType{Kind: primitiveTypeNumber},
}
String = Type{
primitiveType{Kind: primitiveTypeString},
}
Bool = Type{
primitiveType{Kind: primitiveTypeBool},
}
True = Value{
ty: Bool,
v: true,
}
False = Value{
ty: Bool,
v: false,
}
Zero = Value{
ty: Number,
v: big.NewFloat(0),
}
PositiveInfinity = Value{
ty: Number,
v: (&big.Float{}).SetInf(false),
}
NegativeInfinity = Value{
ty: Number,
v: (&big.Float{}).SetInf(true),
}
}
// IsPrimitiveType returns true if and only if the reciever is a primitive
// type, which means it's either number, string, or bool. Any two primitive
// types can be safely compared for equality using the standard == operator
// without panic, which is not a guarantee that holds for all types. Primitive
// types can therefore also be used in switch statements.
func (t Type) IsPrimitiveType() bool {
_, ok := t.typeImpl.(primitiveType)
return ok
}

76
vendor/github.com/zclconf/go-cty/cty/set/gob.go generated vendored Normal file
View File

@ -0,0 +1,76 @@
package set
import (
"bytes"
"encoding/gob"
"fmt"
)
// GobEncode is an implementation of the interface gob.GobEncoder, allowing
// sets to be included in structures encoded via gob.
//
// The set rules are included in the serialized value, so the caller must
// register its concrete rules type with gob.Register before using a
// set in a gob, and possibly also implement GobEncode/GobDecode to customize
// how any parameters are persisted.
//
// The set elements are also included, so if they are of non-primitive types
// they too must be registered with gob.
//
// If the produced gob values will persist for a long time, the caller must
// ensure compatibility of the rules implementation. In particular, if the
// definition of element equivalence changes between encoding and decoding
// then two distinct stored elements may be considered equivalent on decoding,
// causing the recovered set to have fewer elements than when it was stored.
func (s Set) GobEncode() ([]byte, error) {
gs := gobSet{
Version: 0,
Rules: s.rules,
Values: s.Values(),
}
buf := &bytes.Buffer{}
enc := gob.NewEncoder(buf)
err := enc.Encode(gs)
if err != nil {
return nil, fmt.Errorf("error encoding set.Set: %s", err)
}
return buf.Bytes(), nil
}
// GobDecode is the opposite of GobEncode. See GobEncode for information
// on the requirements for and caveats of including set values in gobs.
func (s *Set) GobDecode(buf []byte) error {
r := bytes.NewReader(buf)
dec := gob.NewDecoder(r)
var gs gobSet
err := dec.Decode(&gs)
if err != nil {
return fmt.Errorf("error decoding set.Set: %s", err)
}
if gs.Version != 0 {
return fmt.Errorf("unsupported set.Set encoding version %d; need 0", gs.Version)
}
victim := NewSetFromSlice(gs.Rules, gs.Values)
s.vals = victim.vals
s.rules = victim.rules
return nil
}
type gobSet struct {
Version int
Rules Rules
// The bucket-based representation is for efficient in-memory access, but
// for serialization it's enough to just retain the values themselves,
// which we can re-bucket using the rules (which may have changed!) when
// we re-inflate.
Values []interface{}
}
func init() {
gob.Register([]interface{}(nil))
}

15
vendor/github.com/zclconf/go-cty/cty/set/iterator.go generated vendored Normal file
View File

@ -0,0 +1,15 @@
package set
type Iterator struct {
vals []interface{}
idx int
}
func (it *Iterator) Value() interface{} {
return it.vals[it.idx]
}
func (it *Iterator) Next() bool {
it.idx++
return it.idx < len(it.vals)
}

210
vendor/github.com/zclconf/go-cty/cty/set/ops.go generated vendored Normal file
View File

@ -0,0 +1,210 @@
package set
import (
"sort"
)
// Add inserts the given value into the receiving Set.
//
// This mutates the set in-place. This operation is not thread-safe.
func (s Set) Add(val interface{}) {
hv := s.rules.Hash(val)
if _, ok := s.vals[hv]; !ok {
s.vals[hv] = make([]interface{}, 0, 1)
}
bucket := s.vals[hv]
// See if an equivalent value is already present
for _, ev := range bucket {
if s.rules.Equivalent(val, ev) {
return
}
}
s.vals[hv] = append(bucket, val)
}
// Remove deletes the given value from the receiving set, if indeed it was
// there in the first place. If the value is not present, this is a no-op.
func (s Set) Remove(val interface{}) {
hv := s.rules.Hash(val)
bucket, ok := s.vals[hv]
if !ok {
return
}
for i, ev := range bucket {
if s.rules.Equivalent(val, ev) {
newBucket := make([]interface{}, 0, len(bucket)-1)
newBucket = append(newBucket, bucket[:i]...)
newBucket = append(newBucket, bucket[i+1:]...)
if len(newBucket) > 0 {
s.vals[hv] = newBucket
} else {
delete(s.vals, hv)
}
return
}
}
}
// Has returns true if the given value is in the receiving set, or false if
// it is not.
func (s Set) Has(val interface{}) bool {
hv := s.rules.Hash(val)
bucket, ok := s.vals[hv]
if !ok {
return false
}
for _, ev := range bucket {
if s.rules.Equivalent(val, ev) {
return true
}
}
return false
}
// Copy performs a shallow copy of the receiving set, returning a new set
// with the same rules and elements.
func (s Set) Copy() Set {
ret := NewSet(s.rules)
for k, v := range s.vals {
ret.vals[k] = v
}
return ret
}
// Iterator returns an iterator over values in the set. If the set's rules
// implement OrderedRules then the result is ordered per those rules. If
// no order is provided, or if it is not a total order, then the iteration
// order is undefined but consistent for a particular version of cty. Do not
// rely on specific ordering between cty releases unless the rules order is a
// total order.
//
// The pattern for using the returned iterator is:
//
// it := set.Iterator()
// for it.Next() {
// val := it.Value()
// // ...
// }
//
// Once an iterator has been created for a set, the set *must not* be mutated
// until the iterator is no longer in use.
func (s Set) Iterator() *Iterator {
vals := s.Values()
return &Iterator{
vals: vals,
idx: -1,
}
}
// EachValue calls the given callback once for each value in the set, in an
// undefined order that callers should not depend on.
func (s Set) EachValue(cb func(interface{})) {
it := s.Iterator()
for it.Next() {
cb(it.Value())
}
}
// Values returns a slice of all the values in the set. If the set rules have
// an order then the result is in that order. If no order is provided or if
// it is not a total order then the result order is undefined, but consistent
// for a particular set value within a specific release of cty.
func (s Set) Values() []interface{} {
var ret []interface{}
// Sort the bucketIds to ensure that we always traverse in a
// consistent order.
bucketIDs := make([]int, 0, len(s.vals))
for id := range s.vals {
bucketIDs = append(bucketIDs, id)
}
sort.Ints(bucketIDs)
for _, bucketID := range bucketIDs {
ret = append(ret, s.vals[bucketID]...)
}
if orderRules, ok := s.rules.(OrderedRules); ok {
sort.SliceStable(ret, func(i, j int) bool {
return orderRules.Less(ret[i], ret[j])
})
}
return ret
}
// Length returns the number of values in the set.
func (s Set) Length() int {
var count int
for _, bucket := range s.vals {
count = count + len(bucket)
}
return count
}
// Union returns a new set that contains all of the members of both the
// receiving set and the given set. Both sets must have the same rules, or
// else this function will panic.
func (s1 Set) Union(s2 Set) Set {
mustHaveSameRules(s1, s2)
rs := NewSet(s1.rules)
s1.EachValue(func(v interface{}) {
rs.Add(v)
})
s2.EachValue(func(v interface{}) {
rs.Add(v)
})
return rs
}
// Intersection returns a new set that contains the values that both the
// receiver and given sets have in common. Both sets must have the same rules,
// or else this function will panic.
func (s1 Set) Intersection(s2 Set) Set {
mustHaveSameRules(s1, s2)
rs := NewSet(s1.rules)
s1.EachValue(func(v interface{}) {
if s2.Has(v) {
rs.Add(v)
}
})
return rs
}
// Subtract returns a new set that contains all of the values from the receiver
// that are not also in the given set. Both sets must have the same rules,
// or else this function will panic.
func (s1 Set) Subtract(s2 Set) Set {
mustHaveSameRules(s1, s2)
rs := NewSet(s1.rules)
s1.EachValue(func(v interface{}) {
if !s2.Has(v) {
rs.Add(v)
}
})
return rs
}
// SymmetricDifference returns a new set that contains all of the values from
// both the receiver and given sets, except those that both sets have in
// common. Both sets must have the same rules, or else this function will
// panic.
func (s1 Set) SymmetricDifference(s2 Set) Set {
mustHaveSameRules(s1, s2)
rs := NewSet(s1.rules)
s1.EachValue(func(v interface{}) {
if !s2.Has(v) {
rs.Add(v)
}
})
s2.EachValue(func(v interface{}) {
if !s1.Has(v) {
rs.Add(v)
}
})
return rs
}

47
vendor/github.com/zclconf/go-cty/cty/set/rules.go generated vendored Normal file
View File

@ -0,0 +1,47 @@
package set
// Rules represents the operations that define membership for a Set.
//
// Each Set has a Rules instance, whose methods must satisfy the interface
// contracts given below for any value that will be added to the set.
type Rules interface {
// Hash returns an int that somewhat-uniquely identifies the given value.
//
// A good hash function will minimize collisions for values that will be
// added to the set, though collisions *are* permitted. Collisions will
// simply reduce the efficiency of operations on the set.
Hash(interface{}) int
// Equivalent returns true if and only if the two values are considered
// equivalent for the sake of set membership. Two values that are
// equivalent cannot exist in the set at the same time, and if two
// equivalent values are added it is undefined which one will be
// returned when enumerating all of the set members.
//
// Two values that are equivalent *must* result in the same hash value,
// though it is *not* required that two values with the same hash value
// be equivalent.
Equivalent(interface{}, interface{}) bool
// SameRules returns true if the instance is equivalent to another Rules
// instance.
SameRules(Rules) bool
}
// OrderedRules is an extension of Rules that can apply a partial order to
// element values. When a set's Rules implements OrderedRules an iterator
// over the set will return items in the order described by the rules.
//
// If the given order is not a total order (that is, some pairs of non-equivalent
// elements do not have a defined order) then the resulting iteration order
// is undefined but consistent for a particular version of cty. The exact
// order in that case is not part of the contract and is subject to change
// between versions.
type OrderedRules interface {
Rules
// Less returns true if and only if the first argument should sort before
// the second argument. If the second argument should sort before the first
// or if there is no defined order for the values, return false.
Less(interface{}, interface{}) bool
}

62
vendor/github.com/zclconf/go-cty/cty/set/set.go generated vendored Normal file
View File

@ -0,0 +1,62 @@
package set
import (
"fmt"
)
// Set is an implementation of the concept of a set: a collection where all
// values are conceptually either in or out of the set, but the members are
// not ordered.
//
// This type primarily exists to be the internal type of sets in cty, but
// it is considered to be at the same level of abstraction as Go's built in
// slice and map collection types, and so should make no cty-specific
// assumptions.
//
// Set operations are not thread safe. It is the caller's responsibility to
// provide mutex guarantees where necessary.
//
// Set operations are not optimized to minimize memory pressure. Mutating
// a set will generally create garbage and so should perhaps be avoided in
// tight loops where memory pressure is a concern.
type Set struct {
vals map[int][]interface{}
rules Rules
}
// NewSet returns an empty set with the membership rules given.
func NewSet(rules Rules) Set {
return Set{
vals: map[int][]interface{}{},
rules: rules,
}
}
func NewSetFromSlice(rules Rules, vals []interface{}) Set {
s := NewSet(rules)
for _, v := range vals {
s.Add(v)
}
return s
}
func sameRules(s1 Set, s2 Set) bool {
return s1.rules.SameRules(s2.rules)
}
func mustHaveSameRules(s1 Set, s2 Set) {
if !sameRules(s1, s2) {
panic(fmt.Errorf("incompatible set rules: %#v, %#v", s1.rules, s2.rules))
}
}
// HasRules returns true if and only if the receiving set has the given rules
// instance as its rules.
func (s Set) HasRules(rules Rules) bool {
return s.rules.SameRules(rules)
}
// Rules returns the receiving set's rules instance.
func (s Set) Rules() Rules {
return s.rules
}

132
vendor/github.com/zclconf/go-cty/cty/set_helper.go generated vendored Normal file
View File

@ -0,0 +1,132 @@
package cty
import (
"fmt"
"github.com/zclconf/go-cty/cty/set"
)
// ValueSet is to cty.Set what []cty.Value is to cty.List and
// map[string]cty.Value is to cty.Map. It's provided to allow callers a
// convenient interface for manipulating sets before wrapping them in cty.Set
// values using cty.SetValFromValueSet.
//
// Unlike value slices and value maps, ValueSet instances have a single
// homogenous element type because that is a requirement of the underlying
// set implementation, which uses the element type to select a suitable
// hashing function.
//
// Set mutations are not concurrency-safe.
type ValueSet struct {
// ValueSet is just a thin wrapper around a set.Set with our value-oriented
// "rules" applied. We do this so that the caller can work in terms of
// cty.Value objects even though the set internals use the raw values.
s set.Set
}
// NewValueSet creates and returns a new ValueSet with the given element type.
func NewValueSet(ety Type) ValueSet {
return newValueSet(set.NewSet(setRules{Type: ety}))
}
func newValueSet(s set.Set) ValueSet {
return ValueSet{
s: s,
}
}
// ElementType returns the element type for the receiving ValueSet.
func (s ValueSet) ElementType() Type {
return s.s.Rules().(setRules).Type
}
// Add inserts the given value into the receiving set.
func (s ValueSet) Add(v Value) {
s.requireElementType(v)
s.s.Add(v.v)
}
// Remove deletes the given value from the receiving set, if indeed it was
// there in the first place. If the value is not present, this is a no-op.
func (s ValueSet) Remove(v Value) {
s.requireElementType(v)
s.s.Remove(v.v)
}
// Has returns true if the given value is in the receiving set, or false if
// it is not.
func (s ValueSet) Has(v Value) bool {
s.requireElementType(v)
return s.s.Has(v.v)
}
// Copy performs a shallow copy of the receiving set, returning a new set
// with the same rules and elements.
func (s ValueSet) Copy() ValueSet {
return newValueSet(s.s.Copy())
}
// Length returns the number of values in the set.
func (s ValueSet) Length() int {
return s.s.Length()
}
// Values returns a slice of all of the values in the set in no particular
// order.
func (s ValueSet) Values() []Value {
l := s.s.Length()
if l == 0 {
return nil
}
ret := make([]Value, 0, l)
ety := s.ElementType()
for it := s.s.Iterator(); it.Next(); {
ret = append(ret, Value{
ty: ety,
v: it.Value(),
})
}
return ret
}
// Union returns a new set that contains all of the members of both the
// receiving set and the given set. Both sets must have the same element type,
// or else this function will panic.
func (s ValueSet) Union(other ValueSet) ValueSet {
return newValueSet(s.s.Union(other.s))
}
// Intersection returns a new set that contains the values that both the
// receiver and given sets have in common. Both sets must have the same element
// type, or else this function will panic.
func (s ValueSet) Intersection(other ValueSet) ValueSet {
return newValueSet(s.s.Intersection(other.s))
}
// Subtract returns a new set that contains all of the values from the receiver
// that are not also in the given set. Both sets must have the same element
// type, or else this function will panic.
func (s ValueSet) Subtract(other ValueSet) ValueSet {
return newValueSet(s.s.Subtract(other.s))
}
// SymmetricDifference returns a new set that contains all of the values from
// both the receiver and given sets, except those that both sets have in
// common. Both sets must have the same element type, or else this function
// will panic.
func (s ValueSet) SymmetricDifference(other ValueSet) ValueSet {
return newValueSet(s.s.SymmetricDifference(other.s))
}
// requireElementType panics if the given value is not of the set's element type.
//
// It also panics if the given value is marked, because marked values cannot
// be stored in sets.
func (s ValueSet) requireElementType(v Value) {
if v.IsMarked() {
panic("cannot store marked value directly in a set (make the set itself unknown instead)")
}
if !v.Type().Equals(s.ElementType()) {
panic(fmt.Errorf("attempt to use %#v value with set of %#v", v.Type(), s.ElementType()))
}
}

255
vendor/github.com/zclconf/go-cty/cty/set_internals.go generated vendored Normal file
View File

@ -0,0 +1,255 @@
package cty
import (
"bytes"
"fmt"
"hash/crc32"
"math/big"
"sort"
"github.com/zclconf/go-cty/cty/set"
)
// setRules provides a Rules implementation for the ./set package that
// respects the equality rules for cty values of the given type.
//
// This implementation expects that values added to the set will be
// valid internal values for the given Type, which is to say that wrapping
// the given value in a Value struct along with the ruleset's type should
// produce a valid, working Value.
type setRules struct {
Type Type
}
var _ set.OrderedRules = setRules{}
// Hash returns a hash value for the receiver that can be used for equality
// checks where some inaccuracy is tolerable.
//
// The hash function is value-type-specific, so it is not meaningful to compare
// hash results for values of different types.
//
// This function is not safe to use for security-related applications, since
// the hash used is not strong enough.
func (val Value) Hash() int {
hashBytes, marks := makeSetHashBytes(val)
if len(marks) > 0 {
panic("can't take hash of value that has marks or has embedded values that have marks")
}
return int(crc32.ChecksumIEEE(hashBytes))
}
func (r setRules) Hash(v interface{}) int {
return Value{
ty: r.Type,
v: v,
}.Hash()
}
func (r setRules) Equivalent(v1 interface{}, v2 interface{}) bool {
v1v := Value{
ty: r.Type,
v: v1,
}
v2v := Value{
ty: r.Type,
v: v2,
}
eqv := v1v.Equals(v2v)
// By comparing the result to true we ensure that an Unknown result,
// which will result if either value is unknown, will be considered
// as non-equivalent. Two unknown values are not equivalent for the
// sake of set membership.
return eqv.v == true
}
// SameRules is only true if the other Rules instance is also a setRules struct,
// and the types are considered equal.
func (r setRules) SameRules(other set.Rules) bool {
rules, ok := other.(setRules)
if !ok {
return false
}
return r.Type.Equals(rules.Type)
}
// Less is an implementation of set.OrderedRules so that we can iterate over
// set elements in a consistent order, where such an order is possible.
func (r setRules) Less(v1, v2 interface{}) bool {
v1v := Value{
ty: r.Type,
v: v1,
}
v2v := Value{
ty: r.Type,
v: v2,
}
if v1v.RawEquals(v2v) { // Easy case: if they are equal then v1 can't be less
return false
}
// Null values always sort after non-null values
if v2v.IsNull() && !v1v.IsNull() {
return true
} else if v1v.IsNull() {
return false
}
// Unknown values always sort after known values
if v1v.IsKnown() && !v2v.IsKnown() {
return true
} else if !v1v.IsKnown() {
return false
}
switch r.Type {
case String:
// String values sort lexicographically
return v1v.AsString() < v2v.AsString()
case Bool:
// Weird to have a set of bools, but if we do then false sorts before true.
if v2v.True() || !v1v.True() {
return true
}
return false
case Number:
v1f := v1v.AsBigFloat()
v2f := v2v.AsBigFloat()
return v1f.Cmp(v2f) < 0
default:
// No other types have a well-defined ordering, so we just produce a
// default consistent-but-undefined ordering then. This situation is
// not considered a compatibility constraint; callers should rely only
// on the ordering rules for primitive values.
v1h, _ := makeSetHashBytes(v1v)
v2h, _ := makeSetHashBytes(v2v)
return bytes.Compare(v1h, v2h) < 0
}
}
func makeSetHashBytes(val Value) ([]byte, ValueMarks) {
var buf bytes.Buffer
marks := make(ValueMarks)
appendSetHashBytes(val, &buf, marks)
return buf.Bytes(), marks
}
func appendSetHashBytes(val Value, buf *bytes.Buffer, marks ValueMarks) {
// Exactly what bytes we generate here don't matter as long as the following
// constraints hold:
// - Unknown and null values all generate distinct strings from
// each other and from any normal value of the given type.
// - The delimiter used to separate items in a compound structure can
// never appear literally in any of its elements.
// Since we don't support hetrogenous lists we don't need to worry about
// collisions between values of different types, apart from
// PseudoTypeDynamic.
// If in practice we *do* get a collision then it's not a big deal because
// the Equivalent function will still distinguish values, but set
// performance will be best if we are able to produce a distinct string
// for each distinct value, unknown values notwithstanding.
// Marks aren't considered part of a value for equality-testing purposes,
// so we'll unmark our value before we work with it but we'll remember
// the marks in case the caller needs to re-apply them to a derived
// value.
if val.IsMarked() {
unmarkedVal, valMarks := val.Unmark()
for m := range valMarks {
marks[m] = struct{}{}
}
val = unmarkedVal
}
if !val.IsKnown() {
buf.WriteRune('?')
return
}
if val.IsNull() {
buf.WriteRune('~')
return
}
switch val.ty {
case Number:
// Due to an unfortunate quirk of gob encoding for big.Float, we end up
// with non-pointer values immediately after a gob round-trip, and
// we end up in here before we've had a chance to run
// gobDecodeFixNumberPtr on the inner values of a gob-encoded set,
// and so sadly we must make a special effort to handle that situation
// here just so that we can get far enough along to fix it up for
// everything else in this package.
if bf, ok := val.v.(big.Float); ok {
buf.WriteString(bf.String())
return
}
buf.WriteString(val.v.(*big.Float).String())
return
case Bool:
if val.v.(bool) {
buf.WriteRune('T')
} else {
buf.WriteRune('F')
}
return
case String:
buf.WriteString(fmt.Sprintf("%q", val.v.(string)))
return
}
if val.ty.IsMapType() {
buf.WriteRune('{')
val.ForEachElement(func(keyVal, elementVal Value) bool {
appendSetHashBytes(keyVal, buf, marks)
buf.WriteRune(':')
appendSetHashBytes(elementVal, buf, marks)
buf.WriteRune(';')
return false
})
buf.WriteRune('}')
return
}
if val.ty.IsListType() || val.ty.IsSetType() {
buf.WriteRune('[')
val.ForEachElement(func(keyVal, elementVal Value) bool {
appendSetHashBytes(elementVal, buf, marks)
buf.WriteRune(';')
return false
})
buf.WriteRune(']')
return
}
if val.ty.IsObjectType() {
buf.WriteRune('<')
attrNames := make([]string, 0, len(val.ty.AttributeTypes()))
for attrName := range val.ty.AttributeTypes() {
attrNames = append(attrNames, attrName)
}
sort.Strings(attrNames)
for _, attrName := range attrNames {
appendSetHashBytes(val.GetAttr(attrName), buf, marks)
buf.WriteRune(';')
}
buf.WriteRune('>')
return
}
if val.ty.IsTupleType() {
buf.WriteRune('<')
val.ForEachElement(func(keyVal, elementVal Value) bool {
appendSetHashBytes(elementVal, buf, marks)
buf.WriteRune(';')
return false
})
buf.WriteRune('>')
return
}
// should never get down here
panic("unsupported type in set hash")
}

72
vendor/github.com/zclconf/go-cty/cty/set_type.go generated vendored Normal file
View File

@ -0,0 +1,72 @@
package cty
import (
"fmt"
)
type typeSet struct {
typeImplSigil
ElementTypeT Type
}
// Set creates a set type with the given element Type.
//
// Set types are CollectionType implementations.
func Set(elem Type) Type {
return Type{
typeSet{
ElementTypeT: elem,
},
}
}
// Equals returns true if the other Type is a set whose element type is
// equal to that of the receiver.
func (t typeSet) Equals(other Type) bool {
ot, isSet := other.typeImpl.(typeSet)
if !isSet {
return false
}
return t.ElementTypeT.Equals(ot.ElementTypeT)
}
func (t typeSet) FriendlyName(mode friendlyTypeNameMode) string {
elemName := t.ElementTypeT.friendlyNameMode(mode)
if mode == friendlyTypeConstraintName {
if t.ElementTypeT == DynamicPseudoType {
elemName = "any single type"
}
}
return "set of " + elemName
}
func (t typeSet) ElementType() Type {
return t.ElementTypeT
}
func (t typeSet) GoString() string {
return fmt.Sprintf("cty.Set(%#v)", t.ElementTypeT)
}
// IsSetType returns true if the given type is a list type, regardless of its
// element type.
func (t Type) IsSetType() bool {
_, ok := t.typeImpl.(typeSet)
return ok
}
// SetElementType is a convenience method that checks if the given type is
// a set type, returning a pointer to its element type if so and nil
// otherwise. This is intended to allow convenient conditional branches,
// like so:
//
// if et := t.SetElementType(); et != nil {
// // Do something with *et
// }
func (t Type) SetElementType() *Type {
if lt, ok := t.typeImpl.(typeSet); ok {
return &lt.ElementTypeT
}
return nil
}

121
vendor/github.com/zclconf/go-cty/cty/tuple_type.go generated vendored Normal file
View File

@ -0,0 +1,121 @@
package cty
import (
"fmt"
)
type typeTuple struct {
typeImplSigil
ElemTypes []Type
}
// Tuple creates a tuple type with the given element types.
//
// After a slice is passed to this function the caller must no longer access
// the underlying array, since ownership is transferred to this library.
func Tuple(elemTypes []Type) Type {
return Type{
typeTuple{
ElemTypes: elemTypes,
},
}
}
func (t typeTuple) Equals(other Type) bool {
if ot, ok := other.typeImpl.(typeTuple); ok {
if len(t.ElemTypes) != len(ot.ElemTypes) {
// Fast path: if we don't have the same number of elements
// then we can't possibly be equal.
return false
}
for i, ty := range t.ElemTypes {
oty := ot.ElemTypes[i]
if !ok {
return false
}
if !oty.Equals(ty) {
return false
}
}
return true
}
return false
}
func (t typeTuple) FriendlyName(mode friendlyTypeNameMode) string {
// There isn't really a friendly way to write a tuple type due to its
// complexity, so we'll just do something English-ish. Callers will
// probably want to make some extra effort to avoid ever printing out
// a tuple type FriendlyName in its entirety. For example, could
// produce an error message by diffing two object types and saying
// something like "Expected attribute foo to be string, but got number".
// TODO: Finish this
return "tuple"
}
func (t typeTuple) GoString() string {
if len(t.ElemTypes) == 0 {
return "cty.EmptyTuple"
}
return fmt.Sprintf("cty.Tuple(%#v)", t.ElemTypes)
}
// EmptyTuple is a shorthand for Tuple([]Type{}), to more easily talk about
// the empty tuple type.
var EmptyTuple Type
// EmptyTupleVal is the only possible non-null, non-unknown value of type
// EmptyTuple.
var EmptyTupleVal Value
func init() {
EmptyTuple = Tuple([]Type{})
EmptyTupleVal = Value{
ty: EmptyTuple,
v: []interface{}{},
}
}
// IsTupleType returns true if the given type is an object type, regardless
// of its element type.
func (t Type) IsTupleType() bool {
_, ok := t.typeImpl.(typeTuple)
return ok
}
// Length returns the number of elements of the receiving tuple type.
// Will panic if the reciever isn't a tuple type; use IsTupleType to determine
// whether this operation will succeed.
func (t Type) Length() int {
if ot, ok := t.typeImpl.(typeTuple); ok {
return len(ot.ElemTypes)
}
panic("Length on non-tuple Type")
}
// TupleElementType returns the type of the element with the given index. Will
// panic if the receiver is not a tuple type (use IsTupleType to confirm)
// or if the index is out of range (use Length to confirm).
func (t Type) TupleElementType(idx int) Type {
if ot, ok := t.typeImpl.(typeTuple); ok {
return ot.ElemTypes[idx]
}
panic("TupleElementType on non-tuple Type")
}
// TupleElementTypes returns a slice of the recieving tuple type's element
// types. Will panic if the receiver is not a tuple type (use IsTupleType
// to confirm).
//
// The returned slice is part of the internal state of the type, and is provided
// for read access only. It is forbidden for any caller to modify the
// underlying array. For many purposes the element-related methods of Value
// are more appropriate and more convenient to use.
func (t Type) TupleElementTypes() []Type {
if ot, ok := t.typeImpl.(typeTuple); ok {
return ot.ElemTypes
}
panic("TupleElementTypes on non-tuple Type")
}

161
vendor/github.com/zclconf/go-cty/cty/type.go generated vendored Normal file
View File

@ -0,0 +1,161 @@
package cty
// Type represents value types within the type system.
//
// This is a closed interface type, meaning that only the concrete
// implementations provided within this package are considered valid.
type Type struct {
typeImpl
}
type typeImpl interface {
// isTypeImpl is a do-nothing method that exists only to express
// that a type is an implementation of typeImpl.
isTypeImpl() typeImplSigil
// Equals returns true if the other given Type exactly equals the
// receiver Type.
Equals(other Type) bool
// FriendlyName returns a human-friendly *English* name for the given
// type.
FriendlyName(mode friendlyTypeNameMode) string
// GoString implements the GoStringer interface from package fmt.
GoString() string
}
// Base implementation of Type to embed into concrete implementations
// to signal that they are implementations of Type.
type typeImplSigil struct{}
func (t typeImplSigil) isTypeImpl() typeImplSigil {
return typeImplSigil{}
}
// Equals returns true if the other given Type exactly equals the receiver
// type.
func (t Type) Equals(other Type) bool {
if t == NilType || other == NilType {
return t == other
}
return t.typeImpl.Equals(other)
}
// FriendlyName returns a human-friendly *English* name for the given type.
func (t Type) FriendlyName() string {
return t.typeImpl.FriendlyName(friendlyTypeName)
}
// FriendlyNameForConstraint is similar to FriendlyName except that the
// result is specialized for describing type _constraints_ rather than types
// themselves. This is more appropriate when reporting that a particular value
// does not conform to an expected type constraint.
//
// In particular, this function uses the term "any type" to refer to
// cty.DynamicPseudoType, rather than "dynamic" as returned by FriendlyName.
func (t Type) FriendlyNameForConstraint() string {
return t.typeImpl.FriendlyName(friendlyTypeConstraintName)
}
// friendlyNameMode is an internal combination of the various FriendlyName*
// variants that just directly takes a mode, for easy passthrough for
// recursive name construction.
func (t Type) friendlyNameMode(mode friendlyTypeNameMode) string {
return t.typeImpl.FriendlyName(mode)
}
// GoString returns a string approximating how the receiver type would be
// expressed in Go source code.
func (t Type) GoString() string {
if t.typeImpl == nil {
return "cty.NilType"
}
return t.typeImpl.GoString()
}
// NilType is an invalid type used when a function is returning an error
// and has no useful type to return. It should not be used and any methods
// called on it will panic.
var NilType = Type{}
// HasDynamicTypes returns true either if the receiver is itself
// DynamicPseudoType or if it is a compound type whose descendent elements
// are DynamicPseudoType.
func (t Type) HasDynamicTypes() bool {
switch {
case t == DynamicPseudoType:
return true
case t.IsPrimitiveType():
return false
case t.IsCollectionType():
return t.ElementType().HasDynamicTypes()
case t.IsObjectType():
attrTypes := t.AttributeTypes()
for _, at := range attrTypes {
if at.HasDynamicTypes() {
return true
}
}
return false
case t.IsTupleType():
elemTypes := t.TupleElementTypes()
for _, et := range elemTypes {
if et.HasDynamicTypes() {
return true
}
}
return false
case t.IsCapsuleType():
return false
default:
// Should never happen, since above should be exhaustive
panic("HasDynamicTypes does not support the given type")
}
}
// WithoutOptionalAttributesDeep returns a type equivalent to the receiver but
// with any objects with optional attributes converted into fully concrete
// object types. This operation is applied recursively.
func (t Type) WithoutOptionalAttributesDeep() Type {
switch {
case t == DynamicPseudoType, t.IsPrimitiveType(), t.IsCapsuleType():
return t
case t.IsMapType():
return Map(t.ElementType().WithoutOptionalAttributesDeep())
case t.IsListType():
return List(t.ElementType().WithoutOptionalAttributesDeep())
case t.IsSetType():
return Set(t.ElementType().WithoutOptionalAttributesDeep())
case t.IsTupleType():
originalElemTypes := t.TupleElementTypes()
elemTypes := make([]Type, len(originalElemTypes))
for i, et := range originalElemTypes {
elemTypes[i] = et.WithoutOptionalAttributesDeep()
}
return Tuple(elemTypes)
case t.IsObjectType():
originalAttrTypes := t.AttributeTypes()
attrTypes := make(map[string]Type, len(originalAttrTypes))
for k, t := range originalAttrTypes {
attrTypes[k] = t.WithoutOptionalAttributesDeep()
}
// This is the subtle line which does all the work of this function: by
// constructing a new Object type with these attribute types, we drop
// the list of optional attributes (if present). This results in a
// concrete Object type which requires all of the original attributes.
return Object(attrTypes)
default:
// Should never happen, since above should be exhaustive
panic("WithoutOptionalAttributesDeep does not support the given type")
}
}
type friendlyTypeNameMode rune
const (
friendlyTypeName friendlyTypeNameMode = 'N'
friendlyTypeConstraintName friendlyTypeNameMode = 'C'
)

139
vendor/github.com/zclconf/go-cty/cty/type_conform.go generated vendored Normal file
View File

@ -0,0 +1,139 @@
package cty
// TestConformance recursively walks the receiver and the given other type and
// returns nil if the receiver *conforms* to the given type.
//
// Type conformance is similar to type equality but has one crucial difference:
// PseudoTypeDynamic can be used within the given type to represent that
// *any* type is allowed.
//
// If any non-conformities are found, the returned slice will be non-nil and
// contain at least one error value. It will be nil if the type is entirely
// conformant.
//
// Note that the special behavior of PseudoTypeDynamic is the *only* exception
// to normal type equality. Calling applications may wish to apply their own
// automatic conversion logic to the given data structure to create a more
// liberal notion of conformance to a type.
//
// Returned errors are usually (but not always) PathError instances that
// indicate where in the structure the error was found. If a returned error
// is of that type then the error message is written for (English-speaking)
// end-users working within the cty type system, not mentioning any Go-oriented
// implementation details.
func (t Type) TestConformance(other Type) []error {
path := make(Path, 0)
var errs []error
testConformance(t, other, path, &errs)
return errs
}
func testConformance(given Type, want Type, path Path, errs *[]error) {
if want.Equals(DynamicPseudoType) {
// anything goes!
return
}
if given.Equals(want) {
// Any equal types are always conformant
return
}
// The remainder of this function is concerned with detecting
// and reporting the specific non-conformance, since we wouldn't
// have got here if the types were not divergent.
// We treat compound structures as special so that we can report
// specifically what is non-conforming, rather than simply returning
// the entire type names and letting the user puzzle it out.
if given.IsObjectType() && want.IsObjectType() {
givenAttrs := given.AttributeTypes()
wantAttrs := want.AttributeTypes()
for k := range givenAttrs {
if _, exists := wantAttrs[k]; !exists {
*errs = append(
*errs,
errorf(path, "unsupported attribute %q", k),
)
}
}
for k := range wantAttrs {
if _, exists := givenAttrs[k]; !exists {
*errs = append(
*errs,
errorf(path, "missing required attribute %q", k),
)
}
}
path = append(path, nil)
pathIdx := len(path) - 1
for k, wantAttrType := range wantAttrs {
if givenAttrType, exists := givenAttrs[k]; exists {
path[pathIdx] = GetAttrStep{Name: k}
testConformance(givenAttrType, wantAttrType, path, errs)
}
}
path = path[0:pathIdx]
return
}
if given.IsTupleType() && want.IsTupleType() {
givenElems := given.TupleElementTypes()
wantElems := want.TupleElementTypes()
if len(givenElems) != len(wantElems) {
*errs = append(
*errs,
errorf(path, "%d elements are required, but got %d", len(wantElems), len(givenElems)),
)
return
}
path = append(path, nil)
pathIdx := len(path) - 1
for i, wantElemType := range wantElems {
givenElemType := givenElems[i]
path[pathIdx] = IndexStep{Key: NumberIntVal(int64(i))}
testConformance(givenElemType, wantElemType, path, errs)
}
path = path[0:pathIdx]
return
}
if given.IsListType() && want.IsListType() {
path = append(path, IndexStep{Key: UnknownVal(Number)})
pathIdx := len(path) - 1
testConformance(given.ElementType(), want.ElementType(), path, errs)
path = path[0:pathIdx]
return
}
if given.IsMapType() && want.IsMapType() {
path = append(path, IndexStep{Key: UnknownVal(String)})
pathIdx := len(path) - 1
testConformance(given.ElementType(), want.ElementType(), path, errs)
path = path[0:pathIdx]
return
}
if given.IsSetType() && want.IsSetType() {
path = append(path, IndexStep{Key: UnknownVal(given.ElementType())})
pathIdx := len(path) - 1
testConformance(given.ElementType(), want.ElementType(), path, errs)
path = path[0:pathIdx]
return
}
*errs = append(
*errs,
errorf(path, "%s required, but received %s", want.FriendlyName(), given.FriendlyName()),
)
}

View File

@ -0,0 +1,57 @@
package cty
import (
"encoding/gob"
"fmt"
"math/big"
"strings"
"github.com/zclconf/go-cty/cty/set"
)
// InternalTypesToRegister is a slice of values that covers all of the
// internal types used in the representation of cty.Type and cty.Value
// across all cty Types.
//
// This is intended to be used to register these types with encoding
// packages that require registration of types used in interfaces, such as
// encoding/gob, thus allowing cty types and values to be included in streams
// created from those packages. However, registering with gob is not necessary
// since that is done automatically as a side-effect of importing this package.
//
// Callers should not do anything with the values here except pass them on
// verbatim to a registration function.
//
// If the calling application uses Capsule types that wrap local structs either
// directly or indirectly, these structs may also need to be registered in
// order to support encoding and decoding of values of these types. That is the
// responsibility of the calling application.
var InternalTypesToRegister []interface{}
func init() {
InternalTypesToRegister = []interface{}{
primitiveType{},
typeList{},
typeMap{},
typeObject{},
typeSet{},
setRules{},
set.Set{},
typeTuple{},
big.Float{},
capsuleType{},
[]interface{}(nil),
map[string]interface{}(nil),
}
// Register these with gob here, rather than in gob.go, to ensure
// that this will always happen after we build the above.
for _, tv := range InternalTypesToRegister {
typeName := fmt.Sprintf("%T", tv)
if strings.HasPrefix(typeName, "cty.") {
gob.RegisterName(fmt.Sprintf("github.com/zclconf/go-cty/%s", typeName), tv)
} else {
gob.Register(tv)
}
}
}

85
vendor/github.com/zclconf/go-cty/cty/unknown.go generated vendored Normal file
View File

@ -0,0 +1,85 @@
package cty
// unknownType is the placeholder type used for the sigil value representing
// "Unknown", to make it unambigiously distinct from any other possible value.
type unknownType struct {
}
// unknown is a special value that can be used as the internal value of a
// Value to create a placeholder for a value that isn't yet known.
var unknown interface{} = &unknownType{}
// UnknownVal returns an Value that represents an unknown value of the given
// type. Unknown values can be used to represent a value that is
// not yet known. Its meaning is undefined in cty, but it could be used by
// an calling application to allow partial evaluation.
//
// Unknown values of any type can be created of any type. All operations on
// Unknown values themselves return Unknown.
func UnknownVal(t Type) Value {
return Value{
ty: t,
v: unknown,
}
}
func (t unknownType) GoString() string {
// This is the stringification of our internal unknown marker. The
// stringification of the public representation of unknowns is in
// Value.GoString.
return "cty.unknown"
}
type pseudoTypeDynamic struct {
typeImplSigil
}
// DynamicPseudoType represents the dynamic pseudo-type.
//
// This type can represent situations where a type is not yet known. Its
// meaning is undefined in cty, but it could be used by a calling
// application to allow expression type checking with some types not yet known.
// For example, the application might optimistically permit any operation on
// values of this type in type checking, allowing a partial type-check result,
// and then repeat the check when more information is known to get the
// final, concrete type.
//
// It is a pseudo-type because it is used only as a sigil to the calling
// application. "Unknown" is the only valid value of this pseudo-type, so
// operations on values of this type will always short-circuit as per
// the rules for that special value.
var DynamicPseudoType Type
func (t pseudoTypeDynamic) Equals(other Type) bool {
_, ok := other.typeImpl.(pseudoTypeDynamic)
return ok
}
func (t pseudoTypeDynamic) FriendlyName(mode friendlyTypeNameMode) string {
switch mode {
case friendlyTypeConstraintName:
return "any type"
default:
return "dynamic"
}
}
func (t pseudoTypeDynamic) GoString() string {
return "cty.DynamicPseudoType"
}
// DynamicVal is the only valid value of the pseudo-type dynamic.
// This value can be used as a placeholder where a value or expression's
// type and value are both unknown, thus allowing partial evaluation. See
// the docs for DynamicPseudoType for more information.
var DynamicVal Value
func init() {
DynamicPseudoType = Type{
pseudoTypeDynamic{},
}
DynamicVal = Value{
ty: DynamicPseudoType,
v: unknown,
}
}

View File

@ -0,0 +1,64 @@
package cty
// UnknownAsNull returns a value of the same type as the given value but
// with any unknown values (including nested values) replaced with null
// values of the same type.
//
// This can be useful if a result is to be serialized in a format that can't
// represent unknowns, such as JSON, as long as the caller does not need to
// retain the unknown value information.
func UnknownAsNull(val Value) Value {
ty := val.Type()
switch {
case val.IsNull():
return val
case !val.IsKnown():
return NullVal(ty)
case ty.IsListType() || ty.IsTupleType() || ty.IsSetType():
length := val.LengthInt()
if length == 0 {
// If there are no elements then we can't have unknowns
return val
}
vals := make([]Value, 0, length)
it := val.ElementIterator()
for it.Next() {
_, v := it.Element()
vals = append(vals, UnknownAsNull(v))
}
switch {
case ty.IsListType():
return ListVal(vals)
case ty.IsTupleType():
return TupleVal(vals)
default:
return SetVal(vals)
}
case ty.IsMapType() || ty.IsObjectType():
var length int
switch {
case ty.IsMapType():
length = val.LengthInt()
default:
length = len(val.Type().AttributeTypes())
}
if length == 0 {
// If there are no elements then we can't have unknowns
return val
}
vals := make(map[string]Value, length)
it := val.ElementIterator()
for it.Next() {
k, v := it.Element()
vals[k.AsString()] = UnknownAsNull(v)
}
switch {
case ty.IsMapType():
return MapVal(vals)
default:
return ObjectVal(vals)
}
}
return val
}

142
vendor/github.com/zclconf/go-cty/cty/value.go generated vendored Normal file
View File

@ -0,0 +1,142 @@
package cty
// Value represents a value of a particular type, and is the interface by
// which operations are executed on typed values.
//
// Value has two different classes of method. Operation methods stay entirely
// within the type system (methods accept and return Value instances) and
// are intended for use in implementing a language in terms of cty, while
// integration methods either enter or leave the type system, working with
// native Go values. Operation methods are guaranteed to support all of the
// expected short-circuit behavior for unknown and dynamic values, while
// integration methods may not.
//
// The philosophy for the operations API is that it's the caller's
// responsibility to ensure that the given types and values satisfy the
// specified invariants during a separate type check, so that the caller is
// able to return errors to its user from the application's own perspective.
//
// Consequently the design of these methods assumes such checks have already
// been done and panics if any invariants turn out not to be satisfied. These
// panic errors are not intended to be handled, but rather indicate a bug in
// the calling application that should be fixed with more checks prior to
// executing operations.
//
// A related consequence of this philosophy is that no automatic type
// conversions are done. If a method specifies that its argument must be
// number then it's the caller's responsibility to do that conversion before
// the call, thus allowing the application to have more constrained conversion
// rules than are offered by the built-in converter where necessary.
type Value struct {
ty Type
v interface{}
}
// Type returns the type of the value.
func (val Value) Type() Type {
return val.ty
}
// IsKnown returns true if the value is known. That is, if it is not
// the result of the unknown value constructor Unknown(...), and is not
// the result of an operation on another unknown value.
//
// Unknown values are only produced either directly or as a result of
// operating on other unknown values, and so an application that never
// introduces Unknown values can be guaranteed to never receive any either.
func (val Value) IsKnown() bool {
if val.IsMarked() {
return val.unmarkForce().IsKnown()
}
return val.v != unknown
}
// IsNull returns true if the value is null. Values of any type can be
// null, but any operations on a null value will panic. No operation ever
// produces null, so an application that never introduces Null values can
// be guaranteed to never receive any either.
func (val Value) IsNull() bool {
if val.IsMarked() {
return val.unmarkForce().IsNull()
}
return val.v == nil
}
// NilVal is an invalid Value that can be used as a placeholder when returning
// with an error from a function that returns (Value, error).
//
// NilVal is *not* a valid error and so no operations may be performed on it.
// Any attempt to use it will result in a panic.
//
// This should not be confused with the idea of a Null value, as returned by
// NullVal. NilVal is a nil within the *Go* type system, and is invalid in
// the cty type system. Null values *do* exist in the cty type system.
var NilVal = Value{
ty: Type{typeImpl: nil},
v: nil,
}
// IsWhollyKnown is an extension of IsKnown that also recursively checks
// inside collections and structures to see if there are any nested unknown
// values.
func (val Value) IsWhollyKnown() bool {
if val.IsMarked() {
return val.unmarkForce().IsWhollyKnown()
}
if !val.IsKnown() {
return false
}
if val.IsNull() {
// Can't recurse into a null, so we're done
return true
}
switch {
case val.CanIterateElements():
for it := val.ElementIterator(); it.Next(); {
_, ev := it.Element()
if !ev.IsWhollyKnown() {
return false
}
}
return true
default:
return true
}
}
// HasWhollyKnownType checks if the value is dynamic, or contains any nested
// DynamicVal. This implies that both the value is not known, and the final
// type may change.
func (val Value) HasWhollyKnownType() bool {
// a null dynamic type is known
if val.IsNull() {
return true
}
// an unknown DynamicPseudoType is a DynamicVal, but we don't want to
// check that value for equality here, since this method is used within the
// equality check.
if !val.IsKnown() && val.ty == DynamicPseudoType {
return false
}
if val.CanIterateElements() {
// if the value is not known, then we can look directly at the internal
// types
if !val.IsKnown() {
return !val.ty.HasDynamicTypes()
}
for it := val.ElementIterator(); it.Next(); {
_, ev := it.Element()
if !ev.HasWhollyKnownType() {
return false
}
}
}
return true
}

367
vendor/github.com/zclconf/go-cty/cty/value_init.go generated vendored Normal file
View File

@ -0,0 +1,367 @@
package cty
import (
"fmt"
"math/big"
"reflect"
"golang.org/x/text/unicode/norm"
"github.com/zclconf/go-cty/cty/set"
)
// BoolVal returns a Value of type Number whose internal value is the given
// bool.
func BoolVal(v bool) Value {
return Value{
ty: Bool,
v: v,
}
}
// NumberVal returns a Value of type Number whose internal value is the given
// big.Float. The returned value becomes the owner of the big.Float object,
// and so it's forbidden for the caller to mutate the object after it's
// wrapped in this way.
func NumberVal(v *big.Float) Value {
return Value{
ty: Number,
v: v,
}
}
// ParseNumberVal returns a Value of type number produced by parsing the given
// string as a decimal real number. To ensure that two identical strings will
// always produce an equal number, always use this function to derive a number
// from a string; it will ensure that the precision and rounding mode for the
// internal big decimal is configured in a consistent way.
//
// If the given string cannot be parsed as a number, the returned error has
// the message "a number is required", making it suitable to return to an
// end-user to signal a type conversion error.
//
// If the given string contains a number that becomes a recurring fraction
// when expressed in binary then it will be truncated to have a 512-bit
// mantissa. Note that this is a higher precision than that of a float64,
// so coverting the same decimal number first to float64 and then calling
// NumberFloatVal will not produce an equal result; the conversion first
// to float64 will round the mantissa to fewer than 512 bits.
func ParseNumberVal(s string) (Value, error) {
// Base 10, precision 512, and rounding to nearest even is the standard
// way to handle numbers arriving as strings.
f, _, err := big.ParseFloat(s, 10, 512, big.ToNearestEven)
if err != nil {
return NilVal, fmt.Errorf("a number is required")
}
return NumberVal(f), nil
}
// MustParseNumberVal is like ParseNumberVal but it will panic in case of any
// error. It can be used during initialization or any other situation where
// the given string is a constant or otherwise known to be correct by the
// caller.
func MustParseNumberVal(s string) Value {
ret, err := ParseNumberVal(s)
if err != nil {
panic(err)
}
return ret
}
// NumberIntVal returns a Value of type Number whose internal value is equal
// to the given integer.
func NumberIntVal(v int64) Value {
return NumberVal(new(big.Float).SetInt64(v))
}
// NumberUIntVal returns a Value of type Number whose internal value is equal
// to the given unsigned integer.
func NumberUIntVal(v uint64) Value {
return NumberVal(new(big.Float).SetUint64(v))
}
// NumberFloatVal returns a Value of type Number whose internal value is
// equal to the given float.
func NumberFloatVal(v float64) Value {
return NumberVal(new(big.Float).SetFloat64(v))
}
// StringVal returns a Value of type String whose internal value is the
// given string.
//
// Strings must be UTF-8 encoded sequences of valid unicode codepoints, and
// they are NFC-normalized on entry into the world of cty values.
//
// If the given string is not valid UTF-8 then behavior of string operations
// is undefined.
func StringVal(v string) Value {
return Value{
ty: String,
v: NormalizeString(v),
}
}
// NormalizeString applies the same normalization that cty applies when
// constructing string values.
//
// A return value from this function can be meaningfully compared byte-for-byte
// with a Value.AsString result.
func NormalizeString(s string) string {
return norm.NFC.String(s)
}
// ObjectVal returns a Value of an object type whose structure is defined
// by the key names and value types in the given map.
func ObjectVal(attrs map[string]Value) Value {
attrTypes := make(map[string]Type, len(attrs))
attrVals := make(map[string]interface{}, len(attrs))
for attr, val := range attrs {
attr = NormalizeString(attr)
attrTypes[attr] = val.ty
attrVals[attr] = val.v
}
return Value{
ty: Object(attrTypes),
v: attrVals,
}
}
// TupleVal returns a Value of a tuple type whose element types are
// defined by the value types in the given slice.
func TupleVal(elems []Value) Value {
elemTypes := make([]Type, len(elems))
elemVals := make([]interface{}, len(elems))
for i, val := range elems {
elemTypes[i] = val.ty
elemVals[i] = val.v
}
return Value{
ty: Tuple(elemTypes),
v: elemVals,
}
}
// ListVal returns a Value of list type whose element type is defined by
// the types of the given values, which must be homogenous.
//
// If the types are not all consistent (aside from elements that are of the
// dynamic pseudo-type) then this function will panic. It will panic also
// if the given list is empty, since then the element type cannot be inferred.
// (See also ListValEmpty.)
func ListVal(vals []Value) Value {
if len(vals) == 0 {
panic("must not call ListVal with empty slice")
}
elementType := DynamicPseudoType
rawList := make([]interface{}, len(vals))
for i, val := range vals {
if elementType == DynamicPseudoType {
elementType = val.ty
} else if val.ty != DynamicPseudoType && !elementType.Equals(val.ty) {
panic(fmt.Errorf(
"inconsistent list element types (%#v then %#v)",
elementType, val.ty,
))
}
rawList[i] = val.v
}
return Value{
ty: List(elementType),
v: rawList,
}
}
// ListValEmpty returns an empty list of the given element type.
func ListValEmpty(element Type) Value {
return Value{
ty: List(element),
v: []interface{}{},
}
}
// CanListVal returns false if the given Values can not be coalesced
// into a single List due to inconsistent element types.
func CanListVal(vals []Value) bool {
elementType := DynamicPseudoType
for _, val := range vals {
if elementType == DynamicPseudoType {
elementType = val.ty
} else if val.ty != DynamicPseudoType && !elementType.Equals(val.ty) {
return false
}
}
return true
}
// MapVal returns a Value of a map type whose element type is defined by
// the types of the given values, which must be homogenous.
//
// If the types are not all consistent (aside from elements that are of the
// dynamic pseudo-type) then this function will panic. It will panic also
// if the given map is empty, since then the element type cannot be inferred.
// (See also MapValEmpty.)
func MapVal(vals map[string]Value) Value {
if len(vals) == 0 {
panic("must not call MapVal with empty map")
}
elementType := DynamicPseudoType
rawMap := make(map[string]interface{}, len(vals))
for key, val := range vals {
if elementType == DynamicPseudoType {
elementType = val.ty
} else if val.ty != DynamicPseudoType && !elementType.Equals(val.ty) {
panic(fmt.Errorf(
"inconsistent map element types (%#v then %#v)",
elementType, val.ty,
))
}
rawMap[NormalizeString(key)] = val.v
}
return Value{
ty: Map(elementType),
v: rawMap,
}
}
// MapValEmpty returns an empty map of the given element type.
func MapValEmpty(element Type) Value {
return Value{
ty: Map(element),
v: map[string]interface{}{},
}
}
// CanMapVal returns false if the given Values can not be coalesced into a
// single Map due to inconsistent element types.
func CanMapVal(vals map[string]Value) bool {
elementType := DynamicPseudoType
for _, val := range vals {
if elementType == DynamicPseudoType {
elementType = val.ty
} else if val.ty != DynamicPseudoType && !elementType.Equals(val.ty) {
return false
}
}
return true
}
// SetVal returns a Value of set type whose element type is defined by
// the types of the given values, which must be homogenous.
//
// If the types are not all consistent (aside from elements that are of the
// dynamic pseudo-type) then this function will panic. It will panic also
// if the given list is empty, since then the element type cannot be inferred.
// (See also SetValEmpty.)
func SetVal(vals []Value) Value {
if len(vals) == 0 {
panic("must not call SetVal with empty slice")
}
elementType := DynamicPseudoType
rawList := make([]interface{}, len(vals))
var markSets []ValueMarks
for i, val := range vals {
if unmarkedVal, marks := val.UnmarkDeep(); len(marks) > 0 {
val = unmarkedVal
markSets = append(markSets, marks)
}
if elementType == DynamicPseudoType {
elementType = val.ty
} else if val.ty != DynamicPseudoType && !elementType.Equals(val.ty) {
panic(fmt.Errorf(
"inconsistent set element types (%#v then %#v)",
elementType, val.ty,
))
}
rawList[i] = val.v
}
rawVal := set.NewSetFromSlice(setRules{elementType}, rawList)
return Value{
ty: Set(elementType),
v: rawVal,
}.WithMarks(markSets...)
}
// CanSetVal returns false if the given Values can not be coalesced
// into a single Set due to inconsistent element types.
func CanSetVal(vals []Value) bool {
elementType := DynamicPseudoType
var markSets []ValueMarks
for _, val := range vals {
if unmarkedVal, marks := val.UnmarkDeep(); len(marks) > 0 {
val = unmarkedVal
markSets = append(markSets, marks)
}
if elementType == DynamicPseudoType {
elementType = val.ty
} else if val.ty != DynamicPseudoType && !elementType.Equals(val.ty) {
return false
}
}
return true
}
// SetValFromValueSet returns a Value of set type based on an already-constructed
// ValueSet.
//
// The element type of the returned value is the element type of the given
// set.
func SetValFromValueSet(s ValueSet) Value {
ety := s.ElementType()
rawVal := s.s.Copy() // copy so caller can't mutate what we wrap
return Value{
ty: Set(ety),
v: rawVal,
}
}
// SetValEmpty returns an empty set of the given element type.
func SetValEmpty(element Type) Value {
return Value{
ty: Set(element),
v: set.NewSet(setRules{element}),
}
}
// CapsuleVal creates a value of the given capsule type using the given
// wrapVal, which must be a pointer to a value of the capsule type's native
// type.
//
// This function will panic if the given type is not a capsule type, if
// the given wrapVal is not compatible with the given capsule type, or if
// wrapVal is not a pointer.
func CapsuleVal(ty Type, wrapVal interface{}) Value {
if !ty.IsCapsuleType() {
panic("not a capsule type")
}
wv := reflect.ValueOf(wrapVal)
if wv.Kind() != reflect.Ptr {
panic("wrapVal is not a pointer")
}
it := ty.typeImpl.(*capsuleType).GoType
if !wv.Type().Elem().AssignableTo(it) {
panic("wrapVal target is not compatible with the given capsule type")
}
return Value{
ty: ty,
v: wrapVal,
}
}

1371
vendor/github.com/zclconf/go-cty/cty/value_ops.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

238
vendor/github.com/zclconf/go-cty/cty/walk.go generated vendored Normal file
View File

@ -0,0 +1,238 @@
package cty
// Walk visits all of the values in a possibly-complex structure, calling
// a given function for each value.
//
// For example, given a list of strings the callback would first be called
// with the whole list and then called once for each element of the list.
//
// The callback function may prevent recursive visits to child values by
// returning false. The callback function my halt the walk altogether by
// returning a non-nil error. If the returned error is about the element
// currently being visited, it is recommended to use the provided path
// value to produce a PathError describing that context.
//
// The path passed to the given function may not be used after that function
// returns, since its backing array is re-used for other calls.
func Walk(val Value, cb func(Path, Value) (bool, error)) error {
var path Path
return walk(path, val, cb)
}
func walk(path Path, val Value, cb func(Path, Value) (bool, error)) error {
deeper, err := cb(path, val)
if err != nil {
return err
}
if !deeper {
return nil
}
if val.IsNull() || !val.IsKnown() {
// Can't recurse into null or unknown values, regardless of type
return nil
}
// The callback already got a chance to see the mark in our
// call above, so can safely strip it off here in order to
// visit the child elements, which might still have their own marks.
rawVal, _ := val.Unmark()
ty := val.Type()
switch {
case ty.IsObjectType():
for it := rawVal.ElementIterator(); it.Next(); {
nameVal, av := it.Element()
path := append(path, GetAttrStep{
Name: nameVal.AsString(),
})
err := walk(path, av, cb)
if err != nil {
return err
}
}
case rawVal.CanIterateElements():
for it := rawVal.ElementIterator(); it.Next(); {
kv, ev := it.Element()
path := append(path, IndexStep{
Key: kv,
})
err := walk(path, ev, cb)
if err != nil {
return err
}
}
}
return nil
}
// Transformer is the interface used to optionally transform values in a
// possibly-complex structure. The Enter method is called before traversing
// through a given path, and the Exit method is called when traversal of a
// path is complete.
//
// Use Enter when you want to transform a complex value before traversal
// (preorder), and Exit when you want to transform a value after traversal
// (postorder).
//
// The path passed to the given function may not be used after that function
// returns, since its backing array is re-used for other calls.
type Transformer interface {
Enter(Path, Value) (Value, error)
Exit(Path, Value) (Value, error)
}
type postorderTransformer struct {
callback func(Path, Value) (Value, error)
}
func (t *postorderTransformer) Enter(p Path, v Value) (Value, error) {
return v, nil
}
func (t *postorderTransformer) Exit(p Path, v Value) (Value, error) {
return t.callback(p, v)
}
// Transform visits all of the values in a possibly-complex structure,
// calling a given function for each value which has an opportunity to
// replace that value.
//
// Unlike Walk, Transform visits child nodes first, so for a list of strings
// it would first visit the strings and then the _new_ list constructed
// from the transformed values of the list items.
//
// This is useful for creating the effect of being able to make deep mutations
// to a value even though values are immutable. However, it's the responsibility
// of the given function to preserve expected invariants, such as homogenity of
// element types in collections; this function can panic if such invariants
// are violated, just as if new values were constructed directly using the
// value constructor functions. An easy way to preserve invariants is to
// ensure that the transform function never changes the value type.
//
// The callback function may halt the walk altogether by
// returning a non-nil error. If the returned error is about the element
// currently being visited, it is recommended to use the provided path
// value to produce a PathError describing that context.
//
// The path passed to the given function may not be used after that function
// returns, since its backing array is re-used for other calls.
func Transform(val Value, cb func(Path, Value) (Value, error)) (Value, error) {
var path Path
return transform(path, val, &postorderTransformer{cb})
}
// TransformWithTransformer allows the caller to more closely control the
// traversal used for transformation. See the documentation for Transformer for
// more details.
func TransformWithTransformer(val Value, t Transformer) (Value, error) {
var path Path
return transform(path, val, t)
}
func transform(path Path, val Value, t Transformer) (Value, error) {
val, err := t.Enter(path, val)
if err != nil {
return DynamicVal, err
}
ty := val.Type()
var newVal Value
// We need to peel off any marks here so that we can dig around
// inside any collection values. We'll reapply these to any
// new collections we construct, but the transformer's Exit
// method gets the final say on what to do with those.
rawVal, marks := val.Unmark()
switch {
case val.IsNull() || !val.IsKnown():
// Can't recurse into null or unknown values, regardless of type
newVal = val
case ty.IsListType() || ty.IsSetType() || ty.IsTupleType():
l := rawVal.LengthInt()
switch l {
case 0:
// No deep transform for an empty sequence
newVal = val
default:
elems := make([]Value, 0, l)
for it := rawVal.ElementIterator(); it.Next(); {
kv, ev := it.Element()
path := append(path, IndexStep{
Key: kv,
})
newEv, err := transform(path, ev, t)
if err != nil {
return DynamicVal, err
}
elems = append(elems, newEv)
}
switch {
case ty.IsListType():
newVal = ListVal(elems).WithMarks(marks)
case ty.IsSetType():
newVal = SetVal(elems).WithMarks(marks)
case ty.IsTupleType():
newVal = TupleVal(elems).WithMarks(marks)
default:
panic("unknown sequence type") // should never happen because of the case we are in
}
}
case ty.IsMapType():
l := rawVal.LengthInt()
switch l {
case 0:
// No deep transform for an empty map
newVal = val
default:
elems := make(map[string]Value)
for it := rawVal.ElementIterator(); it.Next(); {
kv, ev := it.Element()
path := append(path, IndexStep{
Key: kv,
})
newEv, err := transform(path, ev, t)
if err != nil {
return DynamicVal, err
}
elems[kv.AsString()] = newEv
}
newVal = MapVal(elems).WithMarks(marks)
}
case ty.IsObjectType():
switch {
case ty.Equals(EmptyObject):
// No deep transform for an empty object
newVal = val
default:
atys := ty.AttributeTypes()
newAVs := make(map[string]Value)
for name := range atys {
av := val.GetAttr(name)
path := append(path, GetAttrStep{
Name: name,
})
newAV, err := transform(path, av, t)
if err != nil {
return DynamicVal, err
}
newAVs[name] = newAV
}
newVal = ObjectVal(newAVs).WithMarks(marks)
}
default:
newVal = val
}
newVal, err = t.Exit(path, newVal)
if err != nil {
return DynamicVal, err
}
return newVal, err
}