From 313c2528399239dc1ec401956ef5ac884a8c88d8 Mon Sep 17 00:00:00 2001 From: Andre Harms Date: Thu, 19 Feb 2026 08:17:12 +0100 Subject: [PATCH] fix: refactor connection info structure to use nested write attribute --- .../instance_data_source_gen.go | 501 +++++++++++++++--- .../postgresflexalpha/instance/functions.go | 40 +- .../resources_gen/instance_resource_gen.go | 501 +++++++++++++++--- 3 files changed, 884 insertions(+), 158 deletions(-) diff --git a/stackit/internal/services/postgresflexalpha/instance/datasources_gen/instance_data_source_gen.go b/stackit/internal/services/postgresflexalpha/instance/datasources_gen/instance_data_source_gen.go index 047d0176..844d494e 100644 --- a/stackit/internal/services/postgresflexalpha/instance/datasources_gen/instance_data_source_gen.go +++ b/stackit/internal/services/postgresflexalpha/instance/datasources_gen/instance_data_source_gen.go @@ -33,15 +33,27 @@ func InstanceDataSourceSchema(ctx context.Context) schema.Schema { }, "connection_info": schema.SingleNestedAttribute{ Attributes: map[string]schema.Attribute{ - "host": schema.StringAttribute{ + "write": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "host": schema.StringAttribute{ + Computed: true, + Description: "The host of the instance.", + MarkdownDescription: "The host of the instance.", + }, + "port": schema.Int64Attribute{ + Computed: true, + Description: "The port of the instance.", + MarkdownDescription: "The port of the instance.", + }, + }, + CustomType: WriteType{ + ObjectType: types.ObjectType{ + AttrTypes: WriteValue{}.AttributeTypes(ctx), + }, + }, Computed: true, - Description: "The host of the instance.", - MarkdownDescription: "The host of the instance.", - }, - "port": schema.Int64Attribute{ - Computed: true, - Description: "The port of the instance.", - MarkdownDescription: "The port of the instance.", + Description: "The DNS name and port in the instance overview", + MarkdownDescription: "The DNS name and port in the instance overview", }, }, CustomType: ConnectionInfoType{ @@ -50,8 +62,8 @@ func InstanceDataSourceSchema(ctx context.Context) schema.Schema { }, }, Computed: true, - Description: "The DNS name and port in the instance overview", - MarkdownDescription: "The DNS name and port in the instance overview", + Description: "The connection information of the instance", + MarkdownDescription: "The connection information of the instance", }, "encryption": schema.SingleNestedAttribute{ Attributes: map[string]schema.Attribute{ @@ -243,40 +255,22 @@ func (t ConnectionInfoType) ValueFromObject(ctx context.Context, in basetypes.Ob attributes := in.Attributes() - hostAttribute, ok := attributes["host"] + writeAttribute, ok := attributes["write"] if !ok { diags.AddError( "Attribute Missing", - `host is missing from object`) + `write is missing from object`) return nil, diags } - hostVal, ok := hostAttribute.(basetypes.StringValue) + writeVal, ok := writeAttribute.(basetypes.ObjectValue) if !ok { diags.AddError( "Attribute Wrong Type", - fmt.Sprintf(`host expected to be basetypes.StringValue, was: %T`, hostAttribute)) - } - - portAttribute, ok := attributes["port"] - - if !ok { - diags.AddError( - "Attribute Missing", - `port is missing from object`) - - return nil, diags - } - - portVal, ok := portAttribute.(basetypes.Int64Value) - - if !ok { - diags.AddError( - "Attribute Wrong Type", - fmt.Sprintf(`port expected to be basetypes.Int64Value, was: %T`, portAttribute)) + fmt.Sprintf(`write expected to be basetypes.ObjectValue, was: %T`, writeAttribute)) } if diags.HasError() { @@ -284,8 +278,7 @@ func (t ConnectionInfoType) ValueFromObject(ctx context.Context, in basetypes.Ob } return ConnectionInfoValue{ - Host: hostVal, - Port: portVal, + Write: writeVal, state: attr.ValueStateKnown, }, diags } @@ -353,40 +346,22 @@ func NewConnectionInfoValue(attributeTypes map[string]attr.Type, attributes map[ return NewConnectionInfoValueUnknown(), diags } - hostAttribute, ok := attributes["host"] + writeAttribute, ok := attributes["write"] if !ok { diags.AddError( "Attribute Missing", - `host is missing from object`) + `write is missing from object`) return NewConnectionInfoValueUnknown(), diags } - hostVal, ok := hostAttribute.(basetypes.StringValue) + writeVal, ok := writeAttribute.(basetypes.ObjectValue) if !ok { diags.AddError( "Attribute Wrong Type", - fmt.Sprintf(`host expected to be basetypes.StringValue, was: %T`, hostAttribute)) - } - - portAttribute, ok := attributes["port"] - - if !ok { - diags.AddError( - "Attribute Missing", - `port is missing from object`) - - return NewConnectionInfoValueUnknown(), diags - } - - portVal, ok := portAttribute.(basetypes.Int64Value) - - if !ok { - diags.AddError( - "Attribute Wrong Type", - fmt.Sprintf(`port expected to be basetypes.Int64Value, was: %T`, portAttribute)) + fmt.Sprintf(`write expected to be basetypes.ObjectValue, was: %T`, writeAttribute)) } if diags.HasError() { @@ -394,8 +369,7 @@ func NewConnectionInfoValue(attributeTypes map[string]attr.Type, attributes map[ } return ConnectionInfoValue{ - Host: hostVal, - Port: portVal, + Write: writeVal, state: attr.ValueStateKnown, }, diags } @@ -468,12 +442,401 @@ func (t ConnectionInfoType) ValueType(ctx context.Context) attr.Value { var _ basetypes.ObjectValuable = ConnectionInfoValue{} type ConnectionInfoValue struct { + Write basetypes.ObjectValue `tfsdk:"write"` + state attr.ValueState +} + +func (v ConnectionInfoValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 1) + + var val tftypes.Value + var err error + + attrTypes["write"] = basetypes.ObjectType{ + AttrTypes: WriteValue{}.AttributeTypes(ctx), + }.TerraformType(ctx) + + objectType := tftypes.Object{AttributeTypes: attrTypes} + + switch v.state { + case attr.ValueStateKnown: + vals := make(map[string]tftypes.Value, 1) + + val, err = v.Write.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["write"] = val + + if err := tftypes.ValidateValue(objectType, vals); err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + return tftypes.NewValue(objectType, vals), nil + case attr.ValueStateNull: + return tftypes.NewValue(objectType, nil), nil + case attr.ValueStateUnknown: + return tftypes.NewValue(objectType, tftypes.UnknownValue), nil + default: + panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state)) + } +} + +func (v ConnectionInfoValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v ConnectionInfoValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v ConnectionInfoValue) String() string { + return "ConnectionInfoValue" +} + +func (v ConnectionInfoValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + var diags diag.Diagnostics + + var write basetypes.ObjectValue + + if v.Write.IsNull() { + write = types.ObjectNull( + WriteValue{}.AttributeTypes(ctx), + ) + } + + if v.Write.IsUnknown() { + write = types.ObjectUnknown( + WriteValue{}.AttributeTypes(ctx), + ) + } + + if !v.Write.IsNull() && !v.Write.IsUnknown() { + write = types.ObjectValueMust( + WriteValue{}.AttributeTypes(ctx), + v.Write.Attributes(), + ) + } + + attributeTypes := map[string]attr.Type{ + "write": basetypes.ObjectType{ + AttrTypes: WriteValue{}.AttributeTypes(ctx), + }, + } + + if v.IsNull() { + return types.ObjectNull(attributeTypes), diags + } + + if v.IsUnknown() { + return types.ObjectUnknown(attributeTypes), diags + } + + objVal, diags := types.ObjectValue( + attributeTypes, + map[string]attr.Value{ + "write": write, + }) + + return objVal, diags +} + +func (v ConnectionInfoValue) Equal(o attr.Value) bool { + other, ok := o.(ConnectionInfoValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.Write.Equal(other.Write) { + return false + } + + return true +} + +func (v ConnectionInfoValue) Type(ctx context.Context) attr.Type { + return ConnectionInfoType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v ConnectionInfoValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "write": basetypes.ObjectType{ + AttrTypes: WriteValue{}.AttributeTypes(ctx), + }, + } +} + +var _ basetypes.ObjectTypable = WriteType{} + +type WriteType struct { + basetypes.ObjectType +} + +func (t WriteType) Equal(o attr.Type) bool { + other, ok := o.(WriteType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t WriteType) String() string { + return "WriteType" +} + +func (t WriteType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + hostAttribute, ok := attributes["host"] + + if !ok { + diags.AddError( + "Attribute Missing", + `host is missing from object`) + + return nil, diags + } + + hostVal, ok := hostAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`host expected to be basetypes.StringValue, was: %T`, hostAttribute)) + } + + portAttribute, ok := attributes["port"] + + if !ok { + diags.AddError( + "Attribute Missing", + `port is missing from object`) + + return nil, diags + } + + portVal, ok := portAttribute.(basetypes.Int64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`port expected to be basetypes.Int64Value, was: %T`, portAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return WriteValue{ + Host: hostVal, + Port: portVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewWriteValueNull() WriteValue { + return WriteValue{ + state: attr.ValueStateNull, + } +} + +func NewWriteValueUnknown() WriteValue { + return WriteValue{ + state: attr.ValueStateUnknown, + } +} + +func NewWriteValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (WriteValue, diag.Diagnostics) { + var diags diag.Diagnostics + + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521 + ctx := context.Background() + + for name, attributeType := range attributeTypes { + attribute, ok := attributes[name] + + if !ok { + diags.AddError( + "Missing WriteValue Attribute Value", + "While creating a WriteValue value, a missing attribute value was detected. "+ + "A WriteValue must contain values for all attributes, even if null or unknown. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("WriteValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid WriteValue Attribute Type", + "While creating a WriteValue value, an invalid attribute value was detected. "+ + "A WriteValue must use a matching attribute type for the value. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("WriteValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ + fmt.Sprintf("WriteValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra WriteValue Attribute Value", + "While creating a WriteValue value, an extra attribute value was detected. "+ + "A WriteValue must not contain values beyond the expected attribute types. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Extra WriteValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewWriteValueUnknown(), diags + } + + hostAttribute, ok := attributes["host"] + + if !ok { + diags.AddError( + "Attribute Missing", + `host is missing from object`) + + return NewWriteValueUnknown(), diags + } + + hostVal, ok := hostAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`host expected to be basetypes.StringValue, was: %T`, hostAttribute)) + } + + portAttribute, ok := attributes["port"] + + if !ok { + diags.AddError( + "Attribute Missing", + `port is missing from object`) + + return NewWriteValueUnknown(), diags + } + + portVal, ok := portAttribute.(basetypes.Int64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`port expected to be basetypes.Int64Value, was: %T`, portAttribute)) + } + + if diags.HasError() { + return NewWriteValueUnknown(), diags + } + + return WriteValue{ + Host: hostVal, + Port: portVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewWriteValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) WriteValue { + object, diags := NewWriteValue(attributeTypes, attributes) + + if diags.HasError() { + // This could potentially be added to the diag package. + diagsStrings := make([]string, 0, len(diags)) + + for _, diagnostic := range diags { + diagsStrings = append(diagsStrings, fmt.Sprintf( + "%s | %s | %s", + diagnostic.Severity(), + diagnostic.Summary(), + diagnostic.Detail())) + } + + panic("NewWriteValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t WriteType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewWriteValueNull(), nil + } + + if !in.Type().Equal(t.TerraformType(ctx)) { + return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) + } + + if !in.IsKnown() { + return NewWriteValueUnknown(), nil + } + + if in.IsNull() { + return NewWriteValueNull(), nil + } + + attributes := map[string]attr.Value{} + + val := map[string]tftypes.Value{} + + err := in.As(&val) + + if err != nil { + return nil, err + } + + for k, v := range val { + a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) + + if err != nil { + return nil, err + } + + attributes[k] = a + } + + return NewWriteValueMust(WriteValue{}.AttributeTypes(ctx), attributes), nil +} + +func (t WriteType) ValueType(ctx context.Context) attr.Value { + return WriteValue{} +} + +var _ basetypes.ObjectValuable = WriteValue{} + +type WriteValue struct { Host basetypes.StringValue `tfsdk:"host"` Port basetypes.Int64Value `tfsdk:"port"` state attr.ValueState } -func (v ConnectionInfoValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { +func (v WriteValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { attrTypes := make(map[string]tftypes.Type, 2) var val tftypes.Value @@ -518,19 +881,19 @@ func (v ConnectionInfoValue) ToTerraformValue(ctx context.Context) (tftypes.Valu } } -func (v ConnectionInfoValue) IsNull() bool { +func (v WriteValue) IsNull() bool { return v.state == attr.ValueStateNull } -func (v ConnectionInfoValue) IsUnknown() bool { +func (v WriteValue) IsUnknown() bool { return v.state == attr.ValueStateUnknown } -func (v ConnectionInfoValue) String() string { - return "ConnectionInfoValue" +func (v WriteValue) String() string { + return "WriteValue" } -func (v ConnectionInfoValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { +func (v WriteValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { var diags diag.Diagnostics attributeTypes := map[string]attr.Type{ @@ -556,8 +919,8 @@ func (v ConnectionInfoValue) ToObjectValue(ctx context.Context) (basetypes.Objec return objVal, diags } -func (v ConnectionInfoValue) Equal(o attr.Value) bool { - other, ok := o.(ConnectionInfoValue) +func (v WriteValue) Equal(o attr.Value) bool { + other, ok := o.(WriteValue) if !ok { return false @@ -582,15 +945,15 @@ func (v ConnectionInfoValue) Equal(o attr.Value) bool { return true } -func (v ConnectionInfoValue) Type(ctx context.Context) attr.Type { - return ConnectionInfoType{ +func (v WriteValue) Type(ctx context.Context) attr.Type { + return WriteType{ basetypes.ObjectType{ AttrTypes: v.AttributeTypes(ctx), }, } } -func (v ConnectionInfoValue) AttributeTypes(ctx context.Context) map[string]attr.Type { +func (v WriteValue) AttributeTypes(ctx context.Context) map[string]attr.Type { return map[string]attr.Type{ "host": basetypes.StringType{}, "port": basetypes.Int64Type{}, diff --git a/stackit/internal/services/postgresflexalpha/instance/functions.go b/stackit/internal/services/postgresflexalpha/instance/functions.go index 133be314..e9c9c9c2 100644 --- a/stackit/internal/services/postgresflexalpha/instance/functions.go +++ b/stackit/internal/services/postgresflexalpha/instance/functions.go @@ -33,9 +33,9 @@ func mapGetInstanceResponseToModel( ) } - isConnectionInfoIncomplete := resp.ConnectionInfo == nil || - resp.ConnectionInfo.Host == nil || *resp.ConnectionInfo.Host == "" || - resp.ConnectionInfo.Port == nil || *resp.ConnectionInfo.Port == 0 + isConnectionInfoIncomplete := resp.ConnectionInfo == nil || resp.ConnectionInfo.Write == nil || + resp.ConnectionInfo.Write.Host == nil || *resp.ConnectionInfo.Write.Host == "" || + resp.ConnectionInfo.Write.Port == nil || *resp.ConnectionInfo.Write.Port == 0 if isConnectionInfoIncomplete { m.ConnectionInfo = postgresflexalpharesource.NewConnectionInfoValueNull() @@ -43,22 +43,17 @@ func mapGetInstanceResponseToModel( m.ConnectionInfo = postgresflexalpharesource.NewConnectionInfoValueMust( postgresflexalpharesource.ConnectionInfoValue{}.AttributeTypes(ctx), map[string]attr.Value{ - "host": types.StringPointerValue(resp.ConnectionInfo.Host), - "port": types.Int64PointerValue(resp.ConnectionInfo.Port), + "write": postgresflexalpharesource.NewWriteValueMust( + postgresflexalpharesource.WriteValue{}.AttributeTypes(ctx), + map[string]attr.Value{ + "host": types.StringPointerValue(resp.ConnectionInfo.Write.Host), + "port": types.Int64PointerValue(resp.ConnectionInfo.Write.Port), + }, + ), }, ) } - m.ConnectionInfo.Host = types.StringValue("") - if host, ok := resp.ConnectionInfo.GetHostOk(); ok { - m.ConnectionInfo.Host = types.StringValue(host) - } - - m.ConnectionInfo.Port = types.Int64Value(0) - if port, ok := resp.ConnectionInfo.GetPortOk(); ok { - m.ConnectionInfo.Port = types.Int64Value(port) - } - m.FlavorId = types.StringValue(resp.GetFlavorId()) if m.Id.IsNull() || m.Id.IsUnknown() { m.Id = utils.BuildInternalTerraformId( @@ -164,9 +159,9 @@ func mapGetDataInstanceResponseToModel( } func handleConnectionInfo(ctx context.Context, m *dataSourceModel, resp *postgresflex.GetInstanceResponse) { - isConnectionInfoIncomplete := resp.ConnectionInfo == nil || - resp.ConnectionInfo.Host == nil || *resp.ConnectionInfo.Host == "" || - resp.ConnectionInfo.Port == nil || *resp.ConnectionInfo.Port == 0 + isConnectionInfoIncomplete := resp.ConnectionInfo == nil || resp.ConnectionInfo.Write == nil || + resp.ConnectionInfo.Write.Host == nil || *resp.ConnectionInfo.Write.Host == "" || + resp.ConnectionInfo.Write.Port == nil || *resp.ConnectionInfo.Write.Port == 0 if isConnectionInfoIncomplete { m.ConnectionInfo = postgresflexalphadatasource.NewConnectionInfoValueNull() @@ -174,8 +169,13 @@ func handleConnectionInfo(ctx context.Context, m *dataSourceModel, resp *postgre m.ConnectionInfo = postgresflexalphadatasource.NewConnectionInfoValueMust( postgresflexalphadatasource.ConnectionInfoValue{}.AttributeTypes(ctx), map[string]attr.Value{ - "host": types.StringPointerValue(resp.ConnectionInfo.Host), - "port": types.Int64PointerValue(resp.ConnectionInfo.Port), + "write": postgresflexalphadatasource.NewWriteValueMust( + postgresflexalphadatasource.WriteValue{}.AttributeTypes(ctx), + map[string]attr.Value{ + "host": types.StringPointerValue(resp.ConnectionInfo.Write.Host), + "port": types.Int64PointerValue(resp.ConnectionInfo.Write.Port), + }, + ), }, ) } diff --git a/stackit/internal/services/postgresflexalpha/instance/resources_gen/instance_resource_gen.go b/stackit/internal/services/postgresflexalpha/instance/resources_gen/instance_resource_gen.go index 35d31cbc..25ad092b 100644 --- a/stackit/internal/services/postgresflexalpha/instance/resources_gen/instance_resource_gen.go +++ b/stackit/internal/services/postgresflexalpha/instance/resources_gen/instance_resource_gen.go @@ -35,15 +35,27 @@ func InstanceResourceSchema(ctx context.Context) schema.Schema { }, "connection_info": schema.SingleNestedAttribute{ Attributes: map[string]schema.Attribute{ - "host": schema.StringAttribute{ + "write": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "host": schema.StringAttribute{ + Computed: true, + Description: "The host of the instance.", + MarkdownDescription: "The host of the instance.", + }, + "port": schema.Int64Attribute{ + Computed: true, + Description: "The port of the instance.", + MarkdownDescription: "The port of the instance.", + }, + }, + CustomType: WriteType{ + ObjectType: types.ObjectType{ + AttrTypes: WriteValue{}.AttributeTypes(ctx), + }, + }, Computed: true, - Description: "The host of the instance.", - MarkdownDescription: "The host of the instance.", - }, - "port": schema.Int64Attribute{ - Computed: true, - Description: "The port of the instance.", - MarkdownDescription: "The port of the instance.", + Description: "The DNS name and port in the instance overview", + MarkdownDescription: "The DNS name and port in the instance overview", }, }, CustomType: ConnectionInfoType{ @@ -52,8 +64,8 @@ func InstanceResourceSchema(ctx context.Context) schema.Schema { }, }, Computed: true, - Description: "The DNS name and port in the instance overview", - MarkdownDescription: "The DNS name and port in the instance overview", + Description: "The connection information of the instance", + MarkdownDescription: "The connection information of the instance", }, "encryption": schema.SingleNestedAttribute{ Attributes: map[string]schema.Attribute{ @@ -263,40 +275,22 @@ func (t ConnectionInfoType) ValueFromObject(ctx context.Context, in basetypes.Ob attributes := in.Attributes() - hostAttribute, ok := attributes["host"] + writeAttribute, ok := attributes["write"] if !ok { diags.AddError( "Attribute Missing", - `host is missing from object`) + `write is missing from object`) return nil, diags } - hostVal, ok := hostAttribute.(basetypes.StringValue) + writeVal, ok := writeAttribute.(basetypes.ObjectValue) if !ok { diags.AddError( "Attribute Wrong Type", - fmt.Sprintf(`host expected to be basetypes.StringValue, was: %T`, hostAttribute)) - } - - portAttribute, ok := attributes["port"] - - if !ok { - diags.AddError( - "Attribute Missing", - `port is missing from object`) - - return nil, diags - } - - portVal, ok := portAttribute.(basetypes.Int64Value) - - if !ok { - diags.AddError( - "Attribute Wrong Type", - fmt.Sprintf(`port expected to be basetypes.Int64Value, was: %T`, portAttribute)) + fmt.Sprintf(`write expected to be basetypes.ObjectValue, was: %T`, writeAttribute)) } if diags.HasError() { @@ -304,8 +298,7 @@ func (t ConnectionInfoType) ValueFromObject(ctx context.Context, in basetypes.Ob } return ConnectionInfoValue{ - Host: hostVal, - Port: portVal, + Write: writeVal, state: attr.ValueStateKnown, }, diags } @@ -373,40 +366,22 @@ func NewConnectionInfoValue(attributeTypes map[string]attr.Type, attributes map[ return NewConnectionInfoValueUnknown(), diags } - hostAttribute, ok := attributes["host"] + writeAttribute, ok := attributes["write"] if !ok { diags.AddError( "Attribute Missing", - `host is missing from object`) + `write is missing from object`) return NewConnectionInfoValueUnknown(), diags } - hostVal, ok := hostAttribute.(basetypes.StringValue) + writeVal, ok := writeAttribute.(basetypes.ObjectValue) if !ok { diags.AddError( "Attribute Wrong Type", - fmt.Sprintf(`host expected to be basetypes.StringValue, was: %T`, hostAttribute)) - } - - portAttribute, ok := attributes["port"] - - if !ok { - diags.AddError( - "Attribute Missing", - `port is missing from object`) - - return NewConnectionInfoValueUnknown(), diags - } - - portVal, ok := portAttribute.(basetypes.Int64Value) - - if !ok { - diags.AddError( - "Attribute Wrong Type", - fmt.Sprintf(`port expected to be basetypes.Int64Value, was: %T`, portAttribute)) + fmt.Sprintf(`write expected to be basetypes.ObjectValue, was: %T`, writeAttribute)) } if diags.HasError() { @@ -414,8 +389,7 @@ func NewConnectionInfoValue(attributeTypes map[string]attr.Type, attributes map[ } return ConnectionInfoValue{ - Host: hostVal, - Port: portVal, + Write: writeVal, state: attr.ValueStateKnown, }, diags } @@ -488,12 +462,401 @@ func (t ConnectionInfoType) ValueType(ctx context.Context) attr.Value { var _ basetypes.ObjectValuable = ConnectionInfoValue{} type ConnectionInfoValue struct { + Write basetypes.ObjectValue `tfsdk:"write"` + state attr.ValueState +} + +func (v ConnectionInfoValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 1) + + var val tftypes.Value + var err error + + attrTypes["write"] = basetypes.ObjectType{ + AttrTypes: WriteValue{}.AttributeTypes(ctx), + }.TerraformType(ctx) + + objectType := tftypes.Object{AttributeTypes: attrTypes} + + switch v.state { + case attr.ValueStateKnown: + vals := make(map[string]tftypes.Value, 1) + + val, err = v.Write.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["write"] = val + + if err := tftypes.ValidateValue(objectType, vals); err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + return tftypes.NewValue(objectType, vals), nil + case attr.ValueStateNull: + return tftypes.NewValue(objectType, nil), nil + case attr.ValueStateUnknown: + return tftypes.NewValue(objectType, tftypes.UnknownValue), nil + default: + panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state)) + } +} + +func (v ConnectionInfoValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v ConnectionInfoValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v ConnectionInfoValue) String() string { + return "ConnectionInfoValue" +} + +func (v ConnectionInfoValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + var diags diag.Diagnostics + + var write basetypes.ObjectValue + + if v.Write.IsNull() { + write = types.ObjectNull( + WriteValue{}.AttributeTypes(ctx), + ) + } + + if v.Write.IsUnknown() { + write = types.ObjectUnknown( + WriteValue{}.AttributeTypes(ctx), + ) + } + + if !v.Write.IsNull() && !v.Write.IsUnknown() { + write = types.ObjectValueMust( + WriteValue{}.AttributeTypes(ctx), + v.Write.Attributes(), + ) + } + + attributeTypes := map[string]attr.Type{ + "write": basetypes.ObjectType{ + AttrTypes: WriteValue{}.AttributeTypes(ctx), + }, + } + + if v.IsNull() { + return types.ObjectNull(attributeTypes), diags + } + + if v.IsUnknown() { + return types.ObjectUnknown(attributeTypes), diags + } + + objVal, diags := types.ObjectValue( + attributeTypes, + map[string]attr.Value{ + "write": write, + }) + + return objVal, diags +} + +func (v ConnectionInfoValue) Equal(o attr.Value) bool { + other, ok := o.(ConnectionInfoValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.Write.Equal(other.Write) { + return false + } + + return true +} + +func (v ConnectionInfoValue) Type(ctx context.Context) attr.Type { + return ConnectionInfoType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v ConnectionInfoValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "write": basetypes.ObjectType{ + AttrTypes: WriteValue{}.AttributeTypes(ctx), + }, + } +} + +var _ basetypes.ObjectTypable = WriteType{} + +type WriteType struct { + basetypes.ObjectType +} + +func (t WriteType) Equal(o attr.Type) bool { + other, ok := o.(WriteType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t WriteType) String() string { + return "WriteType" +} + +func (t WriteType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + hostAttribute, ok := attributes["host"] + + if !ok { + diags.AddError( + "Attribute Missing", + `host is missing from object`) + + return nil, diags + } + + hostVal, ok := hostAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`host expected to be basetypes.StringValue, was: %T`, hostAttribute)) + } + + portAttribute, ok := attributes["port"] + + if !ok { + diags.AddError( + "Attribute Missing", + `port is missing from object`) + + return nil, diags + } + + portVal, ok := portAttribute.(basetypes.Int64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`port expected to be basetypes.Int64Value, was: %T`, portAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return WriteValue{ + Host: hostVal, + Port: portVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewWriteValueNull() WriteValue { + return WriteValue{ + state: attr.ValueStateNull, + } +} + +func NewWriteValueUnknown() WriteValue { + return WriteValue{ + state: attr.ValueStateUnknown, + } +} + +func NewWriteValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (WriteValue, diag.Diagnostics) { + var diags diag.Diagnostics + + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521 + ctx := context.Background() + + for name, attributeType := range attributeTypes { + attribute, ok := attributes[name] + + if !ok { + diags.AddError( + "Missing WriteValue Attribute Value", + "While creating a WriteValue value, a missing attribute value was detected. "+ + "A WriteValue must contain values for all attributes, even if null or unknown. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("WriteValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid WriteValue Attribute Type", + "While creating a WriteValue value, an invalid attribute value was detected. "+ + "A WriteValue must use a matching attribute type for the value. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("WriteValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ + fmt.Sprintf("WriteValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra WriteValue Attribute Value", + "While creating a WriteValue value, an extra attribute value was detected. "+ + "A WriteValue must not contain values beyond the expected attribute types. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Extra WriteValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewWriteValueUnknown(), diags + } + + hostAttribute, ok := attributes["host"] + + if !ok { + diags.AddError( + "Attribute Missing", + `host is missing from object`) + + return NewWriteValueUnknown(), diags + } + + hostVal, ok := hostAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`host expected to be basetypes.StringValue, was: %T`, hostAttribute)) + } + + portAttribute, ok := attributes["port"] + + if !ok { + diags.AddError( + "Attribute Missing", + `port is missing from object`) + + return NewWriteValueUnknown(), diags + } + + portVal, ok := portAttribute.(basetypes.Int64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`port expected to be basetypes.Int64Value, was: %T`, portAttribute)) + } + + if diags.HasError() { + return NewWriteValueUnknown(), diags + } + + return WriteValue{ + Host: hostVal, + Port: portVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewWriteValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) WriteValue { + object, diags := NewWriteValue(attributeTypes, attributes) + + if diags.HasError() { + // This could potentially be added to the diag package. + diagsStrings := make([]string, 0, len(diags)) + + for _, diagnostic := range diags { + diagsStrings = append(diagsStrings, fmt.Sprintf( + "%s | %s | %s", + diagnostic.Severity(), + diagnostic.Summary(), + diagnostic.Detail())) + } + + panic("NewWriteValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t WriteType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewWriteValueNull(), nil + } + + if !in.Type().Equal(t.TerraformType(ctx)) { + return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) + } + + if !in.IsKnown() { + return NewWriteValueUnknown(), nil + } + + if in.IsNull() { + return NewWriteValueNull(), nil + } + + attributes := map[string]attr.Value{} + + val := map[string]tftypes.Value{} + + err := in.As(&val) + + if err != nil { + return nil, err + } + + for k, v := range val { + a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) + + if err != nil { + return nil, err + } + + attributes[k] = a + } + + return NewWriteValueMust(WriteValue{}.AttributeTypes(ctx), attributes), nil +} + +func (t WriteType) ValueType(ctx context.Context) attr.Value { + return WriteValue{} +} + +var _ basetypes.ObjectValuable = WriteValue{} + +type WriteValue struct { Host basetypes.StringValue `tfsdk:"host"` Port basetypes.Int64Value `tfsdk:"port"` state attr.ValueState } -func (v ConnectionInfoValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { +func (v WriteValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { attrTypes := make(map[string]tftypes.Type, 2) var val tftypes.Value @@ -538,19 +901,19 @@ func (v ConnectionInfoValue) ToTerraformValue(ctx context.Context) (tftypes.Valu } } -func (v ConnectionInfoValue) IsNull() bool { +func (v WriteValue) IsNull() bool { return v.state == attr.ValueStateNull } -func (v ConnectionInfoValue) IsUnknown() bool { +func (v WriteValue) IsUnknown() bool { return v.state == attr.ValueStateUnknown } -func (v ConnectionInfoValue) String() string { - return "ConnectionInfoValue" +func (v WriteValue) String() string { + return "WriteValue" } -func (v ConnectionInfoValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { +func (v WriteValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { var diags diag.Diagnostics attributeTypes := map[string]attr.Type{ @@ -576,8 +939,8 @@ func (v ConnectionInfoValue) ToObjectValue(ctx context.Context) (basetypes.Objec return objVal, diags } -func (v ConnectionInfoValue) Equal(o attr.Value) bool { - other, ok := o.(ConnectionInfoValue) +func (v WriteValue) Equal(o attr.Value) bool { + other, ok := o.(WriteValue) if !ok { return false @@ -602,15 +965,15 @@ func (v ConnectionInfoValue) Equal(o attr.Value) bool { return true } -func (v ConnectionInfoValue) Type(ctx context.Context) attr.Type { - return ConnectionInfoType{ +func (v WriteValue) Type(ctx context.Context) attr.Type { + return WriteType{ basetypes.ObjectType{ AttrTypes: v.AttributeTypes(ctx), }, } } -func (v ConnectionInfoValue) AttributeTypes(ctx context.Context) map[string]attr.Type { +func (v WriteValue) AttributeTypes(ctx context.Context) map[string]attr.Type { return map[string]attr.Type{ "host": basetypes.StringType{}, "port": basetypes.Int64Type{},