think
16px
820px

Task 4 report — Adapter interface + SyncHTTPAdapter

Status: DONE. Commit 7465a56feat(p2): Adapter interface + sync-http OCR adapter (branch feat/gateway-p2-jobs-ocr, off d71284a).

What shipped

  • internal/jobs/adapter.goAdapter interface, AdapterError, SyncHTTPAdapter.
  • internal/jobs/adapter_test.go — httptest-stub tests, namespace-safe (t4-* ids/tenants/env).
  • internal/jobs/job.go — added ContentType string field (json content_type,omitempty, documented), recorded at submit; empty ⇒ default application/json.

Signatures (Task 5/6/7 must match)

type Adapter interface {
    Run(ctx context.Context, job *Job, payload []byte, endpoint string,
        report func(stage string, progress float64)) (result json.RawMessage, err error)
}

type AdapterError struct {
    Retryable  bool
    Code       string
    Status     int    // upstream HTTP status; 0 for transport/timeout
    RetryAfter string // upstream Retry-After header, verbatim, when present
}
func (e *AdapterError) Error() string

type SyncHTTPAdapter struct {
    Upstream *config.Upstream
    Client   *http.Client // injectable; nil ⇒ http.DefaultClient
}

Behavior

  • POST payload to endpoint + SubmitPath (default /ocr when unset) with the job's ContentType.
  • Calls report("processing", 0) at start (nil-safe). Adapter never touches the store.
  • 2xx → body as json.RawMessage. 429/503 → AdapterError{Retryable:true, Status, RetryAfter}. Other 4xx/5xx → AdapterError{Retryable:false, Code:"UPSTREAM_<n>", Status}. ctx deadline/cancel → AdapterError{Retryable:false, Code:"ADAPTER_TIMEOUT"} promptly; transport error → UPSTREAM_UNREACHABLE.
  • Server-side key injection: only if Upstream.APIKeyEnv is set + env present (mirrors proxy.forward); never forwards a client key (builds its own request).
  • Timeout enforced via ctx, not Client.Timeout; response body closed on every path — no goroutine leak.

Verification

  • go build ./... clean. go vet ./... clean. go test -race ./internal/jobs/... → ok (1.42s), 12 adapter tests + existing store tests pass.

Concerns / notes for Task 7

  • report is called synchronously; manager must keep the report→UpdateStatus mutation cheap (store holds its mutex during the callback). Documented in the interface doc comment.
  • Default SubmitPath is /ocr; example config (Task 9) should set explicit paths (e.g. PaddleOCR /ocr/predict-by-doc).
  • AzureDIAdapter (Task 5) and ClassifyAdapter (Task 6) are not built here, per scope.