package races import ( "encoding/base64" "fmt" "strconv" "strings" ) // Cursor is a keyset cursor: opaque to the client, encoded as // base64url(":") by Encode. Order: descending by ms, then by id. // // Empty cursor = "start from the newest". type Cursor struct { Ms int64 ID string } // String returns a human-readable representation (for debug logs). func (c Cursor) String() string { return fmt.Sprintf("%d:%s", c.Ms, c.ID) } // IsZero reports whether the cursor is the zero value. func (c Cursor) IsZero() bool { return c.Ms == 0 && c.ID == "" } // Encode renders the cursor for an HTTP query string. func (c Cursor) Encode() string { if c.IsZero() { return "" } raw := c.String() return base64.RawURLEncoding.EncodeToString([]byte(raw)) } // DecodeCursor parses a cursor produced by Cursor.Encode. // Returns the zero cursor on empty input. func DecodeCursor(s string) (Cursor, error) { if s == "" { return Cursor{}, nil } raw, err := base64.RawURLEncoding.DecodeString(s) if err != nil { return Cursor{}, fmt.Errorf("%w: bad cursor encoding: %v", ErrInvalidInput, err) } parts := strings.SplitN(string(raw), ":", 2) if len(parts) != 2 { return Cursor{}, fmt.Errorf("%w: cursor must be :", ErrInvalidInput) } ms, err := strconv.ParseInt(parts[0], 10, 64) if err != nil { return Cursor{}, fmt.Errorf("%w: cursor ms: %v", ErrInvalidInput, err) } return Cursor{Ms: ms, ID: parts[1]}, nil }