Decode Datastar signals without the SDK
The SDK was used for exactly one call, ReadSignals, which is a JSON decode of
a query parameter. It brought four modules with it, including an HTTP
compression stack, for an SSE generator this app never touches: the search
handlers answer with plain text/html and let Datastar match the fragment by
id.
Five lines replace it. Absent or empty is deliberately not an error — the
first request carries no signals, and rejecting it would 400 the initial
load. Tests cover absent, empty, malformed, and extra signals present.
Nothing changes on the client: Datastar still sends the same
?datastar={"haku":"..."}, and the smoke checks that assert that wire format
are what make the swap safe.
This commit is contained in:
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
@@ -11,7 +12,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/a-h/templ"
|
||||
"github.com/starfederation/datastar-go/datastar"
|
||||
)
|
||||
|
||||
// maxUpload caps a pasted or uploaded bundle. A household catalog is a few
|
||||
@@ -167,6 +167,20 @@ type searchSignals struct {
|
||||
Haku string `json:"haku"`
|
||||
}
|
||||
|
||||
// readSignals decodes that parameter.
|
||||
//
|
||||
// ponytail: the Datastar SDK does this too, but pulling it in for one JSON
|
||||
// decode dragged along four modules — an HTTP compression stack among them —
|
||||
// for an SSE generator this app never uses. Absent or empty is not an error:
|
||||
// the first request carries no signals.
|
||||
func readSignals(r *http.Request, into any) error {
|
||||
raw := r.URL.Query().Get("datastar")
|
||||
if raw == "" {
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal([]byte(raw), into)
|
||||
}
|
||||
|
||||
// fragment renders a piece of a page for Datastar to patch in. A plain
|
||||
// text/html response is enough — Datastar matches the returned element by its
|
||||
// id and replaces it, so there is no SSE stream to manage.
|
||||
@@ -180,7 +194,7 @@ func fragment(w http.ResponseWriter, r *http.Request, c templ.Component) {
|
||||
// searchBoard re-renders the dish board as the search box is typed into.
|
||||
func (a *app) searchBoard(w http.ResponseWriter, r *http.Request) {
|
||||
var signals searchSignals
|
||||
if err := datastar.ReadSignals(r, &signals); err != nil {
|
||||
if err := readSignals(r, &signals); err != nil {
|
||||
http.Error(w, "bad signals", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
@@ -204,7 +218,7 @@ func (a *app) searchBoard(w http.ResponseWriter, r *http.Request) {
|
||||
// searchCatalog re-renders the catalog lists as the search box is typed into.
|
||||
func (a *app) searchCatalog(w http.ResponseWriter, r *http.Request) {
|
||||
var signals searchSignals
|
||||
if err := datastar.ReadSignals(r, &signals); err != nil {
|
||||
if err := readSignals(r, &signals); err != nil {
|
||||
http.Error(w, "bad signals", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -75,6 +76,35 @@ func TestDateRejectsTheFuture(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadSignals(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
query string
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{"a signal", `/etsi?datastar=` + url.QueryEscape(`{"haku":"keitto"}`), "keitto", false},
|
||||
{"other signals are ignored", `/etsi?datastar=` + url.QueryEscape(`{"haku":"kala","muu":1}`), "kala", false},
|
||||
// The first request carries no signals at all; that is not a failure.
|
||||
{"no parameter", "/etsi", "", false},
|
||||
{"empty parameter", "/etsi?datastar=", "", false},
|
||||
{"malformed json", "/etsi?datastar=%7Bnope", "", true},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
var got searchSignals
|
||||
err := readSignals(httptest.NewRequest(http.MethodGet, c.query, nil), &got)
|
||||
if (err != nil) != c.wantErr {
|
||||
t.Fatalf("err = %v, wantErr %v", err, c.wantErr)
|
||||
}
|
||||
if got.Haku != c.want {
|
||||
t.Errorf("haku = %q, want %q", got.Haku, c.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMigrateCreatesSchema(t *testing.T) {
|
||||
db, err := openDB(t.TempDir() + "/test.db")
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user