diff --git a/internal/fromproto6/validatedatasourceconfig.go b/internal/fromproto6/validatedatasourceconfig.go new file mode 100644 index 000000000..ae8503b67 --- /dev/null +++ b/internal/fromproto6/validatedatasourceconfig.go @@ -0,0 +1,27 @@ +package fromproto6 + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/internal/fwserver" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" +) + +// ValidateDataSourceConfigRequest returns the *fwserver.ValidateDataSourceConfigRequest +// equivalent of a *tfprotov6.ValidateDataSourceConfigRequest. +func ValidateDataSourceConfigRequest(ctx context.Context, proto6 *tfprotov6.ValidateDataResourceConfigRequest, dataSourceType tfsdk.DataSourceType, dataSourceSchema *tfsdk.Schema) (*fwserver.ValidateDataSourceConfigRequest, diag.Diagnostics) { + if proto6 == nil { + return nil, nil + } + + fw := &fwserver.ValidateDataSourceConfigRequest{} + + config, diags := Config(ctx, proto6.Config, dataSourceSchema) + + fw.Config = config + fw.DataSourceType = dataSourceType + + return fw, diags +} diff --git a/internal/fromproto6/validatedatasourceconfig_test.go b/internal/fromproto6/validatedatasourceconfig_test.go new file mode 100644 index 000000000..632eb4f3d --- /dev/null +++ b/internal/fromproto6/validatedatasourceconfig_test.go @@ -0,0 +1,106 @@ +package fromproto6_test + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/internal/fromproto6" + "github.com/hashicorp/terraform-plugin-framework/internal/fwserver" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +func TestValidateDataSourceConfigRequest(t *testing.T) { + t.Parallel() + + testProto6Type := tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test_attribute": tftypes.String, + }, + } + + testProto6Value := tftypes.NewValue(testProto6Type, map[string]tftypes.Value{ + "test_attribute": tftypes.NewValue(tftypes.String, "test-value"), + }) + + testProto6DynamicValue, err := tfprotov6.NewDynamicValue(testProto6Type, testProto6Value) + + if err != nil { + t.Fatalf("unexpected error calling tfprotov6.NewDynamicValue(): %s", err) + } + + testFwSchema := &tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test_attribute": { + Required: true, + Type: types.StringType, + }, + }, + } + + testCases := map[string]struct { + input *tfprotov6.ValidateDataResourceConfigRequest + dataSourceSchema *tfsdk.Schema + dataSourceType tfsdk.DataSourceType + expected *fwserver.ValidateDataSourceConfigRequest + expectedDiagnostics diag.Diagnostics + }{ + "nil": { + input: nil, + expected: nil, + }, + "empty": { + input: &tfprotov6.ValidateDataResourceConfigRequest{}, + expected: &fwserver.ValidateDataSourceConfigRequest{}, + }, + "config-missing-schema": { + input: &tfprotov6.ValidateDataResourceConfigRequest{ + Config: &testProto6DynamicValue, + }, + expected: &fwserver.ValidateDataSourceConfigRequest{}, + expectedDiagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Unable to Convert Configuration", + "An unexpected error was encountered when converting the configuration from the protocol type. "+ + "This is always an issue in the Terraform Provider SDK used to implement the provider and should be reported to the provider developers.\n\n"+ + "Please report this to the provider developer:\n\n"+ + "Missing schema.", + ), + }, + }, + "config": { + input: &tfprotov6.ValidateDataResourceConfigRequest{ + Config: &testProto6DynamicValue, + }, + dataSourceSchema: testFwSchema, + expected: &fwserver.ValidateDataSourceConfigRequest{ + Config: &tfsdk.Config{ + Raw: testProto6Value, + Schema: *testFwSchema, + }, + }, + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got, diags := fromproto6.ValidateDataSourceConfigRequest(context.Background(), testCase.input, testCase.dataSourceType, testCase.dataSourceSchema) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + + if diff := cmp.Diff(diags, testCase.expectedDiagnostics); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + }) + } +} diff --git a/internal/fwserver/server.go b/internal/fwserver/server.go index 6b40051c8..fa0f99351 100644 --- a/internal/fwserver/server.go +++ b/internal/fwserver/server.go @@ -2,6 +2,7 @@ package fwserver import ( "context" + "fmt" "sync" "github.com/hashicorp/terraform-plugin-framework/diag" @@ -15,6 +16,34 @@ import ( type Server struct { Provider tfsdk.Provider + // dataSourceSchemas is the cached DataSource Schemas for RPCs that need to + // convert configuration data from the protocol. If not found, it will be + // fetched from the DataSourceType.GetSchema() method. + dataSourceSchemas map[string]*tfsdk.Schema + + // dataSourceSchemasDiags is the cached Diagnostics obtained while populating + // dataSourceSchemas. This is to ensure any warnings or errors are also + // returned appropriately when fetching dataSourceSchemas. + dataSourceSchemasDiags diag.Diagnostics + + // dataSourceSchemasMutex is a mutex to protect concurrent dataSourceSchemas + // access from race conditions. + dataSourceSchemasMutex sync.Mutex + + // dataSourceTypes is the cached DataSourceTypes for RPCs that need to + // access data sources. If not found, it will be fetched from the + // Provider.GetDataSources() method. + dataSourceTypes map[string]tfsdk.DataSourceType + + // dataSourceTypesDiags is the cached Diagnostics obtained while populating + // dataSourceTypes. This is to ensure any warnings or errors are also + // returned appropriately when fetching dataSourceTypes. + dataSourceTypesDiags diag.Diagnostics + + // dataSourceTypesMutex is a mutex to protect concurrent dataSourceTypes + // access from race conditions. + dataSourceTypesMutex sync.Mutex + // providerSchema is the cached Provider Schema for RPCs that need to // convert configuration data from the protocol. If not found, it will be // fetched from the Provider.GetSchema() method. @@ -30,6 +59,101 @@ type Server struct { providerSchemaMutex sync.Mutex } +// DataSourceSchema returns the Schema associated with the DataSourceType for +// the given type name. +func (s *Server) DataSourceSchema(ctx context.Context, typeName string) (*tfsdk.Schema, diag.Diagnostics) { + dataSourceSchemas, diags := s.DataSourceSchemas(ctx) + + dataSourceSchema, ok := dataSourceSchemas[typeName] + + if !ok { + diags.AddError( + "Data Source Schema Not Found", + fmt.Sprintf("No data source type named %q was found in the provider to fetch the schema. ", typeName)+ + "This is always an issue in the Terraform Provider SDK used to implement the provider and should be reported to the provider developers.", + ) + + return nil, diags + } + + return dataSourceSchema, diags +} + +// DataSourceSchemas returns the map of DataSourceType Schemas. The results are +// cached on first use. +func (s *Server) DataSourceSchemas(ctx context.Context) (map[string]*tfsdk.Schema, diag.Diagnostics) { + logging.FrameworkTrace(ctx, "Checking DataSourceSchemas lock") + s.dataSourceSchemasMutex.Lock() + defer s.dataSourceSchemasMutex.Unlock() + + if s.dataSourceSchemas != nil { + return s.dataSourceSchemas, s.dataSourceSchemasDiags + } + + dataSourceTypes, diags := s.DataSourceTypes(ctx) + + s.dataSourceSchemas = map[string]*tfsdk.Schema{} + s.dataSourceSchemasDiags = diags + + if s.dataSourceSchemasDiags.HasError() { + return s.dataSourceSchemas, s.dataSourceSchemasDiags + } + + for dataSourceTypeName, dataSourceType := range dataSourceTypes { + logging.FrameworkTrace(ctx, "Found data source type", map[string]interface{}{logging.KeyDataSourceType: dataSourceTypeName}) + + logging.FrameworkDebug(ctx, "Calling provider defined DataSourceType GetSchema", map[string]interface{}{logging.KeyDataSourceType: dataSourceTypeName}) + schema, diags := dataSourceType.GetSchema(ctx) + logging.FrameworkDebug(ctx, "Called provider defined DataSourceType GetSchema", map[string]interface{}{logging.KeyDataSourceType: dataSourceTypeName}) + + s.dataSourceSchemasDiags.Append(diags...) + + if s.dataSourceSchemasDiags.HasError() { + return s.dataSourceSchemas, s.dataSourceSchemasDiags + } + + s.dataSourceSchemas[dataSourceTypeName] = &schema + } + + return s.dataSourceSchemas, s.dataSourceSchemasDiags +} + +// DataSourceType returns the DataSourceType for a given type name. +func (s *Server) DataSourceType(ctx context.Context, typeName string) (tfsdk.DataSourceType, diag.Diagnostics) { + dataSourceTypes, diags := s.DataSourceTypes(ctx) + + dataSourceType, ok := dataSourceTypes[typeName] + + if !ok { + diags.AddError( + "Data Source Type Not Found", + fmt.Sprintf("No data source type named %q was found in the provider.", typeName), + ) + + return nil, diags + } + + return dataSourceType, diags +} + +// DataSourceTypes returns the map of DataSourceTypes. The results are cached +// on first use. +func (s *Server) DataSourceTypes(ctx context.Context) (map[string]tfsdk.DataSourceType, diag.Diagnostics) { + logging.FrameworkTrace(ctx, "Checking DataSourceTypes lock") + s.dataSourceTypesMutex.Lock() + defer s.dataSourceTypesMutex.Unlock() + + if s.dataSourceTypes != nil { + return s.dataSourceTypes, s.dataSourceTypesDiags + } + + logging.FrameworkDebug(ctx, "Calling provider defined Provider GetDataSources") + s.dataSourceTypes, s.dataSourceTypesDiags = s.Provider.GetDataSources(ctx) + logging.FrameworkDebug(ctx, "Called provider defined Provider GetDataSources") + + return s.dataSourceTypes, s.dataSourceTypesDiags +} + // ProviderSchema returns the Schema associated with the Provider. The Schema // and Diagnostics are cached on first use. func (s *Server) ProviderSchema(ctx context.Context) (*tfsdk.Schema, diag.Diagnostics) { diff --git a/internal/fwserver/server_getproviderschema.go b/internal/fwserver/server_getproviderschema.go index 1c69b21dc..debc5bbba 100644 --- a/internal/fwserver/server_getproviderschema.go +++ b/internal/fwserver/server_getproviderschema.go @@ -83,11 +83,7 @@ func (s *Server) GetProviderSchema(ctx context.Context, req *GetProviderSchemaRe resp.ResourceSchemas[k] = &schema } - // TODO: Cache GetDataSources call - // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/299 - logging.FrameworkDebug(ctx, "Calling provider defined Provider GetDataSources") - dataSourceSchemas, diags := s.Provider.GetDataSources(ctx) - logging.FrameworkDebug(ctx, "Called provider defined Provider GetDataSources") + dataSourceSchemas, diags := s.DataSourceSchemas(ctx) resp.Diagnostics.Append(diags...) @@ -95,24 +91,5 @@ func (s *Server) GetProviderSchema(ctx context.Context, req *GetProviderSchemaRe return } - if len(dataSourceSchemas) > 0 { - resp.DataSourceSchemas = map[string]*tfsdk.Schema{} - } - - for k, v := range dataSourceSchemas { - // KeyDataSourceType field only necessary here since we are in GetProviderSchema RPC - logging.FrameworkTrace(ctx, "Found data source type", map[string]interface{}{logging.KeyDataSourceType: k}) - - logging.FrameworkDebug(ctx, "Calling provider defined DataSourceType GetSchema", map[string]interface{}{logging.KeyDataSourceType: k}) - schema, diags := v.GetSchema(ctx) - logging.FrameworkDebug(ctx, "Called provider defined DataSourceType GetSchema", map[string]interface{}{logging.KeyDataSourceType: k}) - - resp.Diagnostics.Append(diags...) - - if resp.Diagnostics.HasError() { - return - } - - resp.DataSourceSchemas[k] = &schema - } + resp.DataSourceSchemas = dataSourceSchemas } diff --git a/internal/fwserver/server_getproviderschema_test.go b/internal/fwserver/server_getproviderschema_test.go index 5311478bf..ba981cb64 100644 --- a/internal/fwserver/server_getproviderschema_test.go +++ b/internal/fwserver/server_getproviderschema_test.go @@ -31,7 +31,8 @@ func TestServerGetProviderSchema(t *testing.T) { Provider: &emptyprovider.Provider{}, }, expectedResponse: &fwserver.GetProviderSchemaResponse{ - Provider: &tfsdk.Schema{}, + DataSourceSchemas: map[string]*tfsdk.Schema{}, + Provider: &tfsdk.Schema{}, }, }, } diff --git a/internal/fwserver/server_validatedatasourceconfig.go b/internal/fwserver/server_validatedatasourceconfig.go new file mode 100644 index 000000000..631e50b09 --- /dev/null +++ b/internal/fwserver/server_validatedatasourceconfig.go @@ -0,0 +1,98 @@ +package fwserver + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/internal/logging" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" +) + +// ValidateDataSourceConfigRequest is the framework server request for the +// ValidateDataSourceConfig RPC. +type ValidateDataSourceConfigRequest struct { + Config *tfsdk.Config + DataSourceType tfsdk.DataSourceType +} + +// ValidateDataSourceConfigResponse is the framework server response for the +// ValidateDataSourceConfig RPC. +type ValidateDataSourceConfigResponse struct { + PreparedConfig *tfsdk.Config + Diagnostics diag.Diagnostics +} + +// ValidateDataSourceConfig implements the framework server ValidateDataSourceConfig RPC. +func (s *Server) ValidateDataSourceConfig(ctx context.Context, req *ValidateDataSourceConfigRequest, resp *ValidateDataSourceConfigResponse) { + if req == nil || req.Config == nil { + return + } + + // Always instantiate new DataSource instances. + logging.FrameworkDebug(ctx, "Calling provider defined DataSourceType NewDataSource") + dataSource, diags := req.DataSourceType.NewDataSource(ctx, s.Provider) + logging.FrameworkDebug(ctx, "Called provider defined DataSourceType NewDataSource") + + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + + vdscReq := tfsdk.ValidateDataSourceConfigRequest{ + Config: *req.Config, + } + + if dataSource, ok := dataSource.(tfsdk.DataSourceWithConfigValidators); ok { + logging.FrameworkTrace(ctx, "DataSource implements DataSourceWithConfigValidators") + + for _, configValidator := range dataSource.ConfigValidators(ctx) { + vdscResp := &tfsdk.ValidateDataSourceConfigResponse{ + Diagnostics: resp.Diagnostics, + } + + logging.FrameworkDebug( + ctx, + "Calling provider defined DataSourceConfigValidator", + map[string]interface{}{ + logging.KeyDescription: configValidator.Description(ctx), + }, + ) + configValidator.Validate(ctx, vdscReq, vdscResp) + logging.FrameworkDebug( + ctx, + "Called provider defined DataSourceConfigValidator", + map[string]interface{}{ + logging.KeyDescription: configValidator.Description(ctx), + }, + ) + + resp.Diagnostics = vdscResp.Diagnostics + } + } + + if dataSource, ok := dataSource.(tfsdk.DataSourceWithValidateConfig); ok { + logging.FrameworkTrace(ctx, "DataSource implements DataSourceWithValidateConfig") + + vdscResp := &tfsdk.ValidateDataSourceConfigResponse{ + Diagnostics: resp.Diagnostics, + } + + logging.FrameworkDebug(ctx, "Calling provider defined Provider ValidateConfig") + dataSource.ValidateConfig(ctx, vdscReq, vdscResp) + logging.FrameworkDebug(ctx, "Called provider defined Provider ValidateConfig") + + resp.Diagnostics = vdscResp.Diagnostics + } + + validateSchemaReq := ValidateSchemaRequest{ + Config: *req.Config, + } + validateSchemaResp := ValidateSchemaResponse{ + Diagnostics: resp.Diagnostics, + } + + SchemaValidate(ctx, req.Config.Schema, validateSchemaReq, &validateSchemaResp) + + resp.Diagnostics = validateSchemaResp.Diagnostics +} diff --git a/internal/fwserver/server_validatedatasourceconfig_test.go b/internal/fwserver/server_validatedatasourceconfig_test.go new file mode 100644 index 000000000..7265fa397 --- /dev/null +++ b/internal/fwserver/server_validatedatasourceconfig_test.go @@ -0,0 +1,50 @@ +package fwserver_test + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/internal/fwserver" + "github.com/hashicorp/terraform-plugin-framework/internal/testing/emptyprovider" +) + +// TODO: Migrate tfsdk.Provider bits of proto6server.testProviderServer to +// new internal/testing/provider.Provider that allows customization of all +// method implementations via struct fields. Then, create additional test +// cases in this unit test. +// +// For now this testing is covered by proto6server.ValidateDataSourceConfig. +// +// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/215 +func TestServerValidateDataSourceConfig(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + server *fwserver.Server + request *fwserver.ValidateDataSourceConfigRequest + expectedResponse *fwserver.ValidateDataSourceConfigResponse + }{ + "empty-provider": { + server: &fwserver.Server{ + Provider: &emptyprovider.Provider{}, + }, + expectedResponse: &fwserver.ValidateDataSourceConfigResponse{}, + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + response := &fwserver.ValidateDataSourceConfigResponse{} + testCase.server.ValidateDataSourceConfig(context.Background(), testCase.request, response) + + if diff := cmp.Diff(response, testCase.expectedResponse); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/internal/proto6server/serve.go b/internal/proto6server/serve.go index b9ddcc1de..8fedcf987 100644 --- a/internal/proto6server/serve.go +++ b/internal/proto6server/serve.go @@ -1298,120 +1298,39 @@ func (r validateDataResourceConfigResponse) toTfprotov6() *tfprotov6.ValidateDat } } -func (s *Server) ValidateDataResourceConfig(ctx context.Context, req *tfprotov6.ValidateDataResourceConfigRequest) (*tfprotov6.ValidateDataResourceConfigResponse, error) { +func (s *Server) ValidateDataResourceConfig(ctx context.Context, proto6Req *tfprotov6.ValidateDataResourceConfigRequest) (*tfprotov6.ValidateDataResourceConfigResponse, error) { ctx = s.registerContext(ctx) ctx = logging.InitContext(ctx) - resp := &validateDataResourceConfigResponse{} - s.validateDataResourceConfig(ctx, req, resp) + fwResp := &fwserver.ValidateDataSourceConfigResponse{} - return resp.toTfprotov6(), nil -} - -func (s *Server) validateDataResourceConfig(ctx context.Context, req *tfprotov6.ValidateDataResourceConfigRequest, resp *validateDataResourceConfigResponse) { - - // Get the type of data source, so we can get its schema and create an - // instance - dataSourceType, diags := s.getDataSourceType(ctx, req.TypeName) - resp.Diagnostics.Append(diags...) - - if resp.Diagnostics.HasError() { - return - } - - // Get the schema from the data source type, so we can embed it in the - // config - logging.FrameworkDebug(ctx, "Calling provider defined DataSourceType GetSchema") - dataSourceSchema, diags := dataSourceType.GetSchema(ctx) - logging.FrameworkDebug(ctx, "Called provider defined DataSourceType GetSchema") - resp.Diagnostics.Append(diags...) - - if resp.Diagnostics.HasError() { - return - } + dataSourceType, diags := s.FrameworkServer.DataSourceType(ctx, proto6Req.TypeName) - // Create the data source instance, so we can call its methods and handle - // the request - logging.FrameworkDebug(ctx, "Calling provider defined DataSourceType NewDataSource") - dataSource, diags := dataSourceType.NewDataSource(ctx, s.Provider) - logging.FrameworkDebug(ctx, "Called provider defined DataSourceType NewDataSource") - resp.Diagnostics.Append(diags...) - - if resp.Diagnostics.HasError() { - return - } - - config, err := req.Config.Unmarshal(dataSourceSchema.TerraformType(ctx)) - - if err != nil { - resp.Diagnostics.AddError( - "Error parsing config", - "The provider had a problem parsing the config. Report this to the provider developer:\n\n"+err.Error(), - ) - - return - } + fwResp.Diagnostics.Append(diags...) - vrcReq := tfsdk.ValidateDataSourceConfigRequest{ - Config: tfsdk.Config{ - Raw: config, - Schema: dataSourceSchema, - }, + if fwResp.Diagnostics.HasError() { + return toproto6.ValidateDataSourceConfigResponse(ctx, fwResp), nil } - if dataSource, ok := dataSource.(tfsdk.DataSourceWithConfigValidators); ok { - logging.FrameworkTrace(ctx, "DataSource implements DataSourceWithConfigValidators") - for _, configValidator := range dataSource.ConfigValidators(ctx) { - vrcRes := &tfsdk.ValidateDataSourceConfigResponse{ - Diagnostics: resp.Diagnostics, - } + dataSourceSchema, diags := s.FrameworkServer.DataSourceSchema(ctx, proto6Req.TypeName) - logging.FrameworkDebug( - ctx, - "Calling provider defined DataSourceConfigValidator", - map[string]interface{}{ - logging.KeyDescription: configValidator.Description(ctx), - }, - ) - configValidator.Validate(ctx, vrcReq, vrcRes) - logging.FrameworkDebug( - ctx, - "Called provider defined DataSourceConfigValidator", - map[string]interface{}{ - logging.KeyDescription: configValidator.Description(ctx), - }, - ) + fwResp.Diagnostics.Append(diags...) - resp.Diagnostics = vrcRes.Diagnostics - } + if fwResp.Diagnostics.HasError() { + return toproto6.ValidateDataSourceConfigResponse(ctx, fwResp), nil } - if dataSource, ok := dataSource.(tfsdk.DataSourceWithValidateConfig); ok { - logging.FrameworkTrace(ctx, "DataSource implements DataSourceWithValidateConfig") - vrcRes := &tfsdk.ValidateDataSourceConfigResponse{ - Diagnostics: resp.Diagnostics, - } - - logging.FrameworkDebug(ctx, "Calling provider defined DataSource ValidateConfig") - dataSource.ValidateConfig(ctx, vrcReq, vrcRes) - logging.FrameworkDebug(ctx, "Called provider defined DataSource ValidateConfig") + fwReq, diags := fromproto6.ValidateDataSourceConfigRequest(ctx, proto6Req, dataSourceType, dataSourceSchema) - resp.Diagnostics = vrcRes.Diagnostics - } + fwResp.Diagnostics.Append(diags...) - validateSchemaReq := fwserver.ValidateSchemaRequest{ - Config: tfsdk.Config{ - Raw: config, - Schema: dataSourceSchema, - }, - } - validateSchemaResp := fwserver.ValidateSchemaResponse{ - Diagnostics: resp.Diagnostics, + if fwResp.Diagnostics.HasError() { + return toproto6.ValidateDataSourceConfigResponse(ctx, fwResp), nil } - fwserver.SchemaValidate(ctx, dataSourceSchema, validateSchemaReq, &validateSchemaResp) + s.FrameworkServer.ValidateDataSourceConfig(ctx, fwReq, fwResp) - resp.Diagnostics = validateSchemaResp.Diagnostics + return toproto6.ValidateDataSourceConfigResponse(ctx, fwResp), nil } // readDataSourceResponse is a thin abstraction to allow native Diagnostics usage diff --git a/internal/proto6server/serve_test.go b/internal/proto6server/serve_test.go index 1698dbd13..cc6b95107 100644 --- a/internal/proto6server/serve_test.go +++ b/internal/proto6server/serve_test.go @@ -359,6 +359,16 @@ func TestServerGetProviderSchema_logging(t *testing.T) { "@message": "Called provider defined Provider GetResources", "@module": "sdk.framework", }, + { + "@level": "trace", + "@message": "Checking DataSourceSchemas lock", + "@module": "sdk.framework", + }, + { + "@level": "trace", + "@message": "Checking DataSourceTypes lock", + "@module": "sdk.framework", + }, { "@level": "debug", "@message": "Calling provider defined Provider GetDataSources", @@ -6166,7 +6176,9 @@ func TestServerValidateDataResourceConfig(t *testing.T) { validateDataSourceConfigImpl: tc.impl, } testServer := &Server{ - Provider: s, + FrameworkServer: fwserver.Server{ + Provider: s, + }, } dv, err := tfprotov6.NewDynamicValue(tc.dataSourceType, tc.config) diff --git a/internal/toproto6/validatedatasourceconfig.go b/internal/toproto6/validatedatasourceconfig.go new file mode 100644 index 000000000..aba737ad8 --- /dev/null +++ b/internal/toproto6/validatedatasourceconfig.go @@ -0,0 +1,22 @@ +package toproto6 + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/internal/fwserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" +) + +// ValidateDataSourceConfigResponse returns the *tfprotov6.ValidateDataSourceConfigResponse +// equivalent of a *fwserver.ValidateDataSourceConfigResponse. +func ValidateDataSourceConfigResponse(ctx context.Context, fw *fwserver.ValidateDataSourceConfigResponse) *tfprotov6.ValidateDataResourceConfigResponse { + if fw == nil { + return nil + } + + proto6 := &tfprotov6.ValidateDataResourceConfigResponse{ + Diagnostics: Diagnostics(fw.Diagnostics), + } + + return proto6 +} diff --git a/internal/toproto6/validatedatasourceconfig_test.go b/internal/toproto6/validatedatasourceconfig_test.go new file mode 100644 index 000000000..485262b59 --- /dev/null +++ b/internal/toproto6/validatedatasourceconfig_test.go @@ -0,0 +1,66 @@ +package toproto6_test + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/internal/fwserver" + "github.com/hashicorp/terraform-plugin-framework/internal/toproto6" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" +) + +func TestValidateDataSourceConfigResponse(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + input *fwserver.ValidateDataSourceConfigResponse + expected *tfprotov6.ValidateDataResourceConfigResponse + }{ + "nil": { + input: nil, + expected: nil, + }, + "empty": { + input: &fwserver.ValidateDataSourceConfigResponse{}, + expected: &tfprotov6.ValidateDataResourceConfigResponse{}, + }, + "diagnostics": { + input: &fwserver.ValidateDataSourceConfigResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewWarningDiagnostic("test warning summary", "test warning details"), + diag.NewErrorDiagnostic("test error summary", "test error details"), + }, + }, + expected: &tfprotov6.ValidateDataResourceConfigResponse{ + Diagnostics: []*tfprotov6.Diagnostic{ + { + Severity: tfprotov6.DiagnosticSeverityWarning, + Summary: "test warning summary", + Detail: "test warning details", + }, + { + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "test error summary", + Detail: "test error details", + }, + }, + }, + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := toproto6.ValidateDataSourceConfigResponse(context.Background(), testCase.input) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +}