Documentation ¶
Overview ¶
Vertex is a friendly, fast and flexible RESTful API building framework
What Vertex Includes ¶
1. An API definition framework
2. Request handlers as structs with automatic data mapping
3. Automatic Data Validation
4. An integrated testing framework for your API
5. A middleware framework similar (but not compliant) to negroni
6. Batteries included: JSON rendering, Auto Recover, Static File Serving, Request Logging, and more
Request Handlers ¶
The basic idea of Vertex revolves around friendly, pre-validated request handlers, that leave the developer with the need to write as little boilerplate code as possible. Routes in the API are mapped to the RequestHandler interface:
type RequestHandler interface { Handle(w http.ResponseWriter, r *http.Request) (interface{}, error) }
RequestHandlers have a few interesting characteristics:
1. Fields in structs implementing RequestHandler get automtically filled by request data.
2. Field values are automatically validated and sanitized
3. They do not *(need to)* write to the response writer, they just need to return a response object.
You create structs that have all the parameters you need to handle the requests, define validations for these parameters, and Vertex does the rest for you - just return a response object and you're done.
Here is an example super simple RequestHandler:
type UserHandler struct { Id string `schema:"id" required:"true" doc:"The Id Of the user" maxlen:"30"` } func (h UserHandler) Handle(w http.ResponseWriter, r *http.Request) (interface{}, error) { // load the user from the database user, err := db.Load(h.Id) // return it to the response. No need to write anything directly to the writer return user, err }
As you can see, the "id" parameter that is received as a post/get/path parameter is automatically parsed into the struct when the handler is invoked. If it is missing or invalid, the handler won't even be invoked, but an error will be generated to the client.
Handler Field Tags List ¶
These are the allowed tags for fields in RequestHandler structs:
schema - the parameter name in the request
doc - a short documentation string for the field
default - the default value for the parameter in case it's missing
min - the minimum allowed value for numeric fields (inclusive)
max - the maximum allowed value for numeric fields (inclusive)
maxlen - the maximal allowed length for strings
minlen - the minimal allowed length for strings
required true/false - if set to "true", forces the request to have this parameter set
allowEmpty true/false - do we allow empty values?
pattern - a regular expression that a string must match if this tag is set
in query/body/path - optional for non path params. mainly for documentation needs
TODO: Support min/max length for string lists
Supported types for struct fields are (see :
- bool
- float variants (float32, float64)
- int variants (int, int8, int16, int32, int64)
- string
- uint variants (uint, uint8, uint16, uint32, uint64)
- struct - only if it implements Unmarshaler (see below)
- a pointer to one of the above types
- a slice or a pointer to a slice of one of the above types
Custom Unmarshalers ¶
If a field has a custom type that needs automatic deserialization (e.g. a binary Thrift or Protobuf object), we can define a custom Unmarshal method to the type, letting it automatically deserialize parameters. (See the Unmarshaler interface)
The unmarshaler should return a new instance of itself with the value set correctly.
Example: a type that takes a string and splits in two
type Banana struct { Foo string Bar string } func (b Banana) UnmarshalRequestData(data string) interface{} { parts := strings.Split(data, ",") if len(parts) == 2 { return Banana{parts[0], parts[1]} } return Banana{} }
Defining An API ¶
APIs are defined in a declarative way, preferably separately from defining the the actual handler logic.
An API has a few major parts:
- High level definitions - like name, version, documentation, etc.
- Routes - defining routing paths and mapping them to handlers and tests
- Middleware - defining a middleware chain to pre/post-process requests
- SecurityScheme - defining the default way requests are validated
Here is an example simple API definition:
var myAPI = &vertex.API{ // The API's name, optionally used in the path Name: "testung", // The API's version, optionally used in the path Version: "1.0", // Optional root path. If not set, the root is /<name>/<version> Root: "/testung/1.0", // Some documentation Doc: "This is our Test API. It is used to demonstrate declaring an API", // Friendly API title for documentation Title: "Test API!", // A middleware chain. The default chain includes panic recovery and request logging Middleware: middleware.DefaultMiddleware, // Response renderer. The default is of course a JSON renderer Renderer: vertex.JSONRenderer{}, // A SecurityScheme. Each route can have an alternative scheme if needed DefaultSecurityScheme: APIKeyValidator, // Unless explicitly set, we only allow https traffic AllowInsecure: false, // The routes of the API Routes: vertex.RouteMap{ // Path parameters are defined as {param} "/user/byId/{id}": { // Short request description Description: "Get User Info by id", // An instance of the handler. We use reflection to create a new instance per request Handler: UserHandler{}, // a flag mask of supported requests Methods: vertex.GET | vertex.POST, // An integration test for the request. Each request must have a test. // Tests can be "warning" tests or "critical" tests Test: vertex.WarningTest(testUserHandler), // Optional object returned by the request, that will be automatically added to the documentation Returns: User{}, }, }, }
Security Schemes ¶
Security Schemes are used to validate requests. The scheme simply receives the request, and returns an error if it is not valid. It can be used to authenticate the user, validate the API key, etc.
Middleware ¶
Vertex comes with some middleware modules included. Currently implemented middleware include:
- CORS configuration
- Auto Recover from panic in handlers
- Request Logging
- OAuth authentication
- IP-range filter
- Simple API Key validation
- HTTP Basic Auth
- Response Caching
- Force Secure (https) Access
Renderers ¶
Responses have renderers - that transform the response object to some serialization format.
The default is of course JSON, but an HTML renderer using templates also exists.
Running The Server ¶
TODO ¶
Integration Tests ¶
TODO ¶
API Console ¶
TODO
Index ¶
- Constants
- Variables
- func BackOffError(duration time.Duration) error
- func FormatPath(path string, params Params) string
- func InsecureAccessDenied(msg string, args ...interface{}) error
- func InvalidParamError(msg string, args ...interface{}) error
- func InvalidRequestError(msg string, args ...interface{}) error
- func IsHijacked(err error) bool
- func MissingParamError(msg string, args ...interface{}) error
- func NewError(err error) error
- func NewErrorf(format string, args ...interface{}) error
- func ReadConfigs() error
- func Register(name string, builder func() *API, config interface{})
- func ResourceUnavailableError(msg string, args ...interface{}) error
- func RunCLITest(apiName, serverAddr, category, format string, out io.Writer) bool
- func UnauthorizedError(msg string, args ...interface{}) error
- type API
- type HTMLRenderer
- type HandlerFunc
- type JSONRenderer
- type MethodFlag
- type Middleware
- type MiddlewareFunc
- type Params
- type Renderer
- type Request
- type RequestHandler
- type RequestValidator
- type Route
- type Routes
- type SecurityScheme
- type SecuritySchemeFunc
- type Server
- type TestContext
- func (t *TestContext) Fail(format string, params ...interface{})
- func (t *TestContext) Fatal(format string, params ...interface{})
- func (t *TestContext) FormatUrl(pathParams Params) string
- func (t *TestContext) GetJSON(r *http.Request, v interface{}) (*http.Response, error)
- func (t *TestContext) Log(format string, params ...interface{})
- func (t *TestContext) NewRequest(method string, values url.Values, pathParams Params) (*http.Request, error)
- func (t *TestContext) ServerUrl() string
- func (t *TestContext) Skip()
- type Tester
- type Unmarshaler
- type VoidHandler
Constants ¶
const ( // The request succeeded Ok = iota // General failure ErrGeneralFailure // Input validation failed ErrInvalidRequest // Missing parameter ErrMissingParam // Invalid parameter value ErrInvalidParam ErrUnauthorized // Insecure access denied ErrInsecureAccessDenied ErrResourceUnavailable // Please back off ErrBackOff // Some middleware took over the request, and the renderer should not render the response ErrHijacked )
const ( CriticalTests = "critical" WarningTests = "warning" AllTests = "all" )
test categories
const ( TestFormatText = "text" TestFormatJson = "json" )
const ( // The POST/GET param we pass if we want a JSONP callback response CallbackParam = "callback" HeaderProcessingTime = "X-Vertex-ProcessingTime" HeaderRequestId = "X-Vertex-RequestId" HeaderHost = "X-Vertex-Host" HeaderServerVersion = "X-Vertex-Version" )
Headers for responses
const DefaultLocale = "en-US"
const HeaderGeoPosition = "X-LatLong"
Variables ¶
var Config = struct { Server serverConfig `yaml:"server"` Auth authConfig `yaml:"auth"` APIConfigs map[string]interface{} `yaml:"apis,flow"` apiconfs map[string]interface{} }{ Server: serverConfig{ ListenAddr: ":9944", AllowInsecure: false, ConsoleFilesPath: "../console", LoggingLevel: "INFO", ClientTimeout: 60, }, Auth: authConfig{ User: "vertext", Password: "xetrev", }, APIConfigs: make(map[string]interface{}), // contains filtered or unexported fields }
var Hijacked = newErrorCode(ErrHijacked, "Request Hijacked, Do not rendere response")
A special error that should be returned when hijacking a request, taking over response rendering from the renderer
var NopSecurity = SecuritySchemeFunc(func(r *Request) error { return nil })
Functions ¶
func BackOffError ¶
BackOff returns a back-off error with a message formatted for the given amount of backoff time
func FormatPath ¶
FormatPath takes a path template and formats it according to the given path params
e.g.
FormatPath("/foo/{id}", Params{"id":"bar"}) // Output: "/foo/bar"
func InsecureAccessDenied ¶
InsecureAccessDenied returns an error signifying the client has no access to the requested resource
func InvalidParamError ¶
InvalidParam returns an error signifying an invalid parameter value.
NOTE: The error string will be returned directly to the client
func InvalidRequestError ¶
InvalidRequest returns an error signifying something went bad reading the request data (not the validation process). This in general should not be used by APIs
func IsHijacked ¶
IsHijacked inspects an error and checks whether it represents a hijacked response
func MissingParamError ¶
MissingParamError Returns a formatted error stating that a parameter was missing.
NOTE: The message will be returned to the client directly
func ReadConfigs ¶
func ReadConfigs() error
func Register ¶
Register lest you automatically add an API to the server from your module's init() function.
name is a unique name for your API (doesn't have to match the API name exactly).
builder is a func that creates the API when we are ready to start the server.
Optionally, you can pass a pointer to a config struct, or nil if you don't need to. This way, we can read the config struct's values from a unified config file BEFORE we call the builder, so the builder can use values in the config struct.
func ResourceUnavailableError ¶
ResourceUnavailable returns an error meaning we do not want to serve this request, the client should not retry
func UnauthorizedError ¶
Unauthorized returns an error signifying the request was not authorized, but the client may log-in and retry
Types ¶
type API ¶
type API struct { Name string Title string Version string Root string Doc string DefaultSecurityScheme SecurityScheme Renderer Renderer Routes Routes Middleware []Middleware TestMiddleware []Middleware SwaggerMiddleware []Middleware AllowInsecure bool }
API represents the definition of a single, versioned API and all its routes, middleware and handlers
type HTMLRenderer ¶
type HTMLRenderer struct {
// contains filtered or unexported fields
}
func NewHTMLRenderer ¶
func NewHTMLRenderer(src string, funcMap template.FuncMap) *HTMLRenderer
func NewHTMLRendererFiles ¶
func NewHTMLRendererFiles(funcMap map[string]interface{}, fileNames ...string) *HTMLRenderer
func (*HTMLRenderer) ContentTypes ¶
func (h *HTMLRenderer) ContentTypes() []string
func (*HTMLRenderer) Render ¶
func (h *HTMLRenderer) Render(v interface{}, e error, w http.ResponseWriter, r *Request) error
type HandlerFunc ¶
type HandlerFunc func(http.ResponseWriter, *Request) (interface{}, error)
HandlerFunc is an adapter that allows you to register normal functions as handlers. It is used mainly by middleware and should not be used in an application context
func (HandlerFunc) Handle ¶
func (h HandlerFunc) Handle(w http.ResponseWriter, r *Request) (interface{}, error)
Handle calls the underlying function
type JSONRenderer ¶
type JSONRenderer struct{}
JSONRenderer renders a response as a JSON object
func (JSONRenderer) ContentTypes ¶
func (JSONRenderer) ContentTypes() []string
func (JSONRenderer) Render ¶
func (JSONRenderer) Render(v interface{}, e error, w http.ResponseWriter, r *Request) error
type MethodFlag ¶
type MethodFlag int
MethodFlag is used for const flags for method handling on API declaration
const ( GET MethodFlag = 0x01 POST MethodFlag = 0x02 PUT MethodFlag = 0x03 )
Method flag definitions
type Middleware ¶
type Middleware interface {
Handle(w http.ResponseWriter, r *Request, next HandlerFunc) (interface{}, error)
}
Middleware are pre/post processors that can inspect, change, or fail the request. e.g. authentication, logging, etc
Each middleware needs to call next(w,r) so its next-in-line middleware will work, or return without it if it wishes to terminate the processing chain
func MiddlewareChain ¶
func MiddlewareChain(mw ...Middleware) []Middleware
MiddlewareChain just wraps a variadic list of middlewares to make your code less ugly :)
type MiddlewareFunc ¶
type MiddlewareFunc func(http.ResponseWriter, *Request, HandlerFunc) (interface{}, error)
MiddlewareFunc is a wrapper that allows functions to act as middleware
func (MiddlewareFunc) Handle ¶
func (f MiddlewareFunc) Handle(w http.ResponseWriter, r *Request, next HandlerFunc) (interface{}, error)
Handle runs the underlying func
type Renderer ¶
type Renderer interface { Render(interface{}, error, http.ResponseWriter, *Request) error ContentTypes() []string }
Renderer is an interface for response renderers. A renderer gets the response object after the entire middleware chain processed it, and renders it directly to the client
func RenderFunc ¶
func RenderFunc(f func(interface{}, error, http.ResponseWriter, *Request) error, contentTypes ...string) Renderer
Wrap a rendering function as an renderer
type Request ¶
type Request struct { *http.Request StartTime time.Time Deadline time.Time Locale string UserAgent string RemoteIP string Location struct{ Lat, Long float64 } RequestId string Callback string Secure bool // contains filtered or unexported fields }
Request wraps the standard http request object with higher level contextual data
func NewRequest ¶
NewRequest wraps a new http request with a vertex request
func (*Request) SetAttribute ¶
type RequestHandler ¶
type RequestHandler interface {
Handle(w http.ResponseWriter, r *Request) (interface{}, error)
}
RequestHandler is the interface that request handler structs should implement.
The idea is that you define your request parameters as struct fields, and they get mapped automatically and validated, leaving you with just pure logic work.
An example Request handler:
type UserHandler struct { Id string `schema:"id" required:"true" doc:"The Id Of the user" maxlen:"20" in:"path"` Name string `schema:"name" maxlen:"100" required:"true" doc:"The Name Of the user"` Admin bool `schema:"bool" default:"true" required:"false" doc:"Is this user an admin"` } func (h UserHandler) Handle(w http.ResponseWriter, r *http.Request) (interface{}, error) { return fmt.Sprintf("Your name is %s and id is %s", h.Name, h.Id), nil }
Supported types for automatic param mapping: string, int(32/64), float(32/64), bool, []string
func Hijacker ¶
func Hijacker(f func(w http.ResponseWriter, r *Request)) RequestHandler
func StaticHandler ¶
func StaticHandler(root string, dir http.Dir) RequestHandler
StaticHandler is a batteries-included handler for serving static files inside a directory.
root is the path the root path for this static handler, and will get stripped.
NOTE: root should be the full path to the API root. so if your handler path is "/static/*filepath", root should be something like "/myapi/1.0/static". Because the handler is created before the API object is configured, we do not know the root on creation
func Wrap ¶
func Wrap(f func(w http.ResponseWriter, r *http.Request)) RequestHandler
type RequestValidator ¶
type RequestValidator struct {
// contains filtered or unexported fields
}
func NewRequestValidator ¶
func NewRequestValidator(ri schema.RequestInfo) *RequestValidator
Create new request validator for a request handler interface. This function walks the struct tags of the handler's fields and extracts validation metadata.
You should give it the reflect type of your request handler struct
type Route ¶
type Route struct { Path string Description string Handler RequestHandler Methods MethodFlag Security SecurityScheme Middleware []Middleware Test Tester Returns interface{} Renderer Renderer // contains filtered or unexported fields }
Route represents a single route (path) in the API and its handler and optional extra middleware
type SecurityScheme ¶
SecurityScheme is a special interface that validates a request and is outside the middleware chain. An API has a default security scheme, and each route can override it
type SecuritySchemeFunc ¶
func (SecuritySchemeFunc) Validate ¶
func (f SecuritySchemeFunc) Validate(r *Request) error
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server represents a multi-API http server with a single router
func (*Server) AddAPI ¶
AddAPI adds an API to the server manually. It's preferred to use Register in an init() function
func (*Server) InitAPIs ¶
func (s *Server) InitAPIs()
InitAPIs initializes and adds all the APIs registered from API builders
type TestContext ¶
type TestContext struct {
// contains filtered or unexported fields
}
TestContext is a utility available for all testing functions, allowing them to easily test the current route. It is inspired by Go's testing framework.
In general, a tester needs to call t.Fail(), t.Fatal() or t.Skip() to stop the execution of the test. A test that doesn't call either of them is considered passing
func (*TestContext) Fail ¶
func (t *TestContext) Fail(format string, params ...interface{})
Fail aborts the test with a FAIL status, that is the normal case for failing tests
func (*TestContext) Fatal ¶
func (t *TestContext) Fatal(format string, params ...interface{})
Fatal aborts the test with a FATAL status
func (*TestContext) FormatUrl ¶
func (t *TestContext) FormatUrl(pathParams Params) string
FormatUrl returns a fully formatted URL for the context's route, with all path params replaced by their respective values in the pathParams map
func (*TestContext) GetJSON ¶
GetJSON performs the given request, and tries to deserialize the response object to v. If we received an error or decoding is impossible, we return an error. The raw http response is also returned for inspection
func (*TestContext) Log ¶
func (t *TestContext) Log(format string, params ...interface{})
Log writes a message to be displayed alongside the test result ONLY if the test failed
func (*TestContext) NewRequest ¶
func (t *TestContext) NewRequest(method string, values url.Values, pathParams Params) (*http.Request, error)
NewRequest creates a new http request to the route we are testing now, with optional values for post/get, and optional path params
func (*TestContext) ServerUrl ¶
func (t *TestContext) ServerUrl() string
ServerUrl returns the URL of the vertex server we are testing
func (*TestContext) Skip ¶
func (t *TestContext) Skip()
Skip aborts the test with a SKIP status, that is considered passing
type Tester ¶
type Tester interface { Test(*TestContext) Category() string }
Tester represents a testcase the API runs for a certain API.
Each API contains a list of integration tests that can be run to monitor it. Each test can have a category associated with it, and we can run tests by a specific category only.
A test should fail or succeed, and can optionally write error output
func CriticalTest ¶
func CriticalTest(f func(ctx *TestContext)) Tester
CrititcalTest wraps testers to signify that the tester is considered critical
func WarningTest ¶
func WarningTest(f func(ctx *TestContext)) Tester
WarningTest wraps testers to signify that the tester is a warning test
type Unmarshaler ¶
type Unmarshaler interface {
UnmarshalRequestData(data string) interface{}
}
Unmarshaler is an interface for types who are interested in automatic decoding. The unmarshaler should return a new instance of itself with the value set correctly.
Example: a type that takes a string and splits in two
type Banana struct { Foo string Bar string } func (b Banana) UnmarshalRequestData(data string) interface{} { parts := strings.Split(data, ",") if len(parts) == 2 { return Banana{parts[0], parts[1]} } return Banana{} }
type VoidHandler ¶
type VoidHandler struct{}
VoidHandler is a batteries-included handler that does nothing, useful for testing, or when a middleware takes over the request completely
func (VoidHandler) Handle ¶
func (VoidHandler) Handle(w http.ResponseWriter, r *Request) (interface{}, error)
Handle does nothing :)