From cbfed619dffa9f867a28234e68a735064b297ec1 Mon Sep 17 00:00:00 2001 From: Derek Dowling Date: Sun, 13 Dec 2015 04:15:19 -0800 Subject: [PATCH] ToOne, ToMany, Mutate resource handler helpers --- Godeps/Godeps.json | 4 +- api.go | 23 ++++-- api_test.go | 2 +- mock_storage.go | 70 ++++++++++++++++++ relationship.go | 11 +++ resource.go | 163 ++++++++++++++++++++++++++++------------- resource_test.go | 176 +++++++++++++++++++++++++++++---------------- store/store.go | 24 ++++--- test_util.go | 64 ----------------- 9 files changed, 347 insertions(+), 190 deletions(-) create mode 100644 mock_storage.go create mode 100644 relationship.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 8384b04..4f91ade 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -13,8 +13,8 @@ }, { "ImportPath": "github.com/derekdowling/go-json-spec-handler", - "Comment": "0.5.4", - "Rev": "19686b8b9bd5939783e2f4a168719a5e017de100" + "Comment": "0.6.1-2-g5e8d72a", + "Rev": "5e8d72a55f2ec253c28795b7b3aa16ed57a7b303" }, { "ImportPath": "github.com/jtolds/gls", diff --git a/api.go b/api.go index eb1a2b4..c34088f 100644 --- a/api.go +++ b/api.go @@ -18,7 +18,16 @@ type API struct { Logger *log.Logger } -// New initializes a new top level API Resource Handler. +// New initializes a new top level API Resource Handler. The most basic implementation +// is: +// +// api := New("", nil) +// +// But also supports prefixing(//) and custom logging via +// log.Logger https://godoc.org/log#Logger: +// +// api := New("v1", log.New(os.Stdout, "apiV1: ", log.Ldate|log.Ltime|log.Lshortfile)) +// func New(prefix string, logger *log.Logger) *API { // ensure that our top level prefix is "/" prefixed @@ -38,16 +47,20 @@ func New(prefix string, logger *log.Logger) *API { } } -// AddResource adds a new resource of type "name" to the API's router -func (a *API) AddResource(resource *Resource) { +// Add implements mux support for a given resource which is effectively handled as: +// pat.New("/(prefix/)resource.Plu*) +func (a *API) Add(resource *Resource) { - // add prefix and logger + // ensure the resource is properly prefixed, and has access to the API logger resource.prefix = a.prefix resource.Logger = a.Logger + // track our associated resources, will enable auto-generation docs later a.Resources[resource.Type] = resource - // Add subrouter to main API mux, use Matcher plus catch all + // Add resource wild card to the API mux. Use the resources Matcher() function + // after an API prefix is applied, as it does the dirty work of building the route + // automatically for us a.Mux.HandleC(pat.New(resource.Matcher()+"*"), resource) } diff --git a/api_test.go b/api_test.go index c611f8b..b252152 100644 --- a/api_test.go +++ b/api_test.go @@ -14,7 +14,7 @@ func TestAPI(t *testing.T) { Convey("->AddResource()", func() { resource := NewMockResource("", "test", 1, nil) - api.AddResource(resource) + api.Add(resource) So(resource.prefix, ShouldEqual, "/foo") So(api.Resources["test"], ShouldEqual, resource) diff --git a/mock_storage.go b/mock_storage.go new file mode 100644 index 0000000..54df3e3 --- /dev/null +++ b/mock_storage.go @@ -0,0 +1,70 @@ +package jshapi + +import ( + "log" + "strconv" + + "github.com/derekdowling/go-json-spec-handler" + "golang.org/x/net/context" +) + +// MockStorage allows you to mock out APIs really easily, and is also used internally +// for testing the API layer. +type MockStorage struct { + // ResourceType is the name of the resource you are mocking i.e. "user", "comment" + ResourceType string + // ResourceAttributes a sample set of attributes a resource object should have + // used by GET /resources and GET /resources/:id + ResourceAttributes interface{} + // ListCount is the number of sample objects to return in a GET /resources request + ListCount int +} + +// Save assigns a URL of 1 to the object +func (m *MockStorage) Save(ctx context.Context, object *jsh.Object) (*jsh.Object, *jsh.Error) { + object.ID = "1" + return object, nil +} + +// Get returns a resource with ID as specified by the request +func (m *MockStorage) Get(ctx context.Context, id string) (*jsh.Object, *jsh.Error) { + return m.SampleObject(id), nil +} + +// List returns a sample list +func (m *MockStorage) List(ctx context.Context) (jsh.List, *jsh.Error) { + return m.SampleList(m.ListCount), nil +} + +// Update does nothing +func (m *MockStorage) Update(ctx context.Context, object *jsh.Object) (*jsh.Object, *jsh.Error) { + return object, nil +} + +// Delete does nothing +func (m *MockStorage) Delete(ctx context.Context, id string) *jsh.Error { + return nil +} + +// SampleObject builds an object based on provided resource specifications +func (m *MockStorage) SampleObject(id string) *jsh.Object { + object, err := jsh.NewObject(id, m.ResourceType, m.ResourceAttributes) + if err != nil { + log.Fatal(err.Error()) + } + + return object +} + +// SampleList generates a sample list of resources that can be used for/against the +// mock API +func (m *MockStorage) SampleList(length int) jsh.List { + + list := jsh.List{} + + for id := 1; id <= length; id++ { + list = append(list, m.SampleObject(strconv.Itoa(id))) + } + + return list +} diff --git a/relationship.go b/relationship.go new file mode 100644 index 0000000..ddfac37 --- /dev/null +++ b/relationship.go @@ -0,0 +1,11 @@ +package jshapi + +// Relationship helps define the relationship between two resources +type Relationship string + +const ( + // ToOne signifies a one to one relationship + ToOne Relationship = "One-To-One" + // ToMany signifies a one to many relationship + ToMany Relationship = "One-To-Many" +) diff --git a/resource.go b/resource.go index 95f83e1..ce2ca90 100644 --- a/resource.go +++ b/resource.go @@ -26,7 +26,7 @@ const ( ) // Resource holds the necessary state for creating a REST API endpoint for a -// given resource type. Will be accessible via `/[prefix/]s` where the +// given resource type. Will be accessible via `/(prefix/)types` where the // proceeding `prefix/` is only precent if it is not empty. // // Using NewCRUDResource you can generate a generic CRUD handler for a @@ -56,11 +56,11 @@ type Resource struct { Type string // An implementation of Go's standard logger Logger *log.Logger - // Map of resource type to subresources - Subresources map[string]*Resource // Prefix is set if the resource is not the top level of URI, "/prefix/resources Routes []string - prefix string + // Map of relationships + Relationships map[string]Relationship + prefix string } // NewResource is a resource constructor that makes no assumptions about routes @@ -68,11 +68,11 @@ type Resource struct { // managing routes and handling API calls. func NewResource(resourceType string) *Resource { return &Resource{ - Mux: goji.NewMux(), - Type: resourceType, - Subresources: map[string]*Resource{}, - Routes: []string{}, - prefix: "/", + Mux: goji.NewMux(), + Type: resourceType, + Relationships: map[string]Relationship{}, + Routes: []string{}, + prefix: "/", } } @@ -160,15 +160,94 @@ func (res *Resource) Patch(storage store.Update) { res.addRoute(patch, res.IDMatcher()) } +// ToOne handles the /resources/:id/(relationships/) route which +// represents a One-To-One relationship between the resource and the +// specified resourceType +func (res *Resource) ToOne( + resourceType string, + storage store.Get, +) { + resourceType = strings.TrimSuffix(resourceType, "s") + + res.relationshipHandler( + resourceType, + func(ctx context.Context, w http.ResponseWriter, r *http.Request) { + res.getHandler(ctx, w, r, storage) + }, + ) + + res.Relationships[resourceType] = ToOne +} + +// ToMany handles the /resources/:id/(relationships/)s route which +// represents a One-To-Many relationship between the resource and the +// specified resourceType +func (res *Resource) ToMany( + resourceType string, + storage store.ToMany, +) { + if !strings.HasSuffix(resourceType, "s") { + resourceType = fmt.Sprintf("%ss", resourceType) + } + + res.relationshipHandler( + resourceType, + func(ctx context.Context, w http.ResponseWriter, r *http.Request) { + res.toManyHandler(ctx, w, r, storage) + }, + ) + + res.Relationships[resourceType] = ToMany +} + +// relationshipHandler does the dirty work of setting up both routes for a single +// relationship +func (res *Resource) relationshipHandler( + resourceType string, + handler goji.HandlerFunc, +) { + + // handle /.../:id/ + matcher := fmt.Sprintf("%s/%s", res.IDMatcher(), resourceType) + res.HandleFuncC( + pat.Get(matcher), + handler, + ) + res.addRoute(get, matcher) + + // handle /.../:id/relationships/ + relationshipMatcher := fmt.Sprintf("%s/relationships/%s", res.IDMatcher(), resourceType) + res.HandleFuncC( + pat.Get(relationshipMatcher), + handler, + ) + res.addRoute(get, relationshipMatcher) +} + +// Mutate allows you to add custom actions to your resource types, it uses the +// GET /(prefix/)resourceTypes/:id/ path format +func (res *Resource) Mutate(actionName string, storage store.Get) { + matcher := path.Join(res.IDMatcher(), actionName) + + res.HandleFuncC( + pat.Get(matcher), + func(ctx context.Context, w http.ResponseWriter, r *http.Request) { + res.mutateHandler(ctx, w, r, storage) + }, + ) + + res.addRoute(patch, matcher) +} + // POST /resources func (res *Resource) postHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, storage store.Save) { - object, err := jsh.ParseObject(r) + parsedObject, err := jsh.ParseObject(r) if err != nil { res.SendAndLog(ctx, w, r, err) return } - err = storage(ctx, object) + object, err := storage(ctx, parsedObject) if err != nil { res.SendAndLog(ctx, w, r, err) return @@ -216,13 +295,13 @@ func (res *Resource) deleteHandler(ctx context.Context, w http.ResponseWriter, r // PATCH /resources/:id func (res *Resource) patchHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, storage store.Update) { - object, err := jsh.ParseObject(r) + parsedObject, err := jsh.ParseObject(r) if err != nil { res.SendAndLog(ctx, w, r, err) return } - err = storage(ctx, object) + object, err := storage(ctx, parsedObject) if err != nil { res.SendAndLog(ctx, w, r, err) return @@ -231,20 +310,30 @@ func (res *Resource) patchHandler(ctx context.Context, w http.ResponseWriter, r res.SendAndLog(ctx, w, r, object) } -// NewAction allows you to add custom actions to your resource types, it uses the -// PATCH /(prefix/)resourceTypes/:id/ path format and expects a store.Update -// storage interface. -func (res *Resource) NewAction(actionName string, storage store.Update) { - matcher := path.Join(res.IDMatcher(), actionName) +// GET /resources/:id/(relationships/)s +func (res *Resource) toManyHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, storage store.ToMany) { + id := pat.Param(ctx, "id") - res.HandleFuncC( - pat.Get(matcher), - func(ctx context.Context, w http.ResponseWriter, r *http.Request) { - res.patchHandler(ctx, w, r, storage) - }, - ) + list, err := storage(ctx, id) + if err != nil { + res.SendAndLog(ctx, w, r, err) + return + } - res.addRoute(patch, matcher) + res.SendAndLog(ctx, w, r, list) +} + +// All HTTP Methods for /resources/:id/ +func (res *Resource) mutateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, storage store.Get) { + id := pat.Param(ctx, "id") + + response, err := storage(ctx, id) + if err != nil { + res.SendAndLog(ctx, w, r, err) + return + } + + res.SendAndLog(ctx, w, r, response) } // SendAndLog is a jsh wrapper function that handles logging 500 errors and @@ -280,36 +369,14 @@ func (res *Resource) addRoute(method string, route string) { res.Routes = append(res.Routes, fmt.Sprintf("%s - %s", method, route)) } -// NewNestedResource automatically builds a resource with the proper -// prefixes to ensure that it is accessible via /[prefix/]types/:id/subtypes -// and then returns it so you can register route. You can either manually add -// individual routes like normal, or make use of: -// subResource.CRUD(storage) -// to register the equivalent of what NewCRUDResource() gives you. -func (res *Resource) NewNestedResource(resourceType string) *Resource { - subResource := NewResource(resourceType) - subResource.prefix = res.IDMatcher() - - res.Subresources[resourceType] = subResource - - nestedMatcher := pat.New(fmt.Sprintf("%s/%s*", res.IDMatcher(), subResource.PluralType())) - log.Printf("nestedMatcher.String() = %+v\n", nestedMatcher.String()) - res.HandleC(nestedMatcher, subResource) - - return subResource -} - // RouteTree prints a recursive route tree based on what the resource, and // all subresources have registered func (res *Resource) RouteTree() string { var routes string + for _, route := range res.Routes { routes = strings.Join([]string{routes, route}, "\n") } - for _, resource := range res.Subresources { - routes = strings.Join([]string{routes, resource.RouteTree()}, "\n") - } - return routes } diff --git a/resource_test.go b/resource_test.go index cd3dff4..d35c299 100644 --- a/resource_test.go +++ b/resource_test.go @@ -39,47 +39,37 @@ func TestResource(t *testing.T) { Convey("->Post()", func() { object := sampleObject("", resourceType, attrs) - resp, err := jsc.Post(baseURL, object) - So(resp.StatusCode, ShouldEqual, http.StatusCreated) - So(err, ShouldBeNil) + object, resp, err := jsc.Post(baseURL, object) - obj, err := resp.GetObject() So(err, ShouldBeNil) - - So(obj.ID, ShouldEqual, "1") + So(resp.StatusCode, ShouldEqual, http.StatusCreated) + So(object.ID, ShouldEqual, "1") }) Convey("->List()", func() { - resp, err := jsc.Get(baseURL, resourceType, "") - So(resp.StatusCode, ShouldEqual, http.StatusOK) - So(err, ShouldBeNil) - - list, err := resp.GetList() + list, resp, err := jsc.GetList(baseURL, resourceType) So(err, ShouldBeNil) + So(resp.StatusCode, ShouldEqual, http.StatusOK) So(len(list), ShouldEqual, 2) So(list[0].ID, ShouldEqual, "1") }) Convey("->Get()", func() { - resp, err := jsc.Get(baseURL, resourceType, "3") - So(resp.StatusCode, ShouldEqual, http.StatusOK) - So(err, ShouldBeNil) + object, resp, err := jsc.GetObject(baseURL, resourceType, "3") - obj, err := resp.GetObject() So(err, ShouldBeNil) - So(obj.ID, ShouldEqual, "3") + So(resp.StatusCode, ShouldEqual, http.StatusOK) + So(object.ID, ShouldEqual, "3") }) Convey("->Patch()", func() { object := sampleObject("1", resourceType, attrs) - resp, err := jsc.Patch(baseURL, object) - So(resp.StatusCode, ShouldEqual, http.StatusOK) - So(err, ShouldBeNil) + object, resp, err := jsc.Patch(baseURL, object) - obj, err := resp.GetObject() + So(resp.StatusCode, ShouldEqual, http.StatusOK) So(err, ShouldBeNil) - So(obj.ID, ShouldEqual, "1") + So(object.ID, ShouldEqual, "1") }) Convey("->Delete()", func() { @@ -87,37 +77,48 @@ func TestResource(t *testing.T) { So(err, ShouldBeNil) So(resp.StatusCode, ShouldEqual, http.StatusOK) }) - }) } -func TestCustomAction(t *testing.T) { +func TestMutateHandler(t *testing.T) { - Convey("Resource Action Tests", t, func() { + Convey("Custom Handler Tests", t, func() { attrs := map[string]string{ "foo": "bar", } + resourceType := "bar" resource := NewMockResource("/foo", resourceType, 2, attrs) - Convey("->NewAction()", func() { - resource.NewAction("mutate", func(ctx context.Context, object *jsh.Object) jsh.SendableError { - target := map[string]string{} - object.Unmarshal("bar", target) - target["mutated"] = "true" - return nil - }) + handler := func(ctx context.Context, id string) (*jsh.Object, *jsh.Error) { + object := sampleObject(id, resourceType, attrs) + return object, nil + } + + resource.Mutate("mutate", handler) + + server := httptest.NewServer(resource) + baseURL := server.URL + resource.IDMatcher() + Convey("Resource State", func() { So(len(resource.Routes), ShouldEqual, 6) So(resource.Routes[len(resource.Routes)-1], ShouldEqual, "PATCH - /foo/bars/:id/mutate") }) + + Convey("->Custom()", func() { + response, err := jsc.Get(baseURL + "/mutate") + So(err, ShouldBeNil) + + _, err = jsc.ParseObject(response) + So(err, ShouldBeNil) + }) }) } -func TestNestedResource(t *testing.T) { +func TestToOne(t *testing.T) { - Convey("Nested Resource Tests", t, func() { + Convey("Relationship ToOne Tests", t, func() { attrs := map[string]string{ "foo": "bar", @@ -126,49 +127,104 @@ func TestNestedResource(t *testing.T) { resourceType := "bar" resource := NewMockResource("/foo", resourceType, 2, attrs) - subResourceType := "baz" - subStorageMock := &MockStorage{ - ResourceType: subResourceType, - ResourceAttributes: attrs, - ListCount: 2, + relationshipHandler := func(ctx context.Context, resourceID string) (*jsh.Object, *jsh.Error) { + return sampleObject("1", "baz", map[string]string{"baz": "ball"}), nil } - subResource := resource.NewNestedResource(subResourceType) - subResource.CRUD(subStorageMock) + subResourceType := "baz" + resource.ToOne(subResourceType, relationshipHandler) - // server := httptest.NewServer(resource) - // baseURL := server.URL + subResource.prefix + server := httptest.NewServer(resource) + baseURL := server.URL + resource.IDMatcher() - Convey("Resource", func() { + Convey("Resource State", func() { Convey("should track sub-resources properly", func() { - So(len(resource.Subresources), ShouldEqual, 1) - So(resource.Subresources[subResourceType], ShouldEqual, subResource) + So(len(resource.Relationships), ShouldEqual, 1) + So(len(resource.Routes), ShouldEqual, 7) }) }) - Convey("Sub-Resource prefix", func() { - So(subResource.prefix, ShouldEqual, "/foo/bars/:id") - }) + Convey("->ToOne()", func() { - Convey("->Matcher()", func() { - So(subResource.Matcher(), ShouldEqual, "/foo/bars/:id/bazs") + Convey("/foo/bars/:id/baz", func() { + resp, err := jsc.Get(baseURL + "/" + subResourceType) + So(err, ShouldBeNil) + So(resp.StatusCode, ShouldEqual, http.StatusOK) + + object, err := jsc.ParseObject(resp) + So(err, ShouldBeNil) + So(object.ID, ShouldEqual, "1") + }) + + Convey("/foo/bars/:id/relationships/baz", func() { + resp, err := jsc.Get(baseURL + "/relationships/" + subResourceType) + So(err, ShouldBeNil) + So(resp.StatusCode, ShouldEqual, http.StatusOK) + + obj, err := jsc.ParseObject(resp) + So(err, ShouldBeNil) + So(obj.ID, ShouldEqual, "1") + }) }) + }) +} - Convey("->IDMatcher()", func() { - So(subResource.IDMatcher(), ShouldEqual, "/foo/bars/:id/bazs/:id") +func TestToMany(t *testing.T) { + + Convey("Relationship ToMany Tests", t, func() { + + attrs := map[string]string{ + "foo": "bar", + } + + resourceType := "bar" + resource := NewMockResource("/foo", resourceType, 2, attrs) + + relationshipHandler := func(ctx context.Context, resourceID string) (jsh.List, *jsh.Error) { + return jsh.List{ + sampleObject("1", "baz", map[string]string{"baz": "ball"}), + sampleObject("2", "baz", map[string]string{"baz": "ball2"}), + }, nil + } + + subResourceType := "baz" + resource.ToMany(subResourceType, relationshipHandler) + + server := httptest.NewServer(resource) + baseURL := server.URL + resource.IDMatcher() + + Convey("Resource State", func() { + + Convey("should track sub-resources properly", func() { + So(len(resource.Relationships), ShouldEqual, 1) + So(len(resource.Routes), ShouldEqual, 7) + }) }) - Convey("->List()", func() { - // resp, err := jsc.Get(baseURL, subResourceType, "") - // So(resp.StatusCode, ShouldEqual, http.StatusOK) - // So(err, ShouldBeNil) + Convey("->ToOne()", func() { - // list, err := resp.GetList() - // So(err, ShouldBeNil) + Convey("/foo/bars/:id/bazs", func() { + resp, err := jsc.Get(baseURL + "/" + subResourceType + "s") + So(err, ShouldBeNil) + So(resp.StatusCode, ShouldEqual, http.StatusOK) - // So(len(list), ShouldEqual, 2) - // So(list[0].ID, ShouldEqual, "1") + list, err := jsc.ParseList(resp) + So(err, ShouldBeNil) + So(len(list), ShouldEqual, 2) + So(list[0].ID, ShouldEqual, "1") + }) + + Convey("/foo/bars/:id/relationships/bazs", func() { + resp, err := jsc.Get(baseURL + "/relationships/" + subResourceType + "s") + So(err, ShouldBeNil) + So(resp.StatusCode, ShouldEqual, http.StatusOK) + + list, err := jsc.ParseList(resp) + So(err, ShouldBeNil) + So(len(list), ShouldEqual, 2) + So(list[0].ID, ShouldEqual, "1") + }) }) }) diff --git a/store/store.go b/store/store.go index d1f2658..890f358 100644 --- a/store/store.go +++ b/store/store.go @@ -9,24 +9,28 @@ import ( // CRUD implements all sub-storage functions type CRUD interface { - Save(ctx context.Context, object *jsh.Object) jsh.SendableError - Get(ctx context.Context, id string) (*jsh.Object, jsh.SendableError) - List(ctx context.Context) (jsh.List, jsh.SendableError) - Update(ctx context.Context, object *jsh.Object) jsh.SendableError - Delete(ctx context.Context, id string) jsh.SendableError + Save(ctx context.Context, object *jsh.Object) (*jsh.Object, *jsh.Error) + Get(ctx context.Context, id string) (*jsh.Object, *jsh.Error) + List(ctx context.Context) (jsh.List, *jsh.Error) + Update(ctx context.Context, object *jsh.Object) (*jsh.Object, *jsh.Error) + Delete(ctx context.Context, id string) *jsh.Error } // Save a new resource to storage -type Save func(ctx context.Context, object *jsh.Object) jsh.SendableError +type Save func(ctx context.Context, object *jsh.Object) (*jsh.Object, *jsh.Error) // Get a specific instance of a resource by id from storage -type Get func(ctx context.Context, id string) (*jsh.Object, jsh.SendableError) +type Get func(ctx context.Context, id string) (*jsh.Object, *jsh.Error) // List all instances of a resource from storage -type List func(ctx context.Context) (jsh.List, jsh.SendableError) +type List func(ctx context.Context) (jsh.List, *jsh.Error) // Update an existing object in storage -type Update func(ctx context.Context, object *jsh.Object) jsh.SendableError +type Update func(ctx context.Context, object *jsh.Object) (*jsh.Object, *jsh.Error) // Delete an object from storage by id -type Delete func(ctx context.Context, id string) jsh.SendableError +type Delete func(ctx context.Context, id string) *jsh.Error + +// ToMany retrieves a list of objects of a single resource type that are related to +// the provided resource id +type ToMany func(ctx context.Context, id string) (jsh.List, *jsh.Error) diff --git a/test_util.go b/test_util.go index 3ecc246..87087ed 100644 --- a/test_util.go +++ b/test_util.go @@ -2,76 +2,12 @@ package jshapi import ( "log" - "strconv" - - "golang.org/x/net/context" "github.com/derekdowling/go-json-spec-handler" ) const testType = "test" -// MockStorage allows you to mock out APIs really easily, and is also used internally -// for testing the API layer. -type MockStorage struct { - // ResourceType is the name of the resource you are mocking i.e. "user", "comment" - ResourceType string - // ResourceAttributes a sample set of attributes a resource object should have - // used by GET /resources and GET /resources/:id - ResourceAttributes interface{} - // ListCount is the number of sample objects to return in a GET /resources request - ListCount int -} - -// Save assigns a URL of 1 to the object -func (m *MockStorage) Save(ctx context.Context, object *jsh.Object) jsh.SendableError { - object.ID = "1" - return nil -} - -// Get returns a resource with ID as specified by the request -func (m *MockStorage) Get(ctx context.Context, id string) (*jsh.Object, jsh.SendableError) { - return m.SampleObject(id), nil -} - -// List returns a sample list -func (m *MockStorage) List(ctx context.Context) (jsh.List, jsh.SendableError) { - return m.SampleList(m.ListCount), nil -} - -// Update does nothing -func (m *MockStorage) Update(ctx context.Context, object *jsh.Object) jsh.SendableError { - return nil -} - -// Delete does nothing -func (m *MockStorage) Delete(ctx context.Context, id string) jsh.SendableError { - return nil -} - -// SampleObject builds an object based on provided resource specifications -func (m *MockStorage) SampleObject(id string) *jsh.Object { - object, err := jsh.NewObject(id, m.ResourceType, m.ResourceAttributes) - if err != nil { - log.Fatal(err.Error()) - } - - return object -} - -// SampleList generates a sample list of resources that can be used for/against the -// mock API -func (m *MockStorage) SampleList(length int) jsh.List { - - list := jsh.List{} - - for id := 1; id <= length; id++ { - list = append(list, m.SampleObject(strconv.Itoa(id))) - } - - return list -} - // NewMockResource builds a mock API endpoint that can perform basic CRUD actions: // // GET /types