diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..187669ea86 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea/ +.DS_Store +vendor/ diff --git a/helper/schema/service_builder.go b/helper/schema/service_builder.go new file mode 100644 index 0000000000..3a4d3483ac --- /dev/null +++ b/helper/schema/service_builder.go @@ -0,0 +1,59 @@ +package schema + +import "fmt" + +// ServiceBuilder is a helper type which allows accessing a canonical list of +// Data Sources and Resources supported by this Provider, or Service Package. +type ServiceBuilder struct { + services []ServiceRegistration +} + +// NewServiceBuilder returns a ServiceBuilder which allows accessing a canonical +// list of Data Sources and Resources from a list of Service Registrations +func NewServiceBuilder(services []ServiceRegistration) ServiceBuilder { + return ServiceBuilder{ + services: services, + } +} + +// DataSources returns a canonical list of Data Sources supported by the +// Services registered with this Service Builder. +func (s ServiceBuilder) DataSources() (*map[string]*Resource, error) { + dataSources := make(map[string]*Resource) + dataSourceRegistrationSource := make(map[string]string) + + for _, service := range s.services { + for k, v := range service.SupportedDataSources() { + if existing := dataSources[k]; existing != nil { + existingRegName := dataSourceRegistrationSource[k] + return nil, fmt.Errorf("Both %q and %q register Data Source %q!", existingRegName, service.Name(), k) + } + + dataSources[k] = v + dataSourceRegistrationSource[k] = service.Name() + } + } + + return &dataSources, nil +} + +// Resources returns a canonical list of Resources supported by the +// Services registered with this Service Builder. +func (s ServiceBuilder) Resources() (*map[string]*Resource, error) { + resources := make(map[string]*Resource) + resourceRegistrationSource := make(map[string]string) + + for _, service := range s.services { + for k, v := range service.SupportedResources() { + if existing := resources[k]; existing != nil { + existingRegName := resourceRegistrationSource[k] + return nil, fmt.Errorf("Both %q and %q register Resource %q!", existingRegName, service.Name(), k) + } + + resources[k] = v + resourceRegistrationSource[k] = service.Name() + } + } + + return &resources, nil +} diff --git a/helper/schema/service_builder_test.go b/helper/schema/service_builder_test.go new file mode 100644 index 0000000000..fa975e5ace --- /dev/null +++ b/helper/schema/service_builder_test.go @@ -0,0 +1,278 @@ +package schema + +import ( + "testing" +) + +func TestServiceBuilder_DataSources(t *testing.T) { + testCases := []struct { + name string + services []ServiceRegistration + expectedDataSources map[string]*Resource + shouldError bool + }{ + { + name: "none defined", + services: []ServiceRegistration{ + testServiceRegistration{ + name: "first", + dataSources: map[string]*Resource{}, + }, + }, + expectedDataSources: map[string]*Resource{}, + shouldError: false, + }, + { + name: "single item", + services: []ServiceRegistration{ + testServiceRegistration{ + name: "first", + dataSources: map[string]*Resource{ + "hello_world": {}, + }, + }, + }, + expectedDataSources: map[string]*Resource{ + "hello_world": {}, + }, + shouldError: false, + }, + { + name: "multiple items same service", + services: []ServiceRegistration{ + testServiceRegistration{ + name: "first", + dataSources: map[string]*Resource{ + "hello": {}, + "world": {}, + }, + }, + }, + expectedDataSources: map[string]*Resource{ + "hello": {}, + "world": {}, + }, + shouldError: false, + }, + { + name: "multiple items different service", + services: []ServiceRegistration{ + testServiceRegistration{ + name: "first", + dataSources: map[string]*Resource{ + "hello": {}, + "world": {}, + }, + }, + testServiceRegistration{ + name: "second", + dataSources: map[string]*Resource{ + "rick": {}, + "morty": {}, + }, + }, + }, + expectedDataSources: map[string]*Resource{ + "hello": {}, + "world": {}, + "rick": {}, + "morty": {}, + }, + shouldError: false, + }, + { + name: "conflicting items different services", + services: []ServiceRegistration{ + testServiceRegistration{ + name: "first", + dataSources: map[string]*Resource{ + "hello": {}, + }, + }, + testServiceRegistration{ + name: "second", + dataSources: map[string]*Resource{ + "hello": {}, + }, + }, + }, + expectedDataSources: map[string]*Resource{}, + shouldError: true, + }, + } + + for _, testCase := range testCases { + t.Logf("Testing %q..", testCase.name) + builder := NewServiceBuilder(testCase.services) + dataSources, err := builder.DataSources() + if testCase.shouldError { + if err != nil { + continue + } + + t.Fatalf("Expected an error but didn't get one!") + } + if !testCase.shouldError { + if err == nil { + continue + } + + t.Fatalf("Expected no error but got: %+v", err) + } + + if len(*dataSources) != len(testCase.expectedDataSources) { + t.Fatalf("Expected %d Data Sources but got %d", len(testCase.expectedDataSources), len(*dataSources)) + } + for k := range *dataSources { + if _, ok := testCase.expectedDataSources[k]; !ok { + t.Fatalf("Expected %q to be present but it wasn't!", k) + } + } + } +} + +func TestServiceBuilder_Resources(t *testing.T) { + testCases := []struct { + name string + services []ServiceRegistration + expectedResources map[string]*Resource + shouldError bool + }{ + { + name: "none defined", + services: []ServiceRegistration{ + testServiceRegistration{ + name: "first", + resources: map[string]*Resource{}, + }, + }, + expectedResources: map[string]*Resource{}, + shouldError: false, + }, + { + name: "single item", + services: []ServiceRegistration{ + testServiceRegistration{ + name: "first", + resources: map[string]*Resource{ + "hello_world": {}, + }, + }, + }, + expectedResources: map[string]*Resource{ + "hello_world": {}, + }, + shouldError: false, + }, + { + name: "multiple items same service", + services: []ServiceRegistration{ + testServiceRegistration{ + name: "first", + resources: map[string]*Resource{ + "hello": {}, + "world": {}, + }, + }, + }, + expectedResources: map[string]*Resource{ + "hello": {}, + "world": {}, + }, + shouldError: false, + }, + { + name: "multiple items different service", + services: []ServiceRegistration{ + testServiceRegistration{ + name: "first", + dataSources: map[string]*Resource{ + "hello": {}, + "world": {}, + }, + }, + testServiceRegistration{ + name: "second", + dataSources: map[string]*Resource{ + "rick": {}, + "morty": {}, + }, + }, + }, + expectedResources: map[string]*Resource{ + "hello": {}, + "world": {}, + "rick": {}, + "morty": {}, + }, + shouldError: false, + }, + { + name: "conflicting items different services", + services: []ServiceRegistration{ + testServiceRegistration{ + name: "first", + resources: map[string]*Resource{ + "hello": {}, + }, + }, + testServiceRegistration{ + name: "second", + resources: map[string]*Resource{ + "hello": {}, + }, + }, + }, + expectedResources: map[string]*Resource{}, + shouldError: true, + }, + } + + for _, testCase := range testCases { + t.Logf("Testing %q..", testCase.name) + builder := NewServiceBuilder(testCase.services) + resources, err := builder.Resources() + if testCase.shouldError { + if err != nil { + continue + } + + t.Fatalf("Expected an error but didn't get one!") + } + if !testCase.shouldError { + if err == nil { + continue + } + + t.Fatalf("Expected no error but got: %+v", err) + } + + if len(*resources) != len(testCase.expectedResources) { + t.Fatalf("Expected %d Resources but got %d", len(testCase.expectedResources), len(*resources)) + } + for k := range *resources { + if _, ok := testCase.expectedResources[k]; !ok { + t.Fatalf("Expected %q to be present but it wasn't!", k) + } + } + } +} + +type testServiceRegistration struct { + name string + dataSources map[string]*Resource + resources map[string]*Resource +} + +func (r testServiceRegistration) Name() string { + return r.name +} +func (r testServiceRegistration) WebsiteCategories() []string { + return []string{} +} +func (r testServiceRegistration) SupportedDataSources() map[string]*Resource { + return r.dataSources +} +func (r testServiceRegistration) SupportedResources() map[string]*Resource { + return r.resources +} diff --git a/helper/schema/service_registration.go b/helper/schema/service_registration.go new file mode 100644 index 0000000000..77d7b88b5b --- /dev/null +++ b/helper/schema/service_registration.go @@ -0,0 +1,21 @@ +package schema + +// ServiceRegistration returns the MetaData for a given Service, which +// is a collection of related Data Sources and Resources. +// +// Services allow larger providers to become more maintainable by +// grouping relevant functionality together - which allows helper methods +// to be better scoped and rebuilding only the components that have changed. +type ServiceRegistration interface { + // Name is the name of this Service + Name() string + + // WebsiteCategories returns a list of categories used in the Website + WebsiteCategories() []string + + // SupportedDataSources returns the supported Data Sources supported by this Service + SupportedDataSources() map[string]*Resource + + // SupportedResources returns the supported Resources supported by this Service + SupportedResources() map[string]*Resource +}