diff --git a/.changes/unreleased/ENHANCEMENTS-20240116-102758.yaml b/.changes/unreleased/ENHANCEMENTS-20240116-102758.yaml new file mode 100644 index 00000000..901eafdc --- /dev/null +++ b/.changes/unreleased/ENHANCEMENTS-20240116-102758.yaml @@ -0,0 +1,6 @@ +kind: ENHANCEMENTS +body: Added data source and resource support for query and path parameters specified + in the [OAS Path Item](https://spec.openapis.org/oas/v3.1.0#path-item-object) +time: 2024-01-16T10:27:58.255575839+01:00 +custom: + Issue: "114" diff --git a/DESIGN.md b/DESIGN.md index a39c8707..ca44e24d 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -50,6 +50,7 @@ In these OAS operations, the generator will search the `create` and `read` for s - Will attempt to use `application/json` content-type first. If not found, will grab the first available content-type with a schema (alphabetical order) 4. `read` operation: [parameters](https://spec.openapis.org/oas/v3.1.0#parameterObject) - The generator will merge all `query` and `path` parameters to the root of the schema. + - The generator will consider as parameters the ones in the [OAS Path Item](https://spec.openapis.org/oas/v3.1.0#path-item-object) and the ones in the [OAS Operation](https://spec.openapis.org/oas/v3.1.0#operation-object), merged based on the rules in the specification All schemas found will be deep merged together, with the `requestBody` schema from the `create` operation being the **main schema** that the others will be merged on top. The deep merge has the following characteristics: @@ -72,6 +73,7 @@ data_sources: The generator uses the `read` operation to map to the provider code specification. Multiple schemas will have the [OAS types mapped to Provider Attributes](#oas-types-to-provider-attributes) and then be merged together; with the final result being the [Data Source](https://developer.hashicorp.com/terraform/plugin/code-generation/specification#data-source) `schema`. The schemas that will be merged together (in priority order): 1. `read` operation: [parameters](https://spec.openapis.org/oas/v3.1.0#parameterObject) - The generator will merge all `query` and `path` parameters to the root of the schema. + - The generator will consider as parameters the ones in the [Path Item Object](https://spec.openapis.org/oas/v3.1.0#path-item-object) and the ones in the [Operation Object](https://spec.openapis.org/oas/v3.1.0#operation-object), merged based on the rules in the specification 2. `read` operation: response body in [responses](https://spec.openapis.org/oas/v3.1.0#responsesObject) - The response body is the only schema **required** for data sources. If not found, the generator will skip the data source without mapping. - Will attempt to use `200` or `201` response body. If not found, will grab the first available `2xx` response code with a schema (lexicographic order) diff --git a/internal/cmd/testdata/kubernetes/provider_code_spec.json b/internal/cmd/testdata/kubernetes/provider_code_spec.json index e3226431..9aa89b47 100644 --- a/internal/cmd/testdata/kubernetes/provider_code_spec.json +++ b/internal/cmd/testdata/kubernetes/provider_code_spec.json @@ -9221,6 +9221,27 @@ ], "description": "Most recently observed status of the Deployment." } + }, + { + "name": "name", + "string": { + "computed_optional_required": "computed_optional", + "description": "name of the Deployment" + } + }, + { + "name": "namespace", + "string": { + "computed_optional_required": "computed_optional", + "description": "object name and auth scope, such as for teams and projects" + } + }, + { + "name": "pretty", + "string": { + "computed_optional_required": "computed_optional", + "description": "If 'true', then the output is pretty printed." + } } ] } diff --git a/internal/explorer/config_explorer.go b/internal/explorer/config_explorer.go index d80debee..cc5fa13d 100644 --- a/internal/explorer/config_explorer.go +++ b/internal/explorer/config_explorer.go @@ -81,12 +81,19 @@ func (e configExplorer) FindResources() (map[string]Resource, error) { continue } + commonParameters, err := extractCommonParameters(e.spec.Paths, resourceConfig.Read.Path) + if err != nil { + errResult = errors.Join(errResult, fmt.Errorf("failed to extract '%s' common parameters: %w", name, err)) + continue + } + resources[name] = Resource{ - CreateOp: createOp, - ReadOp: readOp, - UpdateOp: updateOp, - DeleteOp: deleteOp, - SchemaOptions: extractSchemaOptions(resourceConfig.SchemaOptions), + CreateOp: createOp, + ReadOp: readOp, + UpdateOp: updateOp, + DeleteOp: deleteOp, + CommonParameters: commonParameters, + SchemaOptions: extractSchemaOptions(resourceConfig.SchemaOptions), } } @@ -103,9 +110,17 @@ func (e configExplorer) FindDataSources() (map[string]DataSource, error) { errResult = errors.Join(errResult, fmt.Errorf("failed to extract '%s.read': %w", name, err)) continue } + + commonParameters, err := extractCommonParameters(e.spec.Paths, dataSourceConfig.Read.Path) + if err != nil { + errResult = errors.Join(errResult, fmt.Errorf("failed to extract '%s' common parameters: %w", name, err)) + continue + } + dataSources[name] = DataSource{ - ReadOp: readOp, - SchemaOptions: extractSchemaOptions(dataSourceConfig.SchemaOptions), + ReadOp: readOp, + CommonParameters: commonParameters, + SchemaOptions: extractSchemaOptions(dataSourceConfig.SchemaOptions), } } return dataSources, errResult @@ -145,6 +160,17 @@ func extractOp(paths *high.Paths, oasLocation *config.OpenApiSpecLocation) (*hig } } +func extractCommonParameters(paths *high.Paths, path string) ([]*high.Parameter, error) { + // No need to search OAS if not defined + if paths.PathItems.GetOrZero(path) == nil { + return nil, fmt.Errorf("path '%s' not found in OpenAPI spec", path) + } + + pathItem, _ := paths.PathItems.Get(path) + + return pathItem.Parameters, nil +} + func extractSchemaProxy(document high.Document, componentRef string) (*highbase.SchemaProxy, error) { // find the reference using the root document.Index indexRef := document.Index.FindComponentInRoot(componentRef) diff --git a/internal/explorer/explorer.go b/internal/explorer/explorer.go index b8bbc9dd..908b62c9 100644 --- a/internal/explorer/explorer.go +++ b/internal/explorer/explorer.go @@ -17,17 +17,19 @@ type Explorer interface { // Resource contains CRUD operations and schema options for configuration. type Resource struct { - CreateOp *high.Operation - ReadOp *high.Operation - UpdateOp *high.Operation - DeleteOp *high.Operation - SchemaOptions SchemaOptions + CreateOp *high.Operation + ReadOp *high.Operation + UpdateOp *high.Operation + DeleteOp *high.Operation + CommonParameters []*high.Parameter + SchemaOptions SchemaOptions } // DataSource contains a Read operation and schema options for configuration. type DataSource struct { - ReadOp *high.Operation - SchemaOptions SchemaOptions + ReadOp *high.Operation + CommonParameters []*high.Parameter + SchemaOptions SchemaOptions } // Provider contains a name and a schema. diff --git a/internal/explorer/explorer_utils.go b/internal/explorer/explorer_utils.go new file mode 100644 index 00000000..71107952 --- /dev/null +++ b/internal/explorer/explorer_utils.go @@ -0,0 +1,35 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package explorer + +import high "github.com/pb33f/libopenapi/datamodel/high/v3" + +func mergeParameters(commonParameters []*high.Parameter, operation *high.Operation) []*high.Parameter { + mergedParameters := make([]*high.Parameter, len(commonParameters)) + copy(mergedParameters, commonParameters) + if operation != nil { + for _, operationParameter := range operation.Parameters { + found := false + for i, mergedParameter := range mergedParameters { + if operationParameter.Name == mergedParameter.Name { + found = true + mergedParameters[i] = operationParameter + break + } + } + if !found { + mergedParameters = append(mergedParameters, operationParameter) + } + } + } + return mergedParameters +} + +func (e *Resource) ReadOpParameters() []*high.Parameter { + return mergeParameters(e.CommonParameters, e.ReadOp) +} + +func (e *DataSource) ReadOpParameters() []*high.Parameter { + return mergeParameters(e.CommonParameters, e.ReadOp) +} diff --git a/internal/explorer/explorer_utils_test.go b/internal/explorer/explorer_utils_test.go new file mode 100644 index 00000000..36927b3c --- /dev/null +++ b/internal/explorer/explorer_utils_test.go @@ -0,0 +1,672 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package explorer + +import ( + "sync" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-codegen-openapi/internal/mapper/util" + "github.com/pb33f/libopenapi/datamodel/high/base" + high "github.com/pb33f/libopenapi/datamodel/high/v3" +) + +func TestReadOpParameters_Resource(t *testing.T) { + testCases := map[string]struct { + readOp *high.Operation + commonParams []*high.Parameter + want []*high.Parameter + }{ + "merge common and operation": { + readOp: &high.Operation{ + Parameters: []*high.Parameter{ + { + Name: "string_prop", + Required: pointer(true), + In: "path", + Description: "hey this is a string, required and overidden!", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"string"}, + Format: util.OAS_format_password, + Description: "you shouldn't see this because the description is overridden!", + }), + }, + { + Name: "bool_prop", + Required: pointer(true), + In: "query", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"boolean"}, + Description: "hey this is a bool, required!", + }), + }, + { + Name: "float64_prop", + In: "query", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"number"}, + Format: "float", + Description: "hey this is a float64!", + }), + }, + }, + }, + commonParams: []*high.Parameter{ + { + Name: "common_string_prop", + Required: pointer(true), + In: "path", + Description: "hey this is a string, required and overidden!", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"string"}, + Format: util.OAS_format_password, + Description: "you shouldn't see this because the description is overridden!", + }), + }, + }, + want: []*high.Parameter{ + { + Name: "common_string_prop", + Required: pointer(true), + In: "path", + Description: "hey this is a string, required and overidden!", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"string"}, + Format: util.OAS_format_password, + Description: "you shouldn't see this because the description is overridden!", + }), + }, + { + Name: "string_prop", + Required: pointer(true), + In: "path", + Description: "hey this is a string, required and overidden!", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"string"}, + Format: util.OAS_format_password, + Description: "you shouldn't see this because the description is overridden!", + }), + }, + { + Name: "bool_prop", + Required: pointer(true), + In: "query", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"boolean"}, + Description: "hey this is a bool, required!", + }), + }, + { + Name: "float64_prop", + In: "query", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"number"}, + Format: "float", + Description: "hey this is a float64!", + }), + }, + }, + }, + "nil common parameters": { + readOp: &high.Operation{ + Parameters: []*high.Parameter{ + { + Name: "string_prop", + Required: pointer(true), + In: "path", + Description: "hey this is a string, required and overidden!", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"string"}, + Format: util.OAS_format_password, + Description: "you shouldn't see this because the description is overridden!", + }), + }, + { + Name: "bool_prop", + Required: pointer(true), + In: "query", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"boolean"}, + Description: "hey this is a bool, required!", + }), + }, + { + Name: "float64_prop", + In: "query", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"number"}, + Format: "float", + Description: "hey this is a float64!", + }), + }, + }, + }, + commonParams: nil, + want: []*high.Parameter{ + { + Name: "string_prop", + Required: pointer(true), + In: "path", + Description: "hey this is a string, required and overidden!", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"string"}, + Format: util.OAS_format_password, + Description: "you shouldn't see this because the description is overridden!", + }), + }, + { + Name: "bool_prop", + Required: pointer(true), + In: "query", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"boolean"}, + Description: "hey this is a bool, required!", + }), + }, + { + Name: "float64_prop", + In: "query", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"number"}, + Format: "float", + Description: "hey this is a float64!", + }), + }, + }, + }, + "nil read parameters": { + readOp: &high.Operation{ + Parameters: nil, + }, + commonParams: []*high.Parameter{ + { + Name: "common_string_prop", + Required: pointer(true), + In: "path", + Description: "hey this is a string, required and overidden!", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"string"}, + Format: util.OAS_format_password, + Description: "you shouldn't see this because the description is overridden!", + }), + }, + }, + want: []*high.Parameter{ + { + Name: "common_string_prop", + Required: pointer(true), + In: "path", + Description: "hey this is a string, required and overidden!", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"string"}, + Format: util.OAS_format_password, + Description: "you shouldn't see this because the description is overridden!", + }), + }, + }, + }, + "all nil": { + readOp: nil, + commonParams: nil, + want: []*high.Parameter{}, + }, + "read overrides common": { + readOp: &high.Operation{ + Parameters: []*high.Parameter{ + { + Name: "string_prop", + Required: pointer(true), + In: "path", + Description: "hey this is a string, required and overidden!", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"string"}, + Format: util.OAS_format_password, + Description: "you shouldn't see this because the description is overridden!", + }), + }, + { + Name: "bool_prop", + Required: pointer(true), + In: "query", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"boolean"}, + Description: "hey this is a bool, required!", + }), + }, + { + Name: "float64_prop", + In: "query", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"number"}, + Format: "float", + Description: "hey this is a float64!", + }), + }, + }, + }, + commonParams: []*high.Parameter{ + { + Name: "string_prop", + Required: pointer(false), + In: "path", + Description: "hey this is a string, required and overidden!", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"string"}, + Format: util.OAS_format_password, + Description: "you shouldn't see this because the description is overridden!", + }), + }, + }, + want: []*high.Parameter{ + { + Name: "string_prop", + Required: pointer(true), + In: "path", + Description: "hey this is a string, required and overidden!", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"string"}, + Format: util.OAS_format_password, + Description: "you shouldn't see this because the description is overridden!", + }), + }, + { + Name: "bool_prop", + Required: pointer(true), + In: "query", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"boolean"}, + Description: "hey this is a bool, required!", + }), + }, + { + Name: "float64_prop", + In: "query", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"number"}, + Format: "float", + Description: "hey this is a float64!", + }), + }, + }, + }, + "no read op": { + readOp: nil, + commonParams: []*high.Parameter{ + { + Name: "string_prop", + Required: pointer(false), + In: "path", + Description: "hey this is a string, required and overidden!", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"string"}, + Format: util.OAS_format_password, + Description: "you shouldn't see this because the description is overridden!", + }), + }, + }, + want: []*high.Parameter{ + { + Name: "string_prop", + Required: pointer(false), + In: "path", + Description: "hey this is a string, required and overidden!", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"string"}, + Format: util.OAS_format_password, + Description: "you shouldn't see this because the description is overridden!", + }), + }, + }, + }, + } + for name, testCase := range testCases { + name, testCase := name, testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + resource := Resource{ + ReadOp: testCase.readOp, + CommonParameters: testCase.commonParams, + } + + mergedParameters := resource.ReadOpParameters() + + if diff := cmp.Diff(mergedParameters, testCase.want, cmp.AllowUnexported(base.Schema{}, base.SchemaProxy{}, sync.Mutex{}, high.Parameter{})); diff != "" { + t.Errorf("unexpected difference for resource: %s", diff) + } + }) + } +} + +func TestReadOpParameters_DataSource(t *testing.T) { + testCases := map[string]struct { + readOp *high.Operation + commonParams []*high.Parameter + want []*high.Parameter + }{ + "merge common and operation": { + readOp: &high.Operation{ + Parameters: []*high.Parameter{ + { + Name: "string_prop", + Required: pointer(true), + In: "path", + Description: "hey this is a string, required and overidden!", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"string"}, + Format: util.OAS_format_password, + Description: "you shouldn't see this because the description is overridden!", + }), + }, + { + Name: "bool_prop", + Required: pointer(true), + In: "query", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"boolean"}, + Description: "hey this is a bool, required!", + }), + }, + { + Name: "float64_prop", + In: "query", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"number"}, + Format: "float", + Description: "hey this is a float64!", + }), + }, + }, + }, + commonParams: []*high.Parameter{ + { + Name: "common_string_prop", + Required: pointer(true), + In: "path", + Description: "hey this is a string, required and overidden!", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"string"}, + Format: util.OAS_format_password, + Description: "you shouldn't see this because the description is overridden!", + }), + }, + }, + want: []*high.Parameter{ + { + Name: "common_string_prop", + Required: pointer(true), + In: "path", + Description: "hey this is a string, required and overidden!", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"string"}, + Format: util.OAS_format_password, + Description: "you shouldn't see this because the description is overridden!", + }), + }, + { + Name: "string_prop", + Required: pointer(true), + In: "path", + Description: "hey this is a string, required and overidden!", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"string"}, + Format: util.OAS_format_password, + Description: "you shouldn't see this because the description is overridden!", + }), + }, + { + Name: "bool_prop", + Required: pointer(true), + In: "query", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"boolean"}, + Description: "hey this is a bool, required!", + }), + }, + { + Name: "float64_prop", + In: "query", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"number"}, + Format: "float", + Description: "hey this is a float64!", + }), + }, + }, + }, + "nil common parameters": { + readOp: &high.Operation{ + Parameters: []*high.Parameter{ + { + Name: "string_prop", + Required: pointer(true), + In: "path", + Description: "hey this is a string, required and overidden!", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"string"}, + Format: util.OAS_format_password, + Description: "you shouldn't see this because the description is overridden!", + }), + }, + { + Name: "bool_prop", + Required: pointer(true), + In: "query", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"boolean"}, + Description: "hey this is a bool, required!", + }), + }, + { + Name: "float64_prop", + In: "query", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"number"}, + Format: "float", + Description: "hey this is a float64!", + }), + }, + }, + }, + commonParams: nil, + want: []*high.Parameter{ + { + Name: "string_prop", + Required: pointer(true), + In: "path", + Description: "hey this is a string, required and overidden!", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"string"}, + Format: util.OAS_format_password, + Description: "you shouldn't see this because the description is overridden!", + }), + }, + { + Name: "bool_prop", + Required: pointer(true), + In: "query", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"boolean"}, + Description: "hey this is a bool, required!", + }), + }, + { + Name: "float64_prop", + In: "query", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"number"}, + Format: "float", + Description: "hey this is a float64!", + }), + }, + }, + }, + "nil read parameters": { + readOp: &high.Operation{ + Parameters: nil, + }, + commonParams: []*high.Parameter{ + { + Name: "common_string_prop", + Required: pointer(true), + In: "path", + Description: "hey this is a string, required and overidden!", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"string"}, + Format: util.OAS_format_password, + Description: "you shouldn't see this because the description is overridden!", + }), + }, + }, + want: []*high.Parameter{ + { + Name: "common_string_prop", + Required: pointer(true), + In: "path", + Description: "hey this is a string, required and overidden!", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"string"}, + Format: util.OAS_format_password, + Description: "you shouldn't see this because the description is overridden!", + }), + }, + }, + }, + "all nil": { + readOp: nil, + commonParams: nil, + want: []*high.Parameter{}, + }, + "read overrides common": { + readOp: &high.Operation{ + Parameters: []*high.Parameter{ + { + Name: "string_prop", + Required: pointer(true), + In: "path", + Description: "hey this is a string, required and overidden!", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"string"}, + Format: util.OAS_format_password, + Description: "you shouldn't see this because the description is overridden!", + }), + }, + { + Name: "bool_prop", + Required: pointer(true), + In: "query", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"boolean"}, + Description: "hey this is a bool, required!", + }), + }, + { + Name: "float64_prop", + In: "query", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"number"}, + Format: "float", + Description: "hey this is a float64!", + }), + }, + }, + }, + commonParams: []*high.Parameter{ + { + Name: "string_prop", + Required: pointer(false), + In: "path", + Description: "hey this is a string, required and overidden!", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"string"}, + Format: util.OAS_format_password, + Description: "you shouldn't see this because the description is overridden!", + }), + }, + }, + want: []*high.Parameter{ + { + Name: "string_prop", + Required: pointer(true), + In: "path", + Description: "hey this is a string, required and overidden!", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"string"}, + Format: util.OAS_format_password, + Description: "you shouldn't see this because the description is overridden!", + }), + }, + { + Name: "bool_prop", + Required: pointer(true), + In: "query", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"boolean"}, + Description: "hey this is a bool, required!", + }), + }, + { + Name: "float64_prop", + In: "query", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"number"}, + Format: "float", + Description: "hey this is a float64!", + }), + }, + }, + }, + "no read op": { + readOp: nil, + commonParams: []*high.Parameter{ + { + Name: "string_prop", + Required: pointer(false), + In: "path", + Description: "hey this is a string, required and overidden!", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"string"}, + Format: util.OAS_format_password, + Description: "you shouldn't see this because the description is overridden!", + }), + }, + }, + want: []*high.Parameter{ + { + Name: "string_prop", + Required: pointer(false), + In: "path", + Description: "hey this is a string, required and overidden!", + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"string"}, + Format: util.OAS_format_password, + Description: "you shouldn't see this because the description is overridden!", + }), + }, + }, + }, + } + for name, testCase := range testCases { + name, testCase := name, testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + dataSource := DataSource{ + ReadOp: testCase.readOp, + CommonParameters: testCase.commonParams, + } + + mergedParameters := dataSource.ReadOpParameters() + + if diff := cmp.Diff(mergedParameters, testCase.want, cmp.AllowUnexported(base.Schema{}, base.SchemaProxy{}, sync.Mutex{}, high.Parameter{})); diff != "" { + t.Errorf("unexpected difference for data source: %s", diff) + } + }) + } +} + +func pointer[T any](value T) *T { + return &value +} diff --git a/internal/mapper/datasource_mapper.go b/internal/mapper/datasource_mapper.go index 591ae882..ac0229e3 100644 --- a/internal/mapper/datasource_mapper.go +++ b/internal/mapper/datasource_mapper.go @@ -107,48 +107,46 @@ func generateDataSourceSchema(logger *slog.Logger, name string, dataSource explo // READ Parameters (optional) // **************** readParameterAttributes := attrmapper.DataSourceAttributes{} - if dataSource.ReadOp != nil && dataSource.ReadOp.Parameters != nil { - for _, param := range dataSource.ReadOp.Parameters { - if param.In != util.OAS_param_path && param.In != util.OAS_param_query { - continue - } - - pLogger := logger.With("param", param.Name) - schemaOpts := oas.SchemaOpts{ - Ignores: dataSource.SchemaOptions.Ignores, - OverrideDescription: param.Description, - } - - s, schemaErr := oas.BuildSchema(param.Schema, schemaOpts, oas.GlobalSchemaOpts{}) - if schemaErr != nil { - log.WarnLogOnError(pLogger, schemaErr, "skipping mapping of read operation parameter") - continue - } - - computability := schema.ComputedOptional - if param.Required != nil && *param.Required { - computability = schema.Required - } - - // Check for any aliases and replace the paramater name if found - paramName := param.Name - if aliasedName, ok := dataSource.SchemaOptions.AttributeOptions.Aliases[param.Name]; ok { - pLogger = pLogger.With("param_alias", aliasedName) - paramName = aliasedName - } - - if s.IsPropertyIgnored(paramName) { - continue - } - - parameterAttribute, schemaErr := s.BuildDataSourceAttribute(paramName, computability) - if schemaErr != nil { - log.WarnLogOnError(pLogger, schemaErr, "skipping mapping of read operation parameter") - continue - } - - readParameterAttributes = append(readParameterAttributes, parameterAttribute) + for _, param := range dataSource.ReadOpParameters() { + if param.In != util.OAS_param_path && param.In != util.OAS_param_query { + continue + } + + pLogger := logger.With("param", param.Name) + schemaOpts := oas.SchemaOpts{ + Ignores: dataSource.SchemaOptions.Ignores, + OverrideDescription: param.Description, + } + + s, schemaErr := oas.BuildSchema(param.Schema, schemaOpts, oas.GlobalSchemaOpts{}) + if schemaErr != nil { + log.WarnLogOnError(pLogger, schemaErr, "skipping mapping of read operation parameter") + continue } + + computability := schema.ComputedOptional + if param.Required != nil && *param.Required { + computability = schema.Required + } + + // Check for any aliases and replace the paramater name if found + paramName := param.Name + if aliasedName, ok := dataSource.SchemaOptions.AttributeOptions.Aliases[param.Name]; ok { + pLogger = pLogger.With("param_alias", aliasedName) + paramName = aliasedName + } + + if s.IsPropertyIgnored(paramName) { + continue + } + + parameterAttribute, schemaErr := s.BuildDataSourceAttribute(paramName, 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 diff --git a/internal/mapper/resource_mapper.go b/internal/mapper/resource_mapper.go index 67f4751c..1b393db2 100644 --- a/internal/mapper/resource_mapper.go +++ b/internal/mapper/resource_mapper.go @@ -141,44 +141,42 @@ func generateResourceSchema(logger *slog.Logger, explorerResource explorer.Resou // READ Parameters (optional) // **************** readParameterAttributes := attrmapper.ResourceAttributes{} - if explorerResource.ReadOp != nil && explorerResource.ReadOp.Parameters != nil { - for _, param := range explorerResource.ReadOp.Parameters { - if param.In != util.OAS_param_path && param.In != util.OAS_param_query { - continue - } - - pLogger := logger.With("param", param.Name) - schemaOpts := oas.SchemaOpts{ - Ignores: explorerResource.SchemaOptions.Ignores, - OverrideDescription: param.Description, - } - globalSchemaOpts := oas.GlobalSchemaOpts{OverrideComputability: schema.ComputedOptional} - - s, schemaErr := oas.BuildSchema(param.Schema, schemaOpts, globalSchemaOpts) - if schemaErr != nil { - log.WarnLogOnError(pLogger, schemaErr, "skipping mapping of read operation parameter") - continue - } - - // Check for any aliases and replace the paramater name if found - paramName := param.Name - if aliasedName, ok := explorerResource.SchemaOptions.AttributeOptions.Aliases[param.Name]; ok { - pLogger = pLogger.With("param_alias", aliasedName) - paramName = aliasedName - } - - if s.IsPropertyIgnored(paramName) { - continue - } - - parameterAttribute, schemaErr := s.BuildResourceAttribute(paramName, schema.ComputedOptional) - if schemaErr != nil { - log.WarnLogOnError(pLogger, schemaErr, "skipping mapping of read operation parameter") - continue - } - - readParameterAttributes = append(readParameterAttributes, parameterAttribute) + for _, param := range explorerResource.ReadOpParameters() { + if param.In != util.OAS_param_path && param.In != util.OAS_param_query { + continue + } + + pLogger := logger.With("param", param.Name) + schemaOpts := oas.SchemaOpts{ + Ignores: explorerResource.SchemaOptions.Ignores, + OverrideDescription: param.Description, + } + globalSchemaOpts := oas.GlobalSchemaOpts{OverrideComputability: schema.ComputedOptional} + + s, schemaErr := oas.BuildSchema(param.Schema, schemaOpts, globalSchemaOpts) + if schemaErr != nil { + log.WarnLogOnError(pLogger, schemaErr, "skipping mapping of read operation parameter") + continue + } + + // Check for any aliases and replace the paramater name if found + paramName := param.Name + if aliasedName, ok := explorerResource.SchemaOptions.AttributeOptions.Aliases[param.Name]; ok { + pLogger = pLogger.With("param_alias", aliasedName) + paramName = aliasedName } + + if s.IsPropertyIgnored(paramName) { + continue + } + + parameterAttribute, schemaErr := s.BuildResourceAttribute(paramName, 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