From 09c64e63de4cdbb8324145d6cf31b935c942cd55 Mon Sep 17 00:00:00 2001 From: Roshan Gade Date: Sat, 13 Apr 2019 01:37:02 +0530 Subject: [PATCH 1/5] Working on POC --- .gitignore | 5 ++ README.md | 9 +++- api.go | 129 +++++++++++++++++++++++++++++++++++++++++++++ context.go | 75 ++++++++++++++++++++++++++ examples/server.go | 40 ++++++++++++++ utils/url.go | 68 ++++++++++++++++++++++++ 6 files changed, 325 insertions(+), 1 deletion(-) create mode 100644 api.go create mode 100644 context.go create mode 100644 examples/server.go create mode 100644 utils/url.go diff --git a/.gitignore b/.gitignore index f1c181e..bcaaa39 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,8 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out + +# editors +.idea +.DS_Store +.vscode \ No newline at end of file diff --git a/README.md b/README.md index 125023a..0b52f76 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,14 @@ REST API framework for go lang # Framework is under development ## Status: -- Working on POC as per concept +- Working on POC + - Request Interceptors/Middlewares + - Routes with URL pattern + - Methods [GET, POST, PUT, DELETE, OPTIONS, HEAD, PATCH] + - Extend routes with namespace + - Error handler + - HTTP, HTTPS support + ``` var api rest.API diff --git a/api.go b/api.go new file mode 100644 index 0000000..cdf9c6a --- /dev/null +++ b/api.go @@ -0,0 +1,129 @@ +/*! + * utils-api-framework + * Copyright(c) 2019 Roshan Gade + * MIT Licensed + */ +package rest + +import ( + "./utils" + "fmt" + "net/http" + "regexp" +) + +type API struct { + routes []route + interceptors []interceptor + errors []errorHandler +} + +type route struct { + method string + pattern string + regex *regexp.Regexp + params []string + handle func(ctx *Context) +} + +type interceptor struct { + handle func(ctx *Context) +} + +//TODO: check errors in language specifications +type errorHandler struct { + code string + handle func(ctx *Context) +} + +func (api *API) handler(method string, pattern string, handle func(ctx *Context)) { + regex, params, err := utils.Compile(pattern) + if err != nil { + fmt.Println("Error in pattern", err) + panic(1) + } + api.routes = append(api.routes, route{ + method: method, + pattern: pattern, + regex: regex, + params: params, + handle: handle, + }) +} + +func (api API) ServeHTTP(res http.ResponseWriter, req *http.Request) { + //fmt.Println("-------------------------------------") + //fmt.Println("URI: ", req.RequestURI) + //fmt.Println("Method: ", req.Method) + //fmt.Println("Form: ", req.Form) + //fmt.Println("RawPath: ", req.URL.RawPath) + //fmt.Println("Header: ", req.Header) + //fmt.Println("Path: ", req.URL.Path) + //fmt.Println("RawQuery: ", req.URL.RawQuery) + //fmt.Println("Opaque: ", req.URL.Opaque) + //fmt.Println("-------------------------------------") + urlPath := []byte(req.URL.Path) + + ctx := Context{ + req: req, + res: res, + } + + ctx.init() + + for _, route := range api.interceptors { + if ctx.err != nil { + break + } + + if ctx.end == true { + break + } + + route.handle(&ctx) + } + + for _, route := range api.routes { + if ctx.err != nil { + break + } + + if ctx.end == true { + break + } + + if route.method == req.Method && route.regex.Match(urlPath) { + ctx.Params = utils.Exec(route.regex, route.params, urlPath) + route.handle(&ctx) + } + } + + //TODO: NOT FOUND, INTERNAL SERVER ERROR, ETC + + for _, route := range api.errors { + if ctx.end == true { + break + } + + if ctx.err != nil && route.code == ctx.err.Error() { + route.handle(&ctx) + } + } +} + +func (api *API) Use(handle func(ctx *Context)) { + api.interceptors = append(api.interceptors, interceptor{handle: handle}) +} + +func (api *API) GET(pattern string, handle func(ctx *Context)) { + api.handler("GET", pattern, handle) +} + +//TODO: POST, PUT, DELETE, OPTIONS, HEAD, PATCH + +func (api *API) Error(code string, handle func(ctx *Context)) { + api.errors = append(api.errors, errorHandler{ + code: code, + handle: handle, + }) +} diff --git a/context.go b/context.go new file mode 100644 index 0000000..0110c29 --- /dev/null +++ b/context.go @@ -0,0 +1,75 @@ +/*! + * utils-api-framework + * Copyright(c) 2019 Roshan Gade + * MIT Licensed + */ +package rest + +import ( + "encoding/json" + "errors" + "net/http" +) + +type Context struct { + req *http.Request + res http.ResponseWriter + Params *map[string]string + data map[string]interface{} + err error + end bool +} + +func (ctx *Context) init() { + ctx.data = make(map[string]interface{}) + //TODO: initialization +} + +func (ctx *Context) Set(key string, val interface{}) { + ctx.data[key] = val +} + +func (ctx *Context) Get(key string) interface{} { + return ctx.data[key] +} + +func (ctx *Context) Status(code int) *Context { + ctx.res.WriteHeader(code) + return ctx +} + +func (ctx *Context) SetHeader(key string, val string) *Context { + ctx.res.Header().Set(key, val) + return ctx +} + +func (ctx *Context) Throw(err error) { + ctx.err = err +} + +func (ctx *Context) Write(data []byte) { + if ctx.end == true { + return + } + _, err := ctx.res.Write(data) + if err != nil { + ctx.err = errors.New("RESPONSE ERROR") + return + } + + ctx.end = true +} + +func (ctx *Context) Send(data string) { + ctx.Write([]byte(data)) +} + +func (ctx *Context) SendJSON(data interface{}) { + body, err := json.Marshal(data) + if err != nil { + ctx.err = errors.New("INVALID JSON") + return + } + ctx.SetHeader("Content-Type", "application/json") + ctx.Write(body) +} diff --git a/examples/server.go b/examples/server.go new file mode 100644 index 0000000..4d05c8b --- /dev/null +++ b/examples/server.go @@ -0,0 +1,40 @@ +package main + +import ( + ".." + "errors" + "fmt" + "net/http" +) + +func main() { + var api rest.API + + // request interceptor / middleware + api.Use(func(ctx *rest.Context) { + ctx.Set("authtoken", "roshangade") + }) + + // routes + api.GET("/", func(ctx *rest.Context) { + ctx.Send("Hello World!") + }) + + api.GET("/foo", func(ctx *rest.Context) { + ctx.Status(401).Throw(errors.New("UNAUTHORIZED")) + }) + + api.GET("/:bar", func(ctx *rest.Context) { + fmt.Println("authtoken", ctx.Get("authtoken")) + ctx.SendJSON(ctx.Params) + }) + + // error handler + api.Error("UNAUTHORIZED", func(ctx *rest.Context) { + ctx.Send("You are unauthorized") + }) + + fmt.Println("Starting server.") + + http.ListenAndServe(":8080", api) +} diff --git a/utils/url.go b/utils/url.go new file mode 100644 index 0000000..0d75de4 --- /dev/null +++ b/utils/url.go @@ -0,0 +1,68 @@ +/*! + * utils-api-framework + * Copyright(c) 2019 Roshan Gade + * MIT Licensed + */ +package utils + +import ( + "regexp" + "strings" +) + +const sep = "/" + +func Compile(str string) (*regexp.Regexp, []string, error) { + pattern := "" + keys := make([]string, 0) + _str := strings.Split(str, "/") + + for _, val := range _str { + if val != "" { + switch val[0] { + case 42: + pattern += "(?:/(.*))" + keys = append(keys, "*") + + case 58: + length := len(val) + lastChar := val[length-1] + if lastChar == 63 { + pattern += "(?:/([^/]+?))?" + keys = append(keys, val[1:(length-1)]) + } else { + pattern += sep + "([^/]+?)" + keys = append(keys, val[1:]) + } + + default: + pattern += sep + val + } + } + } + + // if len(keys) == 0 { + // pattern += "(?:/)?" + // } + + regex, err := regexp.Compile("^" + pattern + "/?$") + + return regex, keys, err +} + +func Exec(regex *regexp.Regexp, keys []string, uri []byte) *map[string]string { + params := make(map[string]string) + + matches := regex.FindAllSubmatch(uri, -1) + + for _, val := range matches { + for i, k := range val[1:] { + params[keys[i]] = string(k) + if keys[i] == "*" { + params["*"] = sep + params["*"] + } + } + } + + return ¶ms +} From e8c083a96671dd576b4bbb4282c0f32f6ccb25cd Mon Sep 17 00:00:00 2001 From: Roshan Gade Date: Sat, 13 Apr 2019 23:53:33 +0530 Subject: [PATCH 2/5] POC is almost done --- api.go | 142 +++++++++++++++++++++++++++++++-------------- context.go | 138 ++++++++++++++++++++++++++++++++++--------- examples/server.go | 33 +++++++++-- render/json.go | 40 +++++++++++++ render/text.go | 27 +++++++++ utils/url.go | 2 +- 6 files changed, 304 insertions(+), 78 deletions(-) create mode 100644 render/json.go create mode 100644 render/text.go diff --git a/api.go b/api.go index cdf9c6a..b83e01c 100644 --- a/api.go +++ b/api.go @@ -1,5 +1,5 @@ /*! - * utils-api-framework + * rest-api-framework * Copyright(c) 2019 Roshan Gade * MIT Licensed */ @@ -7,17 +7,25 @@ package rest import ( "./utils" + "errors" "fmt" "net/http" "regexp" ) +/** + * API - Application + */ type API struct { routes []route interceptors []interceptor - errors []errorHandler + exceptions []exception + unhandled func(ctx *Context) } +/** + * Route + */ type route struct { method string pattern string @@ -26,17 +34,25 @@ type route struct { handle func(ctx *Context) } +/** + * Request interceptor + */ type interceptor struct { handle func(ctx *Context) } -//TODO: check errors in language specifications -type errorHandler struct { - code string - handle func(ctx *Context) +/** + * Exception Route + */ +type exception struct { + message string + handle func(ctx *Context) } -func (api *API) handler(method string, pattern string, handle func(ctx *Context)) { +/** + * Common Route + */ +func (api *API) Route(method string, pattern string, handle func(ctx *Context)) { regex, params, err := utils.Compile(pattern) if err != nil { fmt.Println("Error in pattern", err) @@ -51,79 +67,115 @@ func (api *API) handler(method string, pattern string, handle func(ctx *Context) }) } +/** + * Required handle for http module + */ func (api API) ServeHTTP(res http.ResponseWriter, req *http.Request) { - //fmt.Println("-------------------------------------") - //fmt.Println("URI: ", req.RequestURI) - //fmt.Println("Method: ", req.Method) - //fmt.Println("Form: ", req.Form) - //fmt.Println("RawPath: ", req.URL.RawPath) - //fmt.Println("Header: ", req.Header) - //fmt.Println("Path: ", req.URL.Path) - //fmt.Println("RawQuery: ", req.URL.RawQuery) - //fmt.Println("Opaque: ", req.URL.Opaque) - //fmt.Println("-------------------------------------") + urlPath := []byte(req.URL.Path) ctx := Context{ - req: req, - res: res, + Request: req, + Response: res, } + // STEP 1: initialize context ctx.init() + defer ctx.destroy() - for _, route := range api.interceptors { - if ctx.err != nil { - break - } - - if ctx.end == true { + // STEP 2: execute all interceptors + for _, task := range api.interceptors { + if ctx.end || ctx.err != nil { break } - route.handle(&ctx) + task.handle(&ctx) } + // STEP 3: check routes for _, route := range api.routes { - if ctx.err != nil { - break - } - - if ctx.end == true { + if ctx.end || ctx.err != nil { break } if route.method == req.Method && route.regex.Match(urlPath) { + ctx.found = true ctx.Params = utils.Exec(route.regex, route.params, urlPath) route.handle(&ctx) } } - //TODO: NOT FOUND, INTERNAL SERVER ERROR, ETC - - for _, route := range api.errors { - if ctx.end == true { + // STEP 4: check handled exceptions + for _, exp := range api.exceptions { + if ctx.end || ctx.err == nil { break } - if ctx.err != nil && route.code == ctx.err.Error() { - route.handle(&ctx) + if exp.message == ctx.err.Error() { + exp.handle(&ctx) + } + } + + // STEP 5: unhandled exceptions + if !ctx.end { + if ctx.err == nil && !ctx.found { + ctx.err = errors.New("URL_NOT_FOUND") + } + + if api.unhandled != nil { + api.unhandled(&ctx) } } + + // STEP 6: system handle + if !ctx.end { + ctx.unhandledException() + } } func (api *API) Use(handle func(ctx *Context)) { - api.interceptors = append(api.interceptors, interceptor{handle: handle}) + task := interceptor{ + handle: handle, + } + api.interceptors = append(api.interceptors, task) } func (api *API) GET(pattern string, handle func(ctx *Context)) { - api.handler("GET", pattern, handle) + api.Route("GET", pattern, handle) } -//TODO: POST, PUT, DELETE, OPTIONS, HEAD, PATCH +func (api *API) POST(pattern string, handle func(ctx *Context)) { + api.Route("POST", pattern, handle) +} -func (api *API) Error(code string, handle func(ctx *Context)) { - api.errors = append(api.errors, errorHandler{ - code: code, - handle: handle, - }) +func (api *API) PUT(pattern string, handle func(ctx *Context)) { + api.Route("PUT", pattern, handle) +} + +func (api *API) DELETE(pattern string, handle func(ctx *Context)) { + api.Route("DELETE", pattern, handle) +} + +func (api *API) OPTIONS(pattern string, handle func(ctx *Context)) { + api.Route("OPTIONS", pattern, handle) +} + +func (api *API) HEAD(pattern string, handle func(ctx *Context)) { + api.Route("HEAD", pattern, handle) +} + +func (api *API) PATCH(pattern string, handle func(ctx *Context)) { + api.Route("PATCH", pattern, handle) +} + +func (api *API) Exception(err string, handle func(ctx *Context)) { + exp := exception{ + message: err, + handle: handle, + } + api.exceptions = append(api.exceptions, exp) +} + +func (api *API) UnhandledException(handle func(ctx *Context)) { + api.unhandled = handle } diff --git a/context.go b/context.go index 0110c29..b9efeac 100644 --- a/context.go +++ b/context.go @@ -1,75 +1,159 @@ /*! - * utils-api-framework + * rest-api-framework * Copyright(c) 2019 Roshan Gade * MIT Licensed */ package rest import ( - "encoding/json" - "errors" + "./render" "net/http" ) +/** + * Context + */ type Context struct { - req *http.Request - res http.ResponseWriter - Params *map[string]string - data map[string]interface{} - err error - end bool + Request *http.Request + Response http.ResponseWriter + Params *map[string]string + data map[string]interface{} + err error + status int + found bool + end bool } +/** + * Initialization of context on every request + */ func (ctx *Context) init() { ctx.data = make(map[string]interface{}) - //TODO: initialization + ctx.status = 200 + ctx.found = false + ctx.end = false +} + +/** + * Destroy context once request end + */ +func (ctx *Context) destroy() { + ctx.Request = nil + ctx.Response = nil + ctx.Params = nil + ctx.data = nil + ctx.err = nil + ctx.status = 0 + ctx.found = false + ctx.end = false } +/** + * Set request data in context + */ func (ctx *Context) Set(key string, val interface{}) { ctx.data[key] = val } +/** + * Get request data from context + */ func (ctx *Context) Get(key string) interface{} { return ctx.data[key] } +/** + * Set Status + */ func (ctx *Context) Status(code int) *Context { - ctx.res.WriteHeader(code) + ctx.status = code return ctx } +/** + * Set Header + */ func (ctx *Context) SetHeader(key string, val string) *Context { - ctx.res.Header().Set(key, val) + ctx.Response.Header().Set(key, val) return ctx } +/** + * Throw error + */ func (ctx *Context) Throw(err error) { ctx.err = err } +/** + * Get error + */ +func (ctx *Context) GetError() error { + return ctx.err +} + +/** + * End + */ +func (ctx *Context) End() { + ctx.end = true +} + +/** + * Write Bytes + */ func (ctx *Context) Write(data []byte) { - if ctx.end == true { + ctx.send(data, nil) +} + +/** + * Write JSON + */ +func (ctx *Context) JSON(data interface{}) { + json := render.JSON{ + Body: data, + } + body, err := json.Write(ctx.Response) + ctx.send(body, err) +} + +/** + * Write Text + */ +func (ctx *Context) Text(data string) { + txt := render.Text{ + Body: data, + } + body, err := txt.Write(ctx.Response) + ctx.send(body, err) +} + +////////////////////////////////////////////////// +/** + * Send data + */ +func (ctx *Context) send(data []byte, err error) { + if ctx.end && err != nil { return } - _, err := ctx.res.Write(data) + ctx.Response.WriteHeader(ctx.status) + _, err = ctx.Response.Write(data) if err != nil { - ctx.err = errors.New("RESPONSE ERROR") + ctx.err = err return } - ctx.end = true -} - -func (ctx *Context) Send(data string) { - ctx.Write([]byte(data)) + ctx.End() } -func (ctx *Context) SendJSON(data interface{}) { - body, err := json.Marshal(data) - if err != nil { - ctx.err = errors.New("INVALID JSON") - return +/** + * Unhandled Exception + */ +func (ctx *Context) unhandledException() { + err := ctx.GetError().Error() + ctx.Status(500) + if err == "URL_NOT_FOUND" { + ctx.Status(404) } - ctx.SetHeader("Content-Type", "application/json") - ctx.Write(body) + ctx.Write([]byte(err)) } diff --git a/examples/server.go b/examples/server.go index 4d05c8b..c712f46 100644 --- a/examples/server.go +++ b/examples/server.go @@ -11,27 +11,50 @@ func main() { var api rest.API // request interceptor / middleware + // method-override + // body-parser : json, raw, form-data, etc + // security api.Use(func(ctx *rest.Context) { ctx.Set("authtoken", "roshangade") }) // routes api.GET("/", func(ctx *rest.Context) { - ctx.Send("Hello World!") + ctx.Text("Hello World!") }) api.GET("/foo", func(ctx *rest.Context) { - ctx.Status(401).Throw(errors.New("UNAUTHORIZED")) + ctx.Throw(errors.New("UNAUTHORIZED")) + }) + + api.GET("/strut", func(ctx *rest.Context) { + type user struct { + Name string + } + + u := user{ + Name: "roshan", + } + + ctx.JSON(u) + }) + + api.GET("/json", func(ctx *rest.Context) { + ctx.JSON(`{"test": "JSON Response"}`) }) api.GET("/:bar", func(ctx *rest.Context) { fmt.Println("authtoken", ctx.Get("authtoken")) - ctx.SendJSON(ctx.Params) + ctx.JSON(ctx.Params) }) // error handler - api.Error("UNAUTHORIZED", func(ctx *rest.Context) { - ctx.Send("You are unauthorized") + api.Exception("UNAUTHORIZED", func(ctx *rest.Context) { + ctx.Status(401).Text("You are unauthorized") + }) + + api.UnhandledException(func(ctx *rest.Context) { + fmt.Println("---> ", ctx.GetError()) }) fmt.Println("Starting server.") diff --git a/render/json.go b/render/json.go new file mode 100644 index 0000000..66c4732 --- /dev/null +++ b/render/json.go @@ -0,0 +1,40 @@ +/*! + * rest-api-framework + * Copyright(c) 2019 Roshan Gade + * MIT Licensed + */ +package render + +import ( + "encoding/json" + "net/http" + "reflect" +) + +//TODO: JSONP +type JSON struct { + Body interface{} +} + +var ( + jsonType = "application/json" +) + +/** + * JSON Write + */ +func (j JSON) Write(w http.ResponseWriter) ([]byte, error) { + var data []byte + var err error + if reflect.TypeOf(j.Body).String() == "string" { + rawIn := json.RawMessage(j.Body.(string)) + data, err = rawIn.MarshalJSON() + } else { + data, err = json.Marshal(j.Body) + } + if err != nil { + return nil, err + } + w.Header().Set("Content-Type", jsonType) + return data, nil +} diff --git a/render/text.go b/render/text.go new file mode 100644 index 0000000..8b12d34 --- /dev/null +++ b/render/text.go @@ -0,0 +1,27 @@ +/*! + * rest-api-framework + * Copyright(c) 2019 Roshan Gade + * MIT Licensed + */ +package render + +import ( + "net/http" +) + +type Text struct { + Body string +} + +var ( + plainType = "text/plain" +) + +/** + * Text Write + */ +func (j Text) Write(w http.ResponseWriter) ([]byte, error) { + data := []byte(j.Body) + w.Header().Set("Content-Type", plainType) + return data, nil +} diff --git a/utils/url.go b/utils/url.go index 0d75de4..7c57304 100644 --- a/utils/url.go +++ b/utils/url.go @@ -1,5 +1,5 @@ /*! - * utils-api-framework + * rest-api-framework * Copyright(c) 2019 Roshan Gade * MIT Licensed */ From 34f9e1ea04a3df6318704bf95d6c714ccc61d94c Mon Sep 17 00:00:00 2001 From: Roshan Gade Date: Sun, 14 Apr 2019 22:50:35 +0530 Subject: [PATCH 3/5] Added namespace support --- LICENSE | 21 ++++++++++++++ api.go | 43 +++++++++++++++++------------ context.go | 15 ++++++---- examples/server.go | 37 +++++-------------------- examples/user/user.go | 24 ++++++++++++++++ namespace.go | 64 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 150 insertions(+), 54 deletions(-) create mode 100644 LICENSE create mode 100644 examples/user/user.go create mode 100644 namespace.go diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bbe327c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Roshan Gade + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/api.go b/api.go index b83e01c..cf497b8 100644 --- a/api.go +++ b/api.go @@ -6,21 +6,24 @@ package rest import ( - "./utils" "errors" "fmt" + "github.com/go-rs/rest-api-framework/utils" "net/http" "regexp" ) +type Handler func(ctx *Context) + /** * API - Application */ type API struct { + prefix string routes []route interceptors []interceptor exceptions []exception - unhandled func(ctx *Context) + unhandled Handler } /** @@ -31,14 +34,14 @@ type route struct { pattern string regex *regexp.Regexp params []string - handle func(ctx *Context) + handle Handler } /** * Request interceptor */ type interceptor struct { - handle func(ctx *Context) + handle Handler } /** @@ -46,13 +49,13 @@ type interceptor struct { */ type exception struct { message string - handle func(ctx *Context) + handle Handler } /** * Common Route */ -func (api *API) Route(method string, pattern string, handle func(ctx *Context)) { +func (api *API) Route(method string, pattern string, handle Handler) { regex, params, err := utils.Compile(pattern) if err != nil { fmt.Println("Error in pattern", err) @@ -98,8 +101,8 @@ func (api API) ServeHTTP(res http.ResponseWriter, req *http.Request) { break } - if route.method == req.Method && route.regex.Match(urlPath) { - ctx.found = true + if (route.method == "" || route.method == req.Method) && route.regex.Match(urlPath) { + ctx.found = route.method != "" //? ctx.Params = utils.Exec(route.regex, route.params, urlPath) route.handle(&ctx) } @@ -133,42 +136,46 @@ func (api API) ServeHTTP(res http.ResponseWriter, req *http.Request) { } } -func (api *API) Use(handle func(ctx *Context)) { +func (api *API) Use(handle Handler) { task := interceptor{ handle: handle, } api.interceptors = append(api.interceptors, task) } -func (api *API) GET(pattern string, handle func(ctx *Context)) { +func (api *API) All(pattern string, handle Handler) { + api.Route("", pattern, handle) +} + +func (api *API) Get(pattern string, handle Handler) { api.Route("GET", pattern, handle) } -func (api *API) POST(pattern string, handle func(ctx *Context)) { +func (api *API) Post(pattern string, handle Handler) { api.Route("POST", pattern, handle) } -func (api *API) PUT(pattern string, handle func(ctx *Context)) { +func (api *API) Put(pattern string, handle Handler) { api.Route("PUT", pattern, handle) } -func (api *API) DELETE(pattern string, handle func(ctx *Context)) { +func (api *API) Delete(pattern string, handle Handler) { api.Route("DELETE", pattern, handle) } -func (api *API) OPTIONS(pattern string, handle func(ctx *Context)) { +func (api *API) Options(pattern string, handle Handler) { api.Route("OPTIONS", pattern, handle) } -func (api *API) HEAD(pattern string, handle func(ctx *Context)) { +func (api *API) Head(pattern string, handle Handler) { api.Route("HEAD", pattern, handle) } -func (api *API) PATCH(pattern string, handle func(ctx *Context)) { +func (api *API) Patch(pattern string, handle Handler) { api.Route("PATCH", pattern, handle) } -func (api *API) Exception(err string, handle func(ctx *Context)) { +func (api *API) Exception(err string, handle Handler) { exp := exception{ message: err, handle: handle, @@ -176,6 +183,6 @@ func (api *API) Exception(err string, handle func(ctx *Context)) { api.exceptions = append(api.exceptions, exp) } -func (api *API) UnhandledException(handle func(ctx *Context)) { +func (api *API) UnhandledException(handle Handler) { api.unhandled = handle } diff --git a/context.go b/context.go index b9efeac..aeee4fa 100644 --- a/context.go +++ b/context.go @@ -6,7 +6,7 @@ package rest import ( - "./render" + "github.com/go-rs/rest-api-framework/render" "net/http" ) @@ -150,10 +150,13 @@ func (ctx *Context) send(data []byte, err error) { * Unhandled Exception */ func (ctx *Context) unhandledException() { - err := ctx.GetError().Error() - ctx.Status(500) - if err == "URL_NOT_FOUND" { - ctx.Status(404) + err := ctx.GetError() + if err != nil { + msg := err.Error() + ctx.Status(500) + if msg == "URL_NOT_FOUND" { + ctx.Status(404) + } + ctx.Write([]byte(msg)) } - ctx.Write([]byte(err)) } diff --git a/examples/server.go b/examples/server.go index c712f46..f440add 100644 --- a/examples/server.go +++ b/examples/server.go @@ -4,14 +4,16 @@ import ( ".." "errors" "fmt" + "github.com/go-rs/rest-api-framework/examples/user" "net/http" ) func main() { var api rest.API + user.APIs(&api) + // request interceptor / middleware - // method-override // body-parser : json, raw, form-data, etc // security api.Use(func(ctx *rest.Context) { @@ -19,42 +21,17 @@ func main() { }) // routes - api.GET("/", func(ctx *rest.Context) { - ctx.Text("Hello World!") + api.Get("/", func(ctx *rest.Context) { + ctx.JSON(`{"message": "Hello World!"}`) }) - api.GET("/foo", func(ctx *rest.Context) { + api.Get("/foo", func(ctx *rest.Context) { ctx.Throw(errors.New("UNAUTHORIZED")) }) - api.GET("/strut", func(ctx *rest.Context) { - type user struct { - Name string - } - - u := user{ - Name: "roshan", - } - - ctx.JSON(u) - }) - - api.GET("/json", func(ctx *rest.Context) { - ctx.JSON(`{"test": "JSON Response"}`) - }) - - api.GET("/:bar", func(ctx *rest.Context) { - fmt.Println("authtoken", ctx.Get("authtoken")) - ctx.JSON(ctx.Params) - }) - // error handler api.Exception("UNAUTHORIZED", func(ctx *rest.Context) { - ctx.Status(401).Text("You are unauthorized") - }) - - api.UnhandledException(func(ctx *rest.Context) { - fmt.Println("---> ", ctx.GetError()) + ctx.Status(401).JSON(`{"message": "You are unauthorized"}`) }) fmt.Println("Starting server.") diff --git a/examples/user/user.go b/examples/user/user.go new file mode 100644 index 0000000..54af9e1 --- /dev/null +++ b/examples/user/user.go @@ -0,0 +1,24 @@ +package user + +import ( + "../.." +) + +func APIs(api *rest.API) { + + var user rest.Namespace + + user.Set("/user", api) + + user.Use(func(ctx *rest.Context) { + println("User middleware > /user/*") + }) + + user.Get("/:uid/profile", func(ctx *rest.Context) { + ctx.JSON(`{"user": "profile"}`) + }) + + user.Get("/:uid", func(ctx *rest.Context) { + ctx.JSON(ctx.Params) + }) +} diff --git a/namespace.go b/namespace.go new file mode 100644 index 0000000..fce0a96 --- /dev/null +++ b/namespace.go @@ -0,0 +1,64 @@ +/*! + * rest-api-framework + * Copyright(c) 2019 Roshan Gade + * MIT Licensed + */ +package rest + +/** + * Namespace - Application + */ +type Namespace struct { + prefix string + api *API +} + +//TODO: error handling on unset api +func (n *Namespace) Set(prefix string, api *API) { + n.prefix = prefix + n.api = api +} + +func (n *Namespace) Use(handle Handler) { + n.api.Route("", n.prefix+"/*", handle) +} + +func (n *Namespace) All(pattern string, handle Handler) { + n.api.Route("", n.prefix+pattern, handle) +} + +func (n *Namespace) Get(pattern string, handle Handler) { + n.api.Route("GET", n.prefix+pattern, handle) +} + +func (n *Namespace) Post(pattern string, handle Handler) { + n.api.Route("POST", n.prefix+pattern, handle) +} + +func (n *Namespace) Put(pattern string, handle Handler) { + n.api.Route("PUT", n.prefix+pattern, handle) +} + +func (n *Namespace) Delete(pattern string, handle Handler) { + n.api.Route("DELETE", n.prefix+pattern, handle) +} + +func (n *Namespace) Options(pattern string, handle Handler) { + n.api.Route("OPTIONS", n.prefix+pattern, handle) +} + +func (n *Namespace) Head(pattern string, handle Handler) { + n.api.Route("HEAD", n.prefix+pattern, handle) +} + +func (n *Namespace) Patch(pattern string, handle Handler) { + n.api.Route("PATCH", n.prefix+pattern, handle) +} + +func (n *Namespace) Exception(err string, handle Handler) { + exp := exception{ + message: err, + handle: handle, + } + n.api.exceptions = append(n.api.exceptions, exp) +} From 380029ed9e1dbb734c241a22826fb46f910ba5ef Mon Sep 17 00:00:00 2001 From: Roshan Gade Date: Sun, 14 Apr 2019 22:52:30 +0530 Subject: [PATCH 4/5] version - 0.0.1-alpha.1 --- version.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 version.txt diff --git a/version.txt b/version.txt new file mode 100644 index 0000000..d75fc87 --- /dev/null +++ b/version.txt @@ -0,0 +1 @@ +0.0.1-alpha.1 \ No newline at end of file From d48342e452e03f00bfd880a7efb8990f7b204d67 Mon Sep 17 00:00:00 2001 From: Roshan Gade Date: Sun, 14 Apr 2019 22:58:23 +0530 Subject: [PATCH 5/5] version - 0.0.1-alpha.1 --- README.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 0b52f76..77f651a 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,9 @@ REST API framework for go lang # Framework is under development ## Status: -- Working on POC +Released alpha version +
+See examples - Request Interceptors/Middlewares - Routes with URL pattern - Methods [GET, POST, PUT, DELETE, OPTIONS, HEAD, PATCH] @@ -20,22 +22,22 @@ api.Use(func(ctx *rest.Context) { }) // routes -api.GET("/", func(ctx *rest.Context) { - ctx.Send("Hello World!") +api.Get("/", func(ctx *rest.Context) { + ctx.Text("Hello World!") }) -api.GET("/foo", func(ctx *rest.Context) { +api.Get("/foo", func(ctx *rest.Context) { ctx.Status(401).Throw(errors.New("UNAUTHORIZED")) }) -api.GET("/:bar", func(ctx *rest.Context) { +api.Get("/:bar", func(ctx *rest.Context) { fmt.Println("authtoken", ctx.Get("authtoken")) - ctx.SendJSON(ctx.Params) + ctx.JSON(ctx.Params) }) // error handler api.Error("UNAUTHORIZED", func(ctx *rest.Context) { - ctx.Send("You are unauthorized") + ctx.Text("You are unauthorized") }) fmt.Println("Starting server.")