Optional capabilities¶
Two of this module's interfaces are not part of Provider, and are discovered by
runtime type assertion instead:
if cp, ok := provider.(forge.ChecksumProvider); ok {
manifest, err := cp.DownloadChecksumManifest(ctx, rel, maxBytes)
// ...
}
Type assertion is usually a smell — a sign the interface is wrong. Here it is deliberate, and the reason is compatibility.
Why not just add the method¶
If checksum retrieval were a fifth method on Provider, adding it would break
every existing implementation, including ones written outside this
repository that we cannot see or fix. The contract's own doc comment states the
goal:
This keeps third-party
Providerimplementations source-compatible: they gain the feature by opting in, not by implementing a new required method.
An optional interface adds capability without invalidating anything. A provider
that ignores it keeps compiling and keeps working, falling back to the default
behaviour — locating checksums.txt by name in the release's asset list.
That fallback is what makes the pattern honest. The capability is not lost when a
provider opts out; it is obtained a different way. The interface exists for the
cases the default cannot serve — the direct provider composes a checksum URL
from a template that may point outside the asset listing entirely, so there is no
asset to find by name.
Two ways to say "no", and they mean the same thing¶
A caller must treat these identically:
- The provider does not implement the interface — the type assertion fails.
- The provider implements it but returns
ErrNotSupported— it is configured in a way that disables the capability, such as an empty URL template.
Both mean fall back. Only the second is easy to get wrong: returning a bespoke error instead of the sentinel turns a recoverable fallback into a hard failure, and the caller has no way to tell it was meant to be recoverable.
// wrong — the caller cannot recover
return nil, errors.New("checksums not configured")
// right — the caller falls back
return nil, forge.ErrNotSupported
This is exactly the kind of rule that is easy to state and easy to breach, which is why it is mechanically checked rather than left to prose.
Size bounds: the caller decides, the provider enforces¶
Both optional methods take a maxBytes bound, and the split of responsibility is
deliberate:
- The caller owns the policy. It passes the value, so a tool shipping
unusually large artefacts can raise the ceiling without every provider needing
a configuration knob.
DefaultMaxChecksumsSizeandDefaultMaxSignatureSizeare defaults, not mandates. - The provider owns the enforcement. It is the only code that sees the response before it is buffered, so it is the only place a cap can prevent a hostile server streaming indefinitely rather than merely truncate afterwards.
A provider that accepts the parameter and ignores it looks correct and is not. Nothing at compile time notices, so the conformance harness does.