Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Service Packages / Registration #421

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
@@ -0,0 +1,3 @@
.idea/
.DS_Store
vendor/
53 changes: 53 additions & 0 deletions helper/schema/service_builder.go
@@ -0,0 +1,53 @@
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,
}
}

// Resources returns a canonical list of Data Sources supported by the
tombuildsstuff marked this conversation as resolved.
Show resolved Hide resolved
// Services registered with this Service Builder.
func (s ServiceBuilder) DataSources() (*map[string]*Resource, error) {
dataSources := make(map[string]*Resource)

for _, service := range s.services {
for k, v := range service.SupportedDataSources() {
if existing := dataSources[k]; existing != nil {
return nil, fmt.Errorf("An existing Data Source exists for %q", k)
}

dataSources[k] = v
}
}

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)

for _, service := range s.services {
for k, v := range service.SupportedResources() {
if existing := resources[k]; existing != nil {
return nil, fmt.Errorf("An existing Resource exists for %q", k)
tombuildsstuff marked this conversation as resolved.
Show resolved Hide resolved
}

resources[k] = v
}
}

return &resources, nil
}
263 changes: 263 additions & 0 deletions helper/schema/service_builder_test.go
@@ -0,0 +1,263 @@
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{
dataSources: map[string]*Resource{},
},
},
expectedDataSources: map[string]*Resource{},
shouldError: false,
},
{
name: "single item",
services: []ServiceRegistration{
testServiceRegistration{
dataSources: map[string]*Resource{
"hello_world": {},
},
},
},
expectedDataSources: map[string]*Resource{
"hello_world": {},
},
shouldError: false,
},
{
name: "multiple items same service",
services: []ServiceRegistration{
testServiceRegistration{
dataSources: map[string]*Resource{
"hello": {},
"world": {},
},
},
},
expectedDataSources: map[string]*Resource{
"hello": {},
"world": {},
},
shouldError: false,
},
{
name: "multiple items different service",
services: []ServiceRegistration{
testServiceRegistration{
dataSources: map[string]*Resource{
"hello": {},
"world": {},
},
},
testServiceRegistration{
dataSources: map[string]*Resource{
"rick": {},
"morty": {},
},
},
},
expectedDataSources: map[string]*Resource{
"hello": {},
"world": {},
"rick": {},
"morty": {},
},
shouldError: false,
},
{
name: "conflicting items different services",
services: []ServiceRegistration{
testServiceRegistration{
dataSources: map[string]*Resource{
"hello": {},
},
},
testServiceRegistration{
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{
resources: map[string]*Resource{},
},
},
expectedResources: map[string]*Resource{},
shouldError: false,
},
{
name: "single item",
services: []ServiceRegistration{
testServiceRegistration{
resources: map[string]*Resource{
"hello_world": {},
},
},
},
expectedResources: map[string]*Resource{
"hello_world": {},
},
shouldError: false,
},
{
name: "multiple items same service",
services: []ServiceRegistration{
testServiceRegistration{
resources: map[string]*Resource{
"hello": {},
"world": {},
},
},
},
expectedResources: map[string]*Resource{
"hello": {},
"world": {},
},
shouldError: false,
},
{
name: "multiple items different service",
services: []ServiceRegistration{
testServiceRegistration{
dataSources: map[string]*Resource{
"hello": {},
"world": {},
},
},
testServiceRegistration{
dataSources: map[string]*Resource{
"rick": {},
"morty": {},
},
},
},
expectedResources: map[string]*Resource{
"hello": {},
"world": {},
"rick": {},
"morty": {},
},
shouldError: false,
},
{
name: "conflicting items different services",
services: []ServiceRegistration{
testServiceRegistration{
resources: map[string]*Resource{
"hello": {},
},
},
testServiceRegistration{
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 {
dataSources map[string]*Resource
resources map[string]*Resource
}

func (r testServiceRegistration) Name() string {
return "test"
}
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
}
21 changes: 21 additions & 0 deletions 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
}