Skip to content

Commit ddac7c0

Browse files
committed
feat: adding httsuite basics
1 parent ade232d commit ddac7c0

File tree

14 files changed

+739
-1
lines changed

14 files changed

+739
-1
lines changed

.idea/.gitignore

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/httpsuite.iml

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/modules.xml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/vcs.xml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,86 @@
11
# httpsuite
2-
A Go library to simplify request parsing, validation, and response handling in microservices, making code cleaner and more maintainable.
2+
3+
**httpsuite** is a Go library designed to simplify the handling of HTTP requests, validations, and responses
4+
in microservices. By providing a clear structure and modular approach, it helps developers write
5+
cleaner, more maintainable code with reduced boilerplate.
6+
7+
## Features
8+
9+
- **Request Parsing**: Streamline the parsing of incoming HTTP requests, including URL parameters.
10+
- **Validation:** Centralize validation logic for easy reuse and consistency.
11+
- **Response Handling:** Standardize responses across your microservices for a unified client experience.
12+
- **Modular Design:** Each component (Request, Validation, Response) can be used independently,
13+
enhancing testability and flexibility.
14+
15+
> **Note:** Currently it only supports Chi.
16+
17+
## Installation
18+
19+
To install **httpsuite**, run:
20+
21+
```
22+
go get github.com/rluders/httpsuite
23+
```
24+
25+
## Usage
26+
27+
### Request Parsing with URL Parameters
28+
29+
Easily parse incoming requests and set URL parameters:
30+
31+
```go
32+
package main
33+
34+
import (
35+
"github.com/go-chi/chi/v5"
36+
"github.com/go-chi/chi/v5/middleware"
37+
"github.com/rluders/httpsuite"
38+
"log"
39+
"net/http"
40+
)
41+
42+
type SampleRequest struct {
43+
Name string `json:"name" validate:"required,min=3"`
44+
Age int `json:"age" validate:"required,min=1"`
45+
}
46+
47+
func (r *SampleRequest) SetParam(fieldName, value string) error {
48+
switch fieldName {
49+
case "name":
50+
r.Name = value
51+
}
52+
return nil
53+
}
54+
55+
func main() {
56+
r := chi.NewRouter()
57+
r.Use(middleware.Logger)
58+
r.Use(middleware.Recoverer)
59+
60+
r.Post("/submit/{name}", func(w http.ResponseWriter, r *http.Request) {
61+
// Step 1: Parse the request and validate it
62+
req, err := httpsuite.ParseRequest[*SampleRequest](w, r, "name")
63+
if err != nil {
64+
log.Printf("Error parsing or validating request: %v", err)
65+
return
66+
}
67+
68+
// Step 2: Send a success response
69+
httpsuite.SendResponse(w, "Request received successfully", http.StatusOK, &req)
70+
})
71+
72+
log.Println("Starting server on :8080")
73+
http.ListenAndServe(":8080", r)
74+
}
75+
```
76+
77+
Check out the [example folder for a complete project](./examples) demonstrating how to integrate **httpsuite** into
78+
your Go microservices.
79+
80+
## Contributing
81+
82+
Contributions are welcome! Feel free to open issues, submit pull requests, and help improve **httpsuite**.
83+
84+
## License
85+
86+
The MIT License (MIT). Please see [License File](LICENSE) for more information.

examples/main.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package main
2+
3+
import (
4+
"github.com/go-chi/chi/v5"
5+
"github.com/go-chi/chi/v5/middleware"
6+
"github.com/rluders/httpsuite"
7+
"log"
8+
"net/http"
9+
)
10+
11+
type SampleRequest struct {
12+
Name string `json:"name" validate:"required,min=3"`
13+
Age int `json:"age" validate:"required,min=1"`
14+
}
15+
16+
func (r *SampleRequest) SetParam(fieldName, value string) error {
17+
switch fieldName {
18+
case "name":
19+
r.Name = value
20+
}
21+
return nil
22+
}
23+
24+
func main() {
25+
r := chi.NewRouter()
26+
r.Use(middleware.Logger)
27+
r.Use(middleware.Recoverer)
28+
29+
r.Post("/submit/{name}", func(w http.ResponseWriter, r *http.Request) {
30+
// Step 1: Parse the request and validate it
31+
req, err := httpsuite.ParseRequest[*SampleRequest](w, r, "name")
32+
if err != nil {
33+
log.Printf("Error parsing or validating request: %v", err)
34+
return
35+
}
36+
37+
// Step 2: Send a success response
38+
httpsuite.SendResponse(w, "Request received successfully", http.StatusOK, &req)
39+
})
40+
41+
log.Println("Starting server on :8080")
42+
http.ListenAndServe(":8080", r)
43+
}

go.mod

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
module github.com/rluders/httpsuite
2+
3+
go 1.23.1
4+
5+
require (
6+
github.com/go-chi/chi/v5 v5.1.0
7+
github.com/go-playground/validator/v10 v10.22.1
8+
github.com/stretchr/testify v1.9.0
9+
)
10+
11+
require (
12+
github.com/davecgh/go-spew v1.1.1 // indirect
13+
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
14+
github.com/go-playground/locales v0.14.1 // indirect
15+
github.com/go-playground/universal-translator v0.18.1 // indirect
16+
github.com/leodido/go-urn v1.4.0 // indirect
17+
github.com/pmezard/go-difflib v1.0.0 // indirect
18+
golang.org/x/crypto v0.19.0 // indirect
19+
golang.org/x/net v0.21.0 // indirect
20+
golang.org/x/sys v0.17.0 // indirect
21+
golang.org/x/text v0.14.0 // indirect
22+
gopkg.in/yaml.v3 v3.0.1 // indirect
23+
)

go.sum

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
4+
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
5+
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
6+
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
7+
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
8+
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
9+
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
10+
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
11+
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
12+
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
13+
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
14+
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
15+
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
16+
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
17+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
18+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
19+
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
20+
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
21+
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
22+
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
23+
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
24+
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
25+
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
26+
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
27+
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
28+
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
29+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
30+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
31+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
32+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

request.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package httpsuite
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"github.com/go-chi/chi/v5"
7+
"net/http"
8+
"reflect"
9+
)
10+
11+
// RequestParamSetter defines the interface used to set the parameters to the HTTP request object by the request parser.
12+
// Implementing this interface allows custom handling of URL parameters.
13+
type RequestParamSetter interface {
14+
// SetParam assigns a value to a specified field in the request struct.
15+
// The fieldName parameter is the name of the field, and value is the value to set.
16+
SetParam(fieldName, value string) error
17+
}
18+
19+
// ParseRequest parses the incoming HTTP request into a specified struct type, handling JSON decoding and URL parameters.
20+
// It validates the parsed request and returns it along with any potential errors.
21+
// The pathParams variadic argument allows specifying URL parameters to be extracted.
22+
// If an error occurs during parsing, validation, or parameter setting, it responds with an appropriate HTTP status.
23+
func ParseRequest[T RequestParamSetter](w http.ResponseWriter, r *http.Request, pathParams ...string) (T, error) {
24+
var request T
25+
var empty T
26+
27+
defer func() {
28+
_ = r.Body.Close()
29+
}()
30+
31+
if r.Body != http.NoBody {
32+
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
33+
SendResponse[any](w, "Invalid JSON format", http.StatusBadRequest, nil)
34+
return empty, err
35+
}
36+
}
37+
38+
// If body wasn't parsed request may be nil and cause problems ahead
39+
if isRequestNil(request) {
40+
request = reflect.New(reflect.TypeOf(request).Elem()).Interface().(T)
41+
}
42+
43+
// Parse URL parameters
44+
for _, key := range pathParams {
45+
value := chi.URLParam(r, key)
46+
if value == "" {
47+
SendResponse[any](w, "Parameter "+key+" not found in request", http.StatusBadRequest, nil)
48+
return empty, errors.New("missing parameter: " + key)
49+
}
50+
51+
if err := request.SetParam(key, value); err != nil {
52+
SendResponse[any](w, "Failed to set field "+key, http.StatusInternalServerError, nil)
53+
return empty, err
54+
}
55+
}
56+
57+
// Validate the combined request struct
58+
if validationErr := IsRequestValid(request); validationErr != nil {
59+
SendResponse[ValidationErrors](w, "Validation error", http.StatusBadRequest, validationErr)
60+
return empty, errors.New("validation error")
61+
}
62+
63+
return request, nil
64+
}
65+
66+
func isRequestNil(i interface{}) bool {
67+
return i == nil || (reflect.ValueOf(i).Kind() == reflect.Ptr && reflect.ValueOf(i).IsNil())
68+
}

0 commit comments

Comments
 (0)