github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/command/jsonformat/differ/attribute_path/matcher.go (about)

     1  package attribute_path
     2  
     3  import "encoding/json"
     4  
     5  // Matcher provides an interface for stepping through changes following an
     6  // attribute path.
     7  //
     8  // GetChildWithKey and GetChildWithIndex will check if any of the internal paths
     9  // match the provided key or index, and return a new Matcher that will match
    10  // that children or potentially it's children.
    11  //
    12  // The caller of the above functions is required to know whether the next value
    13  // in the path is a list type or an object type and call the relevant function,
    14  // otherwise these functions will crash/panic.
    15  //
    16  // The Matches function returns true if the paths you have traversed until now
    17  // ends.
    18  type Matcher interface {
    19  	// Matches returns true if we have reached the end of a path and found an
    20  	// exact match.
    21  	Matches() bool
    22  
    23  	// MatchesPartial returns true if the current attribute is part of a path
    24  	// but not necessarily at the end of the path.
    25  	MatchesPartial() bool
    26  
    27  	GetChildWithKey(key string) Matcher
    28  	GetChildWithIndex(index int) Matcher
    29  }
    30  
    31  // Parse accepts a json.RawMessage and outputs a formatted Matcher object.
    32  //
    33  // Parse expects the message to be a JSON array of JSON arrays containing
    34  // strings and floats. This function happily accepts a null input representing
    35  // none of the changes in this resource are causing a replacement. The propagate
    36  // argument tells the matcher to propagate any matches to the matched attributes
    37  // children.
    38  //
    39  // In general, this function is designed to accept messages that have been
    40  // produced by the lossy cty.Paths conversion functions within the jsonplan
    41  // package. There is nothing particularly special about that conversion process
    42  // though, it just produces the nested JSON arrays described above.
    43  func Parse(message json.RawMessage, propagate bool) Matcher {
    44  	matcher := &PathMatcher{
    45  		Propagate: propagate,
    46  	}
    47  	if message == nil {
    48  		return matcher
    49  	}
    50  
    51  	if err := json.Unmarshal(message, &matcher.Paths); err != nil {
    52  		panic("failed to unmarshal attribute paths: " + err.Error())
    53  	}
    54  
    55  	return matcher
    56  }
    57  
    58  // Empty returns an empty PathMatcher that will by default match nothing.
    59  //
    60  // We give direct access to the PathMatcher struct so a matcher can be built
    61  // in parts with the Append and AppendSingle functions.
    62  func Empty(propagate bool) *PathMatcher {
    63  	return &PathMatcher{
    64  		Propagate: propagate,
    65  	}
    66  }
    67  
    68  // Append accepts an existing PathMatcher and returns a new one that attaches
    69  // all the paths from message with the existing paths.
    70  //
    71  // The new PathMatcher is created fresh, and the existing one is unchanged.
    72  func Append(matcher *PathMatcher, message json.RawMessage) *PathMatcher {
    73  	var values [][]interface{}
    74  	if err := json.Unmarshal(message, &values); err != nil {
    75  		panic("failed to unmarshal attribute paths: " + err.Error())
    76  	}
    77  
    78  	return &PathMatcher{
    79  		Propagate: matcher.Propagate,
    80  		Paths:     append(matcher.Paths, values...),
    81  	}
    82  }
    83  
    84  // AppendSingle accepts an existing PathMatcher and returns a new one that
    85  // attaches the single path from message with the existing paths.
    86  //
    87  // The new PathMatcher is created fresh, and the existing one is unchanged.
    88  func AppendSingle(matcher *PathMatcher, message json.RawMessage) *PathMatcher {
    89  	var values []interface{}
    90  	if err := json.Unmarshal(message, &values); err != nil {
    91  		panic("failed to unmarshal attribute paths: " + err.Error())
    92  	}
    93  
    94  	return &PathMatcher{
    95  		Propagate: matcher.Propagate,
    96  		Paths:     append(matcher.Paths, values),
    97  	}
    98  }
    99  
   100  // PathMatcher contains a slice of paths that represent paths through the values
   101  // to relevant/tracked attributes.
   102  type PathMatcher struct {
   103  	// We represent our internal paths as a [][]interface{} as the cty.Paths
   104  	// conversion process is lossy. Since the type information is lost there
   105  	// is no (easy) way to reproduce the original cty.Paths object. Instead,
   106  	// we simply rely on the external callers to know the type information and
   107  	// call the correct GetChild function.
   108  	Paths [][]interface{}
   109  
   110  	// Propagate tells the matcher that it should propagate any matches it finds
   111  	// onto the children of that match.
   112  	Propagate bool
   113  }
   114  
   115  func (p *PathMatcher) Matches() bool {
   116  	for _, path := range p.Paths {
   117  		if len(path) == 0 {
   118  			return true
   119  		}
   120  	}
   121  	return false
   122  }
   123  
   124  func (p *PathMatcher) MatchesPartial() bool {
   125  	return len(p.Paths) > 0
   126  }
   127  
   128  func (p *PathMatcher) GetChildWithKey(key string) Matcher {
   129  	child := &PathMatcher{
   130  		Propagate: p.Propagate,
   131  	}
   132  	for _, path := range p.Paths {
   133  		if len(path) == 0 {
   134  			// This means that the current value matched, but not necessarily
   135  			// it's child.
   136  
   137  			if p.Propagate {
   138  				// If propagate is true, then our child match our matches
   139  				child.Paths = append(child.Paths, path)
   140  			}
   141  
   142  			// If not we would simply drop this path from our set of paths but
   143  			// either way we just continue.
   144  			continue
   145  		}
   146  
   147  		if path[0].(string) == key {
   148  			child.Paths = append(child.Paths, path[1:])
   149  		}
   150  	}
   151  	return child
   152  }
   153  
   154  func (p *PathMatcher) GetChildWithIndex(index int) Matcher {
   155  	child := &PathMatcher{
   156  		Propagate: p.Propagate,
   157  	}
   158  	for _, path := range p.Paths {
   159  		if len(path) == 0 {
   160  			// This means that the current value matched, but not necessarily
   161  			// it's child.
   162  
   163  			if p.Propagate {
   164  				// If propagate is true, then our child match our matches
   165  				child.Paths = append(child.Paths, path)
   166  			}
   167  
   168  			// If not we would simply drop this path from our set of paths but
   169  			// either way we just continue.
   170  			continue
   171  		}
   172  
   173  		if int(path[0].(float64)) == index {
   174  			child.Paths = append(child.Paths, path[1:])
   175  		}
   176  	}
   177  	return child
   178  }
   179  
   180  // AlwaysMatcher returns a matcher that will always match all paths.
   181  func AlwaysMatcher() Matcher {
   182  	return &alwaysMatcher{}
   183  }
   184  
   185  type alwaysMatcher struct{}
   186  
   187  func (a *alwaysMatcher) Matches() bool {
   188  	return true
   189  }
   190  
   191  func (a *alwaysMatcher) MatchesPartial() bool {
   192  	return true
   193  }
   194  
   195  func (a *alwaysMatcher) GetChildWithKey(_ string) Matcher {
   196  	return a
   197  }
   198  
   199  func (a *alwaysMatcher) GetChildWithIndex(_ int) Matcher {
   200  	return a
   201  }