Skip to content

Commit bdb9f86

Browse files
authored
path+website: Additional Go documentation and initial website documentation for paths and path expressions (#413)
Reference: #81
1 parent bd0d5f1 commit bdb9f86

File tree

8 files changed

+882
-4
lines changed

8 files changed

+882
-4
lines changed

path/expression.go

+40-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,43 @@ import (
55
)
66

77
// Expression represents an attribute path with expression steps, which can
8-
// represent zero, one, or more actual Paths.
8+
// represent zero, one, or more actual paths in schema data. This logic is
9+
// either based on an absolute path starting at the root of the schema data,
10+
// similar to Path, or a relative path which is intended to be merged with an
11+
// existing absolute path.
12+
//
13+
// Use the MatchRoot() function to create an Expression for an absolute path
14+
// with an initial AtName() step. Use the MatchRelative() function to create
15+
// an Expression for a relative path, which will be merged with an existing
16+
// absolute path.
17+
//
18+
// Similar to Path, Expression functionality has some overlapping method names
19+
// and follows a builder pattern, which allows for chaining method calls to
20+
// construct a full expression. The available traversal steps after Expression
21+
// creation are:
22+
//
23+
// - AtAnyListIndex(): Step into a list at any index
24+
// - AtAnyMapKey(): Step into a map at any key
25+
// - AtAnySetValue(): Step into a set at any attr.Value element
26+
// - AtListIndex(): Step into a list at a specific index
27+
// - AtMapKey(): Step into a map at a specific key
28+
// - AtName(): Step into an attribute or block with a specific name
29+
// - AtParent(): Step backwards one step
30+
// - AtSetValue(): Step into a set at a specific attr.Value element
31+
//
32+
// For example, to express any list element with a root list attribute named
33+
// "some_attribute":
34+
//
35+
// path.MatchRoot("some_attribute").AtAnyListIndex()
36+
//
37+
// An Expression is generally preferable over a Path in schema-defined
38+
// functionality that is intended to accept paths as parameters, such as
39+
// attribute validators and attribute plan modifiers, since it allows consumers
40+
// to support relative paths. Use the Merge() or MergeExpressions() method to
41+
// combine the current attribute path expression with those expression(s).
42+
//
43+
// To find Paths from an Expression in schema based data structures, such as
44+
// tfsdk.Config, tfsdk.Plan, and tfsdk.State, use their PathMatches() method.
945
type Expression struct {
1046
// root stores whether an expression was intentionally created to start
1147
// from the root of the data. This is used with Merge to overwrite steps
@@ -163,6 +199,9 @@ func (e Expression) Merge(other Expression) Expression {
163199

164200
// MergeExpressions returns collection of expressions that calls Merge() on
165201
// the current expression with each of the others.
202+
//
203+
// If no Expression are given, then it will return a collection of expressions
204+
// containing only the current expression.
166205
func (e Expression) MergeExpressions(others ...Expression) Expressions {
167206
var result Expressions
168207

path/expressions.go

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package path
33
import "strings"
44

55
// Expressions is a collection of attribute path expressions.
6+
//
7+
// Refer to the Expression documentation for more details about intended usage.
68
type Expressions []Expression
79

810
// Append adds the given Expressions to the collection without duplication and

path/path.go

+29-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,33 @@ import (
44
"github.com/hashicorp/terraform-plugin-framework/attr"
55
)
66

7-
// Path represents an attribute path with exact steps. Only exact path
8-
// transversals are supported with this implementation as it must remain
9-
// compatible with all protocol implementations.
7+
// Path represents exact traversal steps into a schema or schema-based data.
8+
// These steps always start from the root of the schema, which is an object
9+
// with zero or more attributes and blocks.
10+
//
11+
// Use the Root() function to create a Path with an initial AtName() step. Path
12+
// functionality follows a builder pattern, which allows for chaining method
13+
// calls to construct a full path. The available traversal steps after Path
14+
// creation are:
15+
//
16+
// - AtListIndex(): Step into a list at a specific 0-based index
17+
// - AtMapKey(): Step into a map at a specific key
18+
// - AtName(): Step into an attribute or block with a specific name
19+
// - AtSetValue(): Step into a set at a specific attr.Value element
20+
//
21+
// For example, to represent the first list element with a root list attribute
22+
// named "some_attribute":
23+
//
24+
// path.MatchRoot("some_attribute").AtListIndex(0)
25+
//
26+
// Path is used for functionality which must exactly match the underlying
27+
// schema structure and types, such as diagnostics that are intended for a
28+
// specific attribute or working with specific attribute values in a schema
29+
// based data structure such as tfsdk.Config, tfsdk.Plan, or tfsdk.State.
30+
//
31+
// Refer to Expression for situations where relative or wildcard step logic is
32+
// desirable for schema defined functionality, such as attribute validators or
33+
// attribute plan modifiers.
1034
type Path struct {
1135
// steps is the transversals included with the path. In general, operations
1236
// against the path should protect against modification of the original.
@@ -15,6 +39,8 @@ type Path struct {
1539

1640
// AtListIndex returns a copied path with a new list index step at the end.
1741
// The returned path is safe to modify without affecting the original.
42+
//
43+
// List indices are 0-based. The first element of a list is 0.
1844
func (p Path) AtListIndex(index int) Path {
1945
copiedPath := p.Copy()
2046

path/paths.go

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package path
33
import "strings"
44

55
// Paths is a collection of exact attribute paths.
6+
//
7+
// Refer to the Path documentation for more details about intended usage.
68
type Paths []Path
79

810
// Append adds the given Paths to the collection without duplication and

website/data/plugin-framework-nav-data.json

+8
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@
4444
"title": "Schemas",
4545
"path": "schemas"
4646
},
47+
{
48+
"title": "Paths",
49+
"path": "paths"
50+
},
51+
{
52+
"title": "Path Expressions",
53+
"path": "path-expressions"
54+
},
4755
{
4856
"title": "Attribute Types",
4957
"path": "types"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
---
2+
page_title: 'Plugin Development - Framework: Path Expressions'
3+
description: >-
4+
How to implement path expressions in the provider development framework.
5+
Path expressions are logic built on top of paths, which may represent one or
6+
more actual paths within schema data.
7+
---
8+
9+
# Path Expressions
10+
11+
Path expressions are logic built on top of [paths](/plugin/framework/paths), which may represent one or more actual paths within a schema or schema-based data. Expressions enable providers to work outside the restrictions of absolute paths and steps.
12+
13+
## Usage
14+
15+
Example uses include:
16+
17+
- [Path based attribute validators](/plugin/framework/validation#path-based-attribute-validators), such as those in the [`terraform-plugin-framework-validators` module `schemavalidator` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/schemavalidator).
18+
19+
Use cases which require exact locations, such as [diagnostics](/plugin/framework/diagnostics), implement [paths](/plugin/framework/paths).
20+
21+
## Concepts
22+
23+
Path expressions are an abstraction above [paths](/plugin/framework/paths). This page assumes knowledge of path concepts and implementations.
24+
25+
At its core, expressions implement the following on top of paths:
26+
27+
- Information that designates whether path information is intended to be absolute, similar to paths, or relative, where it is assumed it will be merged with other absolute path information.
28+
- Parent steps, which enables backwards traversal towards the root of a schema in relative paths, after being merged with other absolute path information.
29+
- Path matching, which enables path information to logically return one or more actual paths.
30+
31+
Similar to paths, expressions are built using steps. There are expression steps which directly correspond to exact path steps, such as `AtListIndex()`, `AtMapKey()`, `AtName()`, `AtSetValue()`. Their implementation is the same. However, there are additional expression steps, such as `AtAnyListIndex()`, which cannot be represented in paths due to the potential for ambiguity.
32+
33+
Path matching is the notion that each expression step implements a method that logically determines if a given exact path step should match. For example, the `AtAnyListIndex()` expression step will accept any exact path step for a list index. Path matching with an expression is a collection of matching each expression step against each exact path step, after resolving any potential parent steps.
34+
35+
Every path expression must align with the schema definition or an error diagnostic will be raised when working with path matching within the framework. Provider-defined functionality that is schema-based, such as attribute validation and attribute plan modification, are provided an accurate current path expression since that functionality would not be able to determine its own path expression.
36+
37+
## Building Path Expressions
38+
39+
The framework implementation for path expressions is in the [`path` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path), with the [`path.Expression` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#Expression) being the main provider developer interaction point.
40+
41+
### Building Absolute Path Expressions
42+
43+
Call the [`path.MatchRoot()` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#MatchRoot) with an attribute name or block name at the root of the schema to begin an absolute path expression.
44+
45+
Given this example schema with a root attribute named `example_root_attribute`:
46+
47+
```go
48+
tfsdk.Schema{
49+
Attributes: map[string]tfsdk.Attribute{
50+
"example_root_attribute": {
51+
Required: true,
52+
Type: types.StringType,
53+
},
54+
},
55+
}
56+
```
57+
58+
The call to `path.MatchRoot()` which matches the location of `example_root_attribute` string value is:
59+
60+
```go
61+
path.MatchRoot("example_root_attribute")
62+
```
63+
64+
For blocks, the beginning of a path expression is similarly defined. Attribute and block names cannot overlap, so the framework automatically handles whether a path expression is referring to an attribute or block to start.
65+
66+
Given this example schema with a root block named `example_root_block`:
67+
68+
```go
69+
tfsdk.Schema{
70+
Blocks: map[string]tfsdk.Block{
71+
"example_root_block": {
72+
Attributes: map[string]tfsdk.Attribute{/* ... */},
73+
NestingMode: tfsdk.BlockNestingModeList,
74+
},
75+
},
76+
}
77+
```
78+
79+
The call to `path.MatchRoot()` which matches the location of `example_root_block` list value is:
80+
81+
```go
82+
path.MatchRoot("example_root_block")
83+
```
84+
85+
Once a `path.Expression` is started, it supports a builder pattern, which allows for chaining method calls to construct a full path.
86+
87+
This example shows a hypothetical path expression that points to any element of a list attribute to highlight the builder pattern:
88+
89+
```go
90+
path.MatchRoot("example_list_attribute").AtAnyListIndex()
91+
```
92+
93+
This pattern can be extended to as many calls as necessary. The [Building Expression Steps section](#building-expression-steps) covers the different framework schema types and any special path step methods.
94+
95+
### Building Relative Path Expressions
96+
97+
Relative path expressions are, by nature, contextual to the actual path where they are defined in a schema. Call the [`path.MatchRelative()` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#MatchRelative) to begin a relative path expression.
98+
99+
This example shows a relative path expression which references a child attribute:
100+
101+
```go
102+
tfsdk.Schema{
103+
Attributes: map[string]tfsdk.Attribute{
104+
"root_list_attribute": {
105+
Attributes: tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{
106+
"nested_list_attribute": {
107+
Attributes: tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{
108+
"deeply_nested_string_attribute": {
109+
Required: true,
110+
Type: types.StringType,
111+
},
112+
}),
113+
Required: true,
114+
Validators: []tfsdk.AttributeValidator{
115+
exampleValidatorThatAcceptsExpressions(
116+
path.MatchRelative().AtAnyListIndex().AtName("deeply_nested_string_attribute"),
117+
),
118+
},
119+
},
120+
"nested_string_attribute": {
121+
Required: true,
122+
Type: types.StringType,
123+
},
124+
}),
125+
Required: true,
126+
},
127+
},
128+
}
129+
```
130+
131+
This example shows a relative path expression which references a different attribute within the same list index:
132+
133+
```go
134+
tfsdk.Schema{
135+
Attributes: map[string]tfsdk.Attribute{
136+
"root_list_attribute": {
137+
Attributes: tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{
138+
"nested_list_attribute": {
139+
Attributes: tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{
140+
"deeply_nested_string_attribute": {
141+
Required: true,
142+
Type: types.StringType,
143+
},
144+
}),
145+
Required: true,
146+
Validators: []tfsdk.AttributeValidator{
147+
exampleValidatorThatAcceptsExpressions(
148+
path.MatchRelative().AtParent().AtName("nested_string_attribute"),
149+
),
150+
},
151+
},
152+
"nested_string_attribute": {
153+
Required: true,
154+
Type: types.StringType,
155+
},
156+
}),
157+
Required: true,
158+
},
159+
},
160+
}
161+
```
162+
163+
### Building Expression Steps
164+
165+
Expressions follow similar schema type rules as paths, in particular [Building Attribute Paths](/plugin/framework/paths#building-attribute-paths), [Building Nested Attribute Paths](/plugin/framework/paths#building-nested-attribute-paths), and [Building Block Paths](/plugin/framework/paths#building-block-paths).
166+
167+
The following list shows the [`path.Expression` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#Expression) methods that behave similar to [`path.Path` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#Path) methods.
168+
169+
- `AtListIndex()`
170+
- `AtMapKey()`
171+
- `AtName()`
172+
- `AtSetValue()`
173+
174+
The following table shows the additional [`path.Expression` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#Expression) methods and their descriptions.
175+
176+
| Expression Method | Description |
177+
| ------------------ | ----------- |
178+
| `AtAnyListIndex()` | Will return matches for any list index. Can be used anywhere `AtListIndex()` can be used. |
179+
| `AtAnyMapKey()` | Will return matches for any map key. Can be used anywhere `AtMapKey()` can be used. |
180+
| `AtAnySetValue()` | Will return matches for any set value. Can be used anywhere `AtSetValue()` can be used. |
181+
| `AtParent()` | Will remove the last expression step, or put differently, will match the path closer to the root of the schema. |
182+

0 commit comments

Comments
 (0)