Ejemplo n.º 1
0
func main() {
	index := resource.NewIndex()

	users := index.Bind("users", resource.New(models.UserSchema, mem.NewHandler(), resource.Conf{
		AllowedModes:           resource.ReadWrite,
		PaginationDefaultLimit: 50,
	}))

	models.SetAuthUserResource(users)

	index.Bind("auth", resource.New(models.AuthSchema, mem.NewHandler(), resource.Conf{
		AllowedModes: []resource.Mode{resource.Create},
	}))

	api, err := rest.NewHandler(index)
	if err != nil {
		log.Fatal(err)
	}

	http.Handle("/", api)

	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatal(err)
	}
}
Ejemplo n.º 2
0
func main() {
	// Create a REST API resource index
	index := resource.NewIndex()

	// Add a resource on /users[/:user_id]
	users := index.Bind("users", resource.New(user, mem.NewHandler(), resource.Conf{
		// We allow all REST methods
		// (rest.ReadWrite is a shortcut for []resource.Mode{resource.Create, resource.Read, resource.Update, resource.Delete, resource,List})
		AllowedModes: resource.ReadWrite,
	}))

	// Bind a sub resource on /users/:user_id/posts[/:post_id]
	// and reference the user on each post using the "user" field of the posts resource.
	posts := users.Bind("posts", "user", resource.New(post, mem.NewHandler(), resource.Conf{
		// Posts can only be read, created and deleted, not updated
		AllowedModes: []resource.Mode{resource.Read, resource.List, resource.Create, resource.Delete},
	}))

	// Add a friendly alias to public posts
	// (equivalent to /users/:user_id/posts?filter={"published":true})
	posts.Alias("public", url.Values{"filter": []string{"{\"published\":true}"}})

	// Create API HTTP handler for the resource graph
	api, err := rest.NewHandler(index)
	if err != nil {
		log.Fatalf("Invalid API configuration: %s", err)
	}

	// Init a xhandler chain (see https://github.com/rs/xhandler)
	c := xhandler.Chain{}

	// Add close notifier handler so context is cancelled when the client closes
	// the connection
	c.UseC(xhandler.CloseHandler)

	// Add timeout handler
	c.UseC(xhandler.TimeoutHandler(2 * time.Second))

	// Install a logger (see https://github.com/rs/xlog)
	c.UseC(xlog.NewHandler(xlog.Config{}))

	// Log API access
	c.UseC(xaccess.NewHandler())

	// Add CORS support with passthrough option on so rest-layer can still
	// handle OPTIONS method
	c.UseC(cors.New(cors.Options{OptionsPassthrough: true}).HandlerC)

	// Bind the API under /api/ path
	http.Handle("/api/", http.StripPrefix("/api/", c.Handler(api)))

	// Serve it
	log.Print("Serving API on http://localhost:8080")
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatal(err)
	}
}
Ejemplo n.º 3
0
func TestHandlerServeHTTPParentNotFound(t *testing.T) {
	i := resource.NewIndex()
	foo := i.Bind("foo", resource.New(schema.Schema{}, mem.NewHandler(), resource.DefaultConf))
	foo.Bind("bar", "f", resource.New(schema.Schema{"f": schema.Field{}}, nil, resource.DefaultConf))
	h, _ := NewHandler(i)
	w := newRecorder()
	defer w.Close()
	u, _ := url.ParseRequestURI("/foo/1/bar/2")
	h.ServeHTTP(w, &http.Request{Method: "GET", URL: u})
	assert.Equal(t, 404, w.Code)
	b, _ := ioutil.ReadAll(w.Body)
	assert.Equal(t, "{\"code\":404,\"message\":\"Parent Resource Not Found\"}", string(b))
}
Ejemplo n.º 4
0
func main() {
	// Create a REST API resource index
	index := resource.NewIndex()

	// Bind user on /users
	users := index.Bind("users", resource.New(user, mem.NewHandler(), resource.Conf{
		AllowedModes: resource.ReadWrite,
	}))

	// Init the db with some users (user registration is not handled by this example)
	secret, _ := schema.Password{}.Validate("secret")
	users.Insert(context.Background(), []*resource.Item{
		&resource.Item{ID: "admin", Updated: time.Now(), ETag: "abcd", Payload: map[string]interface{}{
			"id":       "admin",
			"name":     "Dilbert",
			"password": secret,
		}},
		&resource.Item{ID: "john", Updated: time.Now(), ETag: "efgh", Payload: map[string]interface{}{
			"id":       "john",
			"name":     "John Doe",
			"password": secret,
		}},
	})

	// Bind post on /posts
	index.Bind("posts", resource.New(post, mem.NewHandler(), resource.Conf{
		AllowedModes: resource.ReadWrite,
	}))

	// Create API HTTP handler for the resource graph
	api, err := rest.NewHandler(index)
	if err != nil {
		log.Fatalf("Invalid API configuration: %s", err)
	}

	// Bind the authentication middleware
	// api.Use(myAuthMiddleware{userResource: users})

	// Bind the API under /
	http.Handle("/", api)

	// Serve it
	log.Print("Serving API on http://localhost:8080")
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatal(err)
	}
}
Ejemplo n.º 5
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)
	}
}
Ejemplo n.º 6
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")
	}
}
Ejemplo n.º 7
0
func TestHandlerServeHTTP(t *testing.T) {
	i := resource.NewIndex()
	i.Bind("foo", resource.New(schema.Schema{}, nil, resource.DefaultConf))
	h, _ := NewHandler(i)
	w := newRecorder()
	defer w.Close()
	u, _ := url.ParseRequestURI("/foo")
	h.ServeHTTP(w, &http.Request{Method: "GET", URL: u})
	assert.Equal(t, 501, w.Code)
	b, _ := ioutil.ReadAll(w.Body)
	assert.Equal(t, "{\"code\":501,\"message\":\"No Storage Defined\"}", string(b))
}
Ejemplo n.º 8
0
func TestNewHandlerNoCompile(t *testing.T) {
	i := resource.NewIndex()
	i.Bind("foo", resource.New(schema.Schema{
		"f": schema.Field{
			Validator: schema.String{
				Regexp: "[",
			},
		},
	}, nil, resource.DefaultConf))
	_, err := NewHandler(i)
	assert.EqualError(t, err, "foo: schema compilation error: f: not a schema.Validator pointer")
}
Ejemplo n.º 9
0
func main() {
	// Create a REST API resource index
	index := resource.NewIndex()

	// Add a resource on /users[/:user_id]
	users := index.Bind("users", resource.New(user, mem.NewHandler(), resource.Conf{
		// We allow all REST methods
		// (rest.ReadWrite is a shortcut for []resource.Mode{resource.Create, resource.Read, resource.Update, resource.Delete, resource,List})
		AllowedModes: resource.ReadWrite,
	}))

	// Bind a sub resource on /users/:user_id/posts[/:post_id]
	// and reference the user on each post using the "user" field of the posts resource.
	posts := users.Bind("posts", "user", resource.New(post, mem.NewHandler(), resource.Conf{
		// Posts can only be read, created and deleted, not updated
		AllowedModes: []resource.Mode{resource.Read, resource.List, resource.Create, resource.Delete},
	}))

	// Add a friendly alias to public posts
	// (equivalent to /users/:user_id/posts?filter={"published":true})
	posts.Alias("public", url.Values{"filter": []string{"{\"published\":true}"}})

	// Create API HTTP handler for the resource graph
	api, err := rest.NewHandler(index)
	if err != nil {
		log.Fatalf("Invalid API configuration: %s", err)
	}

	// Add cors support
	h := cors.New(cors.Options{OptionsPassthrough: true}).Handler(api)

	// Bind the API under /api/ path
	http.Handle("/api/", http.StripPrefix("/api/", h))

	// Serve it
	log.Print("Serving API on http://localhost:8080")
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatal(err)
	}
}
Ejemplo n.º 10
0
func main() {
	index := resource.NewIndex()

	index.Bind("posts", resource.New(post, mem.NewHandler(), resource.Conf{
		// Posts can only be read, created and deleted, not updated
		AllowedModes: resource.ReadWrite,
	}))

	// Create API HTTP handler for the resource graph
	api, err := rest.NewHandler(index)
	if err != nil {
		log.Fatalf("Invalid API configuration: %s", err)
	}

	// Bind the API under /api/ path
	http.Handle("/", api)

	// Serve it
	log.Print("Serving API on http://localhost:8080")
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatal(err)
	}
}
Ejemplo n.º 11
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())
}
Ejemplo n.º 12
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)
}
Ejemplo n.º 13
0
func Example() {
	var (
		// Define a user resource schema
		user = schema.Schema{
			"id": schema.Field{
				Required: true,
				// When a field is read-only, on default values or hooks can
				// set their value. The client can't change it.
				ReadOnly: true,
				// This is a field hook called when a new user is created.
				// The schema.NewID hook is a provided hook to generate a
				// unique id when no value is provided.
				OnInit: &schema.NewID,
				// The Filterable and Sortable allows usage of filter and sort
				// on this field in requests.
				Filterable: true,
				Sortable:   true,
				Validator: &schema.String{
					Regexp: "^[0-9a-f]{32}$",
				},
			},
			"created": schema.Field{
				Required:   true,
				ReadOnly:   true,
				Filterable: true,
				Sortable:   true,
				OnInit:     &schema.Now,
				Validator:  &schema.Time{},
			},
			"updated": schema.Field{
				Required:   true,
				ReadOnly:   true,
				Filterable: true,
				Sortable:   true,
				OnInit:     &schema.Now,
				// The OnUpdate hook is called when the item is edited. Here we use
				// provided Now hook which just return the current time.
				OnUpdate:  &schema.Now,
				Validator: &schema.Time{},
			},
			// Define a name field as required with a string validator
			"name": schema.Field{
				Required:   true,
				Filterable: true,
				Validator: &schema.String{
					MaxLen: 150,
				},
			},
		}

		// Define a post resource schema
		post = schema.Schema{
			// schema.*Field are shortcuts for common fields (identical to users' same fields)
			"id":      schema.IDField,
			"created": schema.CreatedField,
			"updated": schema.UpdatedField,
			// Define a user field which references the user owning the post.
			// See bellow, the content of this field is enforced by the fact
			// that posts is a sub-resource of users.
			"user": schema.Field{
				Required:   true,
				Filterable: true,
				Validator: &schema.Reference{
					Path: "users",
				},
			},
			"public": schema.Field{
				Filterable: true,
				Validator:  &schema.Bool{},
			},
			// Sub-documents are handled via a sub-schema
			"meta": schema.Field{
				Schema: &schema.Schema{
					"title": schema.Field{
						Required: true,
						Validator: &schema.String{
							MaxLen: 150,
						},
					},
					"body": schema.Field{
						Validator: &schema.String{
							MaxLen: 100000,
						},
					},
				},
			},
		}
	)

	// Create a REST API root resource
	index := resource.NewIndex()

	// Add a resource on /users[/:user_id]
	users := index.Bind("users", resource.New(user, mem.NewHandler(), resource.Conf{
		// We allow all REST methods
		// (rest.ReadWrite is a shortcut for []rest.Mode{Create, Read, Update, Delete, List})
		AllowedModes: resource.ReadWrite,
	}))

	// Bind a sub resource on /users/:user_id/posts[/:post_id]
	// and reference the user on each post using the "user" field of the posts resource.
	posts := users.Bind("posts", "user", resource.New(post, mem.NewHandler(), resource.Conf{
		// Posts can only be read, created and deleted, not updated
		AllowedModes: []resource.Mode{resource.Read, resource.List, resource.Create, resource.Delete},
	}))

	// Add a friendly alias to public posts
	// (equivalent to /users/:user_id/posts?filter={"public":true})
	posts.Alias("public", url.Values{"filter": []string{"{\"public\"=true}"}})

	// Create API HTTP handler for the resource graph
	api, err := rest.NewHandler(index)
	if err != nil {
		log.Fatalf("Invalid API configuration: %s", err)
	}

	// Add cors support
	h := cors.New(cors.Options{OptionsPassthrough: true}).Handler(api)

	// Bind the API under /api/ path
	http.Handle("/api/", http.StripPrefix("/api/", h))

	// Serve it
	log.Print("Serving API on http://localhost:8080")
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatal(err)
	}
}