Пример #1
0
// 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
}
Пример #2
0
// 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
}
Пример #3
0
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)
	}
}
Пример #4
0
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")
	}
}
Пример #5
0
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())
}
Пример #6
0
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)
}
Пример #7
0
// 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
}