blob: 6aff38c071437495c8f04e6032f8d23fa734027a [file] [log] [blame]
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"errors"
"net/http"
"strconv"
"strings"
"golang.org/x/oscar/internal/storage"
"rsc.io/ordered"
)
var _ page = dbviewPage{}
// dbviewPage holds the fields needed to display a view of the database.
type dbviewPage struct {
Form dbviewForm // the raw form inputs
Result *dbviewResult
Error error // if non-nil, the error to display instead of the result
}
type dbviewForm struct {
Start, End string // comma-separated lists; see parseOredered for details
Limit int
}
type dbviewResult struct {
Items []item
}
// An item is a string representation of a key-value pair.
type item struct {
Key, Value string
}
var dbviewPageTmpl = newTemplate(dbviewPageTmplFile, nil)
func (g *Gaby) handleDBview(w http.ResponseWriter, r *http.Request) {
g.slog.Info("handleDBView")
handlePage(w, g.populateDBviewPage(r), dbviewPageTmpl)
}
// populateDBviewPage returns the contents of the dbView page.
func (g *Gaby) populateDBviewPage(r *http.Request) dbviewPage {
limit := parseInt(r.FormValue("limit"), 100)
p := dbviewPage{
Form: dbviewForm{
Start: r.FormValue("start"),
End: r.FormValue("end"),
Limit: limit,
},
}
start := parseOrdered(p.Form.Start)
end := parseOrdered(p.Form.End)
g.slog.Info("calling dbview", "limit", limit)
res, err := g.dbview(start, end, limit)
g.slog.Info("done")
if err != nil {
p.Error = err
return p
}
p.Result = res
return p
}
func (g *Gaby) dbview(start, end []byte, limit int) (*dbviewResult, error) {
if len(start) == 0 && len(end) > 0 {
return nil, errors.New("missing start key")
}
if len(start) > 0 && len(end) == 0 {
val, ok := g.db.Get(start)
if !ok {
return nil, nil
}
return &dbviewResult{Items: []item{makeItem(start, val)}}, nil
}
var items []item
for k, vf := range g.db.Scan(start, end) {
g.slog.Info("item", "key", k)
items = append(items, makeItem(k, vf()))
if len(items) >= limit {
break
}
}
if len(items) == 0 {
return nil, nil
}
return &dbviewResult{Items: items}, nil
}
func makeItem(k, v []byte) item {
return item{Key: storage.Fmt(k), Value: storage.Fmt(v)}
}
// parseOrdered parses a comma-separated list into an [ordered] value.
// It returns nil on the empty string.
// Special cases for each comma-separated part are integers and the special strings
// "inf" or "-inf". Anything else is treated as a string.
func parseOrdered(s string) []byte {
var parts []any
words := strings.Split(s, ",")
if len(words) == 1 && strings.TrimSpace(words[0]) == "" {
return nil
}
for _, p := range words {
var part any
p = strings.TrimSpace(p)
pl := strings.ToLower(p)
if pl == "inf" {
part = ordered.Inf
} else if pl == "-inf" {
part = ordered.Rev(ordered.Inf)
} else if i, err := strconv.Atoi(p); err == nil {
part = i
} else {
part = p
}
parts = append(parts, part)
}
return ordered.Encode(parts...)
}
// parseInt returns the int represented by s.
// If s does not represent an int, it returns defaultValue.
func parseInt(s string, defaultValue int) int {
if i, err := strconv.Atoi(s); err == nil {
return i
}
return defaultValue
}