github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/command/jsonformat/structured/map.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package structured
     5  
     6  import (
     7  	"github.com/terramate-io/tf/command/jsonformat/structured/attribute_path"
     8  )
     9  
    10  // ChangeMap is a Change that represents a Map or an Object type, and has
    11  // converted the relevant interfaces into maps for easier access.
    12  type ChangeMap struct {
    13  	// Before contains the value before the proposed change.
    14  	Before map[string]interface{}
    15  
    16  	// After contains the value after the proposed change.
    17  	After map[string]interface{}
    18  
    19  	// Unknown contains the unknown status of any elements/attributes of this
    20  	// map/object.
    21  	Unknown map[string]interface{}
    22  
    23  	// BeforeSensitive contains the before sensitive status of any
    24  	// elements/attributes of this map/object.
    25  	BeforeSensitive map[string]interface{}
    26  
    27  	// AfterSensitive contains the after sensitive status of any
    28  	// elements/attributes of this map/object.
    29  	AfterSensitive map[string]interface{}
    30  
    31  	// ReplacePaths matches the same attributes in Change exactly.
    32  	ReplacePaths attribute_path.Matcher
    33  
    34  	// RelevantAttributes matches the same attributes in Change exactly.
    35  	RelevantAttributes attribute_path.Matcher
    36  }
    37  
    38  // AsMap converts the Change into an object or map representation by converting
    39  // the internal Before, After, Unknown, BeforeSensitive, and AfterSensitive
    40  // data structures into generic maps.
    41  func (change Change) AsMap() ChangeMap {
    42  	return ChangeMap{
    43  		Before:             genericToMap(change.Before),
    44  		After:              genericToMap(change.After),
    45  		Unknown:            genericToMap(change.Unknown),
    46  		BeforeSensitive:    genericToMap(change.BeforeSensitive),
    47  		AfterSensitive:     genericToMap(change.AfterSensitive),
    48  		ReplacePaths:       change.ReplacePaths,
    49  		RelevantAttributes: change.RelevantAttributes,
    50  	}
    51  }
    52  
    53  // GetChild safely packages up a Change object for the given child, handling
    54  // all the cases where the data might be null or a static boolean.
    55  func (m ChangeMap) GetChild(key string) Change {
    56  	before, beforeExplicit := getFromGenericMap(m.Before, key)
    57  	after, afterExplicit := getFromGenericMap(m.After, key)
    58  	unknown, _ := getFromGenericMap(m.Unknown, key)
    59  	beforeSensitive, _ := getFromGenericMap(m.BeforeSensitive, key)
    60  	afterSensitive, _ := getFromGenericMap(m.AfterSensitive, key)
    61  
    62  	return Change{
    63  		BeforeExplicit:     beforeExplicit,
    64  		AfterExplicit:      afterExplicit,
    65  		Before:             before,
    66  		After:              after,
    67  		Unknown:            unknown,
    68  		BeforeSensitive:    beforeSensitive,
    69  		AfterSensitive:     afterSensitive,
    70  		ReplacePaths:       m.ReplacePaths.GetChildWithKey(key),
    71  		RelevantAttributes: m.RelevantAttributes.GetChildWithKey(key),
    72  	}
    73  }
    74  
    75  // ExplicitKeys returns the keys in the Before and After, as opposed to AllKeys
    76  // which also includes keys from the additional meta structures (like the
    77  // sensitive and unknown values).
    78  //
    79  // This function is useful for processing nested attributes and repeated blocks
    80  // where the unknown and sensitive structs contain information about the actual
    81  // attributes, while the before and after structs hold the actual nested values.
    82  func (m ChangeMap) ExplicitKeys() []string {
    83  	keys := make(map[string]bool)
    84  	for before := range m.Before {
    85  		if _, ok := keys[before]; ok {
    86  			continue
    87  		}
    88  		keys[before] = true
    89  	}
    90  	for after := range m.After {
    91  		if _, ok := keys[after]; ok {
    92  			continue
    93  		}
    94  		keys[after] = true
    95  	}
    96  
    97  	var dedupedKeys []string
    98  	for key := range keys {
    99  		dedupedKeys = append(dedupedKeys, key)
   100  	}
   101  	return dedupedKeys
   102  }
   103  
   104  // AllKeys returns all the possible keys for this map. The keys for the map are
   105  // potentially hidden and spread across multiple internal data structures and
   106  // so this function conveniently packages them up.
   107  func (m ChangeMap) AllKeys() []string {
   108  	keys := make(map[string]bool)
   109  	for before := range m.Before {
   110  		if _, ok := keys[before]; ok {
   111  			continue
   112  		}
   113  		keys[before] = true
   114  	}
   115  	for after := range m.After {
   116  		if _, ok := keys[after]; ok {
   117  			continue
   118  		}
   119  		keys[after] = true
   120  	}
   121  	for unknown := range m.Unknown {
   122  		if _, ok := keys[unknown]; ok {
   123  			continue
   124  		}
   125  		keys[unknown] = true
   126  	}
   127  	for sensitive := range m.AfterSensitive {
   128  		if _, ok := keys[sensitive]; ok {
   129  			continue
   130  		}
   131  		keys[sensitive] = true
   132  	}
   133  	for sensitive := range m.BeforeSensitive {
   134  		if _, ok := keys[sensitive]; ok {
   135  			continue
   136  		}
   137  		keys[sensitive] = true
   138  	}
   139  
   140  	var dedupedKeys []string
   141  	for key := range keys {
   142  		dedupedKeys = append(dedupedKeys, key)
   143  	}
   144  	return dedupedKeys
   145  }
   146  
   147  func getFromGenericMap(generic map[string]interface{}, key string) (interface{}, bool) {
   148  	if generic == nil {
   149  		return nil, false
   150  	}
   151  
   152  	if child, ok := generic[key]; ok {
   153  		return child, ok
   154  	}
   155  	return nil, false
   156  }
   157  
   158  func genericToMap(generic interface{}) map[string]interface{} {
   159  	if concrete, ok := generic.(map[string]interface{}); ok {
   160  		return concrete
   161  	}
   162  	return nil
   163  }