2

GitHub - altlimit/restruct: RESTruct is a rest router framework written in Go (G...

 1 year ago
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.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK