GitHub - altlimit/restruct: RESTruct is a rest router framework written in Go (G...
source link: https://github.com/altlimit/restruct
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
restruct
RESTruct is a go rest framework based on structs. The goal of this project is to automate routing, request and response based on struct methods.
Install
go get github.com/altlimit/restruct
Examples
Let's create a calculator service:
type Calculator struct {
Advance bool
}
func (c *Calculator) Add(r *http.Request) interface{} {
var req struct {
A int64 `json:"a"`
B int64 `json:"b"`
}
if err := restruct.Bind(r, &req, http.MethodPost); err != nil {
return err
}
return req.A + req.B
}
We define our services using struct methods. Here we define a single endpoint Add
that is translated to "add" in the endpoint. We use our utility method Bind to restrict other methods and bind request body into our struct. You can ofcourse handle all this on your own and return any value or if you prefer have both r *http.Request and w http.ResponseWriter without a return and it will just be like a regular handler.
To register the above service:
func main() {
restruct.Handle("/api/v1/", &Calculator{})
http.ListenAndServe(":8080", nil)
}
You can now try to do a post to http://localhost:8080/api/v1/add with body:
{
"a": 1,
"b": 2
}
You can create additional service with a different prefix by call NewHandler on your struct then adding it with AddService.
h := restruct.NewHandler(&Calculator{})
h.AddService("/advance/{tag}/", &Calculator{Advance: true})
restruct.Handle("/api/v1/", h)
All your services will now be at /api/v1/advance/{tag}. You can also register the returned Handler in a third party router but make sure you call WithPrefix(...)
on it if it's not a root route.
http.Handle("/api/v1/", h.WithPrefix("/api/v1/"))
You can have parameters with method using number and access them using restruct.Params()
:
func (c *Calculator) Edit_0(r *http.Request) interface{} {
params := restruct.Params(r)
log.Println("Edited", params["0"], "with tag", params["tag"])
return "OK"
}
Refer to cmd/example for some advance usage.
Route By Methods
Public method will become routed using the pattern below.
UpperCase turns to upper-case
With_Underscore to with/underscore
HasParam_0 to has-param/{0}
HasParam_0_AndMore_1 to has-param/{0}/and-more/{1}
Response Writer
The default ResponseWriter
is DefaultWriter
which uses json.Encoder().Encode to write outputs. This also handles errors and status codes. You can modify the output by implementing the ResponseWriter interface and set it in your Handler
.
type TextWriter struct {}
func (tw *TextWriter) Write(w http.ResponseWriter, r *http.Request, out interface{}) {
if err, ok := out.(error); ok {
w.WriteHeader(http.StatusInternalServerError)
} else {
w.WriteHeader(http.StatusOK)
}
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte(fmt.Sprintf("%v", out)))
}
h := restruct.NewHandler(&Calculator{})
h.Writer = &TextWriter{}
Middleware
Uses standard middleware and add by handler.Use(...)
func auth(next http.Handler) http.Handler {
// you can use your h.Writer here if it's accessible somewhere
wr := rs.DefaultWriter{}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Authorization") != "abc" {
wr.Write(w, rs.Error{Status: http.StatusUnauthorized})
return
}
next.ServeHTTP(w, r)
})
}
h := restruct.NewHandler(&Calculator{})
h.Use(auth)
Nested Structs
Nested structs are automatically routed. You can use route tag to customize or add route:"-"
to skip exported structs.
type (
V1 struct {
Users User
DB DB `route:"-"`
}
User struct {
}
)
func (v *V1) Drop() {}
func (u *User) SendEmail() {}
func main() {
restruct.Handle("/api/v1/", &V1{})
http.ListenAndServe(":8080", nil)
}
Will generate route: /api/v1/drop and /api/v1/users/send-email
Custom Routes
You can override default method named routes using Router
interface. Implement Router in your service and return a map of method name to Route
. You can also add regular expression in your params and restrict methods in the Route.
func (v *V1) Routes() map[string]Route {
return map[string]Route{"Drop": Route{Path:".custom/path/{here:.+}", Methods: []string{http.MethodGet}}}
}
This will change the path to /api/v1/.custom/path/{here}. The param here
will match anything even with additional nested paths. It will also return a Error{Status: http.StatusMethodNotAllowed}
if it's not a GET request.
You can restrict methods in multiple ways:
func (c *Calculator) ReadFile(r *http.Request) interface{} {
// using standard way
if r.Method != http.MethodGet {
return Error{Status: http.StatusNotFound}
}
// using the Bind method
// if you support both get and post you add , http.MethodPost and a pointer to struct to bind request body.
if err := restruct.Bind(r, nil, http.MethodGet); err != nil {
return err
}
}
or use the above method by implementing Router
interface.
func (v *V1) Routes() map[string]Route {
// optional Path to use the default behavior "read-file"
return map[string]Route{"ReadFile": Route{Methods: []string{http.MethodGet}}}
}
License
MIT licened. See the LICENSE file for details.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK