Files
plexterraform/internal/provider/reflect/struct.go

275 lines
8.8 KiB
Go

// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT.
package reflect
import (
"context"
"fmt"
"reflect"
"strings"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/attr/xattr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Struct builds a new struct using the data in `object`, as long as `object`
// is a `tftypes.Object`. It will take the struct type from `target`, which
// must be a struct type.
//
// The properties on `target` must be tagged with a "tfsdk" label containing
// the field name to map to that property. Every property must be tagged, and
// every property must be present in the type of `object`, and all the
// attributes in the type of `object` must have a corresponding property.
// Properties that don't map to object attributes must have a `tfsdk:"-"` tag,
// explicitly defining them as not part of the object. This is to catch typos
// and other mistakes early.
//
// Struct is meant to be called from Into, not directly.
func Struct(ctx context.Context, typ attr.Type, object tftypes.Value, target reflect.Value, opts Options, path path.Path) (reflect.Value, diag.Diagnostics) {
var diags diag.Diagnostics
// this only works with object values, so make sure that constraint is
// met
if target.Kind() != reflect.Struct {
diags.Append(diag.WithPath(path, DiagIntoIncompatibleType{
Val: object,
TargetType: target.Type(),
Err: fmt.Errorf("expected a struct type, got %s", target.Type()),
}))
return target, diags
}
if !object.Type().Is(tftypes.Object{}) {
diags.Append(diag.WithPath(path, DiagIntoIncompatibleType{
Val: object,
TargetType: target.Type(),
Err: fmt.Errorf("cannot reflect %s into a struct, must be an object", object.Type().String()),
}))
return target, diags
}
attrsType, ok := typ.(attr.TypeWithAttributeTypes)
if !ok {
diags.Append(diag.WithPath(path, DiagIntoIncompatibleType{
Val: object,
TargetType: target.Type(),
Err: fmt.Errorf("cannot reflect object using type information provided by %T, %T must be an attr.TypeWithAttributeTypes", typ, typ),
}))
return target, diags
}
// collect a map of fields that are in the object passed in
var objectFields map[string]tftypes.Value
err := object.As(&objectFields)
if err != nil {
diags.Append(diag.WithPath(path, DiagIntoIncompatibleType{
Val: object,
TargetType: target.Type(),
Err: err,
}))
return target, diags
}
// collect a map of fields that are defined in the tags of the struct
// passed in
targetFields, err := getStructTags(ctx, target, path, opts)
if err != nil {
diags.Append(diag.WithPath(path, DiagIntoIncompatibleType{
Val: object,
TargetType: target.Type(),
Err: fmt.Errorf("error retrieving field names from struct tags: %w", err),
}))
return target, diags
}
// we require an exact, 1:1 match of these fields to avoid typos
// leading to surprises, so let's ensure they have the exact same
// fields defined
var objectMissing, targetMissing []string
for field := range targetFields {
if _, ok := objectFields[field]; !ok {
objectMissing = append(objectMissing, field)
}
}
for field := range objectFields {
if _, ok := targetFields[field]; !ok {
targetMissing = append(targetMissing, field)
}
}
if len(objectMissing) > 0 || len(targetMissing) > 0 {
var missing []string
if len(objectMissing) > 0 {
missing = append(missing, fmt.Sprintf("Struct defines fields not found in object: %s.", commaSeparatedString(objectMissing)))
}
if len(targetMissing) > 0 {
missing = append(missing, fmt.Sprintf("Object defines fields not found in struct: %s.", commaSeparatedString(targetMissing)))
}
diags.Append(diag.WithPath(path, DiagIntoIncompatibleType{
Val: object,
TargetType: target.Type(),
Err: fmt.Errorf("mismatch between struct and object: %s", strings.Join(missing, " ")),
}))
return target, diags
}
attrTypes := attrsType.AttributeTypes()
// now that we know they match perfectly, fill the struct with the
// values in the object
// Fork start
//result := reflect.New(target.Type()).Elem()
var result reflect.Value
if target.CanSet() {
result = target
} else {
result = reflect.New(target.Type()).Elem()
}
structType := trueReflectValue(target).Type()
for field, structFieldPos := range targetFields {
attrType, ok := attrTypes[field]
if !ok {
diags.Append(diag.WithPath(path, DiagIntoIncompatibleType{
Val: object,
TargetType: target.Type(),
Err: fmt.Errorf("could not find type information for attribute in supplied attr.Type %T", typ),
}))
return target, diags
}
fieldReflected := structType.Field(structFieldPos)
if opts.SourceType == SourceTypeState && fieldReflected.Tag.Get(`tfPlanOnly`) == "true" {
// skip explicitly excluded fields
continue
}
// Fork End
structField := result.Field(structFieldPos)
fieldVal, fieldValDiags := BuildValue(ctx, attrType, objectFields[field], structField, opts, path.AtName(field))
diags.Append(fieldValDiags...)
if diags.HasError() {
return target, diags
}
structField.Set(fieldVal)
}
return result, diags
}
// FromStruct builds an attr.Value as produced by `typ` from the data in `val`.
// `val` must be a struct type, and must have all its properties tagged and be
// a 1:1 match with the attributes reported by `typ`. FromStruct will recurse
// into FromValue for each attribute, using the type of the attribute as
// reported by `typ`.
//
// It is meant to be called through FromValue, not directly.
func FromStruct(ctx context.Context, typ attr.TypeWithAttributeTypes, val reflect.Value, path path.Path) (attr.Value, diag.Diagnostics) {
var diags diag.Diagnostics
objTypes := map[string]tftypes.Type{}
objValues := map[string]tftypes.Value{}
// collect a map of fields that are defined in the tags of the struct
// passed in
targetFields, err := getStructTags(ctx, val, path, Options{})
if err != nil {
err = fmt.Errorf("error retrieving field names from struct tags: %w", err)
diags.AddAttributeError(
path,
"Value Conversion Error",
"An unexpected error was encountered trying to convert from struct value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(),
)
return nil, diags
}
attrTypes := typ.AttributeTypes()
var objectMissing, structMissing []string
for field := range targetFields {
if _, ok := attrTypes[field]; !ok {
objectMissing = append(objectMissing, field)
}
}
for attrName, attrType := range attrTypes {
if attrType == nil {
objectMissing = append(objectMissing, attrName)
}
if _, ok := targetFields[attrName]; !ok {
structMissing = append(structMissing, attrName)
}
}
if len(objectMissing) > 0 || len(structMissing) > 0 {
missing := make([]string, 0, len(objectMissing)+len(structMissing))
if len(objectMissing) > 0 {
missing = append(missing, fmt.Sprintf("Struct defines fields not found in object: %s.", commaSeparatedString(objectMissing)))
}
if len(structMissing) > 0 {
missing = append(missing, fmt.Sprintf("Object defines fields not found in struct: %s.", commaSeparatedString(structMissing)))
}
diags.AddAttributeError(
path,
"Value Conversion Error",
"An unexpected error was encountered trying to convert from struct into an object. "+
"This is always an error in the provider. Please report the following to the provider developer:\n\n"+
fmt.Sprintf("Mismatch between struct and object type: %s\n", strings.Join(missing, " "))+
fmt.Sprintf("Struct: %s\n", val.Type())+
fmt.Sprintf("Object type: %s", typ),
)
return nil, diags
}
for name, fieldNo := range targetFields {
path := path.AtName(name)
fieldValue := val.Field(fieldNo)
attrVal, attrValDiags := FromValue(ctx, attrTypes[name], fieldValue.Interface(), path)
diags.Append(attrValDiags...)
if diags.HasError() {
return nil, diags
}
tfObjVal, err := attrVal.ToTerraformValue(ctx)
if err != nil {
return nil, append(diags, toTerraformValueErrorDiag(err, path))
}
if typeWithValidate, ok := typ.(xattr.TypeWithValidate); ok {
diags.Append(typeWithValidate.Validate(ctx, tfObjVal, path)...)
if diags.HasError() {
return nil, diags
}
}
objValues[name] = tfObjVal
objTypes[name] = tfObjVal.Type()
}
tfVal := tftypes.NewValue(tftypes.Object{
AttributeTypes: objTypes,
}, objValues)
if typeWithValidate, ok := typ.(xattr.TypeWithValidate); ok {
diags.Append(typeWithValidate.Validate(ctx, tfVal, path)...)
if diags.HasError() {
return nil, diags
}
}
ret, err := typ.ValueFromTerraform(ctx, tfVal)
if err != nil {
return nil, append(diags, valueFromTerraformErrorDiag(err, path))
}
return ret, diags
}