Loading
Commits on Source 4
-
Matt Cockayne authored
Adds forge.WithHTTPTransport and forge.WithHTTPClient, so a consumer that has already assembled an HTTP stack can hand it to a provider built through the blank-import path. Additive: ProviderFactory's signature is unchanged, because Option was already varargs on it. Two rungs rather than one, because they differ in who owns the redirect policy — a security question rather than a matter of taste. WithHTTPTransport shares the expensive part. The connection pool and TLS session cache both live in the transport, so several providers stop opening their own connections to a host the consumer is already talking to, while each still builds its own client and keeps its own redirect and sensitive-header policy. A provider that attaches a credential by hand depends on that policy to stop the credential following a redirect off the host it pinned. WithHTTPClient hands over the whole client and that policy with it. It is documented as transferring the obligation rather than merely the object, and the docs say to prefer the transport. This package builds neither, and cannot: depfootprint_test.go forbids go/httpclient here so the contract stays cheap for anyone merely authoring a provider. The options carry standard-library types and each provider composes its own policy over what arrives. The root package's dependency graph is unchanged. A nil is ignored rather than treated as an instruction to unset, and WithHTTPTransport ignores a TYPED nil too. That is not defensive tidying: http.RoundTripper is an interface, so a nil *http.Transport inside one is not equal to nil, would sail past the check a provider would naturally write, and would panic at the first request far from the call that introduced it. staticcheck rejecting that comparison as "never true" is the evidence, and the test says so rather than asserting it at runtime. Implements forge spec 0008 D4.
-
Matt Cockayne authored
Adds forge/pool, so several components asking for the same forge get one provider — and one connection pool, and one credential resolution — between them rather than one each. The state machine is NOT written here. org 0003 P-4 places it once, in go/clientlifecycle, and this package uses its invalidatable strategy and owns only the keying: a map from Endpoint to one resolver each. Writing another copy of that concurrency is the thing that module exists to prevent. A pool is a value a consumer constructs, holds and passes, never package state, so sharing is visible in the wiring and a component handed nothing shares nothing. The configuration is fixed at New rather than passed per call: a pool answers from a memo, so a per-call configuration would be read on the first call and silently ignored on every later one — a parameter that sometimes does nothing, written into the signature. Source returns an accessor bound to one endpoint, and that is what a component should be given. Handing over a resolved provider would let the wiring code choose when resolution happens, and therefore when it fails, for every component at once. Handing over the pool would give a component reach over endpoints that are not its business. Consumers declare the one-method interface themselves, so the zero-conf fallback — a closure over forge.Lookup — satisfies the same shape and needs no second code path through the component. Resolve exists so a command can fail at its own boundary rather than one operation into work that appeared to start cleanly. Documented, and load-bearing: a pool is scoped to a UNIT OF WORK, not a process. Every adapter here captures its credential at construction and holds the string; nothing refreshes it and nothing can. Reuse therefore extends a captured credential's life from one component's work to the pool's. The transport is what carries connection reuse across cycles, and a transport holds no credential, so a daemon builds the transport once and the pool per cycle. Invalidate is documented as gated on asserted credential-invalidity, and the documentation says plainly that this module does not yet surface such errors — invalidating on a rate limit fans concurrent resolutions at an API already throttling, which is how a throttle becomes a lockout. Cost is one module, clientlifecycle, which has none of its own. TestPoolAddsExactlyOneModule measures that on the package rather than the module, because ./pool is what a consumer inherits and ./... would fold every sibling into one graph and prove nothing about the split. Implements forge spec 0008 D6 and D12.
-
Matt Cockayne authored
Adds ErrUnauthorized, ErrForbidden and ErrRateLimited, so a caller can tell which refusal it received and respond differently to each. Answering all of them the same way is wrong in three of four cases: a missing release is often tolerable, a lapsed credential wants re-authentication, a permission failure wants neither, and a rate limit wants a wait. Each is added to the error chain rather than replacing what the platform said, so errors.Is answers "which refusal" while errors.As still reaches the platform's own error. ErrUnauthorized is the only one that authorises discarding a cached connection, and its documentation says so while naming the other two as the cases that must not. Invalidating on a permission failure loops silently; invalidating on a rate limit is worse, because a consumer reusing providers resolves concurrently when invalidated — so it aims a burst of resolutions at an API already refusing, which is how a throttle becomes a lockout. A rate limit says WHEN it resets, twice: as a hint for the human reading a terminal, and as a duration through RetryAfter for the caller that has to decide. forge still does not retry — Provider.ListReleases already states that pagination loops here do not sleep on a caller's behalf, and that stance is right for a library without the program's deadline. What was awkward is that the same documentation told a caller to inspect the response for a Retry-After while giving it no supported way to do so except reaching into a platform's error type. Now it asks forge. The delay is SERVER-CONTROLLED input, so parsing it lives in one place rather than in five adapters, and it is bounded. A broken instance sending Retry-After: 999999999, or a hostile one sending it deliberately, would otherwise stall whoever honours it for eleven days. Three dialects are read by one helper — delta-seconds, HTTP-date, and X-RateLimit-Reset as an epoch — and anything absent, unparseable, negative or already past reports unknown rather than zero, so a caller cannot mistake silence for permission to retry now. ErrReleaseNotFound's caveat is updated to say what is true today rather than what was true when it was written: gitea does honour it, and the alignment of the rest is this spec's remaining work. Implements forge spec 0009 D1, D2 and D7.
-
Matt Cockayne authored