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