Added content-range headers

This commit is contained in:
Hein 2025-11-10 12:25:09 +02:00
parent 3eb17666bf
commit faafe5abea
5 changed files with 42 additions and 20 deletions

View File

@ -0,0 +1,13 @@
package common
import "reflect"
func Len(v any) int {
val := reflect.ValueOf(v)
switch val.Kind() {
case reflect.Slice, reflect.Array, reflect.Map, reflect.String, reflect.Chan:
return val.Len()
default:
return 0
}
}

View File

@ -18,6 +18,11 @@ type RequestOptions struct {
CustomOperators []CustomOperator `json:"customOperators"` CustomOperators []CustomOperator `json:"customOperators"`
ComputedColumns []ComputedColumn `json:"computedColumns"` ComputedColumns []ComputedColumn `json:"computedColumns"`
Parameters []Parameter `json:"parameters"` Parameters []Parameter `json:"parameters"`
// Cursor pagination
CursorForward string `json:"cursor_forward"`
CursorBackward string `json:"cursor_backward"`
FetchRowNumber *string `json:"fetch_row_number"`
} }
type Parameter struct { type Parameter struct {
@ -68,6 +73,7 @@ type Response struct {
type Metadata struct { type Metadata struct {
Total int64 `json:"total"` Total int64 `json:"total"`
Count int64 `json:"count"`
Filtered int64 `json:"filtered"` Filtered int64 `json:"filtered"`
Limit int `json:"limit"` Limit int `json:"limit"`
Offset int `json:"offset"` Offset int `json:"offset"`

View File

@ -31,7 +31,9 @@ func (opts *ExtendedRequestOptions) GetCursorFilter(
modelColumns []string, // optional: for validation modelColumns []string, // optional: for validation
expandJoins map[string]string, // optional: alias → JOIN SQL expandJoins map[string]string, // optional: alias → JOIN SQL
) (string, error) { ) (string, error) {
if strings.Contains(tableName, ".") {
tableName = strings.SplitN(tableName, ".", 2)[1]
}
// --------------------------------------------------------------------- // // --------------------------------------------------------------------- //
// 1. Determine active cursor // 1. Determine active cursor
// --------------------------------------------------------------------- // // --------------------------------------------------------------------- //
@ -137,11 +139,11 @@ func (opts *ExtendedRequestOptions) GetCursorFilter(
// ------------------------------------------------------------------------- // // ------------------------------------------------------------------------- //
// Helper: get active cursor (forward or backward) // Helper: get active cursor (forward or backward)
func (opts *ExtendedRequestOptions) getActiveCursor() (id string, direction CursorDirection) { func (opts *ExtendedRequestOptions) getActiveCursor() (id string, direction CursorDirection) {
if opts.CursorForward != "" { if opts.RequestOptions.CursorForward != "" {
return opts.CursorForward, CursorForward return opts.RequestOptions.CursorForward, CursorForward
} }
if opts.CursorBackward != "" { if opts.RequestOptions.CursorBackward != "" {
return opts.CursorBackward, CursorBackward return opts.RequestOptions.CursorBackward, CursorBackward
} }
return "", 0 return "", 0
} }

View File

@ -339,7 +339,7 @@ func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, id st
} }
// Apply cursor-based pagination // Apply cursor-based pagination
if len(options.CursorForward) > 0 || len(options.CursorBackward) > 0 { if len(options.RequestOptions.CursorForward) > 0 || len(options.RequestOptions.CursorBackward) > 0 {
logger.Debug("Applying cursor pagination") logger.Debug("Applying cursor pagination")
// Get primary key name // Get primary key name
@ -389,6 +389,7 @@ func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, id st
metadata := &common.Metadata{ metadata := &common.Metadata{
Total: int64(total), Total: int64(total),
Count: int64(common.Len(modelPtr)),
Filtered: int64(total), Filtered: int64(total),
Limit: limit, Limit: limit,
Offset: offset, Offset: offset,
@ -937,7 +938,12 @@ func (h *Handler) sendFormattedResponse(w common.ResponseWriter, data interface{
if options.CleanJSON { if options.CleanJSON {
data = h.cleanJSON(data) data = h.cleanJSON(data)
} }
w.SetHeader("Content-Type", "application/json") w.SetHeader("Content-Type", "application/json")
w.SetHeader("Content-Range", fmt.Sprintf("%d-%d/%d", metadata.Offset, int64(metadata.Offset)+metadata.Count, metadata.Filtered))
w.SetHeader("X-Api-Range-Total", fmt.Sprintf("%d", metadata.Filtered))
w.SetHeader("X-Api-Range-Size", fmt.Sprintf("%d", metadata.Count))
// Format response based on response format option // Format response based on response format option
switch options.ResponseFormat { switch options.ResponseFormat {
case "simple": case "simple":

View File

@ -28,23 +28,18 @@ type ExtendedRequestOptions struct {
Expand []ExpandOption Expand []ExpandOption
// Advanced features // Advanced features
AdvancedSQL map[string]string // Column -> SQL expression AdvancedSQL map[string]string // Column -> SQL expression
ComputedQL map[string]string // Column -> CQL expression ComputedQL map[string]string // Column -> CQL expression
Distinct bool Distinct bool
SkipCount bool SkipCount bool
SkipCache bool SkipCache bool
FetchRowNumber *string PKRow *string
PKRow *string
// Response format // Response format
ResponseFormat string // "simple", "detail", "syncfusion" ResponseFormat string // "simple", "detail", "syncfusion"
// Transaction // Transaction
AtomicTransaction bool AtomicTransaction bool
// Cursor pagination
CursorForward string
CursorBackward string
} }
// ExpandOption represents a relation expansion configuration // ExpandOption represents a relation expansion configuration
@ -171,9 +166,9 @@ func (h *Handler) parseOptionsFromHeaders(r common.Request) ExtendedRequestOptio
options.Offset = &offset options.Offset = &offset
} }
case strings.HasPrefix(normalizedKey, "x-cursor-forward"): case strings.HasPrefix(normalizedKey, "x-cursor-forward"):
options.CursorForward = decodedValue options.RequestOptions.CursorForward = decodedValue
case strings.HasPrefix(normalizedKey, "x-cursor-backward"): case strings.HasPrefix(normalizedKey, "x-cursor-backward"):
options.CursorBackward = decodedValue options.RequestOptions.CursorBackward = decodedValue
// Advanced Features // Advanced Features
case strings.HasPrefix(normalizedKey, "x-advsql-"): case strings.HasPrefix(normalizedKey, "x-advsql-"):
@ -189,7 +184,7 @@ func (h *Handler) parseOptionsFromHeaders(r common.Request) ExtendedRequestOptio
case strings.HasPrefix(normalizedKey, "x-skipcache"): case strings.HasPrefix(normalizedKey, "x-skipcache"):
options.SkipCache = strings.ToLower(decodedValue) == "true" options.SkipCache = strings.ToLower(decodedValue) == "true"
case strings.HasPrefix(normalizedKey, "x-fetch-rownumber"): case strings.HasPrefix(normalizedKey, "x-fetch-rownumber"):
options.FetchRowNumber = &decodedValue options.RequestOptions.FetchRowNumber = &decodedValue
case strings.HasPrefix(normalizedKey, "x-pkrow"): case strings.HasPrefix(normalizedKey, "x-pkrow"):
options.PKRow = &decodedValue options.PKRow = &decodedValue