github.com/hashicorp/terraform-plugin-sdk@v1.17.2/helper/schema/set.go (about) 1 package schema 2 3 import ( 4 "bytes" 5 "fmt" 6 "reflect" 7 "sort" 8 "strconv" 9 "sync" 10 11 "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" 12 ) 13 14 // HashString hashes strings. If you want a Set of strings, this is the 15 // SchemaSetFunc you want. 16 func HashString(v interface{}) int { 17 return hashcode.String(v.(string)) 18 } 19 20 // HashInt hashes integers. If you want a Set of integers, this is the 21 // SchemaSetFunc you want. 22 func HashInt(v interface{}) int { 23 return hashcode.String(strconv.Itoa(v.(int))) 24 } 25 26 // HashResource hashes complex structures that are described using 27 // a *Resource. This is the default set implementation used when a set's 28 // element type is a full resource. 29 func HashResource(resource *Resource) SchemaSetFunc { 30 return func(v interface{}) int { 31 var buf bytes.Buffer 32 SerializeResourceForHash(&buf, v, resource) 33 return hashcode.String(buf.String()) 34 } 35 } 36 37 // HashSchema hashes values that are described using a *Schema. This is the 38 // default set implementation used when a set's element type is a single 39 // schema. 40 func HashSchema(schema *Schema) SchemaSetFunc { 41 return func(v interface{}) int { 42 var buf bytes.Buffer 43 SerializeValueForHash(&buf, v, schema) 44 return hashcode.String(buf.String()) 45 } 46 } 47 48 // Set is a set data structure that is returned for elements of type 49 // TypeSet. 50 type Set struct { 51 F SchemaSetFunc 52 53 m map[string]interface{} 54 once sync.Once 55 } 56 57 // NewSet is a convenience method for creating a new set with the given 58 // items. 59 func NewSet(f SchemaSetFunc, items []interface{}) *Set { 60 s := &Set{F: f} 61 for _, i := range items { 62 s.Add(i) 63 } 64 65 return s 66 } 67 68 // CopySet returns a copy of another set. 69 func CopySet(otherSet *Set) *Set { 70 return NewSet(otherSet.F, otherSet.List()) 71 } 72 73 // Add adds an item to the set if it isn't already in the set. 74 func (s *Set) Add(item interface{}) { 75 s.add(item, false) 76 } 77 78 // Remove removes an item if it's already in the set. Idempotent. 79 func (s *Set) Remove(item interface{}) { 80 s.remove(item) 81 } 82 83 // Contains checks if the set has the given item. 84 func (s *Set) Contains(item interface{}) bool { 85 _, ok := s.m[s.hash(item)] 86 return ok 87 } 88 89 // Len returns the amount of items in the set. 90 func (s *Set) Len() int { 91 return len(s.m) 92 } 93 94 // List returns the elements of this set in slice format. 95 // 96 // The order of the returned elements is deterministic. Given the same 97 // set, the order of this will always be the same. 98 func (s *Set) List() []interface{} { 99 result := make([]interface{}, len(s.m)) 100 for i, k := range s.listCode() { 101 result[i] = s.m[k] 102 } 103 104 return result 105 } 106 107 // Difference performs a set difference of the two sets, returning 108 // a new third set that has only the elements unique to this set. 109 func (s *Set) Difference(other *Set) *Set { 110 result := &Set{F: s.F} 111 result.once.Do(result.init) 112 113 for k, v := range s.m { 114 if _, ok := other.m[k]; !ok { 115 result.m[k] = v 116 } 117 } 118 119 return result 120 } 121 122 // Intersection performs the set intersection of the two sets 123 // and returns a new third set. 124 func (s *Set) Intersection(other *Set) *Set { 125 result := &Set{F: s.F} 126 result.once.Do(result.init) 127 128 for k, v := range s.m { 129 if _, ok := other.m[k]; ok { 130 result.m[k] = v 131 } 132 } 133 134 return result 135 } 136 137 // Union performs the set union of the two sets and returns a new third 138 // set. 139 func (s *Set) Union(other *Set) *Set { 140 result := &Set{F: s.F} 141 result.once.Do(result.init) 142 143 for k, v := range s.m { 144 result.m[k] = v 145 } 146 for k, v := range other.m { 147 result.m[k] = v 148 } 149 150 return result 151 } 152 153 func checkSetMapEqual(m1, m2 map[string]interface{}) bool { 154 if (m1 == nil) != (m2 == nil) { 155 return false 156 } 157 if len(m1) != len(m2) { 158 return false 159 } 160 for k := range m1 { 161 v1 := m1[k] 162 v2, ok := m2[k] 163 if !ok { 164 return false 165 } 166 switch v1.(type) { 167 case map[string]interface{}: 168 same := checkSetMapEqual(v1.(map[string]interface{}), v2.(map[string]interface{})) 169 if !same { 170 return false 171 } 172 case *Set: 173 same := v1.(*Set).Equal(v2) 174 if !same { 175 return false 176 } 177 default: 178 same := reflect.DeepEqual(v1, v2) 179 if !same { 180 return false 181 } 182 } 183 } 184 return true 185 } 186 187 func (s *Set) Equal(raw interface{}) bool { 188 other, ok := raw.(*Set) 189 if !ok { 190 return false 191 } 192 return checkSetMapEqual(s.m, other.m) 193 } 194 195 // HashEqual simply checks to the keys the top-level map to the keys in the 196 // other set's top-level map to see if they are equal. This obviously assumes 197 // you have a properly working hash function - use HashResource if in doubt. 198 func (s *Set) HashEqual(raw interface{}) bool { 199 other, ok := raw.(*Set) 200 if !ok { 201 return false 202 } 203 204 ks1 := make([]string, 0) 205 ks2 := make([]string, 0) 206 207 for k := range s.m { 208 ks1 = append(ks1, k) 209 } 210 for k := range other.m { 211 ks2 = append(ks2, k) 212 } 213 214 sort.Strings(ks1) 215 sort.Strings(ks2) 216 217 return reflect.DeepEqual(ks1, ks2) 218 } 219 220 func (s *Set) GoString() string { 221 return fmt.Sprintf("*Set(%#v)", s.m) 222 } 223 224 func (s *Set) init() { 225 s.m = make(map[string]interface{}) 226 } 227 228 func (s *Set) add(item interface{}, computed bool) string { 229 s.once.Do(s.init) 230 231 code := s.hash(item) 232 if computed { 233 code = "~" + code 234 235 if isProto5() { 236 tmpCode := code 237 count := 0 238 for _, exists := s.m[tmpCode]; exists; _, exists = s.m[tmpCode] { 239 count++ 240 tmpCode = fmt.Sprintf("%s%d", code, count) 241 } 242 code = tmpCode 243 } 244 } 245 246 if _, ok := s.m[code]; !ok { 247 s.m[code] = item 248 } 249 250 return code 251 } 252 253 func (s *Set) hash(item interface{}) string { 254 code := s.F(item) 255 // Always return a nonnegative hashcode. 256 if code < 0 { 257 code = -code 258 } 259 return strconv.Itoa(code) 260 } 261 262 func (s *Set) remove(item interface{}) string { 263 s.once.Do(s.init) 264 265 code := s.hash(item) 266 delete(s.m, code) 267 268 return code 269 } 270 271 func (s *Set) listCode() []string { 272 // Sort the hash codes so the order of the list is deterministic 273 keys := make([]string, 0, len(s.m)) 274 for k := range s.m { 275 keys = append(keys, k) 276 } 277 sort.Sort(sort.StringSlice(keys)) 278 return keys 279 }