// docs.go — the human-readable API reference, generated from the same
// registry the router and spec serve (0003: a docs page generated from
// the spec is each phase's exit criterion; the registry is the source
// of truth, never the document).
package ops
import (
"fmt"
"reflect"
"sort"
"strings"
)
func (a Auth) String() string {
switch a {
case Token:
return "token"
case Admin:
return "admin"
}
return "anonymous"
}
// Docs renders the registry as markdown. Deterministic: ops sort by
// name, fields keep struct order. Regenerate with `make docs`.
func Docs(r *Registry, title string) []byte {
var b strings.Builder
fmt.Fprintf(&b, "# %s API\n\n", title)
b.WriteString("Generated from the op registry by `quarry docs` — edit the ops, not this file; regenerate with `make docs`.\n")
b.WriteString("Every op also appears in the OpenAPI document (`quarry spec`). Query inputs ride the query string; the same names work as JSON.\n")
b.WriteString("\nRead your own writes: any query on a repository takes `?indexed=HASH` (a full hash or a prefix of at least 4 hex digits), " +
"for example the check-in a push just wrote. It answers once that repository's index holds the artifact, waiting a few seconds at most, " +
"and otherwise `425 Too Early` with `Retry-After`. Without it, a read straight after a write may answer from the index as it was before the write.\n")
ds := r.All()
sort.Slice(ds, func(i, j int) bool { return ds[i].Name < ds[j].Name })
for _, d := range ds {
fmt.Fprintf(&b, "\n## %s\n\n", d.Name)
fmt.Fprintf(&b, "`%s %s` — %s, %s\n\n", d.Method, d.Pattern, d.Kind, d.Auth)
if d.Doc != "" {
fmt.Fprintf(&b, "%s\n\n", d.Doc)
}
if fields := exportedFields(d.In); len(fields) > 0 {
b.WriteString("Input:\n\n")
b.WriteString("| field | type | rules |\n|---|---|---|\n")
for _, f := range fields {
fmt.Fprintf(&b, "| `%s` | %s | %s |\n",
jsonName(f), typeName(f.Type), rulesOf(f))
}
b.WriteString("\n")
}
if fields := exportedFields(d.Out); len(fields) > 0 {
b.WriteString("Output fields:\n\n")
for _, f := range fields {
fmt.Fprintf(&b, "- `%s` (%s)\n", jsonName(f), typeName(f.Type))
}
}
}
return []byte(b.String())
}
func jsonName(f reflect.StructField) string {
name, _, _ := strings.Cut(f.Tag.Get("json"), ",")
if name == "" {
return f.Name
}
return name
}
func rulesOf(f reflect.StructField) string {
tag := f.Tag.Get("validate")
if tag == "" {
return "—"
}
// A bare | would split the markdown table cell.
return "`" + strings.ReplaceAll(tag, "|", `\|`) + "`"
}
// typeName renders a field type the way the JSON reads.
func typeName(t reflect.Type) string {
switch t.Kind() {
case reflect.String:
return "string"
case reflect.Bool:
return "boolean"
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return "integer"
case reflect.Float32, reflect.Float64:
return "number"
case reflect.Slice:
return "array of " + typeName(t.Elem())
case reflect.Struct:
var parts []string
for _, f := range exportedFields(t) {
parts = append(parts, jsonName(f)+": "+typeName(f.Type))
}
return "object {" + strings.Join(parts, ", ") + "}"
case reflect.Pointer:
return typeName(t.Elem())
}
return t.Kind().String()
}