Skip to content

Commit

Permalink
Merge pull request #1 from enverbisevac/eb/pagination
Browse files Browse the repository at this point in the history
[FEAT] add pagination support
  • Loading branch information
enverbisevac authored Oct 20, 2022
2 parents 21a597e + 5e34072 commit 954b46a
Show file tree
Hide file tree
Showing 7 changed files with 407 additions and 39 deletions.
17 changes: 17 additions & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,23 @@ linters-settings:
- name: exported
arguments:
- disableStutteringCheck
tagliatelle:
# Check the struck tag name case.
case:
# Use the struct field name to check the name of the struct tag.
# Default: false
use-field-name: true
# `camel` is used for `json` and `yaml` (can be overridden)
# Default: {}
rules:
# Any struct tag type can be used.
# Support string case: `camel`, `pascal`, `kebab`, `snake`, `goCamel`, `goPascal`, `goKebab`, `goSnake`, `upper`, `lower`
json: snake
yaml: snake
xml: snake
bson: snake
avro: snake
mapstructure: snake

linters:
enable-all: true
Expand Down
70 changes: 64 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,24 @@ func createUser(w http.ResponseWriter, r *http.Request) {
render.Render(w, r, user)
}

func dumbLoader(limit, offset int, total *int) []User {
*total = 100
return []User{
{
"Enver",
},
{
"Joe",
},
}
}

func listUsers(w http.ResponseWriter, r *http.Request) {
pagination := render.PaginationFromRequest(r)
data := dumbLoader(pagination.Size(), pagination.Page(), &pagination.Total)
pagination.Render(w, r, data)
}

func errorHandler(w http.ResponseWriter, r *http.Request) {
render.Error(w, r, render.ErrNotFound)
}
Expand All @@ -94,7 +112,7 @@ func main() {

## API Reference

#### Bind request body to data type `v`
### Bind request body to data type `v`

```go
func Bind(r *http.Request, v interface{}) error
Expand All @@ -107,7 +125,7 @@ func main() {
error will be returned if binding fails
#### Render responses based on request `r` headers
### Render responses based on request `r` headers
```go
func Render(w http.ResponseWriter, r *http.Request, v interface{}, params ...interface{})
Expand All @@ -120,7 +138,7 @@ error will be returned if binding fails
| `v` | `interface{}` | **Required**. Pointer to variable. |
| `params` | `...interface{}` | Variadic number of params. (int/string/http.Header) |
#### Render error response and status code based on request `r` headers
### Render error response and status code based on request `r` headers
```go
func Error(w http.ResponseWriter, r *http.Request, err error, params ...interface{})
Expand All @@ -133,7 +151,7 @@ error will be returned if binding fails
| `err` | `error`. | **Required**. Error value. |
| `params` | `...interface{}` | Variadic number of params. (int/string/http.Header) |
#### Params variadic function parameter
### Params variadic function parameter
`params` can be found in almost any function. Param type can be string, http.Header or integer.
Integer value represent status code. String or http.header are just for response headers.
Expand All @@ -150,7 +168,7 @@ render.Render(w, v, http.Header{
}, http.StatusOK)
```

#### Integrate 3rd party JSON/XML lib
### Integrate 3rd party JSON/XML lib

in this example we will replace standard encoder with goccy/go-json.

Expand All @@ -175,7 +193,47 @@ func init() {
}
```

#### Other functions
### Pagination

pagination API function `PaginationFromRequest` accepts single parameter of type \*http.Request
and returns `Pagination` object.

```go
pagination := render.PaginationFromRequest(r)
```

pagination struct contains several read only fields:

```go
type Pagination struct {
page int
size int
prev int
next int
last int
Total int
}
```

only field you can modify is Total field. Query values are mapped to pagination object:

```
http://localhost:3000/users?page=1&per_page=10
```

then you can process your input pagination data:

```go
data := loadUsers(pagination.Size(), pagination.Page(), &pagination.Total)
```

when we have data then we can render output:

```go
pagination.Render(w, r, data)
```

### Other API functions

```go
func Blob(w http.ResponseWriter, v []byte, params ...interface{})
Expand Down
46 changes: 18 additions & 28 deletions error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"errors"
"fmt"
"net/http"
"net/url"
"testing"

"github.com/enverbisevac/render"
Expand All @@ -36,6 +37,15 @@ func TestError(t *testing.T) {
header http.Header
)

req := func() *http.Request {
return &http.Request{
URL: &url.URL{},
Header: http.Header{
render.AcceptHeader: []string{render.ApplicationJSON},
},
}
}

jsonEncoder := func(msg string) []byte {
resErr := render.ErrorResponse{
Message: msg,
Expand Down Expand Up @@ -78,12 +88,8 @@ func TestError(t *testing.T) {
{
name: "default error 500 - Internal Server Error",
args: args{
w: writer,
r: &http.Request{
Header: http.Header{
render.AcceptHeader: []string{render.ApplicationJSON},
},
},
w: writer,
r: req(),
err: errors.New("some error"),
},
body: jsonEncoder("some error"),
Expand All @@ -92,12 +98,8 @@ func TestError(t *testing.T) {
{
name: "default mapped error 404 - file not found",
args: args{
w: writer,
r: &http.Request{
Header: http.Header{
render.AcceptHeader: []string{render.ApplicationJSON},
},
},
w: writer,
r: req(),
err: fmt.Errorf("file %s %w", "demo.txt", render.ErrNotFound),
},
body: jsonEncoder(fmt.Sprintf("file %s %v", "demo.txt", render.ErrNotFound)),
Expand All @@ -106,12 +108,8 @@ func TestError(t *testing.T) {
{
name: "set optional param to BadRequest status",
args: args{
w: writer,
r: &http.Request{
Header: http.Header{
render.AcceptHeader: []string{render.ApplicationJSON},
},
},
w: writer,
r: req(),
err: errors.New("bad input data"),
params: []interface{}{http.StatusBadRequest},
},
Expand All @@ -122,11 +120,7 @@ func TestError(t *testing.T) {
name: "provide http error",
args: args{
w: writer,
r: &http.Request{
Header: http.Header{
render.AcceptHeader: []string{render.ApplicationJSON},
},
},
r: req(),
err: &render.HTTPError{
Err: errors.New("conflict data"),
Status: http.StatusConflict,
Expand All @@ -139,11 +133,7 @@ func TestError(t *testing.T) {
name: "provide map error, http error and param status should return param status",
args: args{
w: writer,
r: &http.Request{
Header: http.Header{
render.AcceptHeader: []string{render.ApplicationJSON},
},
},
r: req(),
err: &render.HTTPError{
Err: render.ErrForbidden,
Status: http.StatusBadGateway,
Expand Down
23 changes: 23 additions & 0 deletions examples/getting_started/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ type User struct {
Name string `json:"name"`
}

func init() {
render.PaginationInHeader = false
}

func helloHandler(w http.ResponseWriter, r *http.Request) {
paths := strings.Split(r.URL.Path, "/")
name := paths[len(paths)-1]
Expand All @@ -49,6 +53,24 @@ func createUser(w http.ResponseWriter, r *http.Request) {
render.Render(w, r, user)
}

func dumbLoader(limit, offset int, total *int) []User {
*total = 100
return []User{
{
"Enver",
},
{
"Joe",
},
}
}

func listUsers(w http.ResponseWriter, r *http.Request) {
pagination := render.PaginationFromRequest(r)
data := dumbLoader(pagination.Size(), pagination.Page(), &pagination.Total)
pagination.Render(w, r, data)
}

func errorHandler(w http.ResponseWriter, r *http.Request) {
render.Error(w, r, render.ErrNotFound)
}
Expand Down Expand Up @@ -78,6 +100,7 @@ func errorHandler4(w http.ResponseWriter, r *http.Request) {
func main() {
http.HandleFunc("/hello/", helloHandler)
http.HandleFunc("/create", createUser)
http.HandleFunc("/users", listUsers)
http.HandleFunc("/error", errorHandler)
http.HandleFunc("/error1", errorHandler1)
http.HandleFunc("/error2", errorHandler2)
Expand Down
Loading

0 comments on commit 954b46a

Please sign in to comment.