github.com/opentofu/opentofu@v1.7.1/internal/legacy/helper/schema/serialize.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package schema
     7  
     8  import (
     9  	"bytes"
    10  	"fmt"
    11  	"sort"
    12  	"strconv"
    13  )
    14  
    15  func SerializeValueForHash(buf *bytes.Buffer, val interface{}, schema *Schema) {
    16  	if val == nil {
    17  		buf.WriteRune(';')
    18  		return
    19  	}
    20  
    21  	switch schema.Type {
    22  	case TypeBool:
    23  		if val.(bool) {
    24  			buf.WriteRune('1')
    25  		} else {
    26  			buf.WriteRune('0')
    27  		}
    28  	case TypeInt:
    29  		buf.WriteString(strconv.Itoa(val.(int)))
    30  	case TypeFloat:
    31  		buf.WriteString(strconv.FormatFloat(val.(float64), 'g', -1, 64))
    32  	case TypeString:
    33  		buf.WriteString(val.(string))
    34  	case TypeList:
    35  		buf.WriteRune('(')
    36  		l := val.([]interface{})
    37  		for _, innerVal := range l {
    38  			serializeCollectionMemberForHash(buf, innerVal, schema.Elem)
    39  		}
    40  		buf.WriteRune(')')
    41  	case TypeMap:
    42  
    43  		m := val.(map[string]interface{})
    44  		var keys []string
    45  		for k := range m {
    46  			keys = append(keys, k)
    47  		}
    48  		sort.Strings(keys)
    49  		buf.WriteRune('[')
    50  		for _, k := range keys {
    51  			innerVal := m[k]
    52  			if innerVal == nil {
    53  				continue
    54  			}
    55  			buf.WriteString(k)
    56  			buf.WriteRune(':')
    57  
    58  			switch innerVal := innerVal.(type) {
    59  			case int:
    60  				buf.WriteString(strconv.Itoa(innerVal))
    61  			case float64:
    62  				buf.WriteString(strconv.FormatFloat(innerVal, 'g', -1, 64))
    63  			case string:
    64  				buf.WriteString(innerVal)
    65  			default:
    66  				panic(fmt.Sprintf("unknown value type in TypeMap %T", innerVal))
    67  			}
    68  
    69  			buf.WriteRune(';')
    70  		}
    71  		buf.WriteRune(']')
    72  	case TypeSet:
    73  		buf.WriteRune('{')
    74  		s := val.(*Set)
    75  		for _, innerVal := range s.List() {
    76  			serializeCollectionMemberForHash(buf, innerVal, schema.Elem)
    77  		}
    78  		buf.WriteRune('}')
    79  	default:
    80  		panic("unknown schema type to serialize")
    81  	}
    82  	buf.WriteRune(';')
    83  }
    84  
    85  // SerializeValueForHash appends a serialization of the given resource config
    86  // to the given buffer, guaranteeing deterministic results given the same value
    87  // and schema.
    88  //
    89  // Its primary purpose is as input into a hashing function in order
    90  // to hash complex substructures when used in sets, and so the serialization
    91  // is not reversible.
    92  func SerializeResourceForHash(buf *bytes.Buffer, val interface{}, resource *Resource) {
    93  	if val == nil {
    94  		return
    95  	}
    96  	sm := resource.Schema
    97  	m := val.(map[string]interface{})
    98  	var keys []string
    99  	for k := range sm {
   100  		keys = append(keys, k)
   101  	}
   102  	sort.Strings(keys)
   103  	for _, k := range keys {
   104  		innerSchema := sm[k]
   105  		// Skip attributes that are not user-provided. Computed attributes
   106  		// do not contribute to the hash since their ultimate value cannot
   107  		// be known at plan/diff time.
   108  		if !(innerSchema.Required || innerSchema.Optional) {
   109  			continue
   110  		}
   111  
   112  		buf.WriteString(k)
   113  		buf.WriteRune(':')
   114  		innerVal := m[k]
   115  		SerializeValueForHash(buf, innerVal, innerSchema)
   116  	}
   117  }
   118  
   119  func serializeCollectionMemberForHash(buf *bytes.Buffer, val interface{}, elem interface{}) {
   120  	switch tElem := elem.(type) {
   121  	case *Schema:
   122  		SerializeValueForHash(buf, val, tElem)
   123  	case *Resource:
   124  		buf.WriteRune('<')
   125  		SerializeResourceForHash(buf, val, tElem)
   126  		buf.WriteString(">;")
   127  	default:
   128  		panic(fmt.Sprintf("invalid element type: %T", tElem))
   129  	}
   130  }