File: capsule-type-operations.md

package info (click to toggle)
golang-github-zclconf-go-cty 1.12.1-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 1,464 kB
  • sloc: makefile: 2
file content (111 lines) | stat: -rw-r--r-- 4,771 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# Capsule Type Operation Definitions

As described in [the main introduction to Capsule types](./types.md#capsule-types),
by default a capsule type supports no operations apart from comparison by
reference identity.

However, there are certain operations that calling applications reasonably
expect to be able to use generically across values of any type, e.g. as part of
decoding arbitrary user input and preparing it for use.

To support this, calling applications can optionally implement some additional
operations for their capsule types. This is limited only to the subset of
operations that, as noted above, are reasonable to expect to have available on
values of any type.

It does not include type-specialized operations like
arithmetic; to perform such operations against capsule types, implement that
logic in your calling application instead where you must presumably already
be making specialized decisions based on value types.

The following operations are implementable:

* The `GoString` result for values of the type, so that values can be included
  in `fmt` operations using `%#v` along with other values and give a more
  useful result.

  To stay within `cty`'s conventions, the `GoString` result should generally
  represent a call to a value constructor function that would produce an
  equivalent value.

* The `GoString` result for the type itself, for similar reasons.

* Equality as an operation. It's unnecessary to implement this unless your
  capsule type represents a container for other `cty` values that would
  need to be recursively compared for equality. For simple capsule types,
  just implement raw equality and it will be used for both situations.

* Raw equality as an integration method. This is commonly used in tests in
  order to take into account not only the value itself but its null-ness and
  unknown-ness. Because capsule types are primarily intended to be simple
  transports for opaque application values, in simple cases this integration
  method can just be a wrapper around whatever normal equality operation would
  apply to the wrapped type.

* Conversion to and from the capsule type, using the `convert` package. Some
  applications use conversion as part of decoding user input in order to
  coerce user values into an expected type, in which case implementing
  conversions can make an application's capsule types participate in such
  coersions as needed.

To implement one or more of these operations on a capsule type, construct it
with `cty.CapsuleWithOps` instead of just `cty.Capsule`.

The operation implementations are provided as function references within a
struct value, and so those function references can potentially be closures
referring to arguments passed to a type constructor function in order to
implement parameterized capsule types.

For more information on the available operations and the contract for
implementing each one, see the documentation on the fields of `cty.CapsuleOps`.

## Extension Data

In addition to the operations that `cty` packages use directly as described
above, `CapsuleOps` includes extra function `ExtensionData` which can be used
for application-specific extensions.

It creates an extensible namespace using arbitrary comparable values of named
types, where an application can define a key in its own package namespace and
then use it to retrieve values from cooperating capsule types.

For example, if there were an application package called `happyapp` which
defined an extension key `Frob`, a cooperating capsule type might implement
`ExtensionData` like this:

```go
    ExtensionData: func (key interface{}) interface{} {
        switch key {
        case happyapp.Frob: // a value of some type belonging to "happyapp"
            return "frob indeed"
        default:
            return nil
        }
    }
```

Package `happyapp` should define this `Frob` symbol as being of a named type
belonging to its own package, so that the type can serve as a namespace:

```go
type frobType int

var Frob frobType
```

`cty` itself doesn't do anything special with `ExtensionData`, but there is a
convenience method `CapsuleExtensionData` on `cty.Type` that can be used with
capsule types to attempt to access extension data keys without first retrieving
the `CapsuleOps` and checking if it implements `ExtensionData`:

```go
// (in package happyapp)
if frobValue, ok := givenType.CapsuleExtensionData(Frob).(string); ok {
    // If we get in here then the capsule type correctly handles Frob,
    // returning a string. If the capsule type does not handle Frob or
    // if it returns the wrong type then we'll ignore it.
    log.Printf("This capsule type has a Frob: %s", frobValue)
}
```

`CapsuleExtensionData` will panic if called on a non-capsule type.