|
| 1 | +// Copyright (c) HashiCorp, Inc. |
| 2 | +// SPDX-License-Identifier: MPL-2.0 |
| 3 | + |
| 4 | +package schema |
| 5 | + |
| 6 | +import ( |
| 7 | + "context" |
| 8 | + "fmt" |
| 9 | + |
| 10 | + "github.com/hashicorp/terraform-plugin-go/tftypes" |
| 11 | + |
| 12 | + "github.com/hashicorp/terraform-plugin-framework/attr" |
| 13 | + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" |
| 14 | + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" |
| 15 | + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" |
| 16 | + "github.com/hashicorp/terraform-plugin-framework/schema/validator" |
| 17 | + "github.com/hashicorp/terraform-plugin-framework/types" |
| 18 | + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" |
| 19 | +) |
| 20 | + |
| 21 | +// Ensure the implementation satisfies the desired interfaces. |
| 22 | +var ( |
| 23 | + _ NestedAttribute = ListNestedAttribute{} |
| 24 | + _ fwschema.AttributeWithValidateImplementation = ListNestedAttribute{} |
| 25 | + _ fwxschema.AttributeWithListValidators = ListNestedAttribute{} |
| 26 | +) |
| 27 | + |
| 28 | +// ListNestedAttribute represents an attribute that is a list of objects where |
| 29 | +// the object attributes can be fully defined, including further nested |
| 30 | +// attributes. When retrieving the value for this attribute, use types.List |
| 31 | +// as the value type unless the CustomType field is set. The NestedObject field |
| 32 | +// must be set. Nested attributes are only compatible with protocol version 6. |
| 33 | +// |
| 34 | +// Use ListAttribute if the underlying elements are of a single type and do |
| 35 | +// not require definition beyond type information. |
| 36 | +// |
| 37 | +// Terraform configurations configure this attribute using expressions that |
| 38 | +// return a list of objects or directly via square and curly brace syntax. |
| 39 | +// |
| 40 | +// # list of objects |
| 41 | +// example_attribute = [ |
| 42 | +// { |
| 43 | +// nested_attribute = #... |
| 44 | +// }, |
| 45 | +// ] |
| 46 | +// |
| 47 | +// Terraform configurations reference this attribute using expressions that |
| 48 | +// accept a list of objects or an element directly via square brace 0-based |
| 49 | +// index syntax: |
| 50 | +// |
| 51 | +// # first known object |
| 52 | +// .example_attribute[0] |
| 53 | +// # first known object nested_attribute value |
| 54 | +// .example_attribute[0].nested_attribute |
| 55 | +type ListNestedAttribute struct { |
| 56 | + // NestedObject is the underlying object that contains nested attributes. |
| 57 | + // This field must be set. |
| 58 | + // |
| 59 | + // Nested attributes that contain a dynamic type (i.e. DynamicAttribute) are not supported. |
| 60 | + // If underlying dynamic values are required, replace this attribute definition with |
| 61 | + // DynamicAttribute instead. |
| 62 | + NestedObject NestedAttributeObject |
| 63 | + |
| 64 | + // CustomType enables the use of a custom attribute type in place of the |
| 65 | + // default types.ListType of types.ObjectType. When retrieving data, the |
| 66 | + // basetypes.ListValuable associated with this custom type must be used in |
| 67 | + // place of types.List. |
| 68 | + CustomType basetypes.ListTypable |
| 69 | + |
| 70 | + // Required indicates whether the practitioner must enter a value for |
| 71 | + // this attribute or not. Required and Optional cannot both be true. |
| 72 | + Required bool |
| 73 | + |
| 74 | + // Optional indicates whether the practitioner can choose to enter a value |
| 75 | + // for this attribute or not. Optional and Required cannot both be true. |
| 76 | + Optional bool |
| 77 | + |
| 78 | + // Description is used in various tooling, like the language server, to |
| 79 | + // give practitioners more information about what this attribute is, |
| 80 | + // what it's for, and how it should be used. It should be written as |
| 81 | + // plain text, with no special formatting. |
| 82 | + Description string |
| 83 | + |
| 84 | + // MarkdownDescription is used in various tooling, like the |
| 85 | + // documentation generator, to give practitioners more information |
| 86 | + // about what this attribute is, what it's for, and how it should be |
| 87 | + // used. It should be formatted using Markdown. |
| 88 | + MarkdownDescription string |
| 89 | + |
| 90 | + // DeprecationMessage defines warning diagnostic details to display when |
| 91 | + // practitioner configurations use this Attribute. The warning diagnostic |
| 92 | + // summary is automatically set to "Attribute Deprecated" along with |
| 93 | + // configuration source file and line information. |
| 94 | + // |
| 95 | + // Set this field to a practitioner actionable message such as: |
| 96 | + // |
| 97 | + // - "Configure other_attribute instead. This attribute will be removed |
| 98 | + // in the next major version of the provider." |
| 99 | + // - "Remove this attribute's configuration as it no longer is used and |
| 100 | + // the attribute will be removed in the next major version of the |
| 101 | + // provider." |
| 102 | + // |
| 103 | + // In Terraform 1.2.7 and later, this warning diagnostic is displayed any |
| 104 | + // time a practitioner attempts to configure a value for this attribute and |
| 105 | + // certain scenarios where this attribute is referenced. |
| 106 | + // |
| 107 | + // In Terraform 1.2.6 and earlier, this warning diagnostic is only |
| 108 | + // displayed when the Attribute is Required or Optional, and if the |
| 109 | + // practitioner configuration sets the value to a known or unknown value |
| 110 | + // (which may eventually be null). It has no effect when the Attribute is |
| 111 | + // Computed-only (read-only; not Required or Optional). |
| 112 | + // |
| 113 | + // Across any Terraform version, there are no warnings raised for |
| 114 | + // practitioner configuration values set directly to null, as there is no |
| 115 | + // way for the framework to differentiate between an unset and null |
| 116 | + // configuration due to how Terraform sends configuration information |
| 117 | + // across the protocol. |
| 118 | + // |
| 119 | + // Additional information about deprecation enhancements for read-only |
| 120 | + // attributes can be found in: |
| 121 | + // |
| 122 | + // - https://github.com/hashicorp/terraform/issues/7569 |
| 123 | + // |
| 124 | + DeprecationMessage string |
| 125 | + |
| 126 | + // Validators define value validation functionality for the attribute. All |
| 127 | + // elements of the slice of AttributeValidator are run, regardless of any |
| 128 | + // previous error diagnostics. |
| 129 | + // |
| 130 | + // Many common use case validators can be found in the |
| 131 | + // github.com/hashicorp/terraform-plugin-framework-validators Go module. |
| 132 | + // |
| 133 | + // If the Type field points to a custom type that implements the |
| 134 | + // xattr.TypeWithValidate interface, the validators defined in this field |
| 135 | + // are run in addition to the validation defined by the type. |
| 136 | + Validators []validator.List |
| 137 | +} |
| 138 | + |
| 139 | +// ApplyTerraform5AttributePathStep returns the Attributes field value if step |
| 140 | +// is ElementKeyInt, otherwise returns an error. |
| 141 | +func (a ListNestedAttribute) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (interface{}, error) { |
| 142 | + _, ok := step.(tftypes.ElementKeyInt) |
| 143 | + |
| 144 | + if !ok { |
| 145 | + return nil, fmt.Errorf("cannot apply step %T to ListNestedAttribute", step) |
| 146 | + } |
| 147 | + |
| 148 | + return a.NestedObject, nil |
| 149 | +} |
| 150 | + |
| 151 | +// Equal returns true if the given Attribute is a ListNestedAttribute |
| 152 | +// and all fields are equal. |
| 153 | +func (a ListNestedAttribute) Equal(o fwschema.Attribute) bool { |
| 154 | + other, ok := o.(ListNestedAttribute) |
| 155 | + |
| 156 | + if !ok { |
| 157 | + return false |
| 158 | + } |
| 159 | + |
| 160 | + return fwschema.NestedAttributesEqual(a, other) |
| 161 | +} |
| 162 | + |
| 163 | +// GetDeprecationMessage returns the DeprecationMessage field value. |
| 164 | +func (a ListNestedAttribute) GetDeprecationMessage() string { |
| 165 | + return a.DeprecationMessage |
| 166 | +} |
| 167 | + |
| 168 | +// GetDescription returns the Description field value. |
| 169 | +func (a ListNestedAttribute) GetDescription() string { |
| 170 | + return a.Description |
| 171 | +} |
| 172 | + |
| 173 | +// GetMarkdownDescription returns the MarkdownDescription field value. |
| 174 | +func (a ListNestedAttribute) GetMarkdownDescription() string { |
| 175 | + return a.MarkdownDescription |
| 176 | +} |
| 177 | + |
| 178 | +// GetNestedObject returns the NestedObject field value. |
| 179 | +func (a ListNestedAttribute) GetNestedObject() fwschema.NestedAttributeObject { |
| 180 | + return a.NestedObject |
| 181 | +} |
| 182 | + |
| 183 | +// GetNestingMode always returns NestingModeList. |
| 184 | +func (a ListNestedAttribute) GetNestingMode() fwschema.NestingMode { |
| 185 | + return fwschema.NestingModeList |
| 186 | +} |
| 187 | + |
| 188 | +// GetType returns ListType of ObjectType or CustomType. |
| 189 | +func (a ListNestedAttribute) GetType() attr.Type { |
| 190 | + if a.CustomType != nil { |
| 191 | + return a.CustomType |
| 192 | + } |
| 193 | + |
| 194 | + return types.ListType{ |
| 195 | + ElemType: a.NestedObject.Type(), |
| 196 | + } |
| 197 | +} |
| 198 | + |
| 199 | +// IsComputed returns false because it does not apply to ListResource schemas. |
| 200 | +func (a ListNestedAttribute) IsComputed() bool { |
| 201 | + return false |
| 202 | +} |
| 203 | + |
| 204 | +// IsOptional returns the Optional field value. |
| 205 | +func (a ListNestedAttribute) IsOptional() bool { |
| 206 | + return a.Optional |
| 207 | +} |
| 208 | + |
| 209 | +// IsRequired returns the Required field value. |
| 210 | +func (a ListNestedAttribute) IsRequired() bool { |
| 211 | + return a.Required |
| 212 | +} |
| 213 | + |
| 214 | +// IsSensitive returns false because it does not apply to ListResource schemas. |
| 215 | +func (a ListNestedAttribute) IsSensitive() bool { |
| 216 | + return false |
| 217 | +} |
| 218 | + |
| 219 | +// IsWriteOnly returns false because it does not apply to ListResource schemas. |
| 220 | +func (a ListNestedAttribute) IsWriteOnly() bool { |
| 221 | + return false |
| 222 | +} |
| 223 | + |
| 224 | +// IsRequiredForImport returns false as this behavior is only relevant |
| 225 | +// for managed resource identity schema attributes. |
| 226 | +func (a ListNestedAttribute) IsRequiredForImport() bool { |
| 227 | + return false |
| 228 | +} |
| 229 | + |
| 230 | +// IsOptionalForImport returns false as this behavior is only relevant |
| 231 | +// for managed resource identity schema attributes. |
| 232 | +func (a ListNestedAttribute) IsOptionalForImport() bool { |
| 233 | + return false |
| 234 | +} |
| 235 | + |
| 236 | +// ListValidators returns the Validators field value. |
| 237 | +func (a ListNestedAttribute) ListValidators() []validator.List { |
| 238 | + return a.Validators |
| 239 | +} |
| 240 | + |
| 241 | +// ValidateImplementation contains logic for validating the |
| 242 | +// provider-defined implementation of the attribute to prevent unexpected |
| 243 | +// errors or panics. This logic runs during the GetProviderSchema RPC and |
| 244 | +// should never include false positives. |
| 245 | +func (a ListNestedAttribute) ValidateImplementation(ctx context.Context, req fwschema.ValidateImplementationRequest, resp *fwschema.ValidateImplementationResponse) { |
| 246 | + if a.CustomType == nil && fwtype.ContainsCollectionWithDynamic(a.GetType()) { |
| 247 | + resp.Diagnostics.Append(fwtype.AttributeCollectionWithDynamicTypeDiag(req.Path)) |
| 248 | + } |
| 249 | +} |
0 commit comments