Skip to content

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 Provider implementations 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:

  1. The provider does not implement the interface — the type assertion fails.
  2. 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. DefaultMaxChecksumsSize and DefaultMaxSignatureSize are 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.