typeexpr

package
v2.0.0-...-ff90a8e Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 27, 2024 License: MPL-2.0 Imports: 11 Imported by: 0

README

HCL Type Expressions Extension

This HCL extension defines a convention for describing HCL types using function call and variable reference syntax, allowing configuration formats to include type information provided by users.

The type syntax is processed statically from a hcl.Expression, so it cannot use any of the usual language operators. This is similar to type expressions in statically-typed programming languages.

variable "example" {
  type = list(string)
}

The extension is built using the hcl.ExprAsKeyword and hcl.ExprCall functions, and so it relies on the underlying syntax to define how "keyword" and "call" are interpreted. The above shows how they are interpreted in the HCL native syntax, while the following shows the same information expressed in JSON:

{
  "variable": {
    "example": {
      "type": "list(string)"
    }
  }
}

Notice that since we have additional contextual information that we intend to allow only calls and keywords the JSON syntax is able to parse the given string directly as an expression, rather than as a template as would be the case for normal expression evaluation.

For more information, see the godoc reference.

Type Expression Syntax

When expressed in the native syntax, the following expressions are permitted in a type expression:

  • string - string
  • bool - boolean
  • number - number
  • any - cty.DynamicPseudoType (in function TypeConstraint only)
  • list(<type_expr>) - list of the type given as an argument
  • set(<type_expr>) - set of the type given as an argument
  • map(<type_expr>) - map of the type given as an argument
  • tuple([<type_exprs...>]) - tuple with the element types given in the single list argument
  • object({<attr_name>=<type_expr>, ...} - object with the attributes and corresponding types given in the single map argument

For example:

  • list(string)
  • object({name=string,age=number})
  • map(object({name=string,age=number}))

Note that the object constructor syntax is not fully-general for all possible object types because it requires the attribute names to be valid identifiers. In practice it is expected that any time an object type is being fixed for type checking it will be one that has identifiers as its attributes; object types with weird attributes generally show up only from arbitrary object constructors in configuration files, which are usually treated either as maps or as the dynamic pseudo-type.

Optional Object Attributes

As part of object expressions attributes can be marked as optional. Missing object attributes would typically result in an error when type constraints are validated or used. Optional missing attributes, however, would not result in an error. The cty "convert" function will populate missing optional attributes with null values.

For example:

  • object({name=string,age=optional(number)})

Optional attributes can also be specified with default values. The TypeConstraintWithDefaults function will return a Defaults object that can be used to populate missing optional attributes with defaults in a given cty.Value.

For example:

  • object({name=string,age=optional(number, 0)})

Type Constraints as Values

Along with defining a convention for writing down types using HCL expression constructs, this package also includes a mechanism for representing types as values that can be used as data within an HCL-based language.

typeexpr.TypeConstraintType is a cty capsule type that encapsulates cty.Type values. You can construct such a value directly using the TypeConstraintVal function:

tyVal := typeexpr.TypeConstraintVal(cty.String)

// We can unpack the type from a value using TypeConstraintFromVal
ty := typeExpr.TypeConstraintFromVal(tyVal)

However, the primary purpose of typeexpr.TypeConstraintType is to be specified as the type constraint for an argument, in which case it serves as a signal for HCL to treat the argument expression as a type constraint expression as defined above, rather than as a normal value expression.

"An argument" in the above in practice means the following two locations:

  • As the type constraint for a parameter of a cty function that will be used in an hcl.EvalContext. In that case, function calls in the HCL native expression syntax will require the argument to be valid type constraint expression syntax and the function implementation will receive a TypeConstraintType value as the argument value for that parameter.

  • As the type constraint for a hcldec.AttrSpec or hcldec.BlockAttrsSpec when decoding an HCL body using hcldec. In that case, the attributes with that type constraint will be required to be valid type constraint expression syntax and the result will be a TypeConstraintType value.

Note that the special handling of these arguments means that an argument marked in this way must use the type constraint syntax directly. It is not valid to pass in a value of TypeConstraintType that has been obtained dynamically via some other expression result.

TypeConstraintType is provided with the intent of using it internally within application code when incorporating type constraint expression syntax into an HCL-based language, not to be used for dynamic "programming with types". A calling application could support programming with types by defining its own capsule type, but that is not the purpose of TypeConstraintType.

The "convert" cty Function

Building on the TypeConstraintType described in the previous section, this package also provides typeexpr.ConvertFunc which is a cty function that can be placed into a cty.EvalContext (conventionally named "convert") in order to provide a general type conversion function in an HCL-based language:

  foo = convert("true", bool)

The second parameter uses the mechanism described in the previous section to require its argument to be a type constraint expression rather than a value expression. In doing so, it allows converting with any type constraint that can be expressed in this package's type constraint syntax. In the above example, the foo argument would receive a boolean true, or cty.True in cty terms.

The target type constraint must always be provided statically using inline type constraint syntax. There is no way to dynamically select a type constraint using this function.

Documentation

Overview

Package typeexpr extends HCL with a convention for describing HCL types within configuration files.

The type syntax is processed statically from a hcl.Expression, so it cannot use any of the usual language operators. This is similar to type expressions in statically-typed programming languages.

variable "example" {
  type = list(string)
}

Index

Constants

This section is empty.

Variables

View Source
var ConvertFunc function.Function

ConvertFunc is a cty function that implements type conversions.

Its signature is as follows:

convert(value, type_constraint)

...where type_constraint is a type constraint expression as defined by typeexpr.TypeConstraint.

It relies on HCL's customdecode extension and so it's not suitable for use in non-HCL contexts or if you are using a HCL syntax implementation that does not support customdecode for function arguments. However, it _is_ supported for function calls in the HCL native expression syntax.

View Source
var TypeConstraintType cty.Type

TypeConstraintType is a cty capsule type that allows cty type constraints to be used as values.

If TypeConstraintType is used in a context supporting the customdecode.CustomExpressionDecoder extension then it will implement expression decoding using the TypeConstraint function, thus allowing type expressions to be used in contexts where value expressions might normally be expected, such as in arguments to function calls.

Functions

func Type

func Type(expr hcl.Expression) (cty.Type, hcl.Diagnostics)

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 TypeConstraint

func TypeConstraint(expr hcl.Expression) (cty.Type, hcl.Diagnostics)

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 TypeConstraintFromVal

func TypeConstraintFromVal(v cty.Value) cty.Type

TypeConstraintFromVal extracts the type from a cty.Value of TypeConstraintType that was previously constructed using TypeConstraintVal.

If the given value isn't a known, non-null value of TypeConstraintType then this function will panic.

func TypeConstraintVal

func TypeConstraintVal(ty cty.Type) cty.Value

TypeConstraintVal constructs a cty.Value whose type is TypeConstraintType.

func TypeString

func TypeString(ty cty.Type) string

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.

Types

type Defaults

type Defaults struct {
	// Type of the node for which these defaults apply. This is necessary in
	// order to determine how to inspect the Defaults and Children collections.
	Type cty.Type

	// DefaultValues contains the default values for each object attribute,
	// indexed by attribute name.
	DefaultValues map[string]cty.Value

	// Children is a map of Defaults for elements contained in this type. This
	// only applies to structural and collection types.
	//
	// The map is indexed by string instead of cty.Value because cty.Number
	// instances are non-comparable, due to embedding a *big.Float.
	//
	// Collections have a single element type, which is stored at key "".
	Children map[string]*Defaults
}

Defaults represents a type tree which may contain default values for optional object attributes at any level. This is used to apply nested defaults to a given cty.Value before converting it to a concrete type.

func TypeConstraintWithDefaults

func TypeConstraintWithDefaults(expr hcl.Expression) (cty.Type, *Defaults, hcl.Diagnostics)

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.

func (*Defaults) Apply

func (d *Defaults) Apply(val cty.Value) cty.Value

Apply walks the given value, applying specified defaults wherever optional attributes are missing. The input and output values may have different types, and the result may still require type conversion to the final desired type.

This function is permissive and does not report errors, assuming that the caller will have better context to report useful type conversion failure diagnostics.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL