{"record":{"id":"738918bd12a852bb","repo":"gastownhall/beads","slug":"w-count-edges-direction-q-is-not-q-or-q","errorCode":null,"errorMessage":"%w: count edges direction %q is not %q or %q","messagePattern":"%w: count edges direction %q is not %q or %q","errorType":"validation","errorClass":"storage.ErrValidation","httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/edge_counts.go","lineNumber":42,"sourceCode":"}\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)\n\t\t}\n\t}\n\treturn nil","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/edge_counts.go#L24-L60","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nreq := publicops.EdgeCountRequest{IssueID: \"BD-1\", Direction: \"both\"}\n// after\ndir := publicops.EdgeDirectionOut\nswitch strings.ToLower(userInput) {\ncase \"in\":\n    dir = publicops.EdgeDirectionIn\ncase \"out\":\n    dir = publicops.EdgeDirectionOut\ndefault:\n    return fmt.Errorf(\"direction must be %q or %q\", publicops.EdgeDirectionIn, publicops.EdgeDirectionOut)\n}\nreq := publicops.EdgeCountRequest{IssueID: \"BD-1\", Direction: dir}","handlingStrategy":"validation","validationCode":"func normalizeDirection(s string) (publicops.EdgeDirection, error) {\n    switch strings.ToLower(strings.TrimSpace(s)) {\n    case \"in\":\n        return publicops.EdgeDirectionIn, nil\n    case \"out\":\n        return publicops.EdgeDirectionOut, nil\n    default:\n        return \"\", fmt.Errorf(\"direction %q must be %q or %q\", s,\n            publicops.EdgeDirectionIn, publicops.EdgeDirectionOut)\n    }\n}","typeGuard":null,"tryCatchPattern":"err := issueops.ExecuteEdgeCount(ctx, db, req)\nif err != nil {\n    if errors.Is(err, storage.ErrValidation) && strings.Contains(err.Error(), \"direction\") {\n        return fmt.Errorf(\"accepted directions: %s, %s\",\n            publicops.EdgeDirectionIn, publicops.EdgeDirectionOut)\n    }\n    return err\n}","preventionTips":["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"],"tags":["validation","api","edges","enum"],"backgroundTag":"invalid-enum-value","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}