This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
restkit is a dependency-light HTTP-JSON transport core (a Go library, not an
app) — the shared base for hand-written API clients (moexoptcalc, alor,
tinvest/rest). It owns the request lifecycle — JSON marshal, request build,
hooks, send, read, status check, JSON unmarshal — plus client construction with
otelhttp instrumentation, query helpers, and three typed errors. Everything
API-specific (auth, header/query pins) is injected through a RequestHook;
restkit knows nothing about any upstream API.
Single package (package restkit, module github.com/acidsailor/restkit),
Go 1.26. Three source files (client.go, request.go, errors.go) plus
doc.go and tests. No internal/, no subpackages, no cmd/.
The project uses Task (taskfile.yml):
task test— run tests (go test ./...)task lint—golangci-lint fmt+golangci-lint run --fix(mutates files)task ci— read-only verification:golangci-lint fmt --diff+golangci-lint run(what CI runs, fail-fast)task check— composite: mutating lint then tests (use before committing)task update— resync go-scaffolds template tooling viauvx copier update
Run a single test: go test -run TestName ./... (or -run 'TestX/subtest').
CI (.github/workflows/ci.yml) delegates to the reusable
acidsailor/go-scaffolds/.github/workflows/go-ci.yml@v1 workflow.
Linting is golangci-lint v2: standard linters + modernize, formatted by
gofumpt (extra-rules) and golines at 80 columns. Keep lines ≤80 or the
formatter wraps them.
The whole public API is three concepts wired together:
-
Client(client.go) — immutable, concurrency-safe; built withNew(endpoint, ...Option). Construction trims the trailing/, resolves the*http.Client(default 30s timeout), and wraps its transport inotelhttp.NewTransport, so every call is traced/metered via the global OTel providers. Options:WithHTTPClient,WithName,WithHook. A nil http client or nil hook is ignored, soNewalways yields a usable client. -
Do[T](request.go) — the generic package-level entry point:Do[T](ctx, c, method, path, body, hooks...). Marshalsbodyto JSON, builds the request againstbaseURL+path, setsAccept: application/json(andContent-Typewhen body is non-nil), runs hooks, sends, reads, and unmarshals a 2xx body intoT. The unexportedClient.doreturns the raw 2xx bytes;Doadds the JSON decode. Hooks run client-wide first, then per-call (slices.Concat(c.hooks, hooks)) so a per-call hook can override a client pin. -
RequestHook(request.go) —func(*http.Request) error. The only extension point, and why restkit stays API-agnostic: it can mutate headers andreq.URL(query), so auth, header pins, and query all go through it.WithQuery(url.Values)is a built-in hook that merges (not clobbers) into existing query params.Values/NewValues()is a generic, nil-safeurl.Valuesbuilder whose setters (Str,Int,Int32,Int64,Float,Bool) skip nil pointers and chain.Param(key, QueryValuer)chains package-local types; implementations use pointer receivers and return ok=false for a nil receiver (a typed-nil in the interface is non-nil).
Three typed errors, all matched downstream with errors.As:
ConfigError— bad input toNew(empty endpoint).RequestError— any per-call stage failure before a usable result.Opnames the stage via theOp*consts (OpMarshal,OpBuild,OpHook,OpSend,OpRead,OpUnmarshal) — match on the consts, not string literals. Mirrors*url.Error; wraps the cause viaUnwrap(reachable witherrors.Is).ResponseError— a non-2xx response carryingStatusCodeand the rawBodyverbatim (the error envelope is not decoded). ExposesGetStatusCode() intso callers can read the status via aninterface{ GetStatusCode() int }probe without importing this package.
Every error carries Name (from WithName, default "restkit") so a program
using several clients can attribute failures.
- Keep restkit API-agnostic: anything specific to one upstream API belongs in a hook supplied by the consuming client, never here.
- The tooling (taskfile, lint config, CI) is generated from the
acidsailor/go-scaffoldscopier template (.copier-answers.yml). Don't hand-edit generated tooling — runtask updateto resync; that file's_commitpins the template version.