| Safe Haskell | None |
|---|---|
| Language | GHC2021 |
Ecluse.Registry
Description
The registry-protocol handle: the sole interface between the proxy core and any specific registry's wire protocol.
This is the ecosystem (protocol) axis — fetch, publish, and parse — and
nothing more (see docs/architecture/registry-model.md → "Registry
Abstraction"). It is a record of functions (the Handle pattern): a backend's
smart constructor returns a RegistryClient whose closures capture that
backend's private state (an HTTP manager). The proxy core operates only on
PackageInfo (the packument-level view) and
PackageDetails (the per-version snapshot the rules engine
evaluates); an adapter projects its wire format into those, and nothing above
the registry layer sees registry-specific structures.
Two design points are load-bearing:
- The effectful fields return
IO, notApp. An adapter closes over its own state (HTTP manager, credentials) and never imports the proxy'sEnv/App, so backends stay decoupled from the core (no import cycle) — seedocs/architecture/technology-stack.md→ "Key Decisions". Theparse*fields are pure (Either): parsing a fetched response is a total, side-effect-free projection (parse, don't validate). RegistryClientdeliberately carries no authentication. Protocol and auth are orthogonal axes: every managed npm registry (AWS CodeArtifact, GCP Artifact Registry, a self-hosted Verdaccio) speaks the same npm protocol and differs only in how a bearer token is minted, which lives behind the separate Ecluse.Credential handle. So one npmRegistryClientis reused across every cloud rather than near-duplicated per provider.
The abstraction is the sole interface, so a new ecosystem backend (PyPI, RubyGems, …) is an additive constructor behind this record rather than a structural change.
Synopsis
- data RegistryClient = RegistryClient {
- fetchMetadata :: PackageName -> IO RegistryResponse
- fetchArtifact :: PackageName -> Version -> IO RegistryResponse
- publishArtifact :: PackageName -> Version -> ByteString -> IO (Either PublishFault ())
- parsePackageInfo :: PackageName -> RegistryResponse -> Either ParseError PackageInfo
- parseVersionDetails :: RegistryResponse -> Version -> Either ParseError PackageDetails
- parseVersionList :: RegistryResponse -> Either ParseError [Version]
- newtype RegistryResponse = RegistryResponse {}
- newtype ParseError = ParseError {}
- newtype PublishError = PublishError {}
- data PublishFault
- data UrlFormationError
Protocol handle
data RegistryClient Source #
The registry-protocol handle — a record of functions over a backend whose
private state the closures capture. The effectful fields return IO (decoupled
from the core); the parse* fields are pure. See the module header.
Constructors
| RegistryClient | |
Fields
| |
Fetch payload
newtype RegistryResponse Source #
A raw response fetched from a registry — the unparsed bytes of a metadata
document or an artifact, as returned by fetchMetadata / fetchArtifact. It is
kept opaque-of-bytes here so the protocol/data plane (fetch) is separate from
parsing: a parse* field turns a RegistryResponse into a domain type.
Constructors
| RegistryResponse | |
Fields
| |
Instances
| Show RegistryResponse Source # | |
Defined in Ecluse.Registry Methods showsPrec :: Int -> RegistryResponse -> ShowS # show :: RegistryResponse -> String # showList :: [RegistryResponse] -> ShowS # | |
| Eq RegistryResponse Source # | |
Defined in Ecluse.Registry Methods (==) :: RegistryResponse -> RegistryResponse -> Bool # (/=) :: RegistryResponse -> RegistryResponse -> Bool # | |
Errors
newtype ParseError Source #
Why parsing a RegistryResponse into a domain type failed. Parsing is the
boundary that turns untrusted wire data into the proxy's precise types, so a
failure is reported (not thrown): the caller decides how to respond.
Constructors
| ParseError | |
Fields
| |
Instances
| Show ParseError Source # | |
Defined in Ecluse.Registry Methods showsPrec :: Int -> ParseError -> ShowS # show :: ParseError -> String # showList :: [ParseError] -> ShowS # | |
| Eq ParseError Source # | |
Defined in Ecluse.Registry | |
newtype PublishError Source #
Why publishing an artifact to a registry failed — a genuine write fault
reported by publishArtifact (an Queue job is then left un-acked and
retried; see docs/architecture/cloud-backends.md).
This is the write-path fault and nothing more: forming the request URL is a
separate concern (a UrlFormationError), so a read-path fetch can no longer
surface a failure mislabelled as a publish.
Constructors
| PublishError | |
Fields
| |
Instances
| Show PublishError Source # | |
Defined in Ecluse.Registry Methods showsPrec :: Int -> PublishError -> ShowS # show :: PublishError -> String # showList :: [PublishError] -> ShowS # | |
| Eq PublishError Source # | |
Defined in Ecluse.Registry | |
data PublishFault Source #
Why a publish could not complete, surfaced as a value rather than thrown so the mirror worker decides retry vs. drop by an exhaustive pattern match rather than by catching (and re-classifying) an exception. The two cases differ in exactly that — retryability — which is the whole reason this is a value: one is worth redelivering and the other never is.
Constructors
| PublishUrlUnformable UrlFormationError | The request URL could not be formed (e.g. an empty base URL) — a
configuration fault carried as its |
| PublishRejected PublishError | The registry rejected the write (a non-2xx, non- |
Instances
| Show PublishFault Source # | |
Defined in Ecluse.Registry Methods showsPrec :: Int -> PublishFault -> ShowS # show :: PublishFault -> String # showList :: [PublishFault] -> ShowS # | |
| Eq PublishFault Source # | |
Defined in Ecluse.Registry | |
data UrlFormationError Source #
Why an upstream request URL could not be formed from configuration and an
already-parsed PackageName.
This is a protocol-independent fault shared by every request an adapter
builds — metadata fetch, artifact fetch, and publish alike — so a read-path
failure is reported as what it is rather than borrowing the write-path's
PublishError. It is distinct from Ecluse.Security's UrlError: that is the
pure SSRF/identifier guard (which also rejects unsafe name components), whereas
this is the effectful adapter's report that the configured base URL is unusable.
Constructors
| EmptyBaseUrl | The configured base URL is empty, so no request URL can be formed. |
| UnparseableUrl Text | The formed URL string could not be parsed into a request. Carries the offending URL. |
Instances
| Exception UrlFormationError Source # | A |
Defined in Ecluse.Registry Methods toException :: UrlFormationError -> SomeException # fromException :: SomeException -> Maybe UrlFormationError # | |
| Show UrlFormationError Source # | |
Defined in Ecluse.Registry Methods showsPrec :: Int -> UrlFormationError -> ShowS # show :: UrlFormationError -> String # showList :: [UrlFormationError] -> ShowS # | |
| Eq UrlFormationError Source # | |
Defined in Ecluse.Registry Methods (==) :: UrlFormationError -> UrlFormationError -> Bool # (/=) :: UrlFormationError -> UrlFormationError -> Bool # | |