mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
The optional modifier previously accepted a single argument: the attribute type. This commit adds an optional second argument, which specifies a default value for the attribute. To record the default values for a variable's type, we use a separate parallel structure of `typeexpr.Defaults`, rather than extending `cty.Type` to include a `cty.Value` of defaults (which may in turn include a `cty.Type` with defaults, and so on, and so forth). The new `typeexpr.TypeConstraintWithDefaults` returns a type constraint and defaults value. Defaults will be `nil` unless there are default values specified somewhere in the variable's type.
144 lines
4.3 KiB
Go
144 lines
4.3 KiB
Go
package typeexpr
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"sort"
|
|
|
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
// Type attempts to process the given expression as a type expression and, if
|
|
// successful, returns the resulting type. If unsuccessful, error diagnostics
|
|
// are returned.
|
|
func Type(expr hcl.Expression) (cty.Type, hcl.Diagnostics) {
|
|
ty, _, diags := getType(expr, false, false)
|
|
return ty, diags
|
|
}
|
|
|
|
// TypeConstraint attempts to parse the given expression as a type constraint
|
|
// and, if successful, returns the resulting type. If unsuccessful, error
|
|
// diagnostics are returned.
|
|
//
|
|
// A type constraint has the same structure as a type, but it additionally
|
|
// allows the keyword "any" to represent cty.DynamicPseudoType, which is often
|
|
// used as a wildcard in type checking and type conversion operations.
|
|
func TypeConstraint(expr hcl.Expression) (cty.Type, hcl.Diagnostics) {
|
|
ty, _, diags := getType(expr, true, false)
|
|
return ty, diags
|
|
}
|
|
|
|
// TypeConstraintWithDefaults attempts to parse the given expression as a type
|
|
// constraint which may include default values for object attributes. If
|
|
// successful both the resulting type and corresponding defaults are returned.
|
|
// If unsuccessful, error diagnostics are returned.
|
|
//
|
|
// When using this function, defaults should be applied to the input value
|
|
// before type conversion, to ensure that objects with missing attributes have
|
|
// default values populated.
|
|
func TypeConstraintWithDefaults(expr hcl.Expression) (cty.Type, *Defaults, hcl.Diagnostics) {
|
|
return getType(expr, true, true)
|
|
}
|
|
|
|
// TypeString returns a string rendering of the given type as it would be
|
|
// expected to appear in the HCL native syntax.
|
|
//
|
|
// This is primarily intended for showing types to the user in an application
|
|
// that uses typexpr, where the user can be assumed to be familiar with the
|
|
// type expression syntax. In applications that do not use typeexpr these
|
|
// results may be confusing to the user and so type.FriendlyName may be
|
|
// preferable, even though it's less precise.
|
|
//
|
|
// TypeString produces reasonable results only for types like what would be
|
|
// produced by the Type and TypeConstraint functions. In particular, it cannot
|
|
// support capsule types.
|
|
func TypeString(ty cty.Type) string {
|
|
// Easy cases first
|
|
switch ty {
|
|
case cty.String:
|
|
return "string"
|
|
case cty.Bool:
|
|
return "bool"
|
|
case cty.Number:
|
|
return "number"
|
|
case cty.DynamicPseudoType:
|
|
return "any"
|
|
}
|
|
|
|
if ty.IsCapsuleType() {
|
|
panic("TypeString does not support capsule types")
|
|
}
|
|
|
|
if ty.IsCollectionType() {
|
|
ety := ty.ElementType()
|
|
etyString := TypeString(ety)
|
|
switch {
|
|
case ty.IsListType():
|
|
return fmt.Sprintf("list(%s)", etyString)
|
|
case ty.IsSetType():
|
|
return fmt.Sprintf("set(%s)", etyString)
|
|
case ty.IsMapType():
|
|
return fmt.Sprintf("map(%s)", etyString)
|
|
default:
|
|
// Should never happen because the above is exhaustive
|
|
panic("unsupported collection type")
|
|
}
|
|
}
|
|
|
|
if ty.IsObjectType() {
|
|
var buf bytes.Buffer
|
|
buf.WriteString("object({")
|
|
atys := ty.AttributeTypes()
|
|
names := make([]string, 0, len(atys))
|
|
for name := range atys {
|
|
names = append(names, name)
|
|
}
|
|
sort.Strings(names)
|
|
first := true
|
|
for _, name := range names {
|
|
aty := atys[name]
|
|
if !first {
|
|
buf.WriteByte(',')
|
|
}
|
|
if !hclsyntax.ValidIdentifier(name) {
|
|
// Should never happen for any type produced by this package,
|
|
// but we'll do something reasonable here just so we don't
|
|
// produce garbage if someone gives us a hand-assembled object
|
|
// type that has weird attribute names.
|
|
// Using Go-style quoting here isn't perfect, since it doesn't
|
|
// exactly match HCL syntax, but it's fine for an edge-case.
|
|
buf.WriteString(fmt.Sprintf("%q", name))
|
|
} else {
|
|
buf.WriteString(name)
|
|
}
|
|
buf.WriteByte('=')
|
|
buf.WriteString(TypeString(aty))
|
|
first = false
|
|
}
|
|
buf.WriteString("})")
|
|
return buf.String()
|
|
}
|
|
|
|
if ty.IsTupleType() {
|
|
var buf bytes.Buffer
|
|
buf.WriteString("tuple([")
|
|
etys := ty.TupleElementTypes()
|
|
first := true
|
|
for _, ety := range etys {
|
|
if !first {
|
|
buf.WriteByte(',')
|
|
}
|
|
buf.WriteString(TypeString(ety))
|
|
first = false
|
|
}
|
|
buf.WriteString("])")
|
|
return buf.String()
|
|
}
|
|
|
|
// Should never happen because we covered all cases above.
|
|
panic(fmt.Errorf("unsupported type %#v", ty))
|
|
}
|