textmachine/platform/internal/httpapi/capabilities.go

110 lines
5.5 KiB
Go

package httpapi
import "net/http"
// ContractVersion is the version of the ratified contract this build serves
// (`docs/architecture/14-api-contract/openapi.yaml`, `info.version`).
//
// It is a CONSTANT and it is the only place a non-streaming client learns which contract it is
// talking to. While the major is `0` a differing MINOR carries breaking changes by design, so a
// client generated against another one refuses to work and says so — which is why this must be
// raised in the same commit as the code that implements a new minor, and never as a courtesy
// afterwards.
// ⚠ 0.11.0 was the ORDER FORM's minor (D39.196, unified backlog row 279) and it lived for HOURS.
// 0.12.0 is the same form's correction: `Run.ordered_chapters` became nullable and `ordered_units`
// arrived beside it, because the chapter figure a character order used to publish was the SPAN it
// reached into and overstated what was bought by up to a whole chapter — worst on the cheapest order
// there is. That changes the wire this build serves, so the number moves with it, in this same
// change and not as a courtesy afterwards.
//
// ⚠ Why a bump and not an amendment inside 0.11.0, decided rather than assumed: 0.11.0's canon text
// was edited three times after its landing, and none of those was a change of FORM — an open
// vocabulary growing by one value, a defect note, and an ERRATUM correcting what the document said
// about members the wire already served. Prose and errata do not move a number; a served shape does.
// And 0.11.0 already has its acceptance act (D39.208), which closes it. See the pack's report.
//
// ⚠ THE GATE IS RED WHILE THE CANON READS 0.11.0, and that is the ratified order and its true cause:
// the CODE lands first and the canon follows in the second act. The reverse was refused deliberately
// — errata 04.09-в is the day the canon moved first and the wire spent a working day announcing a
// version it did not serve.
//
// ⛔ AND WHAT THE GATE CANNOT SEE, said here because this constant is where a reader comes looking: it
// compares VERSIONS, not SHAPES. Three separate untruths in the 0.11.0 text passed it in one day —
// the bar described in chapters while the wire sent units, `ordered_chapters: 0` promised where the
// wire sent 2, three values of a vocabulary that had four — because the NUMBER matched each time. The
// bump keeps the number from lying; it does not close the blind spot, which is unified backlog row 309.
const ContractVersion = "0.12.0"
// Capabilities is what this deployment can do: one flat document, the same for every account.
type Capabilities struct {
// Pairs is what this deployment can translate, and what it merely knows about. Unavailable ones
// are LISTED rather than omitted: "absent" and "listed as unavailable" are different facts to a
// user waiting for one.
Pairs []LanguagePair
// IntakeEnabled is whether this deployment takes books at all. False is a read-only instance,
// and without the field a client discovers that only by spending a user's upload.
IntakeEnabled bool
IntakeMaxBytes int64
// ExportFormats is what `POST /books/{bookId}/exports` accepts. Empty means none are built here,
// which is the honest answer while the export path is not built (deferred to P8).
ExportFormats []string
// BankCorrectionsEnabled is whether this deployment serves the correction door,
// `POST /books/{bookId}/bank/corrections`. False means the path answers 404 and a client does
// not offer the correction UI — machine-readable so nobody learns it by failing a user's save
// (canon §Capabilities; the same fact that decides the route's mount).
BankCorrectionsEnabled bool
// PageSizeDefault is how many rows a collection returns when `limit` is omitted. It is the
// DEPLOYMENT's number rather than a constant of the contract, and every read path takes its
// default from the same place this answers from.
PageSizeDefault int
}
// LanguagePair is one direction, as the wire carries it.
type LanguagePair struct {
Source string
Target string
Available bool
}
type wireCapabilities struct {
ContractVersion string `json:"contract_version"`
LanguagePairs []wireLanguagePair `json:"language_pairs"`
IntakeEnabled bool `json:"intake_enabled"`
IntakeMaxBytes int64 `json:"intake_max_bytes"`
ExportFormats []string `json:"export_formats"`
BankCorrectionsEnabled bool `json:"bank_corrections_enabled"`
PageSizeDefault int `json:"page_size_default"`
}
type wireLanguagePair struct {
Source string `json:"source"`
Target string `json:"target"`
State string `json:"state"`
}
func (h *v0) capabilities(w http.ResponseWriter, r *http.Request) {
if _, ok := principal(w, r); !ok {
return
}
out := wireCapabilities{
ContractVersion: ContractVersion,
LanguagePairs: make([]wireLanguagePair, 0, len(h.caps.Pairs)),
IntakeEnabled: h.caps.IntakeEnabled,
IntakeMaxBytes: h.caps.IntakeMaxBytes,
ExportFormats: h.caps.ExportFormats,
BankCorrectionsEnabled: h.caps.BankCorrectionsEnabled,
PageSizeDefault: h.caps.PageSizeDefault,
}
if out.ExportFormats == nil {
out.ExportFormats = []string{} // an empty collection is an empty array, never null
}
for _, p := range h.caps.Pairs {
state := "unavailable"
if p.Available {
state = "available"
}
out.LanguagePairs = append(out.LanguagePairs,
wireLanguagePair{Source: p.Source, Target: p.Target, State: state})
}
h.writeJSON(w, r, http.StatusOK, out)
}