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 nil

View on GitHub (pinned to 71377f2769)

Solutions

  1. Use the publicops.EdgeDirectionIn / EdgeDirectionOut constants instead of string literals
  2. Normalize user input to the constants at the boundary (case-insensitive mapping, reject unknowns)
  3. Check errors.Is(err, storage.ErrValidation) to classify this as invalid input
  4. 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

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


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