terraform-provider-gitea/vendor/github.com/hashicorp/terraform-plugin-go/tftypes/attribute_path.go
Tobias Trabelsi e1266ebf64
Some checks reported errors
continuous-integration/drone/pr Build encountered an error
continuous-integration/drone/push Build encountered an error
updated GHA
Update to v2 SDK
updated dependencies
2022-08-06 16:21:18 +02:00

368 lines
11 KiB
Go

package tftypes
import (
"errors"
"fmt"
"strconv"
"strings"
)
var (
// ErrNotAttributePathStepper is returned when a type that doesn't full
// the AttributePathStepper interface is passed to WalkAttributePath.
ErrNotAttributePathStepper = errors.New("doesn't fill tftypes.AttributePathStepper interface")
// ErrInvalidStep is returned when an AttributePath has the wrong kind
// of AttributePathStep for the type that WalkAttributePath is
// operating on.
ErrInvalidStep = errors.New("step cannot be applied to this value")
)
// AttributePath is a type that can point to a specific value within an
// aggregate Terraform value. It consists of steps, each identifying one
// element or attribute of the current value, and making that the current
// value. This allows referring to arbitrarily precise values.
type AttributePath struct {
// Steps are the steps that must be followed from the root of the value
// to obtain the value being indicated.
steps []AttributePathStep
}
// NewAttributePath returns an empty AttributePath, ready to have steps added
// to it using WithElementKeyString, WithElementKeyInt, WithElementKeyValue, or
// WithAttributeName.
func NewAttributePath() *AttributePath {
return &AttributePath{}
}
// NewAttributePathWithSteps returns an AttributePath populated with the passed
// AttributePathSteps.
func NewAttributePathWithSteps(steps []AttributePathStep) *AttributePath {
return &AttributePath{
steps: steps,
}
}
// Steps returns the AttributePathSteps that make up an AttributePath.
func (a *AttributePath) Steps() []AttributePathStep {
if a == nil {
return nil
}
steps := make([]AttributePathStep, len(a.steps))
copy(steps, a.steps)
return steps
}
func (a *AttributePath) String() string {
var res strings.Builder
for pos, step := range a.Steps() {
if pos != 0 {
res.WriteString(".")
}
switch v := step.(type) {
case AttributeName:
res.WriteString(`AttributeName("` + string(v) + `")`)
case ElementKeyString:
res.WriteString(`ElementKeyString("` + string(v) + `")`)
case ElementKeyInt:
res.WriteString(`ElementKeyInt(` + strconv.FormatInt(int64(v), 10) + `)`)
case ElementKeyValue:
res.WriteString(`ElementKeyValue(` + Value(v).String() + `)`)
}
}
return res.String()
}
// Equal returns true if two AttributePaths should be considered equal.
// AttributePaths are considered equal if they have the same number of steps,
// the steps are all the same types, and the steps have all the same values.
func (a *AttributePath) Equal(o *AttributePath) bool {
if len(a.Steps()) == 0 && len(o.Steps()) == 0 {
return true
}
if len(a.Steps()) != len(o.Steps()) {
return false
}
for pos, aStep := range a.Steps() {
oStep := o.Steps()[pos]
if !aStep.Equal(oStep) {
return false
}
}
return true
}
// NewErrorf returns an error associated with the value indicated by `a`. This
// is equivalent to calling a.NewError(fmt.Errorf(f, args...)).
func (a *AttributePath) NewErrorf(f string, args ...interface{}) error {
return a.NewError(fmt.Errorf(f, args...))
}
// NewError returns an error that associates `err` with the value indicated by
// `a`.
func (a *AttributePath) NewError(err error) error {
var wrapped AttributePathError
if errors.As(err, &wrapped) {
// TODO: at some point we'll probably want to handle the
// AttributePathError-within-AttributePathError situation,
// either by de-duplicating the paths we're surfacing, or
// privileging one, or something. For now, let's just do the
// naive thing and not add our own path.
return err
}
return AttributePathError{
Path: a,
err: err,
}
}
// LastStep returns the last step in the path. If the path was
// empty, nil is returned.
func (a *AttributePath) LastStep() AttributePathStep {
steps := a.Steps()
if len(steps) == 0 {
return nil
}
return steps[len(steps)-1]
}
// WithAttributeName adds an AttributeName step to `a`, using `name` as the
// attribute's name. `a` is copied, not modified.
func (a *AttributePath) WithAttributeName(name string) *AttributePath {
steps := a.Steps()
return &AttributePath{
steps: append(steps, AttributeName(name)),
}
}
// WithElementKeyString adds an ElementKeyString step to `a`, using `key` as
// the element's key. `a` is copied, not modified.
func (a *AttributePath) WithElementKeyString(key string) *AttributePath {
steps := a.Steps()
return &AttributePath{
steps: append(steps, ElementKeyString(key)),
}
}
// WithElementKeyInt adds an ElementKeyInt step to `a`, using `key` as the
// element's key. `a` is copied, not modified.
func (a *AttributePath) WithElementKeyInt(key int) *AttributePath {
steps := a.Steps()
return &AttributePath{
steps: append(steps, ElementKeyInt(key)),
}
}
// WithElementKeyValue adds an ElementKeyValue to `a`, using `key` as the
// element's key. `a` is copied, not modified.
func (a *AttributePath) WithElementKeyValue(key Value) *AttributePath {
steps := a.Steps()
return &AttributePath{
steps: append(steps, ElementKeyValue(key.Copy())),
}
}
// WithoutLastStep removes the last step, whatever kind of step it was, from
// `a`. `a` is copied, not modified.
func (a *AttributePath) WithoutLastStep() *AttributePath {
steps := a.Steps()
if len(steps) == 0 {
return nil
}
return &AttributePath{
steps: steps[:len(steps)-1],
}
}
// AttributePathStep is an intentionally unimplementable interface that
// functions as an enum, allowing us to use different strongly-typed step types
// as a generic "step" type.
//
// An AttributePathStep is meant to indicate a single step in an AttributePath,
// indicating a specific attribute or element that is the next value in the
// path.
type AttributePathStep interface {
// Equal returns true if the AttributePathStep is equal to the other.
Equal(AttributePathStep) bool
unfulfillable() // make this interface fillable only by this package
}
var (
_ AttributePathStep = AttributeName("")
_ AttributePathStep = ElementKeyString("")
_ AttributePathStep = ElementKeyInt(0)
)
// AttributeName is an AttributePathStep implementation that indicates the next
// step in the AttributePath is to select an attribute. The value of the
// AttributeName is the name of the attribute to be selected.
type AttributeName string
// Equal returns true if the other AttributePathStep is an AttributeName and
// has the same value.
func (a AttributeName) Equal(other AttributePathStep) bool {
otherA, ok := other.(AttributeName)
if !ok {
return false
}
return string(a) == string(otherA)
}
func (a AttributeName) unfulfillable() {}
// ElementKeyString is an AttributePathStep implementation that indicates the
// next step in the AttributePath is to select an element using a string key.
// The value of the ElementKeyString is the key of the element to select.
type ElementKeyString string
// Equal returns true if the other AttributePathStep is an ElementKeyString and
// has the same value.
func (e ElementKeyString) Equal(other AttributePathStep) bool {
otherE, ok := other.(ElementKeyString)
if !ok {
return false
}
return string(e) == string(otherE)
}
func (e ElementKeyString) unfulfillable() {}
// ElementKeyInt is an AttributePathStep implementation that indicates the next
// step in the AttributePath is to select an element using an int64 key. The
// value of the ElementKeyInt is the key of the element to select.
type ElementKeyInt int64
// Equal returns true if the other AttributePathStep is an ElementKeyInt and
// has the same value.
func (e ElementKeyInt) Equal(other AttributePathStep) bool {
otherE, ok := other.(ElementKeyInt)
if !ok {
return false
}
return int(e) == int(otherE)
}
func (e ElementKeyInt) unfulfillable() {}
// ElementKeyValue is an AttributePathStep implementation that indicates the
// next step in the AttributePath is to select an element using the element
// itself as a key. The value of the ElementKeyValue is the key of the element
// to select.
type ElementKeyValue Value
// Equal returns true if the other AttributePathStep is an ElementKeyValue and
// has the same value.
func (e ElementKeyValue) Equal(other AttributePathStep) bool {
otherE, ok := other.(ElementKeyValue)
if !ok {
return false
}
return Value(e).Equal(Value(otherE))
}
func (e ElementKeyValue) unfulfillable() {}
// AttributePathStepper is an interface that types can implement to make them
// traversable by WalkAttributePath, allowing providers to retrieve the
// specific value an AttributePath is pointing to.
type AttributePathStepper interface {
// Return the attribute or element the AttributePathStep is referring
// to, or an error if the AttributePathStep is referring to an
// attribute or element that doesn't exist.
ApplyTerraform5AttributePathStep(AttributePathStep) (interface{}, error)
}
// WalkAttributePath will return the Type or Value that `path` is pointing to,
// using `in` as the root. If an error is returned, the AttributePath returned
// will indicate the steps that remained to be applied when the error was
// encountered.
//
// map[string]interface{} and []interface{} types have built-in support. Other
// types need to use the AttributePathStepper interface to tell
// WalkAttributePath how to traverse themselves.
func WalkAttributePath(in interface{}, path *AttributePath) (interface{}, *AttributePath, error) {
if len(path.Steps()) < 1 {
return in, path, nil
}
stepper, ok := in.(AttributePathStepper)
if !ok {
stepper, ok = builtinAttributePathStepper(in)
if !ok {
return in, path, ErrNotAttributePathStepper
}
}
next, err := stepper.ApplyTerraform5AttributePathStep(path.Steps()[0])
if err != nil {
return in, path, err
}
return WalkAttributePath(next, &AttributePath{steps: path.Steps()[1:]})
}
func builtinAttributePathStepper(in interface{}) (AttributePathStepper, bool) {
switch v := in.(type) {
case map[string]interface{}:
return mapStringInterfaceAttributePathStepper(v), true
case []interface{}:
return interfaceSliceAttributePathStepper(v), true
default:
return nil, false
}
}
type mapStringInterfaceAttributePathStepper map[string]interface{}
func (m mapStringInterfaceAttributePathStepper) ApplyTerraform5AttributePathStep(step AttributePathStep) (interface{}, error) {
_, isAttributeName := step.(AttributeName)
_, isElementKeyString := step.(ElementKeyString)
if !isAttributeName && !isElementKeyString {
return nil, ErrInvalidStep
}
var stepValue string
if isAttributeName {
stepValue = string(step.(AttributeName))
}
if isElementKeyString {
stepValue = string(step.(ElementKeyString))
}
v, ok := m[stepValue]
if !ok {
return nil, ErrInvalidStep
}
return v, nil
}
type interfaceSliceAttributePathStepper []interface{}
func (i interfaceSliceAttributePathStepper) ApplyTerraform5AttributePathStep(step AttributePathStep) (interface{}, error) {
eki, isElementKeyInt := step.(ElementKeyInt)
if !isElementKeyInt {
return nil, ErrInvalidStep
}
if eki < 0 {
return nil, ErrInvalidStep
}
// slices can only have items up to the max value of int
// but we get ElementKeyInt as an int64
// we keep ElementKeyInt as an int64 and cast the length of the slice
// to int64 here because if ElementKeyInt is greater than the max value
// of int, we will always (correctly) error out here. This lets us
// confidently cast ElementKeyInt to an int below, knowing we're not
// truncating data
if int64(eki) >= int64(len(i)) {
return nil, ErrInvalidStep
}
return i[int(eki)], nil
}