route/vendor/github.com/zclconf/go-cty/docs/gocty.md

175 lines
7.4 KiB
Markdown
Raw Normal View History

2017-12-02 23:34:35 +00:00
# Converting between Go and `cty` values
While `cty` provides a representation of values within its own type system,
a calling application will inevitably need to eventually pass values
to a native Go API, using native Go types.
[The `gocty` package](https://godoc.org/github.com/apparentlymart/go-cty/cty/gocty)
aims to make conversions between `cty` values and Go values as convenient as
possible, using an approach similar to that used by `encoding/json` where
the `reflect` package is used to define the desired structure using Go
native types.
## From Go to `cty`
Converting Go values to `cty` values is the task of the `ToCtyValue` function.
It takes an arbitrary Go value (as an `interface{}`) and `cty.Type` describing
the shape of the desired value.
The given type is used both as a conformance check and as a source of hints
to resolve ambiguities in the mapping from Go types. For example, it is valid
to convert a Go slice to both a `cty` set and list types, and so the given
`cty` type is used to indicate which is desired.
The errors generated by this function use terminology aimed at the developers
of the calling application, since it's assumed that any problems encountered
are bugs in the calling program and are thus "should never happen" cases.
Since unknown values cannot be represented natively in Go's type system, `gocty`
works only with known values. An error will be generated if a caller attempts
to convert an unknown value into a Go value.
## From `cty` to Go
Converting `cty` values to Go values is done via the `FromCtyValue` function.
In this case, the function mutates a particular Go value in place rather
than returning a new value, as is traditional from similar functions in
packages like `encoding/json`.
The function must be given a non-nil pointer to the value that should be
populated. If the function succeeds without error then this target value has
been populated with data from the given `cty` value.
Any errors returned are written with the target audience being the hypothetical
user that wrote whatever input was transformed into the given cty value, and
thus the terminology used is `cty` type system terminology.
As a concrete example, consider converting a value into a Go `int8`:
```go
var val int8
err := gocty.FromCtyValue(value, &val)
```
There are a few different ways that this can fail:
* If `value` is not a `cty.Number` value, the error message returned says
"a number is required", assuming that this value came from user input
and the user provided a value of the wrong type.
* If `value` is not an integer, or it's an integer outside of the range of
an `int8`, the error message says "must be a whole number between -128 and
127", again assuming that this was user input and that the target type here
is an implied constraint on the value provided by the user.
As a consequence, it is valid and encouraged to convert arbitrary
user-supplied values into concrete Go data structures as a concise way to
express certain data validation constraints in a declarative way, and then
return any error message verbatim to the end-user.
## Converting to and from `struct`s
As well as straightforward mappings of primitive and collection types, `gocty`
can convert object and tuple values to and from values of Go `struct` types.
For tuples, the target `struct` must have exactly the same number of fields
as exist in the tuple, and the fields are used in the order they are defined
with no regard to their names or tags. A `struct` used to decode a tuple must
have all public attributes. These constraints mean that generally-speaking
it will be hard to re-use existing application structs for this purpose, and
instead a specialized struct must be used to represent each tuple type. For
simple uses, a struct defined inline within a function can be used.
For objects, the mapping is more flexible. Field tags are used to express
which struct fields correspond to each object attribute, as in the following
example:
```go
type Example struct {
Name string `cty:"name"`
Age int `cty:"age"`
}
```
For the mapping to be valid, there must be a one-to-one correspondence between
object attributes and tagged struct fields. The presence or absense of attribute
tags in the struct is used to define which attributes are valid, and so error
messages will be generated for any extraneous or missing attributes. Additional
fields may be present without tags, but all fields with tags must be public.
## Dynamically-typed Values
If parts of the `cty` data structure have types that can't be known until
runtime, it is possible to leave these portions un-decoded for later
processing.
To achieve this, `cty.DynamicPseudoType` is used in the type passed to the
two conversion functions, and at the corresponding place in the Go data
structure a `cty.Value` object is placed. When converting from `cty` to Go,
the portion of the value corresponding to the dynamic pseudo-type is
assigned directly to the `cty.Value` object with no conversion,
so the calling program can then use the core `cty` API to interact with it.
The converse is true for converting from Go to `cty`: any valid `cty.Value`
object can be provided, and it will be included verbatim in the returned
`cty.Value`.
```go
type Thing struct {
Name string `cty:"name"`
ExtraData cty.Value `cty:"extra_data"`
}
thingType := cty.Object(map[string]cty.Type{
"name": cty.String,
"extra_data": cty.DynamicPseudoType,
})
thingVal := cty.ObjectVal(map[string]cty.Value{
"name": cty.StringVal("Ermintrude"),
"extra_data": cty.NumberIntVal(12),
})
var thing Thing
err := gocty.FromCtyValue(thingVal, &thing)
// (error check)
fmt.Printf("extra_data is %s", thing.ExtraData.Type().FriendlyName())
// Prints: "extra_data is number"
```
## Conversion of Capsule Types
Since capsule types encapsulate native Go values, their handling in `gocty`
is a simple wrapping and un-wrapping of the encapsulated value. The
encapsulated type and the type of the target value must match.
Since capsule values capture a pointer to the target value, it is possible
to round-trip a pointer from a Go value into a capsule value and back to
a Go value and recover the original pointer value, referring to the same
in-memory object.
## Implied `cty` Type of a Go value
In simple cases it can be desirable to just write a simple type in Go and
use it immediately in conversions, without needing to separately write out a
corresponding `cty.Type` expression.
The `ImpliedType` function helps with this by trying to generate a reasonable
`cty.Type` from a native Go value. Not all `cty` types can be represented in
this way, but if the goal is a straightforward mapping to a convenient Go
data structure then this function is suitable.
The mapping is as follows:
* Go's int, uint and float types all map to `cty.Number`.
* Go's bool type maps to `cty.Bool`
* Go's string type maps to `cty.String`
* Go slice types map to `cty` lists with the element type mapped per these rules.
* Go maps _with string keys_ map to `cty` maps with the element type mapped per these rules.
* Go struct types are converted to `cty` object types using the struct tag
convention described above and these mapping rules for each tagged field.
* A Go value of type `cty.Value` maps to `cty.DynamicPseudoType`, allowing for
values whose precise type isn't known statically.
`ImpliedType` considers only the Go type of the provided value, so it's valid
to pass a nil or zero value of the type. When passing `nil`, be sure to convert
it to the target type, e.g. `[]int(nil)`.