// 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) 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() } // Fork End 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 } 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) 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 }