Skip to content

Commit 5a8771d

Browse files
author
Ravi Atluri
committed
Add Extracter, Validator, StatusSetter, and RawWriter interfaces to xapi package
- Introduced interfaces for extracting request data, validating requests, setting custom status codes, and writing raw responses. - Updated Endpoint handler to utilize these new interfaces for improved request handling and response customization. - Updatedexample tests to demonstrate usage of the new features, including custom response writing and request validation.
1 parent 40c4922 commit 5a8771d

File tree

2 files changed

+130
-15
lines changed

2 files changed

+130
-15
lines changed

xapi/endpoint.go

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,28 @@ func (e EndpointFunc[TReq, TRes]) Handle(ctx context.Context, req *TReq) (*TRes,
1919
return e(ctx, req)
2020
}
2121

22+
// Extracter allows extracting additional data - headers, query params, etc.
23+
// from the HTTP request.
24+
type Extracter interface {
25+
Extract(r *http.Request) error
26+
}
27+
28+
// Validator allows validating endpoint requests.
29+
type Validator interface {
30+
Validate() error
31+
}
32+
33+
// StatusSetter allows setting a custom HTTP status code of the response.
34+
type StatusSetter interface {
35+
StatusCode() int
36+
}
37+
38+
// RawWriter allows writing raw data to the HTTP response instead of
39+
// the default JSON encoder.
40+
type RawWriter interface {
41+
Write(w http.ResponseWriter) error
42+
}
43+
2244
// Endpoint represents a type-safe HTTP endpoint with middleware and error handling.
2345
type Endpoint[TReq, TRes any] struct {
2446
handler EndpointHandler[TReq, TRes]
@@ -46,9 +68,21 @@ func NewEndpoint[TReq, TRes any](handler EndpointHandler[TReq, TRes], opts ...En
4668
func (e *Endpoint[TReq, TRes]) Handler() http.Handler {
4769
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
4870
var req TReq
49-
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
50-
e.opts.errorHandler.HandleError(w, err)
51-
return
71+
72+
if r.Body != nil {
73+
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
74+
e.opts.errorHandler.HandleError(w, err)
75+
return
76+
}
77+
78+
defer r.Body.Close()
79+
}
80+
81+
if validator, ok := any(&req).(Validator); ok {
82+
if err := validator.Validate(); err != nil {
83+
e.opts.errorHandler.HandleError(w, err)
84+
return
85+
}
5286
}
5387

5488
res, err := e.handler.Handle(r.Context(), &req)
@@ -57,6 +91,24 @@ func (e *Endpoint[TReq, TRes]) Handler() http.Handler {
5791
return
5892
}
5993

94+
if rawWriter, ok := any(res).(RawWriter); ok {
95+
if err := rawWriter.Write(w); err != nil {
96+
e.opts.errorHandler.HandleError(w, err)
97+
return
98+
}
99+
100+
return
101+
}
102+
103+
statusCode := http.StatusOK
104+
105+
if statusSetter, ok := any(res).(StatusSetter); ok {
106+
statusCode = statusSetter.StatusCode()
107+
}
108+
109+
w.Header().Set("Content-Type", "application/json; charset=utf-8")
110+
w.WriteHeader(statusCode)
111+
60112
if err := json.NewEncoder(w).Encode(res); err != nil {
61113
e.opts.errorHandler.HandleError(w, err)
62114
return

xapi/example_test.go

Lines changed: 75 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,47 @@ import (
1010
"github.com/gojekfarm/xtools/xapi"
1111
)
1212

13-
func ExampleEndpoint_basic() {
14-
type CreateUserRequest struct {
15-
Name string `json:"name"`
16-
Email string `json:"email"`
17-
}
13+
type CreateUserRequest struct {
14+
Name string `json:"name"`
15+
Email string `json:"email"`
16+
Language string `json:"-"`
17+
}
1818

19-
type CreateUserResponse struct {
20-
ID int `json:"id"`
21-
Name string `json:"name"`
22-
Email string `json:"email"`
19+
func (user *CreateUserRequest) Validate() error {
20+
if user.Name == "" {
21+
return fmt.Errorf("name is required")
2322
}
23+
if user.Email == "" {
24+
return fmt.Errorf("email is required")
25+
}
26+
return nil
27+
}
2428

29+
func (user *CreateUserRequest) Extract(r *http.Request) error {
30+
user.Language = r.Header.Get("Language")
31+
return nil
32+
}
33+
34+
type CreateUserResponse struct {
35+
ID int `json:"id"`
36+
Name string `json:"name"`
37+
Email string `json:"email"`
38+
Language string `json:"language"`
39+
}
40+
41+
func (user *CreateUserResponse) StatusCode() int {
42+
return http.StatusCreated
43+
}
44+
45+
func ExampleEndpoint_basic() {
2546
createUser := xapi.EndpointFunc[CreateUserRequest, CreateUserResponse](
2647
func(ctx context.Context, req *CreateUserRequest) (*CreateUserResponse, error) {
2748
// Simulate user creation logic
2849
return &CreateUserResponse{
29-
ID: 1,
30-
Name: req.Name,
31-
Email: req.Email,
50+
ID: 1,
51+
Name: req.Name,
52+
Email: req.Email,
53+
Language: req.Language,
3254
}, nil
3355
},
3456
)
@@ -148,3 +170,44 @@ func ExampleEndpoint_withMiddleware() {
148170

149171
http.Handle("/data", endpoint.Handler())
150172
}
173+
174+
type GetArticleRequest struct {
175+
ID string `json:"-"`
176+
}
177+
178+
func (article *GetArticleRequest) Extract(r *http.Request) error {
179+
article.ID = r.PathValue("id")
180+
181+
return nil
182+
}
183+
184+
type GetArticleResponse struct {
185+
ID string `json:"id"`
186+
Title string `json:"title"`
187+
}
188+
189+
func (article *GetArticleResponse) Write(w http.ResponseWriter) error {
190+
w.Header().Set("Content-Type", "application/html")
191+
w.WriteHeader(http.StatusOK)
192+
193+
_, _ = fmt.Fprintf(w, "<html><body><h1>%s</h1></body></html>", article.Title)
194+
195+
return nil
196+
}
197+
198+
func ExampleEndpoint_withCustomResponseWriter() {
199+
getArticle := xapi.EndpointFunc[GetArticleRequest, GetArticleResponse](
200+
func(ctx context.Context, req *GetArticleRequest) (*GetArticleResponse, error) {
201+
return &GetArticleResponse{
202+
ID: req.ID,
203+
Title: "Article " + req.ID,
204+
}, nil
205+
},
206+
)
207+
208+
endpoint := xapi.NewEndpoint(getArticle)
209+
210+
http.Handle("/articles/{id}", endpoint.Handler())
211+
log.Println("Server starting on :8080")
212+
log.Fatal(http.ListenAndServe(":8080", nil))
213+
}

0 commit comments

Comments
 (0)