api: JSON API over the engine; server mode in cmd/spreadlab

internal/api stays a translation layer: decode (with
DisallowUnknownFields so typos 400 instead of silently defaulting),
run the engine, encode; all validation stays in the engine. Routes use
Go 1.22 mux patterns ('POST /api/comparison'), so wrong methods get
405 from the stdlib for free. httptest runs handlers fully in memory;
the API test re-pins the 99/70/7 golden values end to end.
ComparisonResponse is added to tygo.yaml with a type mapping so the
generated web/src/types/api.ts reuses the engine's TS types.
cmd/spreadlab now serves on -addr (default localhost:8080); -table
keeps the CLI comparison as a sanity check.
This commit is contained in:
Justin Visser 2026-06-10 13:03:12 +02:00
parent c3f65a4029
commit e9f36f509e
5 changed files with 239 additions and 8 deletions

View file

@ -1,23 +1,37 @@
// Command spreadlab will serve the spreadlab dashboard. Until the HTTP
// server lands (milestone 2), it runs the prototype's three scenarios in
// the default world and prints the comparison.
// Command spreadlab serves the spreadlab API (and, from milestone 4, the
// dashboard itself). The -table flag instead prints the three-scenario
// comparison and exits, a quick engine sanity check.
package main
import (
"flag"
"fmt"
"io"
"log"
"net/http"
"os"
"strings"
"github.com/JustinZeus/spreadlab/internal/api"
"github.com/JustinZeus/spreadlab/internal/engine"
)
// main stays a thin shell: the work lives in run, which takes its output
// as an io.Writer so tests can capture it.
func main() {
if err := run(os.Stdout); err != nil {
fmt.Fprintln(os.Stderr, "spreadlab:", err)
os.Exit(1)
addr := flag.String("addr", "localhost:8080", "address to serve the API on")
table := flag.Bool("table", false, "print the three-scenario comparison and exit")
flag.Parse()
if *table {
if err := run(os.Stdout); err != nil {
fmt.Fprintln(os.Stderr, "spreadlab:", err)
os.Exit(1)
}
return
}
log.Printf("spreadlab API listening on http://%s", *addr)
if err := http.ListenAndServe(*addr, api.NewServer()); err != nil {
log.Fatal(err)
}
}