Skip to content

Commit

Permalink
feat: adding new Context methods
Browse files Browse the repository at this point in the history
  • Loading branch information
i9si authored Jan 28, 2025
2 parents fbc8460 + 0c670fd commit dba1a7f
Show file tree
Hide file tree
Showing 3 changed files with 273 additions and 14 deletions.
27 changes: 18 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,14 @@ import (
func main() {
server := nine.NewServer(8080)

server.Get("/hello", func(req *nine.Request, res *nine.Response) error {
return res.Send([]byte("Hello World"))
server.Get("/hello", func(c *nine.Context) error {
return c.Send([]byte("Hello World"))
})

server.Get("/hello/{name}", func(req *nine.Request, res *nine.Response) error {
name := req.Param("name")
server.Get("/hello/:name", func(c *nine.Context) error {
name := c.Param("name")
message := fmt.Sprintf("Hello %s", name)
return res.Send([]byte(message))
return c.Send([]byte(message))
})

log.Fatal(server.Listen())
Expand All @@ -88,8 +88,17 @@ func main() {
You can register routes for different HTTP methods using the Get, Post, Put, Patch, and Delete methods.

```go
server.Post("/create", func(req *nine.Request, res *nine.Response) error {
return res.Status(http.StatusCreated).Send([]byte("Recurso criado com sucesso"))
server.Post("/create", func(c *nine.Context) error {
return c.Status(http.StatusCreated).Send([]byte("Recurso criado com sucesso"))
})
server.Route("/billing", func(router *nine.RouteGroup) error {
router.Get("/credits", func (c *nine.Context) error {
return c.JSON(nine.JSON{"credits": 5000})
})
})
accountGroup := server.Group("/account", authMiddleware)
accountGroup.Get("/:name", func (c *nine.Context) error {
return c.JSON(nine.JSON{"account": c.Param("name")})
})
```

Expand All @@ -98,9 +107,9 @@ server.Post("/create", func(req *nine.Request, res *nine.Response) error {
The library also provides utilities for working with JSON:

```go
server.Get("/user", func(req *nine.Request, res *nine.Response) error {
server.Get("/user", func(c *nine.Context) error {
data := nine.JSON{"name": "Alice", "age": 30}
res.JSON(data)
c.JSON(data)
})
```

Expand Down
130 changes: 126 additions & 4 deletions context.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package nine

import (
"bytes"
"context"
"encoding/json"
"mime/multipart"
"net/http"
)

Expand All @@ -13,12 +16,131 @@ type Context struct {

func NewContext(
ctx context.Context,
req *http.Request,
req *http.Request,
res http.ResponseWriter,
) Context {
return Context{
ctx: ctx,
Request: &Request{req},
ctx: ctx,
Request: &Request{req: req},
Response: &Response{res: res},
}
}
}

func (c *Context) BodyParser(v any) error {
return json.NewDecoder(c.Request.Body()).Decode(v)
}

func (c *Context) QueryParser(v any) error {
query := c.Request.HTTP().URL.Query()

simplifiedQuery := make(map[string]string)
for key, values := range query {
if len(values) > 0 {
simplifiedQuery[key] = values[0]
}
}

data, err := json.Marshal(simplifiedQuery)
if err != nil {
return err
}

return json.Unmarshal(data, v)
}

func (c *Context) ReqHeaderParser(v any) error {
headers := c.Request.HTTP().Header
return parseForm(headers, v)
}

func (c *Context) Header(key string) string {
return c.Request.Header(key)
}

func (c *Context) Method() string {
return c.Request.Method()
}

func (c *Context) IP() string {
if ip := c.Request.Header("X-Real-IP"); ip != "" {
return ip
}
if ip := c.Request.Header("X-Forwarded-For"); ip != "" {
return ip
}
return c.Request.HTTP().RemoteAddr
}

func (c *Context) IPs() []string {
ips := c.Request.Header("X-Forwarded-For")
if ips == "" {
return []string{c.IP()}
}
return splitComma(ips)
}

func (c *Context) Body() []byte {
body := c.Request.Body()
defer body.Reset()
return body.Bytes()
}

func (c *Context) Query(name string, defaultValue ...string) string {
value := c.Request.Query(name)
if value == "" && len(defaultValue) > 0 {
return defaultValue[0]
}
return value
}

func (c *Context) Params(name string, defaultValue ...string) string {
value := c.Request.Param(name)
if value == "" && len(defaultValue) > 0 {
return defaultValue[0]
}
return value
}

func (c *Context) FormFile(key string) (*multipart.FileHeader, error) {
_, header, err := c.Request.HTTP().FormFile(key)
if err != nil {
return nil, err
}
return header, nil
}

func (c *Context) SendStatus(status int) error {
return c.Response.SendStatus(status)
}

func (c *Context) Send(data []byte) error {
return c.Response.Send(data)
}

func (c *Context) JSON(data any) error {
b, err := json.Marshal(data)
if err != nil {
return err
}
var payload JSON
if err := DecodeJSON(b, &payload); err != nil {
return err
}
return c.Response.JSON(payload)
}

func parseForm(form any, v any) error {
data, err := json.Marshal(form)
if err != nil {
return err
}
return json.Unmarshal(data, v)
}

func splitComma(s string) []string {
var parts []string
for _, part := range bytes.Split([]byte(s), []byte(",")) {
parts = append(parts, string(bytes.TrimSpace(part)))
}
return parts
}
130 changes: 129 additions & 1 deletion context_test.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,150 @@
package nine

import (
"bytes"
"context"
"encoding/json"
"io"
"mime/multipart"
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/i9si-sistemas/assert"
)

func TestContext(t *testing.T) {
ctx := context.Background()
req := httptest.NewRequest(http.MethodGet, "/", nil)
req := httptest.NewRequest(http.MethodGet, "/?key=value", nil)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Real-IP", "192.168.1.1")
req.Header.Set("X-Forwarded-For", "203.0.113.1, 203.0.113.2")

res := httptest.NewRecorder()
c := NewContext(ctx, req, res)

assert.NotEmpty(t, c)
assert.NotNil(t, ctx)
assert.Equal(t, c.ctx, ctx)
assert.Equal(t, c.Request.req, req)
assert.Equal(t, c.Response.res, res)
}

func TestBodyParser(t *testing.T) {
body := `{"name":"test","age":30}`
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
res := httptest.NewRecorder()
c := NewContext(context.Background(), req, res)

var parsedBody map[string]interface{}
err := c.BodyParser(&parsedBody)

assert.Nil(t, err)
assert.Equal(t, parsedBody["name"], "test")
assert.Equal(t, parsedBody["age"], float64(30)) // json.Unmarshal converts numbers to float64 by default
}

func TestQueryParser(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/?key=value&another=42", nil)
res := httptest.NewRecorder()
c := NewContext(context.Background(), req, res)

var queryData map[string]string
err := c.QueryParser(&queryData)

assert.Nil(t, err)
assert.Equal(t, queryData["key"], "value")
assert.Equal(t, queryData["another"], "42")
}

func TestHeaderParsing(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/", nil)
req.Header.Set("Authorization", "Bearer token")
res := httptest.NewRecorder()
c := NewContext(context.Background(), req, res)

headerValue := c.Header("Authorization")
assert.Equal(t, headerValue, "Bearer token")
}

func TestIPRetrieval(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/", nil)
req.Header.Set("X-Real-IP", "192.168.1.1")
req.Header.Set("X-Forwarded-For", "203.0.113.1, 203.0.113.2")
res := httptest.NewRecorder()
c := NewContext(context.Background(), req, res)

assert.Equal(t, c.IP(), "192.168.1.1")
assert.Equal(t, c.IPs(), []string{"203.0.113.1", "203.0.113.2"})
}

func TestQueryWithDefault(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/", nil)
res := httptest.NewRecorder()
c := NewContext(context.Background(), req, res)

queryValue := c.Query("missing", "default")
assert.Equal(t, queryValue, "default")
}

func TestSendStatus(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/", nil)
res := httptest.NewRecorder()
c := NewContext(context.Background(), req, res)

err := c.SendStatus(http.StatusNotFound)
assert.Equal(t, err.Error(), "Not Found")
serverErr, ok := err.(*ServerError)
assert.True(t, ok)
assert.Equal(t, serverErr.StatusCode, http.StatusNotFound)
}

func TestSend(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/", nil)
res := httptest.NewRecorder()
c := NewContext(context.Background(), req, res)

data := []byte("Hello, World!")
err := c.Send(data)
assert.Nil(t, err)
assert.Equal(t, res.Body.String(), "Hello, World!")
}

func TestContextJSON(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/", nil)
res := httptest.NewRecorder()
c := NewContext(context.Background(), req, res)

responseData := map[string]string{"message": "success"}
err := c.JSON(responseData)
assert.Nil(t, err)
assert.Equal(t, res.Header().Get("Content-Type"), "application/json")

var jsonResponse map[string]string
err = json.Unmarshal(res.Body.Bytes(), &jsonResponse)
assert.Nil(t, err)
assert.Equal(t, jsonResponse["message"], "success")
}

func TestFormFile(t *testing.T) {
body := new(bytes.Buffer)
writer := multipart.NewWriter(body)
fileWriter, err := writer.CreateFormFile("file", "test.txt")
assert.Nil(t, err)

_, err = io.WriteString(fileWriter, "file content")
assert.Nil(t, err)

writer.Close()

req := httptest.NewRequest(http.MethodPost, "/upload", body)
req.Header.Set("Content-Type", writer.FormDataContentType())
res := httptest.NewRecorder()
c := NewContext(context.Background(), req, res)

header, err := c.FormFile("file")
assert.Nil(t, err)
assert.Equal(t, header.Filename, "test.txt")
}

0 comments on commit dba1a7f

Please sign in to comment.