Skip to content
Merged
Changes from 3 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
103 changes: 54 additions & 49 deletions internal/pagination/pagination.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,24 @@ 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,
}

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

type Filter struct {
Name string
Value string
Expand All @@ -35,6 +53,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 +74,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 +117,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 +130,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,6 +170,7 @@ 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 {
slog.Error("failed to apply pagination", "pageSize", pageSize, "offset", offset, "error", err)
return nil, err
}
return &paginatedItems, nil
Expand All @@ -165,6 +187,7 @@ func applyPagination[T any](items []T, pageSize, offset int) ([]T, error) {
func FilterItems[T any](items []T, filter string, filterFunc func(T, *Filter) bool) ([]T, error) {
filters, useAnd, err := parseFilter(filter)
if err != nil {
slog.Error("failed to parse filter", "filter", filter, "error", err)
return nil, err
}

Expand Down Expand Up @@ -193,17 +216,20 @@ func FilterItems[T any](items []T, filter string, filterFunc func(T, *Filter) bo
}
}

slog.Debug("applied filter", "filter", filter, "items", filteredItems)

return filteredItems, nil
}

func OrderItems[T any](items []T, orderBy string, orderFunc func(T, T, *OrderBy) bool) ([]T, error) {
orderBys, err := parseOrderBy(orderBy)
if err != nil {
slog.Error("failed to parse order by", "orderBy", orderBy, "error", err)
return nil, err
}

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

Expand All @@ -226,63 +252,42 @@ 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)
}
}

// ValidateParams validates the incoming parameters for pagination
func ValidateParams(params any) (pageSize, offset *int, orderBy, filter *string, err error) {
pageSize, offset, orderBy, filter, err = extractParamsFields(params)
if err != nil {
return nil, nil, nil, nil, err
}

if pageSize == nil || *pageSize <= 0 {
switch {
case err != nil:
return nil, nil, nil, nil, fmt.Errorf("failed to extract params fields: %w", err)
case pageSize == nil, *pageSize <= 0:
return nil, nil, nil, nil, fmt.Errorf("invalid pageSize: must be greater than 0")
}

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

if orderBy != nil {
validOrderByFields := map[string]bool{
"name": true,
"kubernetesVersion": true,
"providerStatus": true,
"lifecyclePhase": true,
}
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")
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: %s", *orderBy)
}
}

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'
})
for _, part := range filterParts {
subParts := strings.Split(part, "=")
if len(subParts) != 2 || !validFilterFields[subParts[0]] {
return nil, nil, nil, nil, fmt.Errorf("invalid filter field")
}
filterParts := strings.FieldsFunc(*filter, func(r rune) bool {
return r == ' ' || r == 'O' || r == 'R' || r == 'A' || r == 'N' || r == 'D'
})
for _, part := range filterParts {
subParts := strings.Split(part, "=")
if len(subParts) != 2 || !validFilterFields[subParts[0]] {
return nil, nil, nil, nil, fmt.Errorf("invalid filter field: %s", *filter)
}
}

Expand Down
Loading