mirror of
https://github.com/kataras/iris.git
synced 2026-01-10 05:25:58 +00:00
new minor features
This commit is contained in:
246
x/pagination/pagination.go
Normal file
246
x/pagination/pagination.go
Normal file
@@ -0,0 +1,246 @@
|
||||
//go:build go1.18
|
||||
|
||||
/*
|
||||
Until go version 2, we can't really apply the type alias feature on a generic type or function,
|
||||
so keep it separated on x/pagination.
|
||||
|
||||
import "github.com/kataras/iris/v12/context"
|
||||
|
||||
type ListResponse[T any] = context.ListResponse[T]
|
||||
OR
|
||||
type ListResponse = context.ListResponse doesn't work.
|
||||
|
||||
The only workable thing for generic aliases is when you know the type e.g.
|
||||
type ListResponse = context.ListResponse[any] but that doesn't fit us.
|
||||
*/
|
||||
|
||||
package iris
|
||||
|
||||
import (
|
||||
"math"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
// MaxSize defines the max size of items to display.
|
||||
MaxSize = 100000
|
||||
// DefaultSize defines the default size when ListOptions.Size is zero.
|
||||
DefaultSize = MaxSize
|
||||
)
|
||||
|
||||
// ListOptions is the list request object which should be provided by the client through
|
||||
// URL Query. Then the server passes that options to a database query,
|
||||
// including any custom filters may be given from the request body and,
|
||||
// then the server responds back with a `Context.JSON(NewList(...))` response based
|
||||
// on the database query's results.
|
||||
type ListOptions struct {
|
||||
// Current page number.
|
||||
// If Page > 0 then:
|
||||
// Limit = DefaultLimit
|
||||
// Offset = DefaultLimit * Page
|
||||
// If Page == 0 then no actual data is return,
|
||||
// internally we must check for this value
|
||||
// because in postgres LIMIT 0 returns the columns but with an empty set.
|
||||
Page int `json:"page" url:"page"`
|
||||
// The elements to get, this modifies the LIMIT clause,
|
||||
// this Size can't be higher than the MaxSize.
|
||||
// If Size is zero then size is set to DefaultSize.
|
||||
Size int `json:"size" url:"size"`
|
||||
}
|
||||
|
||||
// GetLimit returns the LIMIT value of a query.
|
||||
func (opts ListOptions) GetLimit() int {
|
||||
if opts.Size > 0 && opts.Size < MaxSize {
|
||||
return opts.Size
|
||||
}
|
||||
|
||||
return DefaultSize
|
||||
}
|
||||
|
||||
// GetLimit returns the OFFSET value of a query.
|
||||
func (opts ListOptions) GetOffset() int {
|
||||
if opts.Page > 1 {
|
||||
return (opts.Page - 1) * opts.GetLimit()
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// GetCurrentPage returns the Page or 1.
|
||||
func (opts ListOptions) GetCurrentPage() int {
|
||||
current := opts.Page
|
||||
if current == 0 {
|
||||
current = 1
|
||||
}
|
||||
|
||||
return current
|
||||
}
|
||||
|
||||
// GetNextPage returns the next page, current page + 1.
|
||||
func (opts ListOptions) GetNextPage() int {
|
||||
return opts.GetCurrentPage() + 1
|
||||
}
|
||||
|
||||
// Bind binds the ListOptions values to a request value.
|
||||
// It should be used as an x/client.RequestOption to fire requests
|
||||
// on a server that supports pagination.
|
||||
func (opts ListOptions) Bind(r *http.Request) error {
|
||||
page := strconv.Itoa(opts.GetCurrentPage())
|
||||
size := strconv.Itoa(opts.GetLimit())
|
||||
|
||||
q := r.URL.Query()
|
||||
q.Set("page", page)
|
||||
q.Set("size", size)
|
||||
return nil
|
||||
}
|
||||
|
||||
// List is the http response of a server handler which should render
|
||||
// items with pagination support.
|
||||
type List[T any] struct {
|
||||
CurrentPage int `json:"current_page"` // the current page.
|
||||
PageSize int `json:"page_size"` // the total amount of the entities return.
|
||||
TotalPages int `json:"total_pages"` // the total number of pages based on page, size and total count.
|
||||
TotalItems int64 `json:"total_items"` // the total number of rows.
|
||||
HasNextPage bool `json:"has_next_page"` // true if more data can be fetched, depending on the current page * page size and total pages.
|
||||
Filter any `json:"filter"` // if any filter data.
|
||||
Items []T `json:"items"` // Items is empty array if no objects returned. Do NOT modify from outside.
|
||||
}
|
||||
|
||||
// NewList returns a new List response which holds
|
||||
// the current page, page size, total pages, total items count, any custom filter
|
||||
// and the items array.
|
||||
//
|
||||
// Example Code:
|
||||
//
|
||||
// import "github.com/kataras/iris/v12/x/pagination"
|
||||
// ...more code
|
||||
//
|
||||
// type User struct {
|
||||
// Firstname string `json:"firstname"`
|
||||
// Lastname string `json:"lastname"`
|
||||
// }
|
||||
//
|
||||
// type ExtraUser struct {
|
||||
// User
|
||||
// ExtraData string
|
||||
// }
|
||||
//
|
||||
// func main() {
|
||||
// users := []User{
|
||||
// {"Gerasimos", "Maropoulos"},
|
||||
// {"Efi", "Kwfidou"},
|
||||
// }
|
||||
//
|
||||
// t := pagination.NewList(users, 100, nil, pagination.ListOptions{
|
||||
// Page: 1,
|
||||
// Size: 50,
|
||||
// })
|
||||
//
|
||||
// // Optionally, transform a T list of objects to a V list of objects.
|
||||
// v, err := pagination.TransformList(t, func(u User) (ExtraUser, error) {
|
||||
// return ExtraUser{
|
||||
// User: u,
|
||||
// ExtraData: "test extra data",
|
||||
// }, nil
|
||||
// })
|
||||
// if err != nil { panic(err) }
|
||||
//
|
||||
// paginationJSON, err := json.MarshalIndent(v, "", " ")
|
||||
// if err!=nil { panic(err) }
|
||||
// fmt.Println(paginationJSON)
|
||||
// }
|
||||
func NewList[T any](items []T, totalCount int64, filter any, opts ListOptions) *List[T] {
|
||||
pageSize := opts.GetLimit()
|
||||
|
||||
n := len(items)
|
||||
if n == 0 || pageSize <= 0 {
|
||||
return &List[T]{
|
||||
CurrentPage: 1,
|
||||
PageSize: 0,
|
||||
TotalItems: 0,
|
||||
TotalPages: 0,
|
||||
Filter: filter,
|
||||
Items: make([]T, 0),
|
||||
}
|
||||
}
|
||||
|
||||
numberOfPages := int(roundUp(float64(totalCount)/float64(pageSize), 0))
|
||||
if numberOfPages <= 0 {
|
||||
numberOfPages = 1
|
||||
}
|
||||
|
||||
var hasNextPage bool
|
||||
|
||||
currentPage := opts.GetCurrentPage()
|
||||
if totalCount == 0 {
|
||||
currentPage = 1
|
||||
}
|
||||
|
||||
if n > 0 {
|
||||
hasNextPage = currentPage < numberOfPages
|
||||
}
|
||||
|
||||
return &List[T]{
|
||||
CurrentPage: currentPage,
|
||||
PageSize: n,
|
||||
TotalPages: numberOfPages,
|
||||
TotalItems: totalCount,
|
||||
HasNextPage: hasNextPage,
|
||||
Filter: filter,
|
||||
Items: items,
|
||||
}
|
||||
}
|
||||
|
||||
// TransformList accepts a List response and converts to a list of V items.
|
||||
// T => from
|
||||
// V => to
|
||||
//
|
||||
// Example Code:
|
||||
//
|
||||
// listOfUsers := pagination.NewList(...)
|
||||
// newListOfExtraUsers, err := pagination.TransformList(listOfUsers, func(u User) (ExtraUser, error) {
|
||||
// return ExtraUser{
|
||||
// User: u,
|
||||
// ExtraData: "test extra data",
|
||||
// }, nil
|
||||
// })
|
||||
func TransformList[T any, V any](list *List[T], transform func(T) (V, error)) (*List[V], error) {
|
||||
if list == nil {
|
||||
return &List[V]{
|
||||
CurrentPage: 1,
|
||||
PageSize: 0,
|
||||
TotalItems: 0,
|
||||
TotalPages: 0,
|
||||
Filter: nil,
|
||||
Items: make([]V, 0),
|
||||
}, nil
|
||||
}
|
||||
|
||||
items := list.Items
|
||||
|
||||
toItems := make([]V, 0, len(items))
|
||||
for _, fromItem := range items {
|
||||
toItem, err := transform(fromItem)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
toItems = append(toItems, toItem)
|
||||
}
|
||||
|
||||
newList := &List[V]{
|
||||
CurrentPage: list.CurrentPage,
|
||||
PageSize: list.PageSize,
|
||||
TotalItems: list.TotalItems,
|
||||
TotalPages: list.TotalPages,
|
||||
Filter: list.Filter,
|
||||
Items: toItems,
|
||||
}
|
||||
return newList, nil
|
||||
}
|
||||
|
||||
func roundUp(input float64, places float64) float64 {
|
||||
pow := math.Pow(10, places)
|
||||
return math.Ceil(pow*input) / pow
|
||||
}
|
||||
Reference in New Issue
Block a user