github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/legacy/helper/schema/serialize.go (about)

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