Skip to content
quarry Artifact [cffc8f3b7e]
Login

Artifact [cffc8f3b7e]

Artifact cffc8f3b7ebeeb89ecf487a449dd4095479b292e3b463055a73a4e0db313eec3:


// 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")

	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()
}