gastownhall/beads · error · storage.ErrValidation

%w: count edges requires a direction (%q or %q)

Error message

%w: count edges requires a direction (%q or %q)

What it means

ValidateEdgeCountRequest rejects an EdgeCountRequest whose Direction field is empty, wrapping storage.ErrValidation. An edge count is inherently directional (inbound or outbound), so the library refuses to guess. This sentinel-wrapped error lets callers distinguish caller mistakes from legitimately empty results.

Source

Thrown at internal/storage/issueops/edge_counts.go:39

var edgeCountPlanes = []struct{ dependencies, sources string }{
	{dependencies: "dependencies", sources: "issues"},
	{dependencies: "wisp_dependencies", sources: "wisps"},
}

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

View on GitHub (pinned to 71377f2769)

Solutions

  1. Set request.Direction to publicops.EdgeDirectionIn or publicops.EdgeDirectionOut before calling
  2. Check errors.Is(err, storage.ErrValidation) to detect this as a caller-input bug rather than a data problem
  3. Add a default direction at the CLI/API boundary when the user doesn't specify one
  4. Validate the request before executing to produce a friendlier message

Example fix

// before
req := publicops.EdgeCountRequest{IssueID: "BD-1"} // Direction omitted
count, err := ExecuteEdgeCount(ctx, db, req)
// after
req := publicops.EdgeCountRequest{IssueID: "BD-1", Direction: publicops.EdgeDirectionOut}
count, err := ExecuteEdgeCount(ctx, db, req)
Defensive patterns

Strategy: validation

Validate before calling

func validateEdgeCountRequest(req publicops.EdgeCountRequest) error {
    if req.Direction == "" {
        return fmt.Errorf("direction is required: %q or %q",
            publicops.EdgeDirectionOut, publicops.EdgeDirectionIn)
    }
    return nil
}

Try / catch

err := issueops.ExecuteEdgeCount(ctx, db, req)
if err != nil {
    if errors.Is(err, storage.ErrValidation) {
        return fmt.Errorf("bad request: %w", err) // 400-style caller error, not a data failure
    }
    return err
}

Prevention

When it happens

Trigger: Calling ExecuteEdgeCount (or building an EdgeCountRequest) with EdgeCountRequest.Direction left as "" — e.g. a struct literal that omits Direction, or a CLI/JSON decode that left the field unset.

Common situations: JSON payloads missing the "direction" key; refactoring that added new request fields but didn't set Direction; tests constructing requests by field name omitting Direction.

Understand the failure class

Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/6146f37f1acbb6d8. Report an issue: GitHub.