Skip to content

Commit 07de368

Browse files
committed
Updated error handling
1 parent c0caa68 commit 07de368

File tree

8 files changed

+91
-57
lines changed

8 files changed

+91
-57
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ REST API framework for go lang
33

44
# Framework is under development
55
## Status:
6-
Released alpha version
6+
Released beta version
77
<br>
88
See examples
99
- Request Interceptors/Middleware
@@ -27,9 +27,9 @@ api.Use(func(ctx *rest.Context) {
2727
// ctx.PreSend(func() error) OR ctx.PostSend(func() error)
2828
api.Use(func(ctx *rest.Context) {
2929
s := time.Now().UnixNano()
30-
ctx.PreSend(func() error {
30+
ctx.PreSend(func() {
3131
x := time.Now().UnixNano() - s
32-
ctx.SetHeader("x-runtime", strconv.FormatInt(x/int64(time.Millisecond), 10))
32+
ctx.SetHeader("X-Runtime", strconv.FormatInt(x/int64(time.Millisecond), 10))
3333
return nil
3434
})
3535
})
@@ -41,7 +41,7 @@ api.Get("/", func(ctx *rest.Context) {
4141
})
4242
4343
api.Get("/foo", func(ctx *rest.Context) {
44-
ctx.Status(401).Throw(errors.New("UNAUTHORIZED"))
44+
ctx.Status(401).Throw("UNAUTHORIZED")
4545
})
4646
4747
api.Get("/:bar", func(ctx *rest.Context) {

api.go

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
package rest
66

77
import (
8-
"errors"
9-
"log"
8+
"fmt"
109
"net/http"
1110
"regexp"
1211
"strings"
@@ -48,8 +47,8 @@ type interceptor struct {
4847

4948
// user exceptions, which is a common way to handle an error thrown by user
5049
type exception struct {
51-
message string
52-
handle Handler
50+
code string
51+
handle Handler
5352
}
5453

5554
// Initialize an API with prefix value and return the API pointer
@@ -127,10 +126,10 @@ func (api *API) Patch(pattern string, handle Handler) {
127126
}
128127

129128
// OnError method is used to handle a custom errors thrown by users
130-
func (api *API) OnError(err string, handle Handler) {
129+
func (api *API) OnError(code string, handle Handler) {
131130
exp := exception{
132-
message: err,
133-
handle: handle,
131+
code: code,
132+
handle: handle,
134133
}
135134
api.exceptions = append(api.exceptions, exp)
136135
}
@@ -142,16 +141,16 @@ func (api *API) UnhandledException(handle Handler) {
142141

143142
// error variables to handle expected errors
144143
var (
145-
errNotFound = errors.New("URL_NOT_FOUND")
146-
errUncaughtException = errors.New("UNCAUGHT_EXCEPTION")
144+
codeNotFound = "URL_NOT_FOUND"
145+
codeUncaughtException = "UNCAUGHT_EXCEPTION"
147146
)
148147

149148
// It's required handle for http module.
150149
// Every request travels from this method.
151150
func (api *API) ServeHTTP(res http.ResponseWriter, req *http.Request) {
152151

153152
// STEP 1: initialize context
154-
ctx := Context{
153+
ctx := &Context{
155154
Request: req,
156155
Response: res,
157156
Query: req.URL.Query(),
@@ -163,10 +162,9 @@ func (api *API) ServeHTTP(res http.ResponseWriter, req *http.Request) {
163162
defer func() {
164163
err := recover()
165164
if err != nil {
166-
//TODO: log only with debugger mode
167-
log.Println("uncaught exception - ", err)
168-
if ctx.end == false {
169-
ctx.err = errUncaughtException
165+
if !ctx.end {
166+
ctx.code = codeUncaughtException
167+
ctx.err = fmt.Errorf("%v", err)
170168
ctx.unhandledException()
171169
}
172170
return
@@ -179,7 +177,7 @@ func (api *API) ServeHTTP(res http.ResponseWriter, req *http.Request) {
179177
break
180178
}
181179

182-
task.handle(&ctx)
180+
task.handle(ctx)
183181
}
184182

185183
// STEP 3: check routes
@@ -192,29 +190,29 @@ func (api *API) ServeHTTP(res http.ResponseWriter, req *http.Request) {
192190
if (route.method == "" || strings.EqualFold(route.method, req.Method)) && route.regex.Match(urlPath) {
193191
ctx.found = route.method != "" //?
194192
ctx.Params = utils.Exec(route.regex, route.params, urlPath)
195-
route.handle(&ctx)
193+
route.handle(ctx)
196194
}
197195
}
198196

199197
// STEP 4: check handled exceptions
200198
for _, exp := range api.exceptions {
201-
if ctx.end || ctx.err == nil {
199+
if ctx.end || ctx.code == "" {
202200
break
203201
}
204202

205-
if exp.message == ctx.err.Error() {
206-
exp.handle(&ctx)
203+
if exp.code == ctx.code {
204+
exp.handle(ctx)
207205
}
208206
}
209207

210208
// STEP 5: unhandled exceptions
211209
if !ctx.end {
212-
if ctx.err == nil && !ctx.found {
213-
ctx.err = errNotFound
210+
if ctx.code == "" && !ctx.found {
211+
ctx.Throw(codeNotFound)
214212
}
215213

216214
if api.unhandled != nil {
217-
api.unhandled(&ctx)
215+
api.unhandled(ctx)
218216
}
219217
}
220218

api_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ func TestAPI_Exception(t *testing.T) {
9595

9696
flag := true
9797
for _, route := range a.exceptions {
98-
if route.message == "UID_NOT_FOUND" {
98+
if route.code == "UID_NOT_FOUND" {
9999
flag = false
100100
break
101101
}

context.go

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ type Context struct {
2929
// for internal use
3030
headers map[string]string
3131
data map[string]interface{}
32+
code string
3233
err error
3334
status int
3435
found bool
@@ -58,6 +59,7 @@ func (ctx *Context) destroy() {
5859
ctx.Params = nil
5960
ctx.headers = nil
6061
ctx.data = nil
62+
ctx.code = ""
6163
ctx.err = nil
6264
ctx.status = 0
6365
ctx.found = false
@@ -81,7 +83,7 @@ func (ctx *Context) Get(key string) (val interface{}, exists bool) {
8183
}
8284

8385
// Delete data
84-
func (ctx *Context) Delete(key string, val interface{}) {
86+
func (ctx *Context) Delete(key string) {
8587
delete(ctx.data, key)
8688
}
8789

@@ -98,7 +100,13 @@ func (ctx *Context) SetHeader(key string, val string) *Context {
98100
}
99101

100102
// Caught error in context on throw
101-
func (ctx *Context) Throw(err error) {
103+
func (ctx *Context) Throw(code string) {
104+
ctx.code = code
105+
}
106+
107+
// Throw an error with error
108+
func (ctx *Context) ThrowWithError(code string, err error) {
109+
ctx.code = code
102110
ctx.err = err
103111
}
104112

@@ -149,6 +157,7 @@ func (ctx *Context) PostSend(task Task) {
149157
// Send data, which uses bytes or error if any
150158
// Also, it calls pre-send and post-send registered hooks
151159
func (ctx *Context) send(data []byte, err error) {
160+
152161
if ctx.end {
153162
return
154163
}
@@ -198,28 +207,39 @@ func (ctx *Context) send(data []byte, err error) {
198207

199208
// Unhandled Exception
200209
func (ctx *Context) unhandledException() {
201-
defer func() {
202-
err := recover()
203-
if err != nil {
204-
//TODO: debugger mode
205-
log.Println("Unhandled Error: ", err)
206-
if !ctx.requestSent {
207-
ctx.Response.WriteHeader(http.StatusInternalServerError)
208-
ctx.Response.Header().Set("Content-Type", "text/plain;charset=UTF-8")
209-
_, _ = ctx.Response.Write([]byte("Internal Server Error"))
210-
}
211-
}
212-
}()
210+
defer ctx.recover()
213211

214-
err := ctx.GetError()
212+
if ctx.end {
213+
return
214+
}
215215

216-
if err != nil {
217-
msg := err.Error()
218-
ctx.Status(http.StatusInternalServerError)
219-
ctx.SetHeader("Content-Type", "text/plain;charset=UTF-8")
220-
if msg == "URL_NOT_FOUND" {
221-
ctx.Status(http.StatusNotFound)
216+
// NOT FOUND handler
217+
if ctx.code == codeNotFound {
218+
http.NotFound(ctx.Response, ctx.Request)
219+
return
220+
}
221+
222+
if ctx.code != "" || ctx.err != nil {
223+
msg := ctx.code
224+
if ctx.err != nil {
225+
msg = ctx.err.Error()
222226
}
227+
ctx.SetHeader("Content-Type", "text/plain;charset=UTF-8")
228+
ctx.Status(http.StatusInternalServerError)
223229
ctx.Write([]byte(msg))
224230
}
225231
}
232+
233+
// recover
234+
func (ctx *Context) recover() {
235+
err := recover()
236+
if err != nil {
237+
//TODO: debugger mode
238+
log.Println("Unhandled Error: ", err)
239+
if !ctx.requestSent {
240+
ctx.Response.WriteHeader(http.StatusInternalServerError)
241+
ctx.Response.Header().Set("Content-Type", "text/plain;charset=UTF-8")
242+
_, _ = ctx.Response.Write([]byte("Internal Server Error"))
243+
}
244+
}
245+
}

context_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func TestContext_Status(t *testing.T) {
5353

5454
func TestContext_Throw(t *testing.T) {
5555
err := errors.New("test error")
56-
ctx.Throw(err)
56+
ctx.ThrowWithError("TEST_ERROR", err)
5757

5858
if ctx.err != err {
5959
t.Error("Throw is not working")
@@ -64,7 +64,7 @@ func TestContext_Throw(t *testing.T) {
6464

6565
func TestContext_GetError(t *testing.T) {
6666
err := errors.New("test error")
67-
ctx.Throw(err)
67+
ctx.ThrowWithError("TEST_ERROR", err)
6868

6969
if ctx.GetError() != err {
7070
t.Error("GetError is not working")

examples/server.go

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package main
22

33
import (
4-
"errors"
54
"fmt"
65
"net/http"
6+
"strconv"
7+
"time"
78

89
"github.com/go-rs/rest-api-framework"
910
"github.com/go-rs/rest-api-framework/examples/user"
@@ -19,16 +20,29 @@ func main() {
1920
// body-parser : json, raw, form-data, etc
2021
// security
2122
api.Use(func(ctx *rest.Context) {
23+
// way to reproduce uncaught exception
24+
//zero, _ := strconv.ParseInt(ctx.Query.Get("zero"), 10, 32)
25+
//x := 10/zero
26+
//ctx.Set("x", x)
2227
ctx.Set("authtoken", "roshangade")
2328
})
2429

30+
// calculate runtime
31+
api.Use(func(ctx *rest.Context) {
32+
s := time.Now().UnixNano()
33+
ctx.PreSend(func() {
34+
x := time.Now().UnixNano() - s
35+
ctx.SetHeader("X-Runtime", strconv.FormatInt(x/int64(time.Millisecond), 10))
36+
})
37+
})
38+
2539
// routes
2640
api.Get("/", func(ctx *rest.Context) {
2741
ctx.JSON(`{"message": "Hello World!"}`)
2842
})
2943

3044
api.Get("/foo", func(ctx *rest.Context) {
31-
ctx.Throw(errors.New("UNAUTHORIZED"))
45+
ctx.Throw("UNAUTHORIZED")
3246
})
3347

3448
// error handler
@@ -38,5 +52,7 @@ func main() {
3852

3953
fmt.Println("Starting server.")
4054

41-
http.ListenAndServe(":8080", api)
55+
err := http.ListenAndServe(":8080", api)
56+
57+
fmt.Println(err)
4258
}

namespace.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,10 @@ func (n *Namespace) Patch(pattern string, handle Handler) {
6666
}
6767

6868
// OnError method is an exactly same with API.OnError
69-
func (n *Namespace) OnError(err string, handle Handler) {
69+
func (n *Namespace) OnError(code string, handle Handler) {
7070
exp := exception{
71-
message: err,
72-
handle: handle,
71+
code: code,
72+
handle: handle,
7373
}
7474
n.api.exceptions = append(n.api.exceptions, exp)
7575
}

namespace_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ func TestNamespace_Exception(t *testing.T) {
9393

9494
flag := true
9595
for _, route := range api.exceptions {
96-
if route.message == "UID_NOT_FOUND" {
96+
if route.code == "UID_NOT_FOUND" {
9797
flag = false
9898
break
9999
}

0 commit comments

Comments
 (0)