Skip to content

Commit

Permalink
tfsdk: Ensure Config, Plan, and State PathMatches will return null an…
Browse files Browse the repository at this point in the history
…d unknown parent paths (hashicorp#410)

This will also now return an error diagnostic if a given path expression is invalid for the schema data.

The `(path.Expression).MergeExpressions(...path.Expression) Expressions` is a helper method for the common use case of combining an attribute path expression with an incoming collection of expressions to perform generic validation across attributes.
  • Loading branch information
bflad authored Jul 11, 2022
1 parent 656b447 commit 89baaa2
Show file tree
Hide file tree
Showing 12 changed files with 1,774 additions and 159 deletions.
27 changes: 27 additions & 0 deletions path/expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,15 @@ func (e Expression) Matches(path Path) bool {
return e.steps.Matches(path.Steps())
}

// MatchesParent returns true if the given Path is a valid parent for the
// Expression. This is helpful for determining if a child Path would
// potentially match the full Expression during depth-first traversal. Any
// relative expression steps, such as ExpressionStepParent, are automatically
// resolved before matching.
func (e Expression) MatchesParent(path Path) bool {
return e.steps.MatchesParent(path.Steps())
}

// Merge returns a copied expression either with the steps of the given
// expression added to the end of the existing steps, or overwriting the
// steps if the given expression was a root expression.
Expand All @@ -152,6 +161,24 @@ func (e Expression) Merge(other Expression) Expression {
return copiedExpression
}

// MergeExpressions returns collection of expressions that calls Merge() on
// the current expression with each of the others.
func (e Expression) MergeExpressions(others ...Expression) Expressions {
var result Expressions

if len(others) == 0 {
result.Append(e)

return result
}

for _, other := range others {
result.Append(e.Merge(other))
}

return result
}

// Resolve returns a copied expression with any relative steps, such as
// ExpressionStepParent, resolved. This is not necessary before calling methods
// such as Matches(), however it can be useful before returning the String()
Expand Down
31 changes: 31 additions & 0 deletions path/expression_steps.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,37 @@ func (s ExpressionSteps) Matches(pathSteps PathSteps) bool {
return true
}

// MatchesParent returns true if the given PathSteps match each ExpressionStep
// until there are no more PathSteps. This is helpful for determining if the
// PathSteps would potentially match the full ExpressionSteps during
// depth-first traversal.
//
// Any ExpressionStepParent will automatically be resolved.
func (s ExpressionSteps) MatchesParent(pathSteps PathSteps) bool {
resolvedExpressionSteps := s.Resolve()

// Empty expression should not match anything to prevent false positives.
// Ensure to not return false on an empty path since walking a path always
// starts with no steps.
if len(resolvedExpressionSteps) == 0 {
return false
}

// Path steps deeper than or equal to the expression steps should not match
// as a potential parent.
if len(pathSteps) >= len(resolvedExpressionSteps) {
return false
}

for stepIndex, pathStep := range pathSteps {
if !resolvedExpressionSteps[stepIndex].Matches(pathStep) {
return false
}
}

return true
}

// NextStep returns the first ExpressionStep and the remaining ExpressionSteps.
func (s ExpressionSteps) NextStep() (ExpressionStep, ExpressionSteps) {
if len(s) == 0 {
Expand Down
Loading

0 comments on commit 89baaa2

Please sign in to comment.