gastownhall/beads · error · storage.ErrValidation
%w: count edges direction %q is not %q or %q
Error message
%w: count edges direction %q is not %q or %q
What it means
ValidateEdgeCountRequest rejects an EdgeCountRequest whose Direction is neither EdgeDirectionIn nor EdgeDirectionOut, wrapping storage.ErrValidation. The error echoes the offending value and the two legal values. This catches typos and unknown direction strings instead of silently returning zero counts.
Source
Thrown at internal/storage/issueops/edge_counts.go:42
}
// ValidateEdgeCountRequest applies the request rules every GraphCounter
// implementation shares.
//
// THE ORDER IS PART OF THE CONTRACT. The direction is checked FIRST, so an
// empty request is a refusal about the direction rather than an empty answer:
// EdgeCountRequest{} names no anchors, and answering it with no anchors would
// let a caller that forgot the direction get a plausible response forever. The
// per-entry checks that follow tell a caller's mistake from a legitimately
// empty answer, exactly as ValidateEdgeReadRequest's do.
func ValidateEdgeCountRequest(request publicops.EdgeCountRequest) error {
switch request.Direction {
case publicops.EdgeDirectionIn, publicops.EdgeDirectionOut:
case "":
return fmt.Errorf("%w: count edges requires a direction (%q or %q)",
storage.ErrValidation, publicops.EdgeDirectionOut, publicops.EdgeDirectionIn)
default:
return fmt.Errorf("%w: count edges direction %q is not %q or %q",
storage.ErrValidation, request.Direction, publicops.EdgeDirectionOut, publicops.EdgeDirectionIn)
}
if request.Status != "" && request.Direction != publicops.EdgeDirectionIn {
return fmt.Errorf("%w: count edges status %q needs direction %q: an outbound edge's far end may be a row this database does not hold",
storage.ErrValidation, request.Status, publicops.EdgeDirectionIn)
}
for i, id := range request.IDs {
if id == "" {
return fmt.Errorf("%w: count edges id %d is empty", storage.ErrValidation, i)
}
}
for i, depType := range request.Types {
if !depType.IsValid() {
return fmt.Errorf("%w: count edges type %d is not a usable dependency type (non-empty, max %d chars)",
storage.ErrValidation, i, types.MaxDependencyTypeLen)
}
}
return nilView on GitHub (pinned to 71377f2769)
Solutions
- Use the publicops.EdgeDirectionIn / EdgeDirectionOut constants instead of string literals
- Normalize user input to the constants at the boundary (case-insensitive mapping, reject unknowns)
- Check errors.Is(err, storage.ErrValidation) to classify this as invalid input
- List accepted values in your CLI help / API docs
Example fix
// before
req := publicops.EdgeCountRequest{IssueID: "BD-1", Direction: "both"}
// after
dir := publicops.EdgeDirectionOut
switch strings.ToLower(userInput) {
case "in":
dir = publicops.EdgeDirectionIn
case "out":
dir = publicops.EdgeDirectionOut
default:
return fmt.Errorf("direction must be %q or %q", publicops.EdgeDirectionIn, publicops.EdgeDirectionOut)
}
req := publicops.EdgeCountRequest{IssueID: "BD-1", Direction: dir} Defensive patterns
Strategy: validation
Validate before calling
func normalizeDirection(s string) (publicops.EdgeDirection, error) {
switch strings.ToLower(strings.TrimSpace(s)) {
case "in":
return publicops.EdgeDirectionIn, nil
case "out":
return publicops.EdgeDirectionOut, nil
default:
return "", fmt.Errorf("direction %q must be %q or %q", s,
publicops.EdgeDirectionIn, publicops.EdgeDirectionOut)
}
} Try / catch
err := issueops.ExecuteEdgeCount(ctx, db, req)
if err != nil {
if errors.Is(err, storage.ErrValidation) && strings.Contains(err.Error(), "direction") {
return fmt.Errorf("accepted directions: %s, %s",
publicops.EdgeDirectionIn, publicops.EdgeDirectionOut)
}
return err
} Prevention
- Never pass raw string literals for Direction — use the exported constants
- Normalize and case-fold user input before mapping to constants
- Reject unknown direction values with a clear message listing valid options
- Search the codebase for EdgeCountRequest literals during refactors to catch stale values
When it happens
Trigger: Passing Direction set to an arbitrary string (e.g. "both", "inbound", "IN", or a corrupted value from config/JSON) instead of the library's EdgeDirectionIn/EdgeDirectionOut constants.
Common situations: Users typing free-text directions at a CLI; case-mismatched literals ("in" vs "IN"); deserializing legacy payloads with renamed direction values; passing the wrong enum type from another package.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- %w: count edges requires a direction (%q or %q)
- ExternalDoltConfig: TLSCert set without TLSKey
- formula %q: %w
- edge %d %s->%s duplicates a parent-child relationship with d
- %w: apply batch item %d has unknown kind %q
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/738918bd12a852bb.
Report an issue: GitHub.