// listPost handles POST resquests on a resource URL func (r *request) listPost(ctx context.Context, route RouteMatch) (status int, headers http.Header, body interface{}) { var payload map[string]interface{} if e := r.decodePayload(&payload); e != nil { return e.Code, nil, e } rsrc := route.Resource() changes, base := rsrc.Validator().Prepare(payload, nil, false) // Append lookup fields to base payload so it isn't caught by ReadOnly // (i.e.: contains id and parent resource refs if any) route.applyFields(base) doc, errs := rsrc.Validator().Validate(changes, base) if len(errs) > 0 { return 422, nil, &Error{422, "Document contains error(s)", errs} } // Check that fields with the Reference validator reference an existing object if err := r.checkReferences(ctx, doc, rsrc.Validator()); err != nil { e := NewError(err) return e.Code, nil, e } item, err := resource.NewItem(doc) if err != nil { e := NewError(err) return e.Code, nil, e } // TODO: add support for batch insert if err := rsrc.Insert(ctx, []*resource.Item{item}); err != nil { e := NewError(err) return e.Code, nil, e } // See https://www.subbu.org/blog/2008/10/location-vs-content-location headers = http.Header{} headers.Set("Content-Location", fmt.Sprintf("/%s/%s", r.req.URL.Path, item.ID)) return 201, headers, item }
// itemPatch handles PATCH resquests on an item URL // // Reference: http://tools.ietf.org/html/rfc5789 func (r *request) itemPatch(ctx context.Context, route *RouteMatch) (status int, headers http.Header, body interface{}) { var payload map[string]interface{} if e := r.decodePayload(&payload); e != nil { return e.Code, nil, e } lookup, e := route.Lookup() if e != nil { return e.Code, nil, e } // Get original item if any rsrc := route.Resource() var original *resource.Item if l, err := rsrc.Find(ctx, lookup, 1, 1); err != nil { // If item can't be fetch, return an error e = NewError(err) return e.Code, nil, e } else if len(l.Items) == 0 { return ErrNotFound.Code, nil, ErrNotFound } else { original = l.Items[0] } // If-Match / If-Unmodified-Since handling if err := r.checkIntegrityRequest(original); err != nil { return err.Code, nil, err } changes, base := rsrc.Validator().Prepare(payload, &original.Payload, false) // Append lookup fields to base payload so it isn't caught by ReadOnly // (i.e.: contains id and parent resource refs if any) for k, v := range route.ResourcePath.Values() { base[k] = v // Also, ensure there's no tombstone set on the field if changes[k] == schema.Tombstone { delete(changes, k) } } doc, errs := rsrc.Validator().Validate(changes, base) if len(errs) > 0 { return 422, nil, &Error{422, "Document contains error(s)", errs} } // Check that fields with the Reference validator reference an existing object if e := r.checkReferences(ctx, doc, rsrc.Validator()); e != nil { return e.Code, nil, e } item, err := resource.NewItem(doc) if err != nil { e = NewError(err) return e.Code, nil, e } // Store the modified document by providing the orignal doc to instruct // handler to ensure the stored document didn't change between in the // interval. An ErrPreconditionFailed will be thrown in case of race condition // (i.e.: another thread modified the document between the Find() and the Store()) if err := rsrc.Update(ctx, item, original); err != nil { e = NewError(err) return e.Code, nil, e } return 200, nil, item }
func TestRoutePathParentsNotExists(t *testing.T) { index := resource.NewIndex() i, _ := resource.NewItem(map[string]interface{}{"id": "1234"}) h := &mockHandler{[]*resource.Item{i}, nil, []schema.Query{}} empty := &mockHandler{[]*resource.Item{}, nil, []schema.Query{}} foo := index.Bind("foo", resource.New(schema.Schema{}, empty, resource.DefaultConf)) foo.Bind("bar", "f", resource.New(schema.Schema{"f": schema.Field{}}, h, resource.DefaultConf)) ctx := context.Background() route := newRoute("GET") // non existing foo err := findRoute("/foo/4321/bar/1234", index, route) if assert.NoError(t, err) { err := route.ResourcePath.ParentsExist(ctx) assert.Equal(t, &Error{404, "Parent Resource Not Found", nil}, err) } }
func TestRoutePathParentsExists(t *testing.T) { var route *RouteMatch var err error index := resource.NewIndex() i, _ := resource.NewItem(map[string]interface{}{"id": "1234"}) h := &mockHandler{[]*resource.Item{i}, nil, []schema.Query{}} foo := index.Bind("foo", resource.New(schema.Schema{}, h, resource.DefaultConf)) bar := foo.Bind("bar", "f", resource.New(schema.Schema{"f": schema.Field{}}, h, resource.DefaultConf)) bar.Bind("baz", "b", resource.New(schema.Schema{"f": schema.Field{}, "b": schema.Field{}}, h, resource.DefaultConf)) ctx := context.Background() route = newRoute("GET") err = findRoute("/foo/1234/bar/5678/baz/9000", index, route) if assert.NoError(t, err) { err = route.ResourcePath.ParentsExist(ctx) assert.NoError(t, err) // There's 3 components in the path but only 2 are parents assert.Len(t, h.queries, 2) // query on /foo/1234 assert.Contains(t, h.queries, schema.Query{schema.Equal{Field: "id", Value: "1234"}}) // query on /bar/5678 with foo/1234 context assert.Contains(t, h.queries, schema.Query{schema.Equal{Field: "f", Value: "1234"}, schema.Equal{Field: "id", Value: "5678"}}) } route = newRoute("GET") // empty the storage handler h.items = []*resource.Item{} err = findRoute("/foo/1234/bar", index, route) if assert.NoError(t, err) { err = route.ResourcePath.ParentsExist(ctx) assert.Equal(t, &Error{404, "Parent Resource Not Found", nil}, err) } route = newRoute("GET") // for error h.err = errors.New("test") err = findRoute("/foo/1234/bar", index, route) if assert.NoError(t, err) { err = route.ResourcePath.ParentsExist(ctx) assert.EqualError(t, err, "test") } }
func TestFindRoute(t *testing.T) { var route *RouteMatch var err *Error index := resource.NewIndex() i, _ := resource.NewItem(map[string]interface{}{"id": "1234"}) h := &mockHandler{[]*resource.Item{i}, nil, []schema.Query{}} foo := index.Bind("foo", resource.New(schema.Schema{}, h, resource.DefaultConf)) bar := foo.Bind("bar", "f", resource.New(schema.Schema{"f": schema.Field{}}, h, resource.DefaultConf)) barbar := bar.Bind("bar", "b", resource.New(schema.Schema{"b": schema.Field{}}, h, resource.DefaultConf)) bar.Alias("baz", url.Values{"sort": []string{"foo"}}) route = newRoute("GET") err = findRoute("/foo", index, route) if assert.Nil(t, err) { assert.Equal(t, foo, route.Resource()) assert.Equal(t, url.Values{}, route.Params) assert.Nil(t, route.ResourceID()) rp := route.ResourcePath if assert.Len(t, rp, 1) { assert.Equal(t, "foo", rp[0].Name) assert.Equal(t, "", rp[0].Field) assert.Nil(t, rp[0].Value) } } route = newRoute("GET") err = findRoute("/foo/1234", index, route) if assert.Nil(t, err) { assert.Equal(t, foo, route.Resource()) assert.Equal(t, url.Values{}, route.Params) assert.Equal(t, "1234", route.ResourceID()) rp := route.ResourcePath if assert.Len(t, rp, 1) { assert.Equal(t, "foo", rp[0].Name) assert.Equal(t, "id", rp[0].Field) assert.Equal(t, "1234", rp[0].Value) } } route = newRoute("GET") err = findRoute("/foo/1234/bar", index, route) if assert.Nil(t, err) { assert.Equal(t, bar, route.Resource()) assert.Nil(t, route.ResourceID()) assert.Equal(t, url.Values{}, route.Params) rp := route.ResourcePath if assert.Len(t, rp, 2) { assert.Equal(t, "foo", rp[0].Name) assert.Equal(t, "f", rp[0].Field) assert.Equal(t, "1234", rp[0].Value) assert.Equal(t, "bar", rp[1].Name) assert.Equal(t, "", rp[1].Field) assert.Nil(t, rp[1].Value) } } route = newRoute("GET") err = findRoute("/foo/1234/bar/1234", index, route) if assert.Nil(t, err) { assert.Equal(t, bar, route.Resource()) assert.Equal(t, "1234", route.ResourceID()) assert.Equal(t, url.Values{}, route.Params) rp := route.ResourcePath if assert.Len(t, rp, 2) { assert.Equal(t, "foo", rp[0].Name) assert.Equal(t, "f", rp[0].Field) assert.Equal(t, "1234", rp[0].Value) assert.Equal(t, "bar", rp[1].Name) assert.Equal(t, "id", rp[1].Field) assert.Equal(t, "1234", rp[1].Value) } } route = newRoute("GET") err = findRoute("/foo/1234/bar/1234/bar", index, route) if assert.Nil(t, err) { assert.Equal(t, barbar, route.Resource()) assert.Nil(t, route.ResourceID()) assert.Equal(t, url.Values{}, route.Params) rp := route.ResourcePath if assert.Len(t, rp, 3) { assert.Equal(t, "foo", rp[0].Name) assert.Equal(t, "f", rp[0].Field) assert.Equal(t, "1234", rp[0].Value) assert.Equal(t, "bar", rp[1].Name) assert.Equal(t, "b", rp[1].Field) assert.Equal(t, "1234", rp[1].Value) assert.Equal(t, "bar", rp[2].Name) assert.Equal(t, "", rp[2].Field) assert.Nil(t, rp[2].Value) } } route = newRoute("GET") err = findRoute("/foo/1234/bar/1234/bar/1234", index, route) if assert.Nil(t, err) { assert.Equal(t, barbar, route.Resource()) assert.Equal(t, "1234", route.ResourceID()) assert.Equal(t, url.Values{}, route.Params) rp := route.ResourcePath if assert.Len(t, rp, 3) { assert.Equal(t, "foo", rp[0].Name) assert.Equal(t, "f", rp[0].Field) assert.Equal(t, "1234", rp[0].Value) assert.Equal(t, "bar", rp[1].Name) assert.Equal(t, "b", rp[1].Field) assert.Equal(t, "1234", rp[1].Value) assert.Equal(t, "bar", rp[2].Name) assert.Equal(t, "id", rp[2].Field) assert.Equal(t, "1234", rp[2].Value) } } route = newRoute("GET") err = findRoute("/foo/1234/bar/baz", index, route) if assert.Nil(t, err) { assert.Equal(t, bar, route.Resource()) assert.Equal(t, url.Values{"sort": []string{"foo"}}, route.Params) assert.Nil(t, route.ResourceID()) rp := route.ResourcePath if assert.Len(t, rp, 2) { assert.Equal(t, "foo", rp[0].Name) assert.Equal(t, "f", rp[0].Field) assert.Equal(t, "1234", rp[0].Value) assert.Equal(t, "bar", rp[1].Name) assert.Equal(t, "", rp[1].Field) assert.Nil(t, rp[1].Value) } } route = newRoute("GET") err = findRoute("/foo/1234/bar/baz/baz", index, route) assert.Equal(t, &Error{404, "Resource Not Found", nil}, err) assert.Nil(t, route.Resource()) assert.Nil(t, route.ResourceID()) }
func TestFindRoute(t *testing.T) { var route RouteMatch var err *Error index := resource.NewIndex() i, _ := resource.NewItem(map[string]interface{}{"id": "1234"}) h := &mockHandler{[]*resource.Item{i}, nil} foo := index.Bind("foo", resource.New(schema.Schema{}, h, resource.DefaultConf)) bar := foo.Bind("bar", "f", resource.New(schema.Schema{"f": schema.Field{}}, h, resource.DefaultConf)) barbar := bar.Bind("bar", "b", resource.New(schema.Schema{"b": schema.Field{}}, h, resource.DefaultConf)) bar.Alias("baz", url.Values{"sort": []string{"foo"}}) ctx := context.Background() route = newRoute("GET") err = findRoute(ctx, "/foo", index, &route) if assert.Nil(t, err) { assert.Equal(t, foo, route.Resource()) assert.Equal(t, url.Values{}, route.Params) assert.Nil(t, route.ResourceID()) if assert.Len(t, route.ResourcePath, 1) { assert.Equal(t, "foo", route.ResourcePath[0].Name) assert.Equal(t, "", route.ResourcePath[0].Field) assert.Nil(t, route.ResourcePath[0].Value) } } route = newRoute("GET") err = findRoute(ctx, "/foo/1234", index, &route) if assert.Nil(t, err) { assert.Equal(t, foo, route.Resource()) assert.Equal(t, url.Values{}, route.Params) assert.Equal(t, "1234", route.ResourceID()) if assert.Len(t, route.ResourcePath, 1) { assert.Equal(t, "foo", route.ResourcePath[0].Name) assert.Equal(t, "id", route.ResourcePath[0].Field) assert.Equal(t, "1234", route.ResourcePath[0].Value) } } route = newRoute("GET") err = findRoute(ctx, "/foo/1234/bar", index, &route) if assert.Nil(t, err) { assert.Equal(t, bar, route.Resource()) assert.Nil(t, route.ResourceID()) assert.Equal(t, url.Values{}, route.Params) if assert.Len(t, route.ResourcePath, 2) { assert.Equal(t, "foo", route.ResourcePath[0].Name) assert.Equal(t, "f", route.ResourcePath[0].Field) assert.Equal(t, "1234", route.ResourcePath[0].Value) assert.Equal(t, "bar", route.ResourcePath[1].Name) assert.Equal(t, "", route.ResourcePath[1].Field) assert.Nil(t, route.ResourcePath[1].Value) } } route = newRoute("GET") err = findRoute(ctx, "/foo/1234/bar/1234", index, &route) if assert.Nil(t, err) { assert.Equal(t, bar, route.Resource()) assert.Equal(t, "1234", route.ResourceID()) assert.Equal(t, url.Values{}, route.Params) if assert.Len(t, route.ResourcePath, 2) { assert.Equal(t, "foo", route.ResourcePath[0].Name) assert.Equal(t, "f", route.ResourcePath[0].Field) assert.Equal(t, "1234", route.ResourcePath[0].Value) assert.Equal(t, "bar", route.ResourcePath[1].Name) assert.Equal(t, "id", route.ResourcePath[1].Field) assert.Equal(t, "1234", route.ResourcePath[1].Value) } } route = newRoute("GET") err = findRoute(ctx, "/foo/1234/bar/1234/bar", index, &route) if assert.Nil(t, err) { assert.Equal(t, barbar, route.Resource()) assert.Nil(t, route.ResourceID()) assert.Equal(t, url.Values{}, route.Params) if assert.Len(t, route.ResourcePath, 3) { assert.Equal(t, "foo", route.ResourcePath[0].Name) assert.Equal(t, "f", route.ResourcePath[0].Field) assert.Equal(t, "1234", route.ResourcePath[0].Value) assert.Equal(t, "bar", route.ResourcePath[1].Name) assert.Equal(t, "b", route.ResourcePath[1].Field) assert.Equal(t, "1234", route.ResourcePath[1].Value) assert.Equal(t, "bar", route.ResourcePath[2].Name) assert.Equal(t, "", route.ResourcePath[2].Field) assert.Nil(t, route.ResourcePath[2].Value) } } route = newRoute("GET") err = findRoute(ctx, "/foo/1234/bar/1234/bar/1234", index, &route) if assert.Nil(t, err) { assert.Equal(t, barbar, route.Resource()) assert.Equal(t, "1234", route.ResourceID()) assert.Equal(t, url.Values{}, route.Params) if assert.Len(t, route.ResourcePath, 3) { assert.Equal(t, "foo", route.ResourcePath[0].Name) assert.Equal(t, "f", route.ResourcePath[0].Field) assert.Equal(t, "1234", route.ResourcePath[0].Value) assert.Equal(t, "bar", route.ResourcePath[1].Name) assert.Equal(t, "b", route.ResourcePath[1].Field) assert.Equal(t, "1234", route.ResourcePath[1].Value) assert.Equal(t, "bar", route.ResourcePath[2].Name) assert.Equal(t, "id", route.ResourcePath[2].Field) assert.Equal(t, "1234", route.ResourcePath[2].Value) } } route = newRoute("GET") err = findRoute(ctx, "/foo/1234/bar/baz", index, &route) if assert.Nil(t, err) { assert.Equal(t, bar, route.Resource()) assert.Equal(t, url.Values{"sort": []string{"foo"}}, route.Params) assert.Nil(t, route.ResourceID()) if assert.Len(t, route.ResourcePath, 2) { assert.Equal(t, "foo", route.ResourcePath[0].Name) assert.Equal(t, "f", route.ResourcePath[0].Field) assert.Equal(t, "1234", route.ResourcePath[0].Value) assert.Equal(t, "bar", route.ResourcePath[1].Name) assert.Equal(t, "", route.ResourcePath[1].Field) assert.Nil(t, route.ResourcePath[1].Value) } } route = newRoute("GET") err = findRoute(ctx, "/foo/1234/bar/baz/baz", index, &route) assert.Equal(t, &Error{404, "Resource Not Found", nil}, err) assert.Nil(t, route.Resource()) assert.Nil(t, route.ResourceID()) route = newRoute("GET") // empty the storage handler h.items = []*resource.Item{} err = findRoute(ctx, "/foo/1234/bar", index, &route) assert.Equal(t, ErrNotFound, err) route = newRoute("GET") // for error h.err = errors.New("test") err = findRoute(ctx, "/foo/1234/bar", index, &route) assert.Equal(t, &Error{520, "test", nil}, err) }
// itemPut handles PUT resquests on an item URL // // Reference: http://tools.ietf.org/html/rfc2616#section-9.6 func (r *request) itemPut(ctx context.Context, route *RouteMatch) (status int, headers http.Header, body interface{}) { var payload map[string]interface{} if e := r.decodePayload(&payload); e != nil { return e.Code, nil, e } lookup, e := route.Lookup() if e != nil { return e.Code, nil, e } rsrc := route.Resource() // Fetch original item if exist (PUT can be used to create a document with a manual id) var original *resource.Item if l, err := rsrc.Find(ctx, lookup, 1, 1); err != nil && err != ErrNotFound { e = NewError(err) return e.Code, nil, e } else if len(l.Items) == 1 { original = l.Items[0] } // Check if method is allowed based mode := resource.Create if original != nil { // If original is found, the mode is replace rather than create mode = resource.Replace } if !rsrc.Conf().IsModeAllowed(mode) { return 405, nil, &Error{405, "Invalid method", nil} } // If-Match / If-Unmodified-Since handling if err := r.checkIntegrityRequest(original); err != nil { return err.Code, nil, err } status = 200 var changes map[string]interface{} var base map[string]interface{} if original == nil { // PUT used to create a new document changes, base = rsrc.Validator().Prepare(payload, nil, false) status = 201 } else { // PUT used to replace an existing document changes, base = rsrc.Validator().Prepare(payload, &original.Payload, true) } // Append lookup fields to base payload so it isn't caught by ReadOnly // (i.e.: contains id and parent resource refs if any) for k, v := range route.ResourcePath.Values() { base[k] = v // Also, ensure there's no tombstone set on the field if changes[k] == schema.Tombstone { delete(changes, k) } } doc, errs := rsrc.Validator().Validate(changes, base) if len(errs) > 0 { return 422, nil, &Error{422, "Document contains error(s)", errs} } // Check that fields with the Reference validator reference an existing object if err := r.checkReferences(ctx, doc, rsrc.Validator()); err != nil { return err.Code, nil, err } if original != nil { if id, found := doc["id"]; found && id != original.ID { return 422, nil, &Error{422, "Cannot change document ID", nil} } } item, err := resource.NewItem(doc) if err != nil { e = NewError(err) return e.Code, nil, e } // If we have an original item, pass it to the handler so we make sure // we are still replacing the same version of the object as handler is // supposed check the original etag before storing when an original object // is provided. if original != nil { if err := rsrc.Update(ctx, item, original); err != nil { e = NewError(err) return e.Code, nil, e } } else { if err := rsrc.Insert(ctx, []*resource.Item{item}); err != nil { e = NewError(err) return e.Code, nil, e } } return status, nil, item }