feat(api): PR action routes — merge + resolve-comments#88
Conversation
Adds two 501 Not Implemented route shells for the SCM/PR action lane as specified in issue #21. No business logic — the routes are stubs that return a structured planned body with the embedded OpenAPI spec slice, consistent with the existing route-shell pattern. Routes registered: POST /api/v1/prs/{id}/merge POST /api/v1/prs/{id}/resolve-comments Closes part of #18. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Greptile SummaryAdds
Confidence Score: 5/5Safe to merge; the core routing, error mapping, nil-service guard, and body-decode logic are all correct. The major issues from prior review rounds have all been addressed: sentinel errors now live in service/pr (no circular-import risk), the nil-service guard correctly returns 501 via apispec.NotImplemented, and isEmptyBody is correctly narrowed to io.EOF only. The remaining findings are documentation or dead-code nits that do not affect runtime behavior. The stub nature of ActionService is clearly called out with TODOs and poses no runtime hazard since real callers must supply a non-nil ActionManager. No files require special attention; specgen/build.go has a dead schemaNames entry and a missing 400 response unit for resolveComments, but neither affects the running server. Important Files Changed
Sequence DiagramsequenceDiagram
participant Client
participant PRsController
participant ActionManager
participant SCMProvider
Client->>PRsController: "POST /api/v1/prs/{id}/merge"
alt "Svc == nil"
PRsController-->>Client: 501 NOT_IMPLEMENTED
else
PRsController->>ActionManager: Merge(ctx, prID)
ActionManager->>SCMProvider: squash-merge (TODO)
SCMProvider-->>ActionManager: MergeResult / error
alt success
ActionManager-->>PRsController: "MergeResult{PRNumber, Method}"
PRsController-->>Client: 200 MergePRResponse
else ErrPRNotFound
PRsController-->>Client: 404 PR_NOT_FOUND
else ErrPRNotMergeable
PRsController-->>Client: 409 PR_NOT_MERGEABLE
else ErrPRPreconditions
PRsController-->>Client: 422 PR_PRECONDITIONS_UNMET
end
end
Client->>PRsController: "POST /api/v1/prs/{id}/resolve-comments"
alt "Svc == nil"
PRsController-->>Client: 501 NOT_IMPLEMENTED
else
PRsController->>PRsController: decode optional JSON body
alt malformed JSON
PRsController-->>Client: 400 INVALID_JSON
else
PRsController->>ActionManager: ResolveComments(ctx, prID, commentIDs)
ActionManager->>SCMProvider: resolve threads (TODO)
SCMProvider-->>ActionManager: ResolveResult / error
alt success
ActionManager-->>PRsController: "ResolveResult{Resolved}"
PRsController-->>Client: 200 ResolveCommentsResponse
else ErrNothingToResolve
PRsController-->>Client: 422 NOTHING_TO_RESOLVE
end
end
end
Reviews (9): Last reviewed commit: "fix(prs): align nil-service guard with s..." | Re-trigger Greptile |
Builds the two SCM/PR action routes end-to-end per issue #21: POST /api/v1/prs/{id}/merge POST /api/v1/prs/{id}/resolve-comments **ports/scm.go** — new PRService interface, MergeResult, ResolveResult. **adapters/scm/github** — adds ErrNotMergeable/ErrUnprocessable sentinels to the client (405/409/422 classification) and MergePR / ListUnresolvedThreadIDs / ResolveThread methods to the Provider. **internal/scm/pr_service.go** — concrete PRService over PRProvider. Parses the path ID as a PR number, calls the provider, maps github sentinel errors to domain errors (ErrPRNotFound / ErrPRNotMergeable / ErrPRPreconditions / ErrNothingToResolve). Nil PRService keeps routes registered but returns OpenAPI-backed 501s. **httpd/controllers/prs.go** — real handlers; writePRError maps the four domain errors to 404 / 409 / 422 / 500. **prs_test.go** — httptest coverage: 501 (nil service), 200/404/409/422 for both routes, spec-slice present in 501 body. **scm/pr_service_test.go** — table-driven unit tests with a fake PRProvider. Closes part of #18. Closes #21. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements POST /api/v1/prs/{id}/merge and POST /api/v1/prs/{id}/resolve-comments.
Service logic lives in internal/service/pr (ActionManager interface + ActionService
struct). Controllers use the projects pattern — import the service package directly
rather than going through a ports interface. Drops the internal/scm intermediary
package and the ports/scm.go file added in earlier iterations.
Also fixes the ContentLength-based body-decode guard in resolveComments, which
silently dropped JSON bodies sent with chunked transfer encoding; now decodes
unconditionally and treats io.EOF as an absent body.
Closes #21.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ControllersProjectIDParam, ControllersSessionIDParam, and ControllersPRIDParam are never matched by the schemaName interceptor — swaggest reflects path-param structs inline rather than as $ref component schemas, so the hook is never called for these types. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Removed all three dead path-param entries ( |
When commentIDs were provided, the parsed PR number was never used — any thread ID could be resolved regardless of which PR was in the URL path. Add a ListUnresolvedThreadIDs existence probe in the else branch so the PR must be reachable before iterating the caller-supplied IDs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
A truncated body (e.g. {"commentIds":["T_1") returns io.ErrUnexpectedEOF,
not io.EOF. Treating it as an absent body caused the handler to fall through
to "resolve all unresolved threads" instead of returning 400. Only io.EOF
(reader returned no bytes) is a genuine empty-body signal.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove adapter changes (ErrNotMergeable/ErrUnprocessable from client.go, MergePR/ListUnresolvedThreadIDs/ResolveThread from provider.go) - ActionService returns dummy values with TODO; no business logic - Errors (ErrPRNotFound etc.) moved to controllers/errors.go - PR DTOs moved to controllers/dto.go - Remove 501 guards — stub service always wired via NewAPI default Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Restore original client.go alignment (no functional change) - Move PR sentinel errors to service/pr/errors.go - controllers/errors.go now only contains writePRError, referencing prsvc sentinels - Fix schemaNames alignment in specgen/build.go (goimports lint) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The nil-service fallback was silently injecting a stub that returned fake merge/resolve success, misleading callers when no SCM is wired. Remove the injection; nil Svc now returns 503 SCM_NOT_CONFIGURED. Also inline writePRError into prs.go and delete controllers/errors.go. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
reqBody: nil removes the requestBody.required: true annotation so generated SDK clients can omit the body (which resolves all threads). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Use apispec.NotImplemented (501) instead of 503 so nil-service responses match the OpenAPI spec and generated clients hit the documented 501 branch. Echo prID as PRNumber in the stub Merge to avoid claiming the wrong PR was merged if NewActionService is wired by accident before real impl lands. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
POST /api/v1/prs/{id}/mergeandPOST /api/v1/prs/{id}/resolve-commentsinternal/service/pr/asActionManagerinterface +ActionServicestruct — following theprojectscontroller pattern (import service package directly, noportsindirection)internal/ports/scm.goandinternal/scm/intermediary package from earlier iterationsresolveCommentswas guarding JSON decode withr.ContentLength > 0, which silently dropped bodies sent with chunked transfer encoding — replaced with unconditional decode +io.EOFcheck*github.ProvidersatisfiesPRProvidervia duck typing; no extra adapter layerTest plan
go test ./...passes (all packages green, drift guard passes)POST /api/v1/prs/1/merge→ 501 when service is nilPOST /api/v1/prs/1/resolve-comments→ 501 when service is nilservice/pr/action_service_test.gocover happy paths and all error mappingscontrollers/prs_test.gocover 200/404/409/422/501 for both routesCloses #21.
🤖 Generated with Claude Code