Files
Julian Knutsen 33e14b5c82 Move the namespace-neutral error vocabulary into its own leaf (#5395)
memoryops needed one sentinel — ErrValidation — and the value the whole repo
matches lived in issueops. So the memory leaf imported the issue package for
one errors.New and pulled internal/types in behind it, claiming memory sits
downstream of issues. It does not: the two are sibling planes over one config
table.

beadserrors now declares the vocabulary that belongs to no plane in
particular — ErrValidation, ErrNotFound, ErrNotInitialized, the ErrUnsupported
type — and issueops re-exports it by alias, exactly as internal/storage and
backend already re-export from issueops. 141 qualified ErrValidation
references across the tree, zero edited: a Go alias preserves identity.
memoryops now depends on beadserrors and context, nothing more.

What belongs down there is what a leaf for a plane nobody has written yet
would need. ErrCloseBlocked and ErrDependencyCycle name issue concepts and
stay in issueops.

beadserrors imports stdlib and nothing else, enforced by a strict depguard
rule rather than asked for in a comment, because it sits beneath every leaf
and whatever it imports is imported by all of them.

Answers Q4 from #5387.
2026-08-07 04:32:06 -07:00

68 lines
3.4 KiB
Go

// Package beadserrors holds the error vocabulary that is shared by every role
// leaf rather than owned by any one of them.
//
// It exists because the second leaf proved the first one could not keep owning
// it. memoryops needs to classify a validation refusal, and the value the whole
// repo matches lived in issueops — so the memory plane imported the issue
// package for one errors.New, and dragged internal/types in behind it. That
// import claimed memory is downstream of issues. It is not: the two are sibling
// planes over one config table.
//
// WHAT BELONGS HERE is the vocabulary any role needs whatever it operates on: a
// request was invalid, a thing was not there, the substrate was not ready, this
// backend does not implement the capability. WHAT DOES NOT is every refusal
// that names a domain concept — an issue cannot be claimed, a close is blocked,
// a dependency would cycle. Those stay in the leaf that defines the concept.
// The test is whether a leaf for some plane nobody has written yet would need
// it; if the answer requires knowing what the plane holds, it is not shared.
//
// Leaves RE-EXPORT from here rather than making callers import it, so code
// holding one role interface can classify a refusal without discovering a
// second package. Those re-exports are Go aliases, so every value is identical
// and one errors.Is arm matches it under any of its names — which is the whole
// reason to alias instead of minting per-package twins. issueops.ErrValidation,
// storage.ErrValidation, backend.ErrValidation and memoryops.ErrValidation are
// four doorplates on the one value declared below.
//
// The package imports stdlib and nothing else, and it must stay that way: it
// sits beneath every leaf, so anything it imports is imported by all of them.
package beadserrors
import (
"errors"
"fmt"
)
// ErrValidation classifies deterministic request-validation failures.
var ErrValidation = errors.New("validation failed")
// ErrNotFound is returned when a requested entity does not exist in the database.
var ErrNotFound = errors.New("not found")
// ErrNotInitialized is returned when the database has not been initialized
// (e.g., issue_prefix config is missing).
var ErrNotInitialized = errors.New("database not initialized")
// ErrUnsupported reports something the caller asked for that this backend
// cannot serve — a capability accessor it does not implement, or a request
// field it will not honor. It is a TYPE rather than a sentinel because the
// two facts a caller needs are which operation refused and which backend
// refused it, and neither survives a formatted string.
//
// A backend returns it instead of quietly doing something narrower. The case
// that made that rule explicit is Reader's Offset: the store-backed body
// rendered LIMIT without OFFSET, so a caller that paged with it received the
// first page over and over with no error to notice.
//
// It lives here so a caller holding only a role interface can classify the
// refusal with errors.As without importing internal/storage — or, since the
// capability shell is not an issue concept, without importing issueops either.
type ErrUnsupported struct {
Op string // method name, e.g. "AddLabel" or "Transaction.CreateIssues"
Backend string // e.g. "dolt-server"
}
func (e *ErrUnsupported) Error() string {
return fmt.Sprintf("operation %q not supported by the %s backend", e.Op, e.Backend)
}