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 }