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  }