From e7c426618b63939bb2b53b111b95983ef58e40fd Mon Sep 17 00:00:00 2001 From: Bernardo Pastorelli <13519917+randomswdev@users.noreply.github.com> Date: Fri, 5 Jan 2024 19:00:26 +0100 Subject: [PATCH 1/2] Add support for common parameters --- internal/explorer/config_explorer.go | 26 ++ internal/explorer/eplorer_utils_test.go | 361 ++++++++++++++++++++++++ internal/explorer/explorer.go | 2 + internal/explorer/explorer_utils.go | 31 ++ internal/mapper/datasource_mapper.go | 80 +++--- internal/mapper/resource_mapper.go | 72 +++-- 6 files changed, 494 insertions(+), 78 deletions(-) create mode 100644 internal/explorer/eplorer_utils_test.go create mode 100644 internal/explorer/explorer_utils.go diff --git a/internal/explorer/config_explorer.go b/internal/explorer/config_explorer.go index d80debee..a0ceaa83 100644 --- a/internal/explorer/config_explorer.go +++ b/internal/explorer/config_explorer.go @@ -81,11 +81,18 @@ func (e configExplorer) FindResources() (map[string]Resource, error) { continue } + parameters, err := extractParameters(e.spec.Paths, resourceConfig.Read.Path) + if err != nil { + errResult = errors.Join(errResult, fmt.Errorf("failed to extract '%s.delete': %w", name, err)) + continue + } + resources[name] = Resource{ CreateOp: createOp, ReadOp: readOp, UpdateOp: updateOp, DeleteOp: deleteOp, + Parameters: parameters, SchemaOptions: extractSchemaOptions(resourceConfig.SchemaOptions), } } @@ -103,8 +110,16 @@ func (e configExplorer) FindDataSources() (map[string]DataSource, error) { errResult = errors.Join(errResult, fmt.Errorf("failed to extract '%s.read': %w", name, err)) continue } + + parameters, err := extractParameters(e.spec.Paths, dataSourceConfig.Read.Path) + if err != nil { + errResult = errors.Join(errResult, fmt.Errorf("failed to extract '%s.delete': %w", name, err)) + continue + } + dataSources[name] = DataSource{ ReadOp: readOp, + Parameters: parameters, SchemaOptions: extractSchemaOptions(dataSourceConfig.SchemaOptions), } } @@ -145,6 +160,17 @@ func extractOp(paths *high.Paths, oasLocation *config.OpenApiSpecLocation) (*hig } } +func extractParameters(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/eplorer_utils_test.go b/internal/explorer/eplorer_utils_test.go new file mode 100644 index 00000000..9f8cae16 --- /dev/null +++ b/internal/explorer/eplorer_utils_test.go @@ -0,0 +1,361 @@ +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 TestMergeParameters(t *testing.T) { + testCases := map[string]struct { + noReadOp bool + readParams []*high.Parameter + commonParams []*high.Parameter + want []*high.Parameter + }{ + "merge common and operation": { + noReadOp: false, + readParams: []*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": { + noReadOp: false, + readParams: []*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": { + noReadOp: false, + readParams: 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": { + noReadOp: false, + readParams: nil, + commonParams: nil, + want: nil, + }, + "read overrides common": { + noReadOp: false, + readParams: []*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": { + noReadOp: true, + readParams: 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() + + var readOp *high.Operation = nil + if !testCase.noReadOp { + readOp = &high.Operation{ + Parameters: testCase.readParams, + } + } + + resource := Resource{ + ReadOp: readOp, + Parameters: 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) + } + + dataSource := DataSource{ + ReadOp: &high.Operation{ + Parameters: testCase.readParams, + }, + Parameters: 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/explorer/explorer.go b/internal/explorer/explorer.go index b8bbc9dd..c7c4c5be 100644 --- a/internal/explorer/explorer.go +++ b/internal/explorer/explorer.go @@ -21,12 +21,14 @@ type Resource struct { ReadOp *high.Operation UpdateOp *high.Operation DeleteOp *high.Operation + Parameters []*high.Parameter SchemaOptions SchemaOptions } // DataSource contains a Read operation and schema options for configuration. type DataSource struct { ReadOp *high.Operation + Parameters []*high.Parameter SchemaOptions SchemaOptions } diff --git a/internal/explorer/explorer_utils.go b/internal/explorer/explorer_utils.go new file mode 100644 index 00000000..33968c8e --- /dev/null +++ b/internal/explorer/explorer_utils.go @@ -0,0 +1,31 @@ +package explorer + +import high "github.com/pb33f/libopenapi/datamodel/high/v3" + +func mergeParameters(commonParameters []*high.Parameter, operation *high.Operation) []*high.Parameter { + 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.Parameters, e.ReadOp) +} + +func (e *DataSource) ReadOpParameters() []*high.Parameter { + return mergeParameters(e.Parameters, e.ReadOp) +} 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 From 5f4de8d050cd9a199ede5c8b3dd6f00a4061bd52 Mon Sep 17 00:00:00 2001 From: Bernardo Pastorelli <13519917+randomswdev@users.noreply.github.com> Date: Tue, 16 Jan 2024 10:51:44 +0100 Subject: [PATCH 2/2] Address review changes --- .../ENHANCEMENTS-20240116-102758.yaml | 6 + DESIGN.md | 2 + .../kubernetes/provider_code_spec.json | 21 + internal/explorer/config_explorer.go | 28 +- internal/explorer/eplorer_utils_test.go | 361 ---------- internal/explorer/explorer.go | 18 +- internal/explorer/explorer_utils.go | 10 +- internal/explorer/explorer_utils_test.go | 672 ++++++++++++++++++ 8 files changed, 731 insertions(+), 387 deletions(-) create mode 100644 .changes/unreleased/ENHANCEMENTS-20240116-102758.yaml delete mode 100644 internal/explorer/eplorer_utils_test.go create mode 100644 internal/explorer/explorer_utils_test.go 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 a0ceaa83..cc5fa13d 100644 --- a/internal/explorer/config_explorer.go +++ b/internal/explorer/config_explorer.go @@ -81,19 +81,19 @@ func (e configExplorer) FindResources() (map[string]Resource, error) { continue } - parameters, err := extractParameters(e.spec.Paths, resourceConfig.Read.Path) + commonParameters, err := extractCommonParameters(e.spec.Paths, resourceConfig.Read.Path) if err != nil { - errResult = errors.Join(errResult, fmt.Errorf("failed to extract '%s.delete': %w", name, err)) + 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, - Parameters: parameters, - SchemaOptions: extractSchemaOptions(resourceConfig.SchemaOptions), + CreateOp: createOp, + ReadOp: readOp, + UpdateOp: updateOp, + DeleteOp: deleteOp, + CommonParameters: commonParameters, + SchemaOptions: extractSchemaOptions(resourceConfig.SchemaOptions), } } @@ -111,16 +111,16 @@ func (e configExplorer) FindDataSources() (map[string]DataSource, error) { continue } - parameters, err := extractParameters(e.spec.Paths, dataSourceConfig.Read.Path) + commonParameters, err := extractCommonParameters(e.spec.Paths, dataSourceConfig.Read.Path) if err != nil { - errResult = errors.Join(errResult, fmt.Errorf("failed to extract '%s.delete': %w", name, err)) + errResult = errors.Join(errResult, fmt.Errorf("failed to extract '%s' common parameters: %w", name, err)) continue } dataSources[name] = DataSource{ - ReadOp: readOp, - Parameters: parameters, - SchemaOptions: extractSchemaOptions(dataSourceConfig.SchemaOptions), + ReadOp: readOp, + CommonParameters: commonParameters, + SchemaOptions: extractSchemaOptions(dataSourceConfig.SchemaOptions), } } return dataSources, errResult @@ -160,7 +160,7 @@ func extractOp(paths *high.Paths, oasLocation *config.OpenApiSpecLocation) (*hig } } -func extractParameters(paths *high.Paths, path string) ([]*high.Parameter, error) { +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) diff --git a/internal/explorer/eplorer_utils_test.go b/internal/explorer/eplorer_utils_test.go deleted file mode 100644 index 9f8cae16..00000000 --- a/internal/explorer/eplorer_utils_test.go +++ /dev/null @@ -1,361 +0,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 TestMergeParameters(t *testing.T) { - testCases := map[string]struct { - noReadOp bool - readParams []*high.Parameter - commonParams []*high.Parameter - want []*high.Parameter - }{ - "merge common and operation": { - noReadOp: false, - readParams: []*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": { - noReadOp: false, - readParams: []*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": { - noReadOp: false, - readParams: 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": { - noReadOp: false, - readParams: nil, - commonParams: nil, - want: nil, - }, - "read overrides common": { - noReadOp: false, - readParams: []*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": { - noReadOp: true, - readParams: 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() - - var readOp *high.Operation = nil - if !testCase.noReadOp { - readOp = &high.Operation{ - Parameters: testCase.readParams, - } - } - - resource := Resource{ - ReadOp: readOp, - Parameters: 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) - } - - dataSource := DataSource{ - ReadOp: &high.Operation{ - Parameters: testCase.readParams, - }, - Parameters: 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/explorer/explorer.go b/internal/explorer/explorer.go index c7c4c5be..908b62c9 100644 --- a/internal/explorer/explorer.go +++ b/internal/explorer/explorer.go @@ -17,19 +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 - Parameters []*high.Parameter - 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 - Parameters []*high.Parameter - 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 index 33968c8e..71107952 100644 --- a/internal/explorer/explorer_utils.go +++ b/internal/explorer/explorer_utils.go @@ -1,9 +1,13 @@ +// 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 := commonParameters + mergedParameters := make([]*high.Parameter, len(commonParameters)) + copy(mergedParameters, commonParameters) if operation != nil { for _, operationParameter := range operation.Parameters { found := false @@ -23,9 +27,9 @@ func mergeParameters(commonParameters []*high.Parameter, operation *high.Operati } func (e *Resource) ReadOpParameters() []*high.Parameter { - return mergeParameters(e.Parameters, e.ReadOp) + return mergeParameters(e.CommonParameters, e.ReadOp) } func (e *DataSource) ReadOpParameters() []*high.Parameter { - return mergeParameters(e.Parameters, e.ReadOp) + 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 +}