jas: github.com/coocood/jas Index | Files

package jas

import "github.com/coocood/jas"

To build a REST API you need to define resources.

A resource is a struct with one or more exported pointer methods that accept only one argument of type `*jas.Context`

A `*jas.Context` has everything you need to handle the http request, it embeds a anonymous *http.Request field, so you can call *http.Requst methods directly with *jas.Context.

The resource name and method name will be converted to snake case in the url path by default.(can be changed in config).

HTTP GET POST PUT DELETE should be the prefix of the method name.

Methods with no prefix will handle GET request.

Other HTTP request with methods like "HEAD", "OPTIONS" will be routed to resource "Get" method.


	type Users struct {}

    func (*Users) Photo (ctx *jas.Context) {} // `GET /users/photo`

    func (*Users) PostPhoto (ctx *jas.Context) {} // `POST /users/photo`

    func (*Users) PostPost (ctx *jas.Context) {} // `POST /users/post`

    func (*Users) GetPost (ctx *jas.Context) {} // `GET /users/post`

    func (*Users) PutPhoto (ctx *jas.Context) {} // `PUT /users/photo`

    func (*Users) DeletePhoto (ctx *jas.Context) {} // `DELETE /users/photo`

On your application start, make a new *jas.Router with jas.NewRouter method, pass all your resources to it.

router := jas.NewRouter(new(Users), new(Posts), new(Photos)

Then you can config the router, see Config type for detail.

    router.BasePath = "/v1/"
	router.EnableGzip = true

You can get all the handled path printed. they are separated by '\n'

fmt.Println(router.HandledPaths(true)) // true for with base path. false for without base path.

Finally, set the router as http handler and Listen.

	http.Handle(router.BasePath, router)
    http.ListenAndServe(":8080", nil)

You can make it more RESTful by put an Id path between the resource name and method name.

The id value can be obtained from *jas.Context, resource name with `Id` suffix do the trick.

type UsersId struct {}

func (*UsersId) Photo (ctx *jas.Context) {// `GET /users/:id/photo`
    id := ctx.Id
    _ = id

If resource implements ResourceWithGap interface, the handled path will has gap segments between resource name and method name.

If method has a suffix "Id", the handled path will append an `:id` segment after the method segment.

You can obtain the Id value from *jas.Context, but it only works with none Id resource, because there is only one Id field in *jas.Context.

	type Users struct {}

	func (*Users) Gap() string {
		return ":name"

    func (*Users) Photo (ctx *jas.Context) {// `GET /users/:name/photo`
    	// suppose the request path is `/users/john/photo`
        name := ctx.GapSegment("") // "john"
        _ = name

    func (*Users) PhotoId (ctx *jas.Context) { `GET /users/:name/photo/:id`
    	// suppose the request path is `/users/john/photo/7`
    	id := ctx.Id // 7
        _ = id

Find methods will return error if the parameter value is invalid. Require methods will stop the execution in the method and respond an error message if the parameter value is invalid.

func (*Users) Photo (ctx *jas.Context) {
    // will stop execution and response `{"data":null,"error":"nameInvalid"} if "name" parameter is not given..
    name := ctx.RequireString("name")
    age := ctx.RequirePositiveInt("age")
    grade, err := ctx.FindPositiveInt("grade")

    // 6, 60 is the min and max length, error message can be "passwordTooShort" or "passwordTooLong"
    password := ctx.RequireStringLen(6, 60, "password")

    // emailRegexp is a *regexp.Regexp instance.error message would be "emailInvalid"
    email := ctx.RequireStringMatch(emailRegexp, "email")
    _, _, _, _, _, _ = name, age, grade, err,password, email

Get json body parameter: Assume we have a request with json body


Then we can get the value with Find or Require methods. Find and Require methods accept varargs, the type can be either string or int. string argument to get value from json object, int argumnet to get value form json array.

func (*Users) Bar (ctx *jas.Context) {
    name, _ := ctx.Find("photo", 0, "name") // "abc"
    id:= ctx.RequirePositiveInt("photo", 1, "id") //200

If you want unmarshal json body to struct, the `DisableAutoUnmarshal` option must be set to true.

router.DisableAutoUnmarshal = true

Then you can call `Unmarshal` method to unmarshal json body:


HTTP streaming :

FlushData will write []byte data without any modification, other data types will be marshaled to json format.

func (*Users) Feed (ctx *jas.Context) {
    for !ctx.ClientClosed() {
        ctx.FlushData([]byte("some data"))


Package Files

assert.go context.go doc.go error.go finder.go request.go router.go


var DoNotMatchError = errors.New("jas.Finder: do not match")
var EmptyMapError = errors.New("jas.Finder: empty map")
var EmptySliceError = errors.New("jas.Finder: empty slice")
var EmptyStringError = errors.New("jas.Finder: empty string")
var EntryNotExistsError = errors.New("jas.Finder: entry not exists")
var IndexOutOfBoundError = errors.New("jas.Finder: index out of bound")
var InternalErrorStatusCode = 500
var InvalidErrorFormat = "%vInvalid"
var MalformedJsonBody = "MalformedJsonBody"
var NoJsonBody = errors.New("jas.Context: no json body")
var NotFoundError = RequestError{"Not Found", 404}
var NotFoundStatusCode = 404
var NotPositiveError = errors.New("jas.Finder: not positive")
var NotPositiveErrorFormat = "%vNotPositive"
var NullValueError = errors.New("jas.Finder: null value")
var RequestErrorStatusCode = 400
var StackFormat = "%s:%d(0x%x);"

Stack trace format which formats file name, line number and program counter.

var TooLongError = errors.New("jas.Finder: string too long")
var TooLongErrorFormat = "%vTooLong"
var TooShortError = errors.New("jas.Finder: string too short")
var TooShortErrorFormat = "%vTooShort"
var UnauthorizedStatusCode = 401
var WordSeparator = "_"
var WrongTypeError = errors.New("jas.Finder: wrong type")

func AllowCORS Uses

func AllowCORS(r *http.Request, responseHeader http.Header) bool

This is an implementation of HandleCORS function to allow all cross domain request.

func NameValuesToUrlValues Uses

func NameValuesToUrlValues(nameValues ...interface{}) url.Values

func NewGetRequest Uses

func NewGetRequest(baseUrlOrPath, path string, nameValues ...interface{}) *http.Request

func NewPostFormRequest Uses

func NewPostFormRequest(baseUrlOrPath, path string, nameValues ...interface{}) *http.Request

func NewPostJsonRequest Uses

func NewPostJsonRequest(baseUrlOrPath, path string, jsonData []byte, nameValues ...interface{}) *http.Request

type AppError Uses

type AppError interface {

    //The actual error string that will be logged.
    Error() string

    //The status code that will be written to the response header.
    Status() int

    //The error message response to the client.
    //Can be the same string as Error() for request error
    //Should be simple string like "InternalError" for internal error.
    Message() string

    //Log self, it will be called after response is written to the client.
    //It runs on its own goroutine, so long running task will not affect the response time.

If RequestError and internalError is not sufficient for you application, you can implement this interface to define your own error that can log itself in different way..

type Assert Uses

type Assert struct {
    // contains filtered or unexported fields

func NewAssert Uses

func NewAssert(t tester) *Assert

func (*Assert) Equal Uses

func (ast *Assert) Equal(expected, actual interface{}, logs ...interface{})

func (*Assert) MustEqual Uses

func (ast *Assert) MustEqual(expected, actual interface{}, logs ...interface{})

func (*Assert) MustNil Uses

func (ast *Assert) MustNil(value interface{}, logs ...interface{})

func (*Assert) MustNotEqual Uses

func (ast *Assert) MustNotEqual(expected, actual interface{}, logs ...interface{})

func (*Assert) MustNotNil Uses

func (ast *Assert) MustNotNil(value interface{}, logs ...interface{})

func (*Assert) MustTrue Uses

func (ast *Assert) MustTrue(boolValue bool, logs ...interface{})

func (*Assert) Nil Uses

func (ast *Assert) Nil(value interface{}, logs ...interface{})

func (*Assert) NotEqual Uses

func (ast *Assert) NotEqual(expected, actual interface{}, logs ...interface{})

func (*Assert) NotNil Uses

func (ast *Assert) NotNil(value interface{}, logs ...interface{})

func (*Assert) True Uses

func (ast *Assert) True(boolValue bool, logs ...interface{})

type Config Uses

type Config struct {
    //The base path of the request url.
    //If you want to make it work along with other router or http.Handler,
    //it can be used as a pattern string for `http.Handle` method
    //It must starts and ends with "/", e.g. "/api/v1/".
    //Defaults to "/".
    BasePath string

    //Handle Cross-origin Resource Sharing.
    //It accept request and response header parameter.
    //return true to go on handle the request, return false to stop handling and response with header only.
    //Defaults to nil
    //You can set it to AllowCORS function to allow all CORS request.
    HandleCORS func(*http.Request, http.Header) bool

    //gzip is disabled by default. set true to enable it
    EnableGzip bool

    //defaults to nil, if set, request error will be logged.
    RequestErrorLogger *log.Logger

    //log to standard err by default.
    InternalErrorLogger *log.Logger

    //If set, it will be called after recovered from panic.
    //Do time consuming work in the function will not increase response time because it runs in its own goroutine.
    OnAppError func(AppError, *Context)

    //If set, it will be called before calling the matched method.
    BeforeServe func(*Context)

    //If set, it will be called after calling the matched method.
    AfterServe func(*Context)

    //If set, the user id can be obtained by *Context.UserId and will be logged on error.
    //Implementations can be like decode cookie value or token parameter.
    ParseIdFunc func(*http.Request) int64

    //If set, the delimiter will be appended to the end of the data on every call to *Context.FlushData method.
    FlushDelimiter []byte

    //handler function for unhandled path request.
    //default function just send `{"data":null,"error":"NotFound"}` with 404 status code.
    OnNotFound func(http.ResponseWriter, *http.Request)

    //if you do not like the default json format `{"data":...,"error":...}`,
    //you can define your own write function here.
    //The io.Writer may be http.ResponseWriter or GzipWriter depends on if gzip is enabled.
    //The errMessage is of type string or nil, it's not AppError.
    //it should return the number of bytes has been written.
    HijackWrite func(io.Writer, *Context) int

    //If set to true, json request body will not be unmarshaled in Finder automatically.
    //Then you will be able to call `Unmarshal` to unmarshal the body to a struct.
    //If you still want to get body parameter with Finder methods in some cases, you can call `UnmarshalInFinder`
    //explicitly before you get body parameters with Finder methods.
    DisableAutoUnmarshal bool

    //By default gap only matches non-integer segment, set true to allow gap to match integer segment.
    //But then resource with gap will shadow id resource.
    //e.g "/user/123" will be resolved to "User" that has "Gap" method instead of "UserId".
    AllowIntegerGap bool

type Context Uses

type Context struct {
    ResponseHeader http.Header
    Callback       string //jsonp callback
    Status         int
    Error          AppError
    Data           interface{} //The data to be written after the resource method has returned.
    UserId         int64
    Id             int64
    Extra          interface{} //Store extra data generated/used by hook functions, e.g. 'BeforeServe'.
    // contains filtered or unexported fields

Context contains all the information for a single request. it hides the http.ResponseWriter because directly writing to http.ResponseWriter will make Context unable to work correctly. it embeds *http.Request and Finder, so you can call methods and access fields in Finder and *http.Request directly.

func (*Context) AddCookie Uses

func (ctx *Context) AddCookie(cookie *http.Cookie)

override *http.Request AddCookie method to add response header's cookie. Same as SetCookie.

func (*Context) ClientClosed Uses

func (ctx *Context) ClientClosed() bool

Typically used in for loop condition.along with Flush.

func (*Context) FlushData Uses

func (ctx *Context) FlushData(data interface{}) (written int, err error)

Write and flush the data. It can be used for http streaming or to write a portion of large amount of data. If the type of the data is not []byte, it will be marshaled to json format.

func (*Context) GapSegment Uses

func (ctx *Context) GapSegment(key string) string

If the gap has multiple segments, the key should be the segment defined in resource Gap method. e.g. for gap ":domain/:language", use key ":domain" to get the first gap segment, use key ":language" to get the second gap segment. The first gap segment can also be gotten by empty string key "" for convenience.

func (*Context) PathSegment Uses

func (ctx *Context) PathSegment(index int) string

the segment index starts at the resource segment

func (*Context) RequireUserId Uses

func (ctx *Context) RequireUserId() int64

It is an convenient method to validate and get the user id.

func (*Context) SetCookie Uses

func (ctx *Context) SetCookie(cookie *http.Cookie)

Add response header Set-Cookie.

func (*Context) Unmarshal Uses

func (ctx *Context) Unmarshal(in interface{}) error

Unmarshal the request body into the interface. It only works when you set Config option `DisableAutoUnmarshal` to true.

func (*Context) UnmarshalInFinder Uses

func (ctx *Context) UnmarshalInFinder()

If set Config option `DisableAutoUnmarshal` to true, you should call this method first before you can get body parameters in Finder methods..

type ContextWriter Uses

type ContextWriter struct {
    Ctx *Context

func (ContextWriter) Write Uses

func (cw ContextWriter) Write(p []byte) (n int, err error)

type Finder Uses

type Finder struct {
    // contains filtered or unexported fields

All the "Find" methods return error, All the "Require" methods do panic with RequestError when error occured.

func FinderWithBytes Uses

func FinderWithBytes(data []byte) Finder

Construct a Finder with json formatted data.

func FinderWithRequest Uses

func FinderWithRequest(req *http.Request) Finder

Construct a Finder with *http.Request.

func (Finder) FindBool Uses

func (finder Finder) FindBool(paths ...interface{}) (bool, error)

func (Finder) FindChild Uses

func (finder Finder) FindChild(paths ...interface{}) Finder

func (Finder) FindFloat Uses

func (finder Finder) FindFloat(paths ...interface{}) (float64, error)

func (Finder) FindInt Uses

func (finder Finder) FindInt(paths ...interface{}) (int64, error)

func (Finder) FindMap Uses

func (finder Finder) FindMap(paths ...interface{}) (map[string]interface{}, error)

func (Finder) FindOptionalString Uses

func (finder Finder) FindOptionalString(val string, paths ...interface{}) (string, error)

Looks up the given path and returns a default value if not present.

func (Finder) FindPositiveInt Uses

func (finder Finder) FindPositiveInt(paths ...interface{}) (int64, error)

func (Finder) FindSlice Uses

func (finder Finder) FindSlice(paths ...interface{}) ([]interface{}, error)

func (Finder) FindString Uses

func (finder Finder) FindString(paths ...interface{}) (string, error)

func (Finder) FindStringLen Uses

func (finder Finder) FindStringLen(min, max int, paths ...interface{}) (string, error)

func (Finder) FindStringMatch Uses

func (finder Finder) FindStringMatch(reg *regexp.Regexp, paths ...interface{}) (string, error)

func (Finder) FindStringRuneLen Uses

func (finder Finder) FindStringRuneLen(min, max int, paths ...interface{}) (string, error)

func (Finder) Len Uses

func (finder Finder) Len(paths ...interface{}) int

return the length of []interface or map[string]interface{} return -1 if the value not found or has wrong type.

func (Finder) RequireFloat Uses

func (finder Finder) RequireFloat(paths ...interface{}) float64

func (Finder) RequireInt Uses

func (finder Finder) RequireInt(paths ...interface{}) int64

func (Finder) RequireMap Uses

func (finder Finder) RequireMap(paths ...interface{}) map[string]interface{}

func (Finder) RequirePositiveFloat Uses

func (finder Finder) RequirePositiveFloat(paths ...interface{}) float64

func (Finder) RequirePositiveInt Uses

func (finder Finder) RequirePositiveInt(paths ...interface{}) int64

func (Finder) RequireSlice Uses

func (finder Finder) RequireSlice(paths ...interface{}) []interface{}

func (Finder) RequireString Uses

func (finder Finder) RequireString(paths ...interface{}) string

func (Finder) RequireStringLen Uses

func (finder Finder) RequireStringLen(min, max int, paths ...interface{}) string

func (Finder) RequireStringMatch Uses

func (finder Finder) RequireStringMatch(reg *regexp.Regexp, paths ...interface{}) string

func (Finder) RequireStringRuneLen Uses

func (finder Finder) RequireStringRuneLen(min, max int, paths ...interface{}) string

type InternalError Uses

type InternalError struct {
    Err        error
    StatusCode int

InternalError is an AppError implementation which returns "InternalError" message to the client and logs the wrapped error and stack trace in Common Log Format. any panic during a session will be recovered and wrapped in InternalError and then logged.

func NewInternalError Uses

func NewInternalError(err interface{}) InternalError

Wrap an error to InternalError

func (InternalError) Error Uses

func (ie InternalError) Error() string

func (InternalError) Log Uses

func (ie InternalError) Log(context *Context)

func (InternalError) Message Uses

func (ie InternalError) Message() string

func (InternalError) Status Uses

func (ie InternalError) Status() int

type RequestError Uses

type RequestError struct {
    Msg        string
    StatusCode int

RequestError is an AppError implementation which Error() and Message() returns the same string.

func NewRequestError Uses

func NewRequestError(message string) RequestError

Make an RequestError with message which will be sent to the client.

func (RequestError) Error Uses

func (re RequestError) Error() string

func (RequestError) Log Uses

func (re RequestError) Log(context *Context)

func (RequestError) Message Uses

func (re RequestError) Message() string

func (RequestError) Status Uses

func (re RequestError) Status() int

type ResourceWithGap Uses

type ResourceWithGap interface {
    Gap() string

type Response Uses

type Response struct {
    Data  interface{} `json:"data"`
    Error interface{} `json:"error"`

type Router Uses

type Router struct {
    // contains filtered or unexported fields

func NewRouter Uses

func NewRouter(resources ...interface{}) *Router

Construct a Router instance. Then you can set the configuration fields to config the router. Configuration fields applies to a single router, there are also some package level variables you can change if needed. You can make multiple routers with different base path to handle requests to the same host. See documentation about resources at the top of the file.

func (*Router) HandledPaths Uses

func (r *Router) HandledPaths(withBasePath bool) string

Get the paths that have been handled by resources. The paths are sorted, it can be used to detect api path changes.

func (*Router) ServeHTTP Uses

func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request)

Implements http.Handler interface.

Package jas imports 17 packages (graph) and is imported by 15 packages. Updated 2016-09-24. Refresh now. Tools for package owners.