diff --git a/internal/mapper/attrmapper/data_source_attributes.go b/internal/mapper/attrmapper/data_source_attributes.go index e1dc435..cda239c 100644 --- a/internal/mapper/attrmapper/data_source_attributes.go +++ b/internal/mapper/attrmapper/data_source_attributes.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-codegen-openapi/internal/explorer" "github.com/hashicorp/terraform-plugin-codegen-spec/datasource" + "github.com/hashicorp/terraform-plugin-codegen-spec/schema" ) type DataSourceAttribute interface { @@ -20,6 +21,7 @@ type DataSourceAttribute interface { type DataSourceNestedAttribute interface { ApplyNestedOverride([]string, explorer.Override) (DataSourceAttribute, error) + NestedMerge([]string, DataSourceAttribute, schema.ComputedOptionalRequired) (DataSourceAttribute, error) } type DataSourceAttributes []DataSourceAttribute @@ -58,6 +60,46 @@ func (targetSlice DataSourceAttributes) Merge(mergeSlices ...DataSourceAttribute return targetSlice, errResult } +func (targetSlice DataSourceAttributes) MergeAttribute(path []string, attribute DataSourceAttribute, intermediateComputability schema.ComputedOptionalRequired) (DataSourceAttributes, error) { + var errResult error + if len(path) == 0 { + return targetSlice, errResult + } + for i, target := range targetSlice { + if target.GetName() == path[0] { + + if len(path) > 1 { + nestedTarget, ok := target.(DataSourceNestedAttribute) + if !ok { + // TODO: error? there is a nested override for an attribute that is not a nested type + break + } + + // The attribute we need to override is deeper nested, move up + nextPath := path[1:] + + overriddenTarget, err := nestedTarget.NestedMerge(nextPath, attribute, intermediateComputability) + errResult = errors.Join(errResult, err) + + targetSlice[i] = overriddenTarget + + } else { + // No more path to traverse, apply merge, bidirectional + overriddenTarget, err := attribute.Merge(target) + errResult = errors.Join(errResult, err) + overriddenTarget, err = overriddenTarget.Merge(attribute) + errResult = errors.Join(errResult, err) + + targetSlice[i] = overriddenTarget + } + + break + } + } + + return targetSlice, errResult +} + func (attributes DataSourceAttributes) ToSpec() []datasource.Attribute { specAttributes := make([]datasource.Attribute, 0, len(attributes)) for _, attribute := range attributes { diff --git a/internal/mapper/attrmapper/list_nested.go b/internal/mapper/attrmapper/list_nested.go index 68e0737..a1cf3ab 100644 --- a/internal/mapper/attrmapper/list_nested.go +++ b/internal/mapper/attrmapper/list_nested.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-codegen-spec/datasource" "github.com/hashicorp/terraform-plugin-codegen-spec/provider" "github.com/hashicorp/terraform-plugin-codegen-spec/resource" + "github.com/hashicorp/terraform-plugin-codegen-spec/schema" ) type ResourceListNestedAttribute struct { @@ -50,6 +51,16 @@ func (a *ResourceListNestedAttribute) ApplyNestedOverride(path []string, overrid return a, err } +func (a *ResourceListNestedAttribute) NestedMerge(path []string, attribute ResourceAttribute, intermediateComputability schema.ComputedOptionalRequired) (ResourceAttribute, error) { + var err error + a.NestedObject.Attributes, err = a.NestedObject.Attributes.MergeAttribute(path, attribute, intermediateComputability) + if err == nil { + a.ComputedOptionalRequired = intermediateComputability + } + + return a, err +} + func (a *ResourceListNestedAttribute) ToSpec() resource.Attribute { a.ListNestedAttribute.NestedObject = resource.NestedAttributeObject{ Attributes: a.NestedObject.Attributes.ToSpec(), @@ -100,6 +111,16 @@ func (a *DataSourceListNestedAttribute) ApplyNestedOverride(path []string, overr return a, err } +func (a *DataSourceListNestedAttribute) NestedMerge(path []string, attribute DataSourceAttribute, intermediateComputability schema.ComputedOptionalRequired) (DataSourceAttribute, error) { + var err error + a.NestedObject.Attributes, err = a.NestedObject.Attributes.MergeAttribute(path, attribute, intermediateComputability) + if err == nil { + a.ComputedOptionalRequired = intermediateComputability + } + + return a, err +} + func (a *DataSourceListNestedAttribute) ToSpec() datasource.Attribute { a.ListNestedAttribute.NestedObject = datasource.NestedAttributeObject{ Attributes: a.NestedObject.Attributes.ToSpec(), diff --git a/internal/mapper/attrmapper/map_nested.go b/internal/mapper/attrmapper/map_nested.go index 4ba7f40..2950f22 100644 --- a/internal/mapper/attrmapper/map_nested.go +++ b/internal/mapper/attrmapper/map_nested.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-codegen-spec/datasource" "github.com/hashicorp/terraform-plugin-codegen-spec/provider" "github.com/hashicorp/terraform-plugin-codegen-spec/resource" + "github.com/hashicorp/terraform-plugin-codegen-spec/schema" ) type ResourceMapNestedAttribute struct { @@ -50,6 +51,16 @@ func (a *ResourceMapNestedAttribute) ApplyNestedOverride(path []string, override return a, err } +func (a *ResourceMapNestedAttribute) NestedMerge(path []string, attribute ResourceAttribute, intermediateComputability schema.ComputedOptionalRequired) (ResourceAttribute, error) { + var err error + a.NestedObject.Attributes, err = a.NestedObject.Attributes.MergeAttribute(path, attribute, intermediateComputability) + if err == nil { + a.ComputedOptionalRequired = intermediateComputability + } + + return a, err +} + func (a *ResourceMapNestedAttribute) ToSpec() resource.Attribute { a.MapNestedAttribute.NestedObject = resource.NestedAttributeObject{ Attributes: a.NestedObject.Attributes.ToSpec(), @@ -100,6 +111,16 @@ func (a *DataSourceMapNestedAttribute) ApplyNestedOverride(path []string, overri return a, err } +func (a *DataSourceMapNestedAttribute) NestedMerge(path []string, attribute DataSourceAttribute, intermediateComputability schema.ComputedOptionalRequired) (DataSourceAttribute, error) { + var err error + a.NestedObject.Attributes, err = a.NestedObject.Attributes.MergeAttribute(path, attribute, intermediateComputability) + if err == nil { + a.ComputedOptionalRequired = intermediateComputability + } + + return a, err +} + func (a *DataSourceMapNestedAttribute) ToSpec() datasource.Attribute { a.MapNestedAttribute.NestedObject = datasource.NestedAttributeObject{ Attributes: a.NestedObject.Attributes.ToSpec(), diff --git a/internal/mapper/attrmapper/resource_attributes.go b/internal/mapper/attrmapper/resource_attributes.go index 6c772c2..53268f1 100644 --- a/internal/mapper/attrmapper/resource_attributes.go +++ b/internal/mapper/attrmapper/resource_attributes.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-codegen-openapi/internal/explorer" "github.com/hashicorp/terraform-plugin-codegen-spec/resource" + "github.com/hashicorp/terraform-plugin-codegen-spec/schema" ) type ResourceAttribute interface { @@ -20,6 +21,7 @@ type ResourceAttribute interface { type ResourceNestedAttribute interface { ApplyNestedOverride([]string, explorer.Override) (ResourceAttribute, error) + NestedMerge([]string, ResourceAttribute, schema.ComputedOptionalRequired) (ResourceAttribute, error) } type ResourceAttributes []ResourceAttribute @@ -58,6 +60,46 @@ func (targetSlice ResourceAttributes) Merge(mergeSlices ...ResourceAttributes) ( return targetSlice, errResult } +func (targetSlice ResourceAttributes) MergeAttribute(path []string, attribute ResourceAttribute, intermediateComputability schema.ComputedOptionalRequired) (ResourceAttributes, error) { + var errResult error + if len(path) == 0 { + return targetSlice, errResult + } + for i, target := range targetSlice { + if target.GetName() == path[0] { + + if len(path) > 1 { + nestedTarget, ok := target.(ResourceNestedAttribute) + if !ok { + // TODO: error? there is a nested override for an attribute that is not a nested type + break + } + + // The attribute we need to override is deeper nested, move up + nextPath := path[1:] + + overriddenTarget, err := nestedTarget.NestedMerge(nextPath, attribute, intermediateComputability) + errResult = errors.Join(errResult, err) + + targetSlice[i] = overriddenTarget + + } else { + // No more path to traverse, apply merge, bidirectional + overriddenTarget, err := attribute.Merge(target) + errResult = errors.Join(errResult, err) + overriddenTarget, err = overriddenTarget.Merge(attribute) + errResult = errors.Join(errResult, err) + + targetSlice[i] = overriddenTarget + } + + break + } + } + + return targetSlice, errResult +} + func (attributes ResourceAttributes) ToSpec() []resource.Attribute { specAttributes := make([]resource.Attribute, 0, len(attributes)) for _, attribute := range attributes { diff --git a/internal/mapper/attrmapper/set_nested.go b/internal/mapper/attrmapper/set_nested.go index 082a61e..53012b5 100644 --- a/internal/mapper/attrmapper/set_nested.go +++ b/internal/mapper/attrmapper/set_nested.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-codegen-spec/datasource" "github.com/hashicorp/terraform-plugin-codegen-spec/provider" "github.com/hashicorp/terraform-plugin-codegen-spec/resource" + "github.com/hashicorp/terraform-plugin-codegen-spec/schema" ) type ResourceSetNestedAttribute struct { @@ -50,6 +51,16 @@ func (a *ResourceSetNestedAttribute) ApplyNestedOverride(path []string, override return a, err } +func (a *ResourceSetNestedAttribute) NestedMerge(path []string, attribute ResourceAttribute, intermediateComputability schema.ComputedOptionalRequired) (ResourceAttribute, error) { + var err error + a.NestedObject.Attributes, err = a.NestedObject.Attributes.MergeAttribute(path, attribute, intermediateComputability) + if err == nil { + a.ComputedOptionalRequired = intermediateComputability + } + + return a, err +} + func (a *ResourceSetNestedAttribute) ToSpec() resource.Attribute { a.SetNestedAttribute.NestedObject = resource.NestedAttributeObject{ Attributes: a.NestedObject.Attributes.ToSpec(), @@ -100,6 +111,16 @@ func (a *DataSourceSetNestedAttribute) ApplyNestedOverride(path []string, overri return a, err } +func (a *DataSourceSetNestedAttribute) NestedMerge(path []string, attribute DataSourceAttribute, intermediateComputability schema.ComputedOptionalRequired) (DataSourceAttribute, error) { + var err error + a.NestedObject.Attributes, err = a.NestedObject.Attributes.MergeAttribute(path, attribute, intermediateComputability) + if err == nil { + a.ComputedOptionalRequired = intermediateComputability + } + + return a, err +} + func (a *DataSourceSetNestedAttribute) ToSpec() datasource.Attribute { a.SetNestedAttribute.NestedObject = datasource.NestedAttributeObject{ Attributes: a.NestedObject.Attributes.ToSpec(), diff --git a/internal/mapper/attrmapper/single_nested.go b/internal/mapper/attrmapper/single_nested.go index e83f105..972670b 100644 --- a/internal/mapper/attrmapper/single_nested.go +++ b/internal/mapper/attrmapper/single_nested.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-codegen-spec/datasource" "github.com/hashicorp/terraform-plugin-codegen-spec/provider" "github.com/hashicorp/terraform-plugin-codegen-spec/resource" + "github.com/hashicorp/terraform-plugin-codegen-spec/schema" ) type ResourceSingleNestedAttribute struct { @@ -50,6 +51,16 @@ func (a *ResourceSingleNestedAttribute) ApplyNestedOverride(path []string, overr return a, err } +func (a *ResourceSingleNestedAttribute) NestedMerge(path []string, attribute ResourceAttribute, intermediateComputability schema.ComputedOptionalRequired) (ResourceAttribute, error) { + var err error + a.Attributes, err = a.Attributes.MergeAttribute(path, attribute, intermediateComputability) + if err == nil { + a.ComputedOptionalRequired = intermediateComputability + } + + return a, err +} + func (a *ResourceSingleNestedAttribute) ToSpec() resource.Attribute { a.SingleNestedAttribute.Attributes = a.Attributes.ToSpec() @@ -98,6 +109,16 @@ func (a *DataSourceSingleNestedAttribute) ApplyNestedOverride(path []string, ove return a, err } +func (a *DataSourceSingleNestedAttribute) NestedMerge(path []string, attribute DataSourceAttribute, intermediateComputability schema.ComputedOptionalRequired) (DataSourceAttribute, error) { + var err error + a.Attributes, err = a.Attributes.MergeAttribute(path, attribute, intermediateComputability) + if err == nil { + a.ComputedOptionalRequired = intermediateComputability + } + + return a, err +} + func (a *DataSourceSingleNestedAttribute) ToSpec() datasource.Attribute { a.SingleNestedAttribute.Attributes = a.Attributes.ToSpec() diff --git a/internal/mapper/datasource_mapper.go b/internal/mapper/datasource_mapper.go index ac0229e..33e23bd 100644 --- a/internal/mapper/datasource_mapper.go +++ b/internal/mapper/datasource_mapper.go @@ -6,6 +6,7 @@ package mapper import ( "fmt" "log/slog" + "strings" "github.com/hashicorp/terraform-plugin-codegen-openapi/internal/config" "github.com/hashicorp/terraform-plugin-codegen-openapi/internal/explorer" @@ -106,7 +107,6 @@ func generateDataSourceSchema(logger *slog.Logger, name string, dataSource explo // **************** // READ Parameters (optional) // **************** - readParameterAttributes := attrmapper.DataSourceAttributes{} for _, param := range dataSource.ReadOpParameters() { if param.In != util.OAS_param_path && param.In != util.OAS_param_query { continue @@ -129,7 +129,7 @@ func generateDataSourceSchema(logger *slog.Logger, name string, dataSource explo computability = schema.Required } - // Check for any aliases and replace the paramater name if found + // Check for any aliases and replace the parameter name if found paramName := param.Name if aliasedName, ok := dataSource.SchemaOptions.AttributeOptions.Aliases[param.Name]; ok { pLogger = pLogger.With("param_alias", aliasedName) @@ -140,17 +140,20 @@ func generateDataSourceSchema(logger *slog.Logger, name string, dataSource explo continue } - parameterAttribute, schemaErr := s.BuildDataSourceAttribute(paramName, computability) + paramPath := strings.Split(paramName, ".") + + parameterAttribute, schemaErr := s.BuildDataSourceAttribute(paramPath[len(paramPath)-1], computability) if schemaErr != nil { log.WarnLogOnError(pLogger, schemaErr, "skipping mapping of read operation parameter") continue } - readParameterAttributes = append(readParameterAttributes, parameterAttribute) + // TODO: currently, no errors can be returned from merging, but in the future we should consider raising errors/warnings for unexpected scenarios, like type mismatches between attribute schemas + readResponseAttributes, _ = readResponseAttributes.MergeAttribute(paramPath, parameterAttribute, computability) } // TODO: currently, no errors can be returned from merging, but in the future we should consider raising errors/warnings for unexpected scenarios, like type mismatches between attribute schemas - dataSourceAttributes, _ := readParameterAttributes.Merge(readResponseAttributes) + dataSourceAttributes := readResponseAttributes // TODO: handle error for overrides dataSourceAttributes, _ = dataSourceAttributes.ApplyOverrides(dataSource.SchemaOptions.AttributeOptions.Overrides) diff --git a/internal/mapper/resource_mapper.go b/internal/mapper/resource_mapper.go index 1b393db..073adee 100644 --- a/internal/mapper/resource_mapper.go +++ b/internal/mapper/resource_mapper.go @@ -6,6 +6,7 @@ package mapper import ( "errors" "log/slog" + "strings" "github.com/hashicorp/terraform-plugin-codegen-openapi/internal/config" "github.com/hashicorp/terraform-plugin-codegen-openapi/internal/explorer" @@ -140,7 +141,6 @@ func generateResourceSchema(logger *slog.Logger, explorerResource explorer.Resou // **************** // READ Parameters (optional) // **************** - readParameterAttributes := attrmapper.ResourceAttributes{} for _, param := range explorerResource.ReadOpParameters() { if param.In != util.OAS_param_path && param.In != util.OAS_param_query { continue @@ -170,17 +170,20 @@ func generateResourceSchema(logger *slog.Logger, explorerResource explorer.Resou continue } - parameterAttribute, schemaErr := s.BuildResourceAttribute(paramName, schema.ComputedOptional) + paramPath := strings.Split(paramName, ".") + + parameterAttribute, schemaErr := s.BuildResourceAttribute(paramPath[len(paramPath)-1], schema.ComputedOptional) if schemaErr != nil { log.WarnLogOnError(pLogger, schemaErr, "skipping mapping of read operation parameter") continue } - readParameterAttributes = append(readParameterAttributes, parameterAttribute) + // TODO: currently, no errors can be returned from merging, but in the future we should consider raising errors/warnings for unexpected scenarios, like type mismatches between attribute schemas + readResponseAttributes, _ = readResponseAttributes.MergeAttribute(paramPath, parameterAttribute, schema.ComputedOptional) } // TODO: currently, no errors can be returned from merging, but in the future we should consider raising errors/warnings for unexpected scenarios, like type mismatches between attribute schemas - resourceAttributes, _ := createRequestAttributes.Merge(createResponseAttributes, readResponseAttributes, readParameterAttributes) + resourceAttributes, _ := createRequestAttributes.Merge(createResponseAttributes, readResponseAttributes) // TODO: handle error for overrides resourceAttributes, _ = resourceAttributes.ApplyOverrides(explorerResource.SchemaOptions.AttributeOptions.Overrides)