{"record":{"id":"6146f37f1acbb6d8","repo":"gastownhall/beads","slug":"w-count-edges-requires-a-direction-q-or-q","errorCode":null,"errorMessage":"%w: count edges requires a direction (%q or %q)","messagePattern":"%w: count edges requires a direction \\(%q or %q\\)","errorType":"validation","errorClass":"storage.ErrValidation","httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/edge_counts.go","lineNumber":39,"sourceCode":"var edgeCountPlanes = []struct{ dependencies, sources string }{\n\t{dependencies: \"dependencies\", sources: \"issues\"},\n\t{dependencies: \"wisp_dependencies\", sources: \"wisps\"},\n}\n\n// ValidateEdgeCountRequest applies the request rules every GraphCounter\n// implementation shares.\n//\n// THE ORDER IS PART OF THE CONTRACT. The direction is checked FIRST, so an\n// empty request is a refusal about the direction rather than an empty answer:\n// EdgeCountRequest{} names no anchors, and answering it with no anchors would\n// let a caller that forgot the direction get a plausible response forever. The\n// per-entry checks that follow tell a caller's mistake from a legitimately\n// empty answer, exactly as ValidateEdgeReadRequest's do.\nfunc ValidateEdgeCountRequest(request publicops.EdgeCountRequest) error {\n\tswitch request.Direction {\n\tcase publicops.EdgeDirectionIn, publicops.EdgeDirectionOut:\n\tcase \"\":\n\t\treturn fmt.Errorf(\"%w: count edges requires a direction (%q or %q)\",\n\t\t\tstorage.ErrValidation, publicops.EdgeDirectionOut, publicops.EdgeDirectionIn)\n\tdefault:\n\t\treturn fmt.Errorf(\"%w: count edges direction %q is not %q or %q\",\n\t\t\tstorage.ErrValidation, request.Direction, publicops.EdgeDirectionOut, publicops.EdgeDirectionIn)\n\t}\n\tif request.Status != \"\" && request.Direction != publicops.EdgeDirectionIn {\n\t\treturn 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\",\n\t\t\tstorage.ErrValidation, request.Status, publicops.EdgeDirectionIn)\n\t}\n\tfor i, id := range request.IDs {\n\t\tif id == \"\" {\n\t\t\treturn fmt.Errorf(\"%w: count edges id %d is empty\", storage.ErrValidation, i)\n\t\t}\n\t}\n\tfor i, depType := range request.Types {\n\t\tif !depType.IsValid() {\n\t\t\treturn fmt.Errorf(\"%w: count edges type %d is not a usable dependency type (non-empty, max %d chars)\",\n\t\t\t\tstorage.ErrValidation, i, types.MaxDependencyTypeLen)","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/edge_counts.go#L21-L57","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Set request.Direction to publicops.EdgeDirectionIn or publicops.EdgeDirectionOut before calling","Check errors.Is(err, storage.ErrValidation) to detect this as a caller-input bug rather than a data problem","Add a default direction at the CLI/API boundary when the user doesn't specify one","Validate the request before executing to produce a friendlier message"],"exampleFix":"// before\nreq := publicops.EdgeCountRequest{IssueID: \"BD-1\"} // Direction omitted\ncount, err := ExecuteEdgeCount(ctx, db, req)\n// after\nreq := publicops.EdgeCountRequest{IssueID: \"BD-1\", Direction: publicops.EdgeDirectionOut}\ncount, err := ExecuteEdgeCount(ctx, db, req)","handlingStrategy":"validation","validationCode":"func validateEdgeCountRequest(req publicops.EdgeCountRequest) error {\n    if req.Direction == \"\" {\n        return fmt.Errorf(\"direction is required: %q or %q\",\n            publicops.EdgeDirectionOut, publicops.EdgeDirectionIn)\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"err := issueops.ExecuteEdgeCount(ctx, db, req)\nif err != nil {\n    if errors.Is(err, storage.ErrValidation) {\n        return fmt.Errorf(\"bad request: %w\", err) // 400-style caller error, not a data failure\n    }\n    return err\n}","preventionTips":["Always initialize Direction from the EdgeDirection constants when building requests","Add a default direction at the CLI/JSON boundary when the flag is absent","Use errors.Is(err, storage.ErrValidation) to separate input bugs from empty results","Cover request construction with a unit test asserting Direction is set"],"tags":["validation","api","edges","missing-field"],"backgroundTag":"missing-required-field","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}