Files
foundry-core/internal/research/cursor.go
T

66 lines
1.7 KiB
Go

package research
import (
"bytes"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"io"
"strconv"
"strings"
)
// Cursors locate a public corpus row, not an account or authentication token.
// Binding to normalized filters prevents accidentally paging a different query.
type position struct {
Version int `json:"v"`
ID int64 `json:"id"`
Key string `json:"key"`
Before bool `json:"before"`
Filter string `json:"filter"`
}
var errCursor = errors.New("invalid cursor")
func filterKey(f Filters) string {
sort, search := f.Sort, f.SearchIn
if sort == "" {
sort = "recent"
}
if search == "" {
search = "text"
}
data, _ := json.Marshal([]string{strings.TrimSpace(f.Query), f.Topic, sort, search, strconv.Itoa(f.PageSize)})
sum := sha256.Sum256(data)
return hex.EncodeToString(sum[:16])
}
func encodeCursor(f Filters, p position, before bool) string {
p.Version, p.Before, p.Filter = 1, before, filterKey(f)
if f.Sort != "title" {
p.Key = ""
}
data, _ := json.Marshal(p)
return base64.RawURLEncoding.EncodeToString(data)
}
func decodeCursor(f Filters) (position, error) {
var p position
if f.Cursor == "" {
return p, nil
}
if len(f.Cursor) > 32768 {
return p, errCursor
}
data, err := base64.RawURLEncoding.Strict().DecodeString(f.Cursor)
if err != nil {
return p, errCursor
}
decoder := json.NewDecoder(bytes.NewReader(data))
decoder.DisallowUnknownFields()
if decoder.Decode(&p) != nil || decoder.Decode(new(any)) != io.EOF || p.Version != 1 || p.ID <= 0 || p.Filter != filterKey(f) || strings.ContainsRune(p.Key, 0) || (f.Sort != "title" && p.Key != "") {
return position{}, errCursor
}
return p, nil
}