Skip to content

Commit

Permalink
Add ctx.Send method.
Browse files Browse the repository at this point in the history
  • Loading branch information
zensh committed Dec 16, 2017
1 parent 80b771f commit c50ecb0
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 2 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file starting fro
This project adheres to [Semantic Versioning](http://semver.org/).

-----
## [1.9.4] - 2017-12-16

**Changed:**

- Add ctx.Send method.

## [1.9.3] - 2017-12-15

Expand Down
16 changes: 16 additions & 0 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ type Handler interface {
Serve(ctx *Context) error
}

// Sender interface is used by ctx.Send.
type Sender interface {
Send(ctx *Context, code int, data interface{}) error
}

// Renderer interface is used by ctx.Render.
type Renderer interface {
Render(ctx *Context, w io.Writer, name string, data interface{}) error
Expand Down Expand Up @@ -112,6 +117,7 @@ type App struct {

keys []string
renderer Renderer
sender Sender
bodyParser BodyParser
urlParser URLParser
compress Compressible // Default to nil, do not compress response content.
Expand Down Expand Up @@ -189,6 +195,10 @@ const (
// Set a on-error hook to app, value should be `func(ctx *Context, err *Error)`, no default value.
SetOnError

// Set a SetSender to app, it will be used by `ctx.Send`, value should implements `gear.Sender` interface,
// no default value.
SetSender

// Set a renderer to app, it will be used by `ctx.Render`, value should implements `gear.Renderer` interface,
// no default value.
SetRenderer
Expand Down Expand Up @@ -250,6 +260,12 @@ func (app *App) Set(key, val interface{}) *App {
} else {
app.onerror = onerror
}
case SetSender:
if sender, ok := val.(Sender); !ok {
panic(Err.WithMsg("SetSender setting must implemented gear.Sender interface"))
} else {
app.sender = sender
}
case SetRenderer:
if renderer, ok := val.(Renderer); !ok {
panic(Err.WithMsg("SetRenderer setting must implemented gear.Renderer interface"))
Expand Down
44 changes: 43 additions & 1 deletion context.go
Original file line number Diff line number Diff line change
Expand Up @@ -585,8 +585,50 @@ func (ctx *Context) XMLBlob(code int, buf []byte) error {
return ctx.End(code, buf)
}

// Send handle code and data with Sender interface.
// Sender can be registered using `app.Set(gear.SetSender, someSender)`.
// It will end the ctx. The middlewares after current middleware will not run.
// "after hooks" (if no error) and "end hooks" will run normally.
// You can define a custom send function like this:
//
// type mySenderT struct{}
//
// func (s *mySenderT) Send(ctx *Context, code int, data interface{}) error {
// switch v := data.(type) {
// case []byte:
// ctx.Type(MIMETextPlainCharsetUTF8)
// return ctx.End(code, v)
// case string:
// return ctx.HTML(code, v)
// case error:
// return ctx.Error(v)
// default:
// return ctx.JSON(code, data)
// }
// }
//
// app.Set(gear.SetSender, &mySenderT{})
// app.Use(func(ctx *Context) error {
// switch ctx.Path {
// case "/text":
// return ctx.Send(http.StatusOK, []byte("Hello, Gear!"))
// case "/html":
// return ctx.Send(http.StatusOK, "<h1>Hello, Gear!</h1>")
// case "/error":
// return ctx.Send(http.StatusOK, Err.WithMsg("some error"))
// default:
// return ctx.Send(http.StatusOK, map[string]string{"value": "Hello, Gear!"})
// }
// })
func (ctx *Context) Send(code int, data interface{}) (err error) {
if ctx.app.sender == nil {
return Err.WithMsg("sender not registered")
}
return ctx.app.sender.Send(ctx, code, data)
}

// Render renders a template with data and sends a text/html response with status
// code. Templates can be registered using `app.Renderer = Renderer`.
// code. Templates can be registered using `app.Set(gear.SetRenderer, someRenderer)`.
// It will end the ctx. The middlewares after current middleware will not run.
// "after hooks" (if no error) and "end hooks" will run normally.
func (ctx *Context) Render(code int, name string, data interface{}) (err error) {
Expand Down
84 changes: 84 additions & 0 deletions context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1325,6 +1325,90 @@ func TestGearContextXML(t *testing.T) {
assert.Equal(MIMEApplicationJSONCharsetUTF8, res.Header.Get(HeaderContentType))
}

type SenderTest struct{}

func (s *SenderTest) Send(ctx *Context, code int, data interface{}) error {
switch v := data.(type) {
case []byte:
ctx.Type(MIMETextPlainCharsetUTF8)
return ctx.End(code, v)
case string:
return ctx.HTML(code, v)
case error:
return ctx.Error(v)
default:
return ctx.JSON(code, data)
}
}

func TestGearContextSend(t *testing.T) {
t.Run("should panic when sender not registered", func(t *testing.T) {
assert := assert.New(t)

app := New()
app.Use(func(ctx *Context) error {
return ctx.Send(http.StatusOK, "data")
})

srv := app.Start()
defer srv.Close()

res, err := RequestBy("GET", "http://"+srv.Addr().String())
assert.Nil(err)
assert.Equal(500, res.StatusCode)
assert.Equal(`{"error":"Error","message":"sender not registered"}`, PickRes(res.Text()).(string))
})

t.Run("should work", func(t *testing.T) {
assert := assert.New(t)

app := New()
assert.Panics(func() {
app.Set(SetSender, struct{}{})
})
app.Set(SetSender, &SenderTest{})
app.Use(func(ctx *Context) error {
switch ctx.Path {
case "/text":
return ctx.Send(http.StatusOK, []byte("Hello, Gear!"))
case "/html":
return ctx.Send(http.StatusOK, "<h1>Hello, Gear!</h1>")
case "/error":
return ctx.Send(http.StatusOK, Err.WithMsg("some error"))
default:
return ctx.Send(http.StatusOK, map[string]string{"value": "Hello, Gear!"})
}
})

srv := app.Start()
defer srv.Close()

res, err := RequestBy("GET", "http://"+srv.Addr().String()+"/text")
assert.Nil(err)
assert.Equal(200, res.StatusCode)
assert.Equal(MIMETextPlainCharsetUTF8, res.Header.Get(HeaderContentType))
assert.Equal("Hello, Gear!", PickRes(res.Text()).(string))

res, err = RequestBy("GET", "http://"+srv.Addr().String()+"/html")
assert.Nil(err)
assert.Equal(200, res.StatusCode)
assert.Equal(MIMETextHTMLCharsetUTF8, res.Header.Get(HeaderContentType))
assert.Equal("<h1>Hello, Gear!</h1>", PickRes(res.Text()).(string))

res, err = RequestBy("GET", "http://"+srv.Addr().String()+"/error")
assert.Nil(err)
assert.Equal(500, res.StatusCode)
assert.Equal(MIMEApplicationJSONCharsetUTF8, res.Header.Get(HeaderContentType))
assert.Equal(`{"error":"Error","message":"some error"}`, PickRes(res.Text()).(string))

res, err = RequestBy("GET", "http://"+srv.Addr().String())
assert.Nil(err)
assert.Equal(200, res.StatusCode)
assert.Equal(MIMEApplicationJSONCharsetUTF8, res.Header.Get(HeaderContentType))
assert.Equal(`{"value":"Hello, Gear!"}`, PickRes(res.Text()).(string))
})
}

type RenderTest struct {
tpl *template.Template
}
Expand Down
2 changes: 1 addition & 1 deletion doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,4 @@ Learn more at https://github.com/teambition/gear
package gear

// Version is Gear's version
const Version = "1.9.3"
const Version = "1.9.4"

0 comments on commit c50ecb0

Please sign in to comment.