pub struct RegistryClient { /* private fields */ }Expand description
HTTP client for a single ACDP registry.
reqwest::Client clones cheaply (it’s an Arc internally), so this
struct is Clone to enable per-authority caching in
crate::CrossRegistryResolver without re-wiring HTTP+TLS
state on every hop. The same is true of budget (issue #258): it is
an Option<DiscoveryBudget>, and DiscoveryBudget is itself an
Arc handle, so cloning a client that carries a budget shares the
counters rather than resetting them — the shape
RevocationDiscovery
depends on: verify_retrieved attaches one DiscoveryBudget to a
single client clone and hands that SAME clone to both trust-class
lookups.
revocation_cache (issue #257) is the same shape again: an
Option<RevocationCache>, itself an Arc handle, so cloning a client
that carries a cache shares the underlying store rather than resetting
it. Unlike budget, it is caller-injectable via
Self::with_revocation_cache on ANY client, not only the
discovery-scoped clone verify_retrieved builds internally — a caller
verifying many contexts against the same producer attaches one cache up
front and every subsequent discovery benefits. revocation_freshness
travels alongside it: verify_retrieved overrides it per call from
RevocationDiscovery::freshness (crate-private
with_revocation_freshness, crate-private), while a cache attached directly
via with_revocation_cache defaults to Duration::ZERO — pure seeding,
no marker ever suppresses a lookup, until the caller explicitly opts in
through a discovery configuration’s freshness field.
Implementations§
Source§impl RegistryClient
impl RegistryClient
The authority (host, plus port when non-default) of the registry
this client talks to — the value a receipt’s registry_did
must match (RFC-ACDP-0010 serving-authority cross-check).
Sourcepub fn with_revocation_cache(&self, cache: RevocationCache) -> Self
pub fn with_revocation_cache(&self, cache: RevocationCache) -> Self
Return a clone of this client with cache attached (issue #257),
replacing any cache the original carried, and resetting
revocation_freshness to Duration::ZERO — pure seeding by
default, following crate::verified::RevocationDiscovery’s own
zero default. Attach a cache once and reuse the returned client
across many VerifiedContext::fetch* calls against the same
producer(s) to amortize RFC-ACDP-0014 §8 discovery: facts persist
(indefinitely, per §7:114) across calls regardless of freshness;
setting a discovery configuration’s freshness above zero
additionally lets a fresh marker skip a repeat lookup entirely — see
crate::revocation_cache for the two-object model.
Sourcepub fn with_auth_provider(&self, provider: Arc<dyn AuthProvider>) -> Self
pub fn with_auth_provider(&self, provider: Arc<dyn AuthProvider>) -> Self
Return a clone of this client that presents read credentials from
provider (RFC-ACDP-0008 §6.2, bearer_jwt), replacing any provider
the original carried. The provider is asked once per read request,
with this client’s Self::authority; see crate::auth for which
requests carry a token and why writes never do.
A registry with anonymous_public_reads: false answers anonymous
reads not_authorized, which makes revocation discovery against it
unknown (unanswerable); a configured token makes it answerable.
Sourcepub fn has_auth_provider(&self) -> bool
pub fn has_auth_provider(&self) -> bool
Whether this client carries an AuthProvider.
Sourcepub fn new(base_url: &str) -> Result<Self, AcdpError>
pub fn new(base_url: &str) -> Result<Self, AcdpError>
Connect to a registry at base_url (e.g. https://registry.example.com).
Uses rustls for TLS; does not use the system OpenSSL. Applies
the RFC-ACDP-0006 §7.4 default timeouts (5s connect, 30s total)
and §7.5 redirect policy (max 3 follows, same authority only).
§DNS-rebinding posture (default)
This constructor installs the SafeDnsResolver DNS hook
(RFC-ACDP-0006 §7.6): every hostname lookup — the first connect,
each redirect, and every reconnect the pool makes over the
client’s lifetime — is filtered through the SsrfPolicy at
DNS time, before any TCP connect. This is strictly stronger
than pin-once resolution (Self::new_pinned): a pinned
client validates a single answer and reuses that address, so a
hostile authoritative DNS server that only later flips a name
into a forbidden range is still caught here but not there. The
DNS-hook posture is therefore the default for all callers; reach
for Self::builder only when you need a non-default knob (a
private root cert, a custom SsrfPolicy, timeout overrides, or
the legacy pinned mode).
Sourcepub fn builder(base_url: &str) -> RegistryClientBuilder
pub fn builder(base_url: &str) -> RegistryClientBuilder
Start a RegistryClientBuilder for the non-default connection
postures — a private root certificate, a custom SsrfPolicy,
timeout overrides, and the legacy pinned-resolution mode.
The builder’s default is identical to Self::new: the
stronger SafeDnsResolver DNS-hook posture with the default
SSRF policy and the RFC-ACDP-0006 §7.4 timeouts. Opt into
pin-once resolution with RegistryClientBuilder::pinned.
Sourcepub fn with_root_cert_pem(base_url: &str, pem: &[u8]) -> Result<Self, AcdpError>
pub fn with_root_cert_pem(base_url: &str, pem: &[u8]) -> Result<Self, AcdpError>
Connect to a registry that trusts the given PEM-encoded root certificate in addition to the system roots.
Primary use is the in-process self-signed HTTPS server in the
crate’s tests/helpers/tls_did_server.rs harness so the spec
fixtures fed-001..006 can drive CrossRegistryResolver
end-to-end without going over the network.
Sourcepub async fn new_pinned(
base_url: &str,
policy: &SsrfPolicy,
) -> Result<Self, AcdpError>
👎Deprecated since 0.4.0: prefer RegistryClient::new (SafeDnsResolver DNS hook — validates every connection, strictly stronger than pin-once) or, for explicit pin-once mode, RegistryClient::builder(base_url).pinned(true).ssrf_policy(policy).build().await
pub async fn new_pinned( base_url: &str, policy: &SsrfPolicy, ) -> Result<Self, AcdpError>
prefer RegistryClient::new (SafeDnsResolver DNS hook — validates every connection, strictly stronger than pin-once) or, for explicit pin-once mode, RegistryClient::builder(base_url).pinned(true).ssrf_policy(policy).build().await
Connect to a registry with pin-once DNS-rebinding protection (RFC-ACDP-0006 §7.6).
Resolves the hostname once, validates the resolved IP against
policy, then pins that IP into the HTTP client.
Deprecated: the default Self::new posture installs the
SafeDnsResolver DNS hook, which validates the resolved IP on
every connection (including reconnects) rather than just once —
strictly stronger protection. For the rare case that still wants
pin-once semantics with a custom policy, use
RegistryClient::builder(base_url).pinned(true).ssrf_policy(policy).build().await.
Sourcepub async fn capabilities(&self) -> Result<CapabilitiesDocument, AcdpError>
pub async fn capabilities(&self) -> Result<CapabilitiesDocument, AcdpError>
Fetch the registry’s capabilities document and run the
RFC-ACDP-0007 §3 runtime validation
(acdp_validation::validate_capabilities).
Body capped at 64 KB per RFC-ACDP-0006 §7.3.
Sourcepub async fn capabilities_with_ttl(
&self,
) -> Result<(CapabilitiesDocument, Duration), AcdpError>
pub async fn capabilities_with_ttl( &self, ) -> Result<(CapabilitiesDocument, Duration), AcdpError>
Like Self::capabilities but also returns the cache TTL
derived from the response’s Cache-Control: max-age=N header.
Per RFC-ACDP-0006 §4.2, consumers SHOULD cache the capabilities
document for min(max-age, 3600s) seconds. When no
Cache-Control (or no parseable max-age) is returned, the
fallback is 300s — a conservative middle-ground that matches
crate::ResolverOptions::capabilities_ttl’s default.
Sourcepub async fn publish(
&self,
req: &PublishRequest,
) -> Result<PublishResponse, AcdpError>
pub async fn publish( &self, req: &PublishRequest, ) -> Result<PublishResponse, AcdpError>
Publish a context. Returns the registry-assigned identifiers.
Sourcepub async fn publish_idempotent(
&self,
req: &PublishRequest,
idempotency_key: &str,
) -> Result<PublishResponse, AcdpError>
pub async fn publish_idempotent( &self, req: &PublishRequest, idempotency_key: &str, ) -> Result<PublishResponse, AcdpError>
Publish with an idempotency key for safe retries.
Sourcepub async fn publish_with_retry(
&self,
req: &PublishRequest,
idempotency_key: &str,
max_attempts: u32,
) -> Result<PublishResponse, AcdpError>
pub async fn publish_with_retry( &self, req: &PublishRequest, idempotency_key: &str, max_attempts: u32, ) -> Result<PublishResponse, AcdpError>
Publish with bounded retry for transient failures.
Reuses idempotency_key across attempts so the registry can
dedupe (RFC-ACDP-0003 §6). Retries only when the error is
transient per AcdpError::is_transient. Bounded backoff:
250 ms, 500 ms, 1 s, 2 s.
Sourcepub async fn retrieve(&self, ctx_id: &CtxId) -> Result<FullContext, AcdpError>
pub async fn retrieve(&self, ctx_id: &CtxId) -> Result<FullContext, AcdpError>
Retrieve a full context (body + registry_state) by ctx_id.
Body capped at 1 MB per RFC-ACDP-0006 §7.3.
Sourcepub async fn retrieve_with_metadata(
&self,
ctx_id: &CtxId,
) -> Result<(FullContext, RetrievalMetadata), AcdpError>
pub async fn retrieve_with_metadata( &self, ctx_id: &CtxId, ) -> Result<(FullContext, RetrievalMetadata), AcdpError>
Retrieve a full context plus cache / integrity headers.
Sourcepub async fn retrieve_if_none_match(
&self,
ctx_id: &CtxId,
etag: &str,
) -> Result<Option<(FullContext, RetrievalMetadata)>, AcdpError>
pub async fn retrieve_if_none_match( &self, ctx_id: &CtxId, etag: &str, ) -> Result<Option<(FullContext, RetrievalMetadata)>, AcdpError>
Conditional retrieval using If-None-Match.
Returns Ok(None) when the registry responds 304 Not Modified.
Returns Ok(Some((body, metadata))) for a fresh retrieval.
Sourcepub async fn retrieve_body(&self, ctx_id: &CtxId) -> Result<Body, AcdpError>
pub async fn retrieve_body(&self, ctx_id: &CtxId) -> Result<Body, AcdpError>
Retrieve just the body (immutable, highly cacheable).
Sourcepub async fn lineage(
&self,
lineage_id: &LineageId,
) -> Result<Vec<FullContext>, AcdpError>
pub async fn lineage( &self, lineage_id: &LineageId, ) -> Result<Vec<FullContext>, AcdpError>
Retrieve all contexts in a lineage (oldest to newest).
Sourcepub async fn current(
&self,
lineage_id: &LineageId,
) -> Result<FullContext, AcdpError>
pub async fn current( &self, lineage_id: &LineageId, ) -> Result<FullContext, AcdpError>
Retrieve the current (latest) context in a lineage.
Sourcepub async fn changes(
&self,
since: &Since,
filter: &WatchFilter,
limit: Option<u32>,
) -> Result<ChangesResponse, AcdpError>
pub async fn changes( &self, since: &Since, filter: &WatchFilter, limit: Option<u32>, ) -> Result<ChangesResponse, AcdpError>
One page of the registry’s changes feed (GET /changes,
RFC-ACDP-0018 §5): the entries after since that match filter,
at most limit of them (the registry default, 100, when None).
The response is checked against the closed schema (every entry,
cursor bounds, ascending seq), not verified: each entry is a
hint until crate::recall::verify_change passes. A cursor older
than the registry’s retention is AcdpError::CursorExpired: the
consumer has missed an unknown set of entries, must re-verify every
watched context directly, then resume from crate::Since::Head
(§7 “Resync”). Body capped at 1 MB.
Sourcepub async fn search(
&self,
params: &SearchParams,
) -> Result<SearchResponse, AcdpError>
pub async fn search( &self, params: &SearchParams, ) -> Result<SearchResponse, AcdpError>
Keyword search across the registry.
Body capped at 64 KB (search responses are projection-summaries — IMP-03: not the 1 MB context cap).
Sourcepub fn search_builder(&self) -> RegistrySearch<'_>
pub fn search_builder(&self) -> RegistrySearch<'_>
Begin a fluent search via RegistrySearch. Chains parameters
with strong typing, then .send().await issues the request.
let resp = client
.search_builder()
.q("market risk")
.tag("risk")
.tag("portfolio")
.limit(50)
.send()
.await?;