Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 39 additions & 29 deletions internal/pagination/pagination.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package pagination
import (
"errors"
"fmt"
"log/slog"
"math"
"regexp"
"sort"
Expand All @@ -15,6 +14,26 @@ import (
"github.com/open-edge-platform/cluster-manager/v2/pkg/api"
)

var (
// validOrderByFields is a map of valid order by fields
validOrderByFields = map[string]bool{
"name": true,
"kubernetesVersion": true,
"providerStatus": true,
"lifecyclePhase": true,
"version": true,
}

// validFilterFields is a map of valid filter fields
validFilterFields = map[string]bool{
"name": true,
"kubernetesVersion": true,
"providerStatus": true,
"lifecyclePhase": true,
"version": true,
}
)

type Filter struct {
Name string
Value string
Expand All @@ -35,6 +54,7 @@ func parseFilter(filterParameter string) ([]*Filter, bool, error) {
if filterParameter == "" {
return nil, false, nil
}

// Replace the matched pattern in regexp 'normalizeEqualsRe' with just '=' (basically the spaces and tabs are removed)
normalizedFilterParameter := normalizeEqualsRe.ReplaceAllString(filterParameter, "=")

Expand All @@ -55,21 +75,21 @@ func parseFilter(filterParameter string) ([]*Filter, bool, error) {
selectors := strings.Split(element, "=")
if currentFilter != nil || len(selectors) != 2 || selectors[0] == "" || selectors[1] == "" {
// Error condition - too many equals
return nil, false, fmt.Errorf("filter: invalid filter request: %s", elements)
return nil, false, fmt.Errorf("filter: invalid filter request (=): %s", elements)
}
currentFilter = &Filter{
Name: selectors[0],
Value: selectors[1],
}
case element == "OR":
if currentFilter == nil || index == len(elements)-1 {
return nil, false, fmt.Errorf("filter: invalid filter request: %s", elements)
return nil, false, fmt.Errorf("filter: invalid filter request (OR): %s", elements)
}
filters = append(filters, currentFilter)
currentFilter = nil
case element == "AND":
if currentFilter == nil || index == len(elements)-1 {
return nil, false, fmt.Errorf("filter: invalid filter request: %s", elements)
return nil, false, fmt.Errorf("filter: invalid filter request (AND): %s", elements)
}
filters = append(filters, currentFilter)
currentFilter = nil
Expand Down Expand Up @@ -98,6 +118,7 @@ func parseOrderBy(orderByParameter string) ([]*OrderBy, error) {
if orderByParameter == "" {
return nil, nil
}

// orderBy commands should be separated by ',' if there are more than one.
// Split them by ',' delimiter.
elements := strings.Split(orderByParameter, ",")
Expand All @@ -110,14 +131,15 @@ func parseOrderBy(orderByParameter string) ([]*OrderBy, error) {
if len(direction) == 0 || len(direction) > 2 {
return nil, errors.New("invalid order by: " + element)
}

if len(direction) == 2 {
switch direction[1] {
case "asc":
descending = false
case "desc":
descending = true
default:
return nil, errors.New("invalid order by: " + element)
return nil, errors.New("invalid order by direction: " + element)
}
}
orderBys = append(orderBys, &OrderBy{
Expand Down Expand Up @@ -149,8 +171,9 @@ func computePageRange(pageSize int32, offset int32, totalCount int) (int, int) {
func PaginateItems[T any](items []T, pageSize, offset int) (*[]T, error) {
paginatedItems, err := applyPagination(items, pageSize, offset)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to apply pagination: %w", err)
}

return &paginatedItems, nil
}

Expand All @@ -159,13 +182,14 @@ func applyPagination[T any](items []T, pageSize, offset int) ([]T, error) {
if end == -1 {
return nil, fmt.Errorf("no items to paginate")
}

return items[start:end], nil
}

func FilterItems[T any](items []T, filter string, filterFunc func(T, *Filter) bool) ([]T, error) {
filters, useAnd, err := parseFilter(filter)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to parse filter: %w", err)
}

var filteredItems []T
Expand Down Expand Up @@ -199,12 +223,10 @@ func FilterItems[T any](items []T, filter string, filterFunc func(T, *Filter) bo
func OrderItems[T any](items []T, orderBy string, orderFunc func(T, T, *OrderBy) bool) ([]T, error) {
orderBys, err := parseOrderBy(orderBy)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to parse order by: %w", err)
}

orderedItems := applyOrderBy(items, orderBys, orderFunc)
slog.Debug("applied order by", "orderBy", orderBy, "orderedItems", orderedItems)
return orderedItems, nil
return applyOrderBy(items, orderBys, orderFunc), nil
}

func applyOrderBy[T any](items []T, orderBys []*OrderBy, orderFunc orderFunc[T]) []T {
Expand All @@ -216,6 +238,7 @@ func applyOrderBy[T any](items []T, orderBys []*OrderBy, orderFunc orderFunc[T])
}
return false
})

return items
}

Expand All @@ -226,7 +249,7 @@ func extractParamsFields(params any) (pageSize, offset *int, orderBy, filter *st
case api.GetV2TemplatesParams:
return p.PageSize, p.Offset, p.OrderBy, p.Filter, nil
default:
return nil, nil, nil, nil, fmt.Errorf("unsupported params type")
return nil, nil, nil, nil, fmt.Errorf("unsupported params type: %v (%v)", p, params)
}
}

Expand All @@ -236,46 +259,33 @@ func ValidateParams(params any) (pageSize, offset *int, orderBy, filter *string,
if err != nil {
return nil, nil, nil, nil, err
}

if pageSize == nil || *pageSize <= 0 {
return nil, nil, nil, nil, fmt.Errorf("invalid pageSize: must be greater than 0")
}

if offset == nil || *offset < 0 {
return nil, nil, nil, nil, fmt.Errorf("invalid offset: must be non-negative")
}

if orderBy != nil {
validOrderByFields := map[string]bool{
"name": true,
"kubernetesVersion": true,
"providerStatus": true,
"lifecyclePhase": true,
"version": true,
if *orderBy == "" {
return nil, nil, nil, nil, fmt.Errorf("invalid orderBy field")
}

orderByParts := strings.Split(*orderBy, " ")
if len(orderByParts) == 1 {
orderBy = convert.Ptr(orderByParts[0] + " asc")
} else if len(orderByParts) == 2 {
if !validOrderByFields[orderByParts[0]] || (orderByParts[1] != "asc" && orderByParts[1] != "desc") {
return nil, nil, nil, nil, fmt.Errorf("invalid orderBy field")
}
} else if *orderBy == "" {
return nil, nil, nil, nil, fmt.Errorf("invalid orderBy field")
}
}

if filter != nil {
if *filter == "" {
return nil, nil, nil, nil, fmt.Errorf("invalid filter: cannot be empty")
}
validFilterFields := map[string]bool{
"name": true,
"kubernetesVersion": true,
"providerStatus": true,
"lifecyclePhase": true,
"version": true,
}

filterParts := strings.FieldsFunc(*filter, func(r rune) bool {
return r == ' ' || r == 'O' || r == 'R' || r == 'A' || r == 'N' || r == 'D'
})
Expand Down
Loading