feat(api): add /v1/analyze handler and routing#14
Merged
Conversation
First user-facing Phase 2 endpoint. Wires the analyze usecase
adapter (PR 7) behind POST /v1/analyze, with JSON decoding,
request validation, error mapping, and routing.
Handler design:
- AnalyzeRequest holds pull_request_url and has a Validate()
method that chains validation.Required and
validation.GitHubPullRequestURL. Request-type-owned validation
matches development_rules.md §11.
- AnalyzeUsecase is an interface defined in the handler package,
not imported from internal/usecase. The concrete
usecase.Analyzer satisfies it, following the "accept
interfaces, return structs" idiom per §10. This keeps handler
tests independent of pkg/prism — a fake usecase in the same
test file is enough.
- The success response envelope is {"result": prism.Result}.
pkg/prism.Result already carries the canonical JSON tags
(pull_request, analysis, changed_files) so no translation
layer is needed between the usecase output and the wire
format, matching Phase 2 instruction §7.
- Error handling maps three categories of failure to the
standard error envelope:
1. JSON decode errors → CodeInvalidInput (400)
2. *http.MaxBytesError from the body_limit middleware's
MaxBytesReader → CodePayloadTooLarge (413). Handlers
are responsible for surfacing read-time rejection per
PR 3's design note.
3. validation.Error → CodeInvalidInput with the structured
field: message format
4. pkg/prism sentinel errors → matching response.Code:
ErrInvalidInput → invalid_input (400)
ErrUnsupportedProvider → unsupported_provider (400)
ErrAuthRequired → auth_required (401)
ErrUpstreamFailure → upstream_failure (502)
unknown → internal_error (500)
- Client-facing error messages are fixed strings, not the raw
pkg/prism error text, so implementation details never leak.
Routing:
- app.New() constructs usecase.NewAnalyzer() and
handler.NewAnalyzeHandler() and registers POST /v1/analyze on
the existing mux. The middleware chain from PR 6 already wraps
all routes, so the new endpoint gets request_id, recover,
logging, body_limit, rate_limit, concurrency_limit, and
timeout automatically.
Tests:
- Success path verifies the 200 response is exactly
{"result": {pull_request, analysis, changed_files}} with the
fake usecase's canned pkg/prism.Result serialized through.
- Invalid JSON, missing pull_request_url, and four malformed
URL shapes all return 400 with invalid_input and a non-empty
message.
- Body too large is simulated by wrapping the request body in
http.MaxBytesReader directly; the handler must surface the
*http.MaxBytesError as 413 payload_too_large.
- Usecase error mapping covers all four pkg/prism sentinels
plus an unknown error (→ internal_error / 500).
- AnalyzeRequest.Validate() has its own unit test for the valid,
empty, whitespace-only, and non-github URL cases.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PR 8/9 of the Phase 2 series. First user-facing endpoint. Wires the analyze usecase adapter (PR 7) behind `POST /v1/analyze`: request decoding, validation, error mapping, and routing in `app.New()`. The defensive middleware chain from PR 6 is already in place, so the new endpoint automatically inherits request_id, recover, logging, body_limit, rate_limit, concurrency_limit, and timeout.
Handler design
Interface defined in the handler package
`AnalyzeUsecase` is defined in the handler package, not imported from `internal/usecase`. This is the "accept interfaces, return structs" idiom mandated by `docs/development_rules.md` §10. The concrete `usecase.Analyzer` satisfies the interface automatically because Go uses structural typing. A test fake in the same test file is enough to exercise every handler branch.
Zero translation from `pkg/prism.Result` to wire format
`pkg/prism.Result` already has the canonical JSON tags (`pull_request`, `analysis`, `changed_files`) — the same shape required by Phase 2 instruction §7. The handler serializes it directly in the envelope:
No DTO, no translation layer. This matches the instruction's "HTTP 層で独自 shape に変換しすぎない" guidance.
Error mapping
Three layers of error handling in the handler:
Client-facing messages are fixed strings, never the raw pkg/prism error text, so implementation details never leak to users. This is the right default — we can always add more specific messages later if operational feedback demands it.
MaxBytesError handling
The body_limit middleware (PR 3) wraps `r.Body` with `http.MaxBytesReader` but read-time enforcement requires handler cooperation — that was flagged in PR 3's commit message. This PR fulfills that contract: the handler's `json.Decode` detects `*http.MaxBytesError` via `errors.As` and returns 413 instead of 400, matching the early-rejection path.
Routing
3 lines added to `app.New()`. The existing middleware chain and health endpoints are untouched.
Test plan
13 tests across 6 groups, all using a fake usecase in the same package to keep handler tests independent of `pkg/prism`.
Local checks
What this PR does NOT do
Phase 2 context
Intended to be squash-merged.