Skip to content

Commit

Permalink
More binding and rendering functions
Browse files Browse the repository at this point in the history
Signed-off-by: Vishal Rana <vr@labstack.com>
  • Loading branch information
vishr committed Apr 5, 2015
1 parent 3680eb5 commit 9d44f49
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 92 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
language: go
go:
- tip
before_install:
beforeinstall:
- go get github.com/modocache/gover
- go get github.com/mattn/goveralls
- go get golang.org/x/tools/cmd/cover
Expand Down
73 changes: 47 additions & 26 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@ package echo

import (
"encoding/json"
"html/template"
"net/http"
"strings"
)

type (
// Context represents context for the current request. It holds request and
// response references, path parameters, data and registered handler for
// the route.
// response references, path parameters, data and registered handler.
Context struct {
Request *http.Request
Response *response
params Params
store map[string]interface{}
store store
echo *Echo
}
store map[string]interface{}
Expand All @@ -30,40 +30,61 @@ func (c *Context) Param(n string) string {
return c.params.Get(n)
}

// Bind decodes the payload into provided type based on Content-Type header.
func (c *Context) Bind(i interface{}) (err error) {
// Bind decodes the body into provided type based on Content-Type header.
func (c *Context) Bind(i interface{}) error {
ct := c.Request.Header.Get(HeaderContentType)
if strings.HasPrefix(ct, MIMEJSON) {
dec := json.NewDecoder(c.Request.Body)
if err = dec.Decode(i); err != nil {
err = ErrBindJSON
}
return json.NewDecoder(c.Request.Body).Decode(i)
} else if strings.HasPrefix(ct, MIMEForm) {
} else {
err = ErrUnsupportedContentType
return nil
}
return
return ErrUnsupportedMediaType
}

// String sends a text/plain response with status code.
func (c *Context) String(n int, s string) {
c.Response.Header().Set(HeaderContentType, MIMEText+"; charset=utf-8")
c.Response.WriteHeader(n)
c.Response.Write([]byte(s))
// Render encodes the provided type and sends a response with status code
// based on Accept header.
func (c *Context) Render(code int, i interface{}) error {
a := c.Request.Header.Get(HeaderAccept)
if strings.HasPrefix(a, MIMEJSON) {
return c.JSON(code, i)
} else if strings.HasPrefix(a, MIMEText) {
return c.String(code, i.(string))
} else if strings.HasPrefix(a, MIMEHTML) {
return c.HTML(code, i.(string))
}
return ErrUnsupportedMediaType
}

// JSON sends an application/json response with status code.
func (c *Context) JSON(n int, i interface{}) (err error) {
enc := json.NewEncoder(c.Response)
func (c *Context) JSON(code int, i interface{}) error {
c.Response.Header().Set(HeaderContentType, MIMEJSON+"; charset=utf-8")
c.Response.WriteHeader(n)
if err := enc.Encode(i); err != nil {
err = ErrRenderJSON
}
c.Response.WriteHeader(code)
return json.NewEncoder(c.Response).Encode(i)
}

// String sends a text/plain response with status code.
func (c *Context) String(code int, s string) (err error) {
c.Response.Header().Set(HeaderContentType, MIMEText+"; charset=utf-8")
c.Response.WriteHeader(code)
_, err = c.Response.Write([]byte(s))
return
}

// func (c *Context) File(n int, file, name string) {
// HTML sends an html/plain response with status code.
func (c *Context) HTML(code int, html string) (err error) {
c.Response.Header().Set(HeaderContentType, MIMEHTML+"; charset=utf-8")
c.Response.WriteHeader(code)
_, err = c.Response.Write([]byte(html))
return
}

// HTMLTemplate applies the template associated with t that has the given name to
// the specified data object and sends an html/plain response with status code.
func (c *Context) HTMLTemplate(code int, t *template.Template, name string, data interface{}) (err error) {
return t.ExecuteTemplate(c.Response, name, data)
}

// func (c *Context) File(code int, file, name string) {
// }

// Get retrieves data from the context.
Expand All @@ -77,8 +98,8 @@ func (c *Context) Set(key string, val interface{}) {
}

// Redirect redirects the request using http.Redirect with status code.
func (c *Context) Redirect(n int, url string) {
http.Redirect(c.Response, c.Request, url, n)
func (c *Context) Redirect(code int, url string) {
http.Redirect(c.Response, c.Request, url, code)
}

func (c *Context) reset(rw http.ResponseWriter, r *http.Request, e *Echo) {
Expand Down
105 changes: 70 additions & 35 deletions context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,82 @@ package echo
import (
"bytes"
"encoding/json"
"html/template"
"net/http"
"net/http/httptest"
"testing"
)

func TestContext(t *testing.T) {
e := New()
e.Put("/users/:id", func(c *Context) {
u := new(user)

// Param
if c.Param("id") != "1" {
t.Error("param by name, id should be 1")
}
if c.P(0) != "1" {
t.Error("param by index, id should be 1")
}

// Store
c.Set("user", u.Name)
n := c.Get("user")
if n != u.Name {
t.Error("user name should be Joe")
}

// Bind & JSON
if err := c.Bind(u); err == nil {
c.JSON(http.StatusCreated, u)
}

// TODO: fix me later
c.Redirect(http.StatusMovedPermanently, "")
})

b, _ := json.Marshal(u)
w := httptest.NewRecorder()
r, _ := http.NewRequest(MethodPUT, "/users/1", bytes.NewReader(b))
b, _ := json.Marshal(u1)
r, _ := http.NewRequest(MethodPOST, "/users/1", bytes.NewReader(b))
c := &Context{
Response: &response{ResponseWriter: httptest.NewRecorder()},
Request: r,
params: make(Params, 5),
store: make(store),
}

//**********//
// Bind //
//**********//
r.Header.Add(HeaderContentType, MIMEJSON)
e.ServeHTTP(w, r)
if w.Code != http.StatusCreated {
t.Errorf("status code should be 201, found %d", w.Code)
u2 := new(user)
if err := c.Bind(u2); err != nil {
t.Error(err)
}
verifyUser(w.Body, t)
verifyUser(u2, t)

//***********//
// Param //
//***********//
// By id
if c.P(0) != "" {
t.Error("param id should be nil")
}

// By name
if c.Param("id") != "" {
t.Error("param id should be nil")
}

// Store
c.Set("user", u1.Name)
n := c.Get("user")
if n != u1.Name {
t.Error("user name should be Joe")
}

//************//
// Render //
//************//
// JSON
r.Header.Set(HeaderAccept, MIMEJSON)
if err := c.Render(http.StatusOK, u1); err != nil {
t.Errorf("render json %v", err)
}

// String
r.Header.Set(HeaderAccept, MIMEText)
c.Response.committed = false
if err := c.Render(http.StatusOK, "Hello, World!"); err != nil {
t.Errorf("render string %v", err)
}

// HTML
r.Header.Set(HeaderAccept, MIMEHTML)
c.Response.committed = false
if err := c.Render(http.StatusOK, "Hello, <strong>World!</strong>"); err != nil {
t.Errorf("render html %v", err)
}

// HTML template
c.Response.committed = false
tmpl, _ := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
if err := c.HTMLTemplate(http.StatusOK, tmpl, "T", "Joe"); err != nil {
t.Errorf("render html template %v", err)
}

// Redirect
c.Redirect(http.StatusMovedPermanently, "http://labstack.github.io/echo")
}
5 changes: 2 additions & 3 deletions echo.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const (

MIMEJSON = "application/json"
MIMEText = "text/plain"
MIMEHTML = "html/plain"
MIMEForm = "application/x-www-form-urlencoded"
MIMEMultipartForm = "multipart/form-data"

Expand All @@ -60,9 +61,7 @@ var (
}

// Errors
ErrUnsupportedContentType = errors.New("echo: unsupported content type")
ErrBindJSON = errors.New("echo: bind json error")
ErrRenderJSON = errors.New("echo: render json error")
ErrUnsupportedMediaType = errors.New("echo: unsupported media type")
)

// New creates a echo instance.
Expand Down
20 changes: 6 additions & 14 deletions echo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package echo

import (
"bytes"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"testing"
Expand All @@ -16,7 +14,7 @@ type (
}
)

var u = user{
var u1 = user{
ID: "1",
Name: "Joe",
}
Expand Down Expand Up @@ -230,17 +228,11 @@ func TestEchoServeHTTP(t *testing.T) {
// }
}

func verifyUser(rd io.Reader, t *testing.T) {
u2 := new(user)
dec := json.NewDecoder(rd)
err := dec.Decode(u2)
if err != nil {
t.Error(err)
func verifyUser(u2 *user, t *testing.T) {
if u2.ID != u1.ID {
t.Errorf("user id should be %s, found %s", u1.ID, u2.ID)
}
if u2.ID != u.ID {
t.Errorf("user id should be %s, found %s", u.ID, u2.ID)
}
if u2.Name != u.Name {
t.Errorf("user name should be %s, found %s", u.Name, u2.Name)
if u2.Name != u1.Name {
t.Errorf("user name should be %s, found %s", u1.Name, u2.Name)
}
}
6 changes: 3 additions & 3 deletions router.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,7 @@ func NewRouter(e *Echo) (r *router) {
}

func (r *router) Add(method, path string, h HandlerFunc, echo *Echo) {
i := 0
l := len(path)
for ; i < l; i++ {
for i, l := 0, len(path); i < l; i++ {
if path[i] == ':' {
r.insert(method, path[:i], nil, pnode, echo)
for ; i < l && path[i] != '/'; i++ {
Expand Down Expand Up @@ -179,6 +177,7 @@ func (r *router) Find(method, path string) (h HandlerFunc, c *Context, echo *Ech
if l == pl {
search = search[l:]
if cn.has == pnode {
// Param node
cn = cn.edges[0]
i := 0
l = len(search)
Expand All @@ -190,6 +189,7 @@ func (r *router) Find(method, path string) (h HandlerFunc, c *Context, echo *Ech
n++
search = search[i:]
} else if cn.has == anode {
// Catch-all node
p := c.params[:n+1]
p[n].Name = "_name"
p[n].Value = search
Expand Down
11 changes: 1 addition & 10 deletions router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,18 +68,9 @@ func TestRouterMicroParam(t *testing.T) {
}
}

func TestRouterConflict(t *testing.T) {
r := New().Router
r.Add("GET", "/users/new", func(*Context) {}, nil)
r.Add("GET", "/users/wen", func(*Context) {}, nil)
r.Add("GET", "/users/:id", func(*Context) {}, nil)
r.Add("GET", "/users/*", func(*Context) {}, nil)
r.trees["GET"].printTree("", true)
}

func (n *node) printTree(pfx string, tail bool) {
p := prefix(tail, pfx, "└── ", "├── ")
fmt.Printf("%s%s has=%d, h=%v, echo=%d\n", p, n.prefix, n.has, n.handler, n.echo)
fmt.Printf("%s%s has=%d, h=%v, echo=%v\n", p, n.prefix, n.has, n.handler, n.echo)

nodes := n.edges
l := len(nodes)
Expand Down

0 comments on commit 9d44f49

Please sign in to comment.