README
¶
Usher is a REST framework written in Go
(or Golang
) and has an unconventional design inspired by Kubernetes apimachinery
.
Install
With Go
installed:
go get -u github.com/spy16/usher
Getting Started
- See
example/main.go
to understand how to build REST applications. - See
cmd/README.md
for usage of CLI
Features
-
Uniform API Objects: Usher uses
Object
struct to represent all API resources throughout the system. OnlySpec
field will be formatted according to the resource registered.// See types.go for each field description type Object struct { ObjectID `json:",inline"` Meta `json:",inline"` Tags Tags `json:"tags"` State string `json:"state"` Spec Spec `json:"spec"` }
-
Due to this, all API resources will appear similar to the sample below to clients. Only value of
spec
key will change for different resources.{ "kind": "Test", "name": "hello", "parents": [], "created_on": "2017-08-03T20:03:33.184295276+05:30", "modified_on": "2017-08-03T20:03:33.184295313+05:30", "owner": "", "state": "READY", "tags": {}, "spec": { "body": "Hello World" } }
-
-
Automatic data validation: Usher performs basic validations based on the specification struct registered along with its controller.
// This is safe inside the controller. No need to use assertion check // to stay away from panics. Usher allows requests to reach controller // only if the request data conforms to `MySpec` spec := obj.Spec.(*MySpec)
-
Because of the uniformity, a single client library (e.g. See
client
package) can be used to manage all resources via REST API. -
A single CLI (similar to
kubectl
) to remotely manage all resources. -
Easy error handling through helpers:
Error
struct is used throughout the system to manage errors. For example, an unauthorized error as generated byusher.ErrUnauthorized(ctx)
helper:{ "code": 403, "type": "Unauthorized", "details": { "action": "CREATE", "kind": "Test", "name": "hello", "parents": [] }, "message": "You are not allowed to perform requested action" }
-
Middleware support: Any existing
net/http
compatible middlewares (e.g.gorilla/handlers
) can be used with Usher. -
Easy to implement
Authentication
andAuthorization
-
Automatic REST resource nesting
Documentation
¶
Index ¶
- Constants
- Variables
- func ObjectFromRequest(grp *APIGroup, r *http.Request, ps httprouter.Params) (Object, *Error)
- func ResolveParentList(refs *ParentRefs, grp *APIGroup, ps httprouter.Params)
- func ToAPIName(kn string) string
- func ToKindName(apiEntry string) string
- type APIGroup
- type AuthenticatorFunc
- type AuthorizationContext
- type AuthorizerFunc
- type ByName
- type ByOwner
- type ByState
- type Context
- type ContextKey
- type Controller
- type Error
- func CheckAuthorization(action string, grp *APIGroup, ctx Context, id ObjectID) *Error
- func ErrBadData() *Error
- func ErrBadRequest(reason string) *Error
- func ErrBadSpec(v interface{}) *Error
- func ErrConflict(rid string, rtype string) *Error
- func ErrConnection() *Error
- func ErrConversion(err error) *Error
- func ErrMethodNotAllowed() *Error
- func ErrMissingParam(param string) *Error
- func ErrNotFound(rid string, rtype string) *Error
- func ErrPathNotFound(path string) *Error
- func ErrUnauthenticated() *Error
- func ErrUnauthorized(authCtx AuthorizationContext) *Error
- func ErrUnexpected(err error) *Error
- type MapSpec
- type Meta
- type Middleware
- type NoOpController
- func (*NoOpController) Create(obj Object) (*Object, *Error)
- func (*NoOpController) Delete(id ObjectID) (*Object, *Error)
- func (*NoOpController) Exists(id ObjectID) *Error
- func (*NoOpController) Get(id ObjectID) (*Object, *Error)
- func (*NoOpController) GetAll(query Query) (*[]Object, *Error)
- func (*NoOpController) PartialUpdate(id ObjectID, updates []UpdateClause) (*Object, *Error)
- func (*NoOpController) Update(id ObjectID, newObject Object) (*Object, *Error)
- type Object
- type ObjectID
- type ParentRef
- type ParentRefs
- type Query
- type QueryClause
- type SelfValidator
- type Server
- func (srv *Server) AddMiddleware(m Middleware)
- func (srv *Server) GetHandler() http.Handler
- func (srv *Server) Handle(method string, path string, h httprouter.Handle)
- func (srv *Server) HandleFunc(method string, path string, h http.HandlerFunc)
- func (srv *Server) Register(apiName string, spec Spec, controller Controller, ms ...Middleware) *APIGroup
- func (srv *Server) RegisterGroup(grp *APIGroup) *APIGroup
- func (srv *Server) SetAuthenticator(f AuthenticatorFunc)
- func (srv *Server) SetAuthorizer(f AuthorizerFunc)
- func (srv *Server) SetBaseURL(base string)
- type Spec
- type Tags
- type UpdateClause
- type UpdateSpec
- type UpdateState
- type UpdateTags
Constants ¶
const ( // ActionGet represents GET request action. ActionGet = "GET" // ActionCreate represents POST request action ActionCreate = "CREATE" // ActionModify represents PUT and PATCH request action ActionModify = "MODIFY" // ActionDelete represents DELETE request action ActionDelete = "DELETE" )
const (
// StateReady represents the Ready state for an object.
StateReady = "READY"
)
const ( // SystemUser is a special user id which is used whenever // a logical user doesn't exist. SystemUser = "system" )
Variables ¶
var EmptyMap = map[string]interface{}{}
EmptyMap is default empty map
Functions ¶
func ObjectFromRequest ¶
ObjectFromRequest builds object by reading query params and body data. JSON encoding is assumed for body.
func ResolveParentList ¶
func ResolveParentList(refs *ParentRefs, grp *APIGroup, ps httprouter.Params)
ResolveParentList traverses the APIGroup parent hierarchy and populates the refs in order.
func ToKindName ¶
ToKindName converts kind-name from format `projects` to `Project`
Types ¶
type APIGroup ¶
type APIGroup struct { // ParentGroup ParentGroup *APIGroup // APIName is the API partial for the resource (e.g. projects) APIName string // Kind is the type of the API resource (e.g. Project) Kind string // contains filtered or unexported fields }
APIGroup is group of APIs for one type of resource.
func NewAPIGroup ¶
func NewAPIGroup(apiName string, spec Spec, ctrl Controller) *APIGroup
NewAPIGroup initializes an API Group
func (*APIGroup) SetParent ¶
SetParent can be used to add a parent api group to this API Group. This allows nesting of resources which is a core concept in REST conventions. When nested, each object or query will be populated with parent details in the `Meta.Parents` and `Parents` field respectively. The list of parents will be in the order the groups were registered.
type AuthenticatorFunc ¶
AuthenticatorFunc will be used to authenticate requests globally or per-APIGroup basis. If, function returns true, authentication will pass and `id` will be injected into request context with key `SubjectKey` type.
type AuthorizationContext ¶
type AuthorizationContext struct { // Subject is the entity that is performing the action. Subject string `json:"subject"` // These 3 fields together identify the entity on which // action is being performed. Kind string `json:"kind"` Name string `json:"name"` Parents []ParentRef `json:"parent"` // Action is the operation being performed on the object. Action string `json:"action"` }
AuthorizationContext is used to pass information to Authorizer when certain action requires Authorization.
type AuthorizerFunc ¶
type AuthorizerFunc func(reqCtx Context, authCtx AuthorizationContext) bool
AuthorizerFunc is responsible for authorizing a request. This function should answer the question "Is `Subject` Allowed to perform `Action` on `Object` ?".
type Context ¶
Context is used to exchange request information between controllers and middlewares.
func ContextFromRequest ¶
ContextFromRequest extracts relevant information from request object.
type ContextKey ¶
type ContextKey string
ContextKey type is used as type of key for Request Context values.
var ParamsKey ContextKey = "params"
ParamsKey is used to inject params into request context.
var SubjectKey ContextKey = "subject"
SubjectKey will be used as key in request context to store user identity during a request.
type Controller ¶
type Controller interface { // GetAll should fetch all the objects matching the given query. If // query fails for any reason, an appropriate error should be returned // in which case the first return value can be nil-pointer. // GetAll maps to `GET /{kind}?filter1=value&...` GetAll(query Query) (*[]Object, *Error) // Create should store the object. This matches to `POST /{kind}` // endpoints. Create(obj Object) (*Object, *Error) // Resource Operations // Get should return the object identifying the `id`. This maps // to `GET /{kind}/{name}` requests. Get(id ObjectID) (*Object, *Error) // Exists should return nil-pointer if an object exists which // can be identified using `id`. If the object is not found, // a not-found error should be returned. Other errors can be // returned, but note that, in some places all errors might // be considered as NotFound error. // This method maps to `HEAD /{kind}/{name}` Exists(id ObjectID) *Error // Update should replace the existing object identified by the // `id` by the newObject completely. Note that, fields (Kind, // Name, Parents, Meta) should never be changed for an object. // This method maps to `PUT /{kind}/{name}` request. Update(id ObjectID, newObject Object) (*Object, *Error) // PartialUpdate should partially update the object identified by // the `id`. Update should consider only the fields specified in // the `updates` array for modifications. Other fields should be // left untouched. // This method maps to `PATCH /{kind}/{name}`. PartialUpdate(id ObjectID, updates []UpdateClause) (*Object, *Error) // Delete should remove the resource from the server completely. // This method maps to `DELETE /{kind}/{name}`. Delete(id ObjectID) (*Object, *Error) }
Controller interface can be implemented to add business logic around resources. Every request, will be resolved to a controller based on the kind and the request will be delegated to an appropriate method in this interface. A method can be disabled by returning error MethodNotSupported (Hint: Use `usher.ErrMethodNotAllowed()`) NOTE: Controllers are re-used across requests. So no request-scoped data should be stored in the controller.
type Error ¶
type Error struct { // Code is generally a 4xx or 5xx HTTP Standard code Code int `json:"code"` // Type is a single word, camel-cased name for the error. This helps in // differentiating between common error codes. For example, code can be // 400 to represent bad request and type name an be InvalidValue, InvalidType // MissingParameter etc. Type string `json:"type"` // Details provides any additional information abou the error. For example, // in case of MissingParameter error, details will contain the name of the // missing parameter. Details map[string]interface{} `json:"details"` // Message is a string which can be directly shown to user. This should // not contain any technical errors. Message string `json:"message"` // Err object if this struct was initialized in response to an `error`. Err error `json:"error,omitempty"` }
Error is used to communicate different errors throughout usher framework. Generally, instead of directly creating instance of Error object, common helper methods such as `ErrUnauthenticated` are defined in this package which can be used easily.
func CheckAuthorization ¶
CheckAuthorization checks if the request can be allowed under given context. Context should contain the Subject id under key 'SubjectKey'.
func ErrBadData ¶
func ErrBadData() *Error
ErrBadData represents a bad request with non-parsable data
func ErrBadRequest ¶
ErrBadRequest represents a generic bad request
func ErrBadSpec ¶
func ErrBadSpec(v interface{}) *Error
ErrBadSpec is returned when specification is invalid
func ErrConflict ¶
ErrConflict represents a conflicting resource
func ErrConversion ¶
ErrConversion can be used represent conversion error.s
func ErrMissingParam ¶
ErrMissingParam represents a missing parameter
func ErrNotFound ¶
ErrNotFound represents an access to non-existent resource
func ErrPathNotFound ¶
ErrPathNotFound represents an access to non-existent resource
func ErrUnauthorized ¶
func ErrUnauthorized(authCtx AuthorizationContext) *Error
ErrUnauthorized represents 403
func ErrUnexpected ¶
ErrUnexpected represents an unexpected internal error Err field will be populated in this case
type MapSpec ¶
type MapSpec map[string]interface{}
MapSpec is a type alias for map that is used to initialize Object.Spec field.
type Meta ¶
type Meta struct { // CreatedOn should contain the time of creation of this object. CreatedOn time.Time `json:"created_on"` // ModifiedOn should contain the time of last modification of this // object. While creating the object, this should be same as the // CreatedOn timestamp. ModifiedOn time.Time `json:"modified_on"` // Owner can be used to store information regarding the subject who // created the resource to which this meta is attached. Owner string `json:"owner"` // SelfURI contains self link to the resource SelfURI string `json:"self_uri"` }
Meta is used to represent meta information about an object which includes creation and modification timestamps, tags, parent information etc.
type Middleware ¶
type Middleware func(h http.HandlerFunc) http.HandlerFunc
Middleware can be used to intercept request between handlers.
type NoOpController ¶
type NoOpController struct { }
NoOpController is a default implementation of Controller interface. By default NoOpController performs no operation and returns `ErrMethodNotAllowed`.
func (*NoOpController) Exists ¶
func (*NoOpController) Exists(id ObjectID) *Error
func (*NoOpController) PartialUpdate ¶
func (*NoOpController) PartialUpdate(id ObjectID, updates []UpdateClause) (*Object, *Error)
type Object ¶
type Object struct { // ObjectID contains all the information required to uniquey and completely // identify an object in Usher. ObjectID `json:",inline"` // Meta is used to represent meta information about an object which // includes creation and modification timestamps, tags, parent information // etc. Meta `json:",inline"` // Tags can be used to store additional information for the object // which can later be used to filter search results. Tags Tags `json:"tags"` // State represents the state of object. State will not be used by // usher, but is a field to be used by usher applications. State string `json:"state"` // Spec should contain the specification for the object. This is the // field that actually describes the intent/data of the object. The // specification model registered with the resource will be used to // verify and populate this field. So, inside the controller, simple // assertion will be enough without error handling to load this to // a variable of that type. Spec Spec `json:"spec"` }
Object is the core type in usher. Every component is built around object and represents any/all REST resources.
type ObjectID ¶
type ObjectID struct { // Kind represents the type of the object. Kind will be CamelCased. // API name for a kind will be generated by converting it to plural. // For example, for kind `Test` the api endpoint will be `/tests`. // Look at ToAPIName() and ToKindName() for information on how the // conversion is done. Kind string `json:"kind"` // Name represents a unique name for the object. Name is not // exclusively unique. Name will be unique in a (Kind, Parents) // scope. Name string `json:"name"` // Parents represents all the logical parents of this resource. The // array is ordered according to the hierarchy of APIGroups. Parents ParentRefs `json:"parents"` }
ObjectID contains all the information required to uniquey and completely identify an object in Usher.
func IDFromRequest ¶
IDFromRequest extracts ObjectID from a resource request. This method will return zero-value of ObjectID for collection-level requests. (i.e., when name=="").
func (*ObjectID) CollectionPath ¶
CollectionPath creates the path for the collection of objects of given kind.
type ParentRef ¶
ParentRef is used to identify parents of a resource. All parents are appended automatically by Usher request handlers and passed down to the controller.
type ParentRefs ¶
type ParentRefs []ParentRef
ParentRefs is an ordered array of `ParentRef` types which represents parents of an object in a hierarchy.
func (*ParentRefs) Get ¶
func (prs *ParentRefs) Get(kind string) ObjectID
Get returns the ObjectID for the parent of given `kind`.
func (*ParentRefs) Path ¶
func (prs *ParentRefs) Path() string
Path generates the uri-like path for the object representing the parent hierarchy.
type Query ¶
type Query struct { // Filter by parts of ID ObjectID // Owner can be used to filter objects by user who // created the resource. Owner string // State can be used to filter objects by state. State string // Tags can be used to filter objects by tags. Tags map[string]string // Params can be used to provide additional parameters to // controllers and ObjectStore implementations. Params map[string]string }
Query is used to identify an object or collection of objects by controllers, stores etc.
func QueryFromRequest ¶
QueryFromRequest extracts query related information from the request.
func (*Query) IsValidCollectionID ¶
IsValidCollectionID verifies if query can be used to uniquely and completely identify a collection of objects of same kinds. This is possible only if (Kind) has appropriate value.
func (*Query) IsValidItemID ¶
IsValidItemID verifies if query can be used to uniquely and completely identify an object. In Usher, the tuple (Kind, Name, [Parents]) is required to identify an object uniquely.
type QueryClause ¶
type QueryClause string
QueryClause is used to filter results in GetAll requests.
type SelfValidator ¶
type SelfValidator interface {
Validate() *Error
}
SelfValidator is an entity that can validate itself.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is the main usher server.
func (*Server) AddMiddleware ¶
func (srv *Server) AddMiddleware(m Middleware)
AddMiddleware can be used to register middlewares to the server. Note that, middlewares will be executed in the same order they were added in.
func (*Server) GetHandler ¶
GetHandler generates a 'http.Handler' by combining all API Groups and all middlewares.
func (*Server) Handle ¶
func (srv *Server) Handle(method string, path string, h httprouter.Handle)
Handle can be used to register httprouter.Handle implementations to non-resource api paths. This is `httprouter` version of the Server.HandleFunc function.
func (*Server) HandleFunc ¶
func (srv *Server) HandleFunc(method string, path string, h http.HandlerFunc)
HandleFunc can be used to register handlers for non-resource api paths. This can be used to for registering paths for login, health check endpoints etc. This function is a shorthand for `mux.router.HandleFunc`
func (*Server) Register ¶
func (srv *Server) Register(apiName string, spec Spec, controller Controller, ms ...Middleware) *APIGroup
Register can be used to create a new APIGroup with given apiname, spec and controller. This is just a shorthand for Server.RegisterGroup.
func (*Server) RegisterGroup ¶
RegisterGroup can be used to register a APIGroup to the server.
func (*Server) SetAuthenticator ¶
func (srv *Server) SetAuthenticator(f AuthenticatorFunc)
SetAuthenticator initializes the authentication middleware with given Authenticator function. If authentication function authenticates the user, the user id will be injected into the context. In addition to the user id, authentication function itself can inject additional values into the context which will be passed to the Controller. This may be additional user information etc.
func (*Server) SetAuthorizer ¶
func (srv *Server) SetAuthorizer(f AuthorizerFunc)
SetAuthorizer sets authorizer function which will be used to authorize user actions.
func (*Server) SetBaseURL ¶
SetBaseURL appends base to all the API endpoints.
type Tags ¶
Tags can be used to store additional information for the object which can later be used to filter search results.
type UpdateClause ¶
type UpdateClause interface{}
UpdateClause is an alias for interface{} that is used as placeholder in Interface.Update()
type UpdateSpec ¶
type UpdateSpec struct {
Spec Spec
}
UpdateSpec can be used to tell store implementations to update the specification of an object.
type UpdateState ¶
type UpdateState struct {
State string
}
UpdateState can be used to tell store implementations to update 'state' field of the object.
type UpdateTags ¶
UpdateTags can be used to tell store implementations to update the 'tags' field of the object.