diff --git a/internal/pagination/pagination.go b/internal/pagination/pagination.go index 4f9faa57..d4e5f174 100644 --- a/internal/pagination/pagination.go +++ b/internal/pagination/pagination.go @@ -5,7 +5,6 @@ package pagination import ( "errors" "fmt" - "log/slog" "math" "regexp" "sort" @@ -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 @@ -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, "=") @@ -55,7 +75,7 @@ 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], @@ -63,13 +83,13 @@ func parseFilter(filterParameter string) ([]*Filter, bool, error) { } 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 @@ -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, ",") @@ -110,6 +131,7 @@ 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": @@ -117,7 +139,7 @@ func parseOrderBy(orderByParameter string) ([]*OrderBy, error) { 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{ @@ -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 } @@ -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 @@ -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 { @@ -216,6 +238,7 @@ func applyOrderBy[T any](items []T, orderBys []*OrderBy, orderFunc orderFunc[T]) } return false }) + return items } @@ -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) } } @@ -236,23 +259,18 @@ 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") @@ -260,8 +278,6 @@ func ValidateParams(params any) (pageSize, offset *int, orderBy, filter *string, 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") } } @@ -269,13 +285,7 @@ func ValidateParams(params any) (pageSize, offset *int, orderBy, filter *string, 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' })