apikit

package module
v0.0.0-...-972e1d8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 1, 2016 License: MIT Imports: 12 Imported by: 3

README

revel-apikit

A way to rapidly develop REST APIs using the Revel framework

Goal

Writing controllers for a Revel REST API can get repetitive. You might find yourself writing Get(), Create(), Update(), and Delete() for every model in your database.

This package tries to abstract away that CRUD boilerplate. The following is an example of how you might implement a controller for just one of your models, a User:

type UserController struct
  *revel.Controller
  authenticatedUser *User
}

func (c *UserController) Get(id uint64) revel.Result {
	if user := GetUserByID(id); user == nil {
		return ApiMessage{
			StatusCode: http.StatusNotFound,
			Message: fmt.Sprint("User with ID ", id, " not found"),
		}
	} else if !user.CanBeViewedBy(c.authenticatedUser) {
		return ApiMessage{
			StatusCode: http.StatusUnauthorized,
			Message: fmt.Sprint("Unauthorized to view User with ID ", id),
		}
	} else {
		return c.RenderJson(user)
	}
}

func (c *UserController) Post() revel.Result {
	newUser := User{}
	err := json.NewDecoder(c.Request.Body).Decode(&newUser)
	if err != nil {
			return ApiMessage{
				StatusCode: http.StatusBadRequest,
				Message: "Improperly formatted request body",
			}
	}
	
	if !newUser.CanBeModifiedBy(c.authenticatedUser) {
		return ApiMessage{
			StatusCode: http.StatusUnauthorized,
			Message: "Not authorized to post this User",
		}
	}
	if err = newUser.Save(); err != nil {
		return ApiMessage{
			StatusCode: http.StatusBadRequest,
			Message: err.Error(),
		}
	} else {
		return c.RenderJson(newUser)
	}
}

func (c *UserController) Put() revel.Result {
	updatedUser := User{}
	err := json.NewDecoder(c.Request.Body).Decode(&updatedUser)
	if err != nil {
			return ApiMessage{
				StatusCode: http.StatusBadRequest,
				Message: "Improperly formatted request body",
			}
	}

	if !updatedUser.CanBeModifiedBy(c.authenticatedUser) {
		return ApiMessage{
			StatusCode: http.StatusUnauthorized,
			Message: "Not authorized to modify this User",
		}
	}
	if err = updatedUser.Save(); err != nil {
		return ApiMessage{
			StatusCode: http.StatusBadRequest,
			Message: err.Error(),
		}
	} else {
		return c.RenderJson(updatedUser)
	}
}

func (c *UserController) Delete(id uint64) revel.Result {
	if user := GetUserByID(id); user == nil {
		return ApiMessage{
			StatusCode: http.StatusNotFound,
			Message: fmt.Sprint("User with ID ", id, " not found"),
		}
	} else {
		if !user.CanBeModifiedBy(c.authenticatedUser) {
			return ApiMessage{
				StatusCode: http.StatusUnauthorized,
				Message: "Not authorized to delete this User",
			}
		}
		if err := user.Delete(); err != nil {
			return ApiMessage{
				StatusCode: http.StatusBadRequest,
				Message: err.Error(),
			}
		} else {
			return ApiMessage{
				StatusCode: http.StatusOK,
				Message: "Success",
			}
		}
	}
}

The best way to solve this problem would be to create a generic struct, but since Go does not currently support generics, this package tries to emulate a generic class using interfaces.

With this package, you can gain all of the above functionality by embeding a GenericRESTController within a Revel controller that implements the RESTController interface. The RESTController interface serves as a workaround for Go's lack of generics. Using it gives you all of the above functionality with only the following code:

type UserController struct {
  *revel.Controller
  apikit.GenericRESTController
}

func (c *UserController) ModelFactory() RESTObject {
	return &User{}
}

func (c *UserController) GetModelByID(id uint64) RESTObject {
	for _, u := range usersDB {
		if u.ID == id {
			return u
		}
	}
	return nil
}

Doing this will allow your UserController to serve the following Revel routes:

# UserController
GET     /users/:id                              UserController.Get
POST    /users                                  UserController.Post
PUT     /users                                  UserController.Put
DELETE  /users/:id                              UserController.Delete
Limitations
  • RESTController instances cannot have Actions other than those provided by GenericRESTController
  • conf/restcontroller-routes cannot use catchall : Actions
  • conf/restcontroller-routes cannot use wildcard * paths

Documentation

Index

Constants

View Source
const (
	RESTControllerName string = "GenericRESTController"
)

Variables

This section is empty.

Functions

func APIPanicFilter

func APIPanicFilter(c *revel.Controller, fc []revel.Filter)

func CopyImmutableAttributes

func CopyImmutableAttributes(source, dest interface{}) error

func CreateRESTControllerInjectionFilter

func CreateRESTControllerInjectionFilter(authFunction AuthenticationFunction) revel.Filter

func RegisterRESTControllers

func RegisterRESTControllers(controllers []RESTController)

Register the RESTControllers

Types

type ApiMessage

type ApiMessage struct {
	StatusCode int    `json:"code"`
	Message    string `json:"message"`
}

A revel.Result renderable object used to convey a status code and error message

func DefaultBadRequestMessage

func DefaultBadRequestMessage() ApiMessage

func DefaultInternalServerErrorMessage

func DefaultInternalServerErrorMessage() ApiMessage

func DefaultNotFoundMessage

func DefaultNotFoundMessage() ApiMessage

func (ApiMessage) Apply

func (msg ApiMessage) Apply(req *revel.Request, resp *revel.Response)

type AuthenticationFunction

type AuthenticationFunction func(username, password string) User

type DELETEHooker

type DELETEHooker interface {
	RESTController
	PreDELETEHook(model RESTObject, authUser User) revel.Result
	PostDELETEHook(model RESTObject, authUser User, err error) revel.Result
}

type GETHooker

type GETHooker interface {
	RESTController
	PreGETHook(id uint64, authUser User) revel.Result
	PostGETHook(model RESTObject, authUser User) revel.Result
}

type GenericRESTController

type GenericRESTController struct {
	Request *revel.Request
	// contains filtered or unexported fields
}

func (*GenericRESTController) Delete

func (c *GenericRESTController) Delete(id uint64) revel.Result

func (*GenericRESTController) Get

func (*GenericRESTController) Post

func (*GenericRESTController) Put

type HookJsonResult

type HookJsonResult struct {
	Body interface{}
}

Result that can be returned from a hook

func (HookJsonResult) Apply

func (result HookJsonResult) Apply(req *revel.Request, resp *revel.Response)

type ImmutableAttributeCopier

type ImmutableAttributeCopier interface {
	RESTObject
	CopyImmutableAttributesTo(dest interface{}) error
}

Copies attributes that should be immutable from the source RESTObject to the dest RESTObject

type POSTHooker

type POSTHooker interface {
	RESTController
	PrePOSTHook(model RESTObject, authUser User) revel.Result
	PostPOSTHook(model RESTObject, authUser User, err error) revel.Result
}

type PUTHooker

type PUTHooker interface {
	RESTController
	PrePUTHook(newInstance, existingInstance RESTObject, authUser User) revel.Result
	PostPUTHook(newInstance, existingInstance RESTObject, authUser User, err error) revel.Result
}

type RESTController

type RESTController interface {
	ModelFactory() RESTObject
	GetModelByID(id uint64) RESTObject

	EnableGET() bool
	EnablePOST() bool
	EnablePUT() bool
	EnableDELETE() bool
}

A Controller that concerns itself with managing one type of RESTObject

type RESTObject

type RESTObject interface {
	UniqueID() uint64

	CanBeViewedBy(user User) bool
	CanBeCreatedBy(user User) bool
	CanBeModifiedBy(user User) bool
	CanBeDeletedBy(user User) bool

	Validate(v *revel.Validation)

	Delete() error
	Save() error
}

A server-side data model that can be served by RESTControllers

type User

type User interface {
	RESTObject
	HasAdminPrivileges() bool
}

A RESTObject that can be authenticated by RESTControllers

Directories

Path Synopsis
example
app

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL