Skip to main content

AcdpError

Enum AcdpError 

Source
#[non_exhaustive]
pub enum AcdpError {
Show 38 variants Canonicalization(String), HashMismatch { stored: ContentHash, recomputed: ContentHash, }, ContextIdMismatch { requested: String, served: String, }, IncompleteLineage { lineage_id: String, ctx_id: Option<String>, }, SearchTruncated(String), RevocationDiscoveryBudgetExceeded(String), RevocationDiscoveryFailed { source: Box<AcdpError>, }, RemoteHashMismatch(String), DataRefHashMismatch(String), InvalidSignature(String), KeyResolution(String), KeyResolutionUnreachable(String), KeyNotAuthorized(String), InvalidBody(String), MissingField(&'static str), SchemaViolation(String), PayloadTooLarge(String), EmbeddedTooLarge(String), UnsupportedAlgorithm(String), UnsupportedMediaType(String), NotImplemented(String), NotFound(String), NotAuthorized(String), RateLimited(String), CursorExpired, InvalidCursor(String), SupersededTarget { reason: SupersessionReason, message: String, }, DuplicatePublish(String), CrossRegistryResolutionFailed(String), InvalidReceipt(String), InvalidLogProof(String), InvalidWitnessCosignature(String), ImmutableField(String), InvalidLifecycleTransition(String), RegistryInternal(String), Registry(WireError), Serialization(String), Http(String),
}
Expand description

Top-level error type.

#[non_exhaustive]: the wire vocabulary (RFC-ACDP-0007 §5) keeps growing as new RFCs land — 25 wire codes now, up from 21 not long ago, with RFC-ACDP-0009 reserved and still unimplemented. Without this attribute, every new variant is a semver-breaking change for any downstream crate that matches on AcdpError exhaustively. Same rationale as SsrfReason in crates/acdp-safe-http/src/lib.rs (“future spec revisions may add ranges”); match with a wildcard arm. Clone (added for issue #248 Phase 4): VerifiedContext and VerificationReport each need an independently-owned copy of a ProceedWithKnown-swallowed revocation-discovery failure — one via VerifiedContext::revocation_discovery_failure(), the other via VerificationReport::revocation_discovery — so both surfaces stay non-silent from a single call. Every variant field (String, &'static str, ContentHash, WireError, SupersessionReason, and the recursive Box<AcdpError> in RevocationDiscoveryFailed) is already Clone, so this is a free addition with no wire-format or matching impact.

This is a standing constraint on every future variant, not just the ones that exist today: every future variant’s payloads must remain Clone. A variant that needs a structured source should hold Arc<dyn std::error::Error + Send + Sync> (which is Clone regardless of the inner type, and still preserves source() chaining), never Box<dyn Error> or a bare io::Error (neither is Clone). This is not a new tradeoff introduced by adding Clone here — From<std::io::Error> and From<reqwest::Error> already stringify into Http(String) rather than carry the error value, so value semantics over reference/source-chain semantics was already the twice-exercised choice for this type; Clone just makes it a documented rule instead of an implicit pattern.

Variants (Non-exhaustive)§

This enum is marked as non-exhaustive
Non-exhaustive enums could have additional variants added in future. Therefore, when matching against variants of non-exhaustive enums, an extra wildcard arm must be added to account for any future variants.
§

Canonicalization(String)

JCS canonicalization failed (input not serializable).

§

HashMismatch

Stored content_hash did not match the recomputed value (locally detected during signature verification).

Fields

§stored: ContentHash

The hash claimed by the body or request.

§recomputed: ContentHash

The hash recomputed by the verifier.

§

ContextIdMismatch

Locally detected: the registry returned a body whose ctx_id is not the one that was requested (context substitution). Not a wire code — the client detects it. Permanent; fail closed.

Implements RFC-ACDP-0006 §4.1 step 7 (NORMATIVE, “Bind the resolved identity”): step 7 requires exactly this comparison — body.ctx_id against the ctx_id used to construct the request — and permits a consumer to surface “an equivalent typed error” in place of the registry-side cross_registry_resolution_failed wire code; this variant is that typed error. See RFC-ACDP-0008 §9.1 for the threat this closes: without it, a registry can serve any other validly-signed body by the same producer under the requested context’s URL, and both signature verification and content_hash recomputation still pass. It does not close §9.1 in full: a registry that genuinely republishes the same content under a new ctx_id still passes; only serve-time substitution — a different id claimed to be the one requested — is caught.

Fields

§requested: String

The ctx_id the caller requested.

§served: String

The ctx_id actually present on the body the registry served.

§

IncompleteLineage

Locally detected: GET /lineages/{id} (RFC-ACDP-0013 §8.1) did not serve a lineage the caller could accept — either it came back with no members at all, or (when ctx_id is Some) its members did not include the ctx_id a live search match named for that lineage. Not a wire code — RFC-ACDP-0014 §10 (“No new wire error code”): this is a locally-detected consumer-side binding failure, exactly the same kind of thing as AcdpError::ContextIdMismatch (issue #189) but for the lineage-walk path added for issue #226. Permanent; fail closed — never added to AcdpError::is_transient.

Fields

§lineage_id: String

The lineage id that was walked (GET /lineages/{id}).

§ctx_id: Option<String>

The ctx_id a live search match named for this lineage, when the walk was invoked with a specific member to check for. None when the walk was invoked directly with no particular member to verify (e.g. via find_revocations_in_lineage), in which case only the empty-lineage case can produce this variant.

§

SearchTruncated(String)

Locally detected: a revocation-discovery search helper (find_revocations, find_registry_attested_revocations in acdp-client) exhausted its pagination safety cap (MAX_SEARCH_PAGES) with a search cursor still remaining, or named more candidate revocation-lineage ids than its lineage-walk safety cap (MAX_LINEAGE_WALKS) allows to fetch. Not a wire code — RFC-ACDP-0014 §10 (“No new wire error code”): this is a client-local safety-limit signal the registry cannot express, the same rationale as AcdpError::ContextIdMismatch and AcdpError::IncompleteLineage. Permanent for the same request shape — never added to AcdpError::is_transient; retrying an identical query reproduces the same truncation.

Both caps exist as hostile-registry DoS protection (MAX_SEARCH_PAGES bounds search round-trips; MAX_LINEAGE_WALKS bounds the GET /lineages/{id} fetches a search result can trigger, each capped at 1 MB). Search results are ordered created_at DESC, so hitting a cap sheds the OLDEST — most likely earliest-compromised_since — members first: a silently truncated revocation set narrows a compromise window, which is precisely the outcome RFC-ACDP-0014 §4 (“earliest T across a revocation lineage is effective”) forbids. A knowingly-partial answer from these discovery helpers is strictly worse than a loud refusal, so exhausting either cap with more results remaining on the registry is a hard error, never a silently partial Vec.

§

RevocationDiscoveryBudgetExceeded(String)

Locally detected (issue #258): RFC-ACDP-0014 §8 revocation auto-discovery (RevocationDiscovery::max_requests / RevocationDiscovery::max_bytes in acdp-client) exhausted its caller-configured request-count or cumulative-byte budget before both trust-class lookups finished. Not a wire code — RFC-ACDP-0014 §10 (“No new wire error code”) forbids one, and this is client-side only, the same rationale as AcdpError::SearchTruncated. Permanent for the same request shape — never added to AcdpError::is_transient — exhausting a budget is closer to SearchTruncated (“we did not see everything”) than to a transport error, and carries the same attacker-inducible-downgrade warning: a hostile registry that learns a caller’s budget can pad harmless-looking traffic to exhaust it before a real revocation is found.

The two budgets are combined across BOTH lookups (find_revocations and, when opted in, find_registry_attested_revocations), not one ceiling each — enabling the registry-attested trust class does not double the allowance. The request count is enforced exactly: the slot is reserved before the request is issued, so a request is never issued once the budget is reserved out, and — unlike the byte count below — a 503, a parse failure, or a PayloadTooLarge on an already-reserved request still consumes its slot. The byte count is enforced on a check-before-issue basis using the running total from completed requests, so up to TWO in-flight requests (one per concurrently-running trust-class lookup) can push the total over max_bytes before the NEXT request observes the overrun. Counts registry traffic only (acdp-client’s RegistryClient::{capabilities, retrieve, lineage, search}) — DID-document fetches issued via WebResolver are not counted. Only the byte count is scoped to successfully-parsed response bodies: the capped error-envelope read on a non-success response is never charged to max_bytes, but IS charged to max_requests (the slot was already reserved before the response arrived).

§

RevocationDiscoveryFailed

Locally detected: RFC-ACDP-0014 §8 revocation auto-discovery (RevocationPolicy::discover in acdp-client) failed under DiscoveryFailurePolicy::FailClosed. Not a wire code — the same rationale as AcdpError::ContextIdMismatch, AcdpError::IncompleteLineage, and AcdpError::SearchTruncated: this is a client-local decision about how to react to a failure the registry already reported some other way, so it wraps that failure rather than inventing a new wire vocabulary entry for it. source is boxed rather than inline: this variant recurses (AcdpError containing AcdpError), and an unboxed recursive field would make the enum infinite-sized. Box<AcdpError> still gets Clone for free from AcdpError’s own derive (added for issue #248 Phase 4 — see the enum’s doc), which is what lets a ProceedWithKnown-swallowed discovery failure be independently owned by both VerifiedContext::revocation_discovery_failure and VerificationReport::revocation_discovery from one call. AcdpError still has no PartialEq.

Fields

§source: Box<AcdpError>

The underlying error discovery hit — typically a transport failure from the search or lineage-walk requests (AcdpError::KeyResolutionUnreachable, AcdpError::Http, …) or AcdpError::SearchTruncated from exhausting a discovery safety cap. AcdpError::is_transient delegates to this field, so a retry-aware caller still gets a correct answer through the wrapper.

§

RemoteHashMismatch(String)

Wire code: hash_mismatch. The remote registry rejected a publish request because its independent hash recomputation did not match the producer-supplied content_hash. Distinct from the local AcdpError::HashMismatch variant: this one carries the registry’s message verbatim and indicates a producer-side bug (most often canonicalization divergence — see RFC-ACDP-0001 §5.7 and the can-001 conformance fixture).

§

DataRefHashMismatch(String)

Wire code: data_ref_hash_mismatch. A DataRef’s fetched or decoded bytes do not match the producer-declared data_ref.content_hash. The body itself remains cryptographically valid — only the referenced data has diverged. Distinct from AcdpError::RemoteHashMismatch / AcdpError::HashMismatch (body-level ProducerContent failure — the whole body is untrusted) and AcdpError::InvalidSignature (a key / key-binding problem). RFC-ACDP-0002 §6.5–6.6, RFC-ACDP-0007 §5.

§

InvalidSignature(String)

Signature verification failed or signature was malformed. Wire code: invalid_signature.

§

KeyResolution(String)

Wire code: key_resolution_failed (HTTP 400).

§

KeyResolutionUnreachable(String)

Wire code: key_resolution_unreachable (HTTP 502) — transient, may retry.

§

KeyNotAuthorized(String)

Wire code: key_not_authorized (HTTP 403).

§

InvalidBody(String)

Producer body could not be parsed.

§

MissingField(&'static str)

A required field was missing.

§

SchemaViolation(String)

Schema validation failed (string length, array uniqueness, oneOf, etc). Wire code: schema_violation.

§

PayloadTooLarge(String)

Wire code: payload_too_large — request body exceeds the registry limit.

§

EmbeddedTooLarge(String)

Wire code: embedded_too_large — a single DataRef.embedded.content exceeds the 64 KB cap.

§

UnsupportedAlgorithm(String)

Wire code: unsupported_algorithm — the producer used a signature algorithm the registry does not accept.

§

UnsupportedMediaType(String)

Wire code: unsupported_media_type — the request carried a body whose Content-Type is outside the registry’s accept-set (RFC-ACDP-0007 §4.1, §5; HTTP 415). Added on the 0.5.0 line.

Distinct from AcdpError::SchemaViolation on purpose: the body is rejected unparsed, so no structural claim about it is made. A registry answering schema_violation here would be asserting a validation that never ran — and that code is pinned to HTTP 400. Registries advertising acdp_version below 0.5.0 MUST NOT emit this code.

§

NotImplemented(String)

Wire code: not_implemented — endpoint or feature not supported by this registry.

§

NotFound(String)

Wire code: not_found.

§

NotAuthorized(String)

Wire code: not_authorized — the caller is not permitted to access this resource.

§

RateLimited(String)

Wire code: rate_limited.

§

CursorExpired

Wire code: cursor_expired.

§

InvalidCursor(String)

Wire code: invalid_cursor.

§

SupersededTarget

Wire code: superseded_target. The supersession target was rejected; the SupersessionReason disambiguates the cause.

Fields

§reason: SupersessionReason

Why the target was rejected.

§message: String

Human-readable message from the registry.

§

DuplicatePublish(String)

Wire code: duplicate_publish — an Idempotency-Key replay produced a different request body than the original.

§

CrossRegistryResolutionFailed(String)

Wire code: cross_registry_resolution_failed.

§

InvalidReceipt(String)

Wire code: invalid_receipt. A registry_receipt failed verification: bad signature, a cross-check mismatch (ctx_id, content_hash, key_fingerprint, serving authority), a malformed shape, or a receipt required by policy but absent. Permanent — the receipt will not verify on retry.

§

InvalidLogProof(String)

Wire code: invalid_log_proof (RFC-ACDP-0012 §9, §11 — 0.3.0). A transparency-log artifact failed verification: an inclusion proof that does not fold to the checkpoint’s root, a failed consistency proof between tree sizes, or a checkpoint whose signature does not verify. Permanent — a bad proof will not verify on retry (HTTP 502 on the wire: the upstream log is at fault when a federated resolver emits it).

§

InvalidWitnessCosignature(String)

Wire code: invalid_witness_cosignature (RFC-ACDP-0015 §8, §10 — 0.4.0). A transparency-log witness cosignature failed the §8 verification procedure: closed parse, the witness-key signature, witness binding (signature.key_id DID ≠ witness_id), checkpoint binding (witnessed_checkpoint ≠ the checkpoint being evaluated), or the witnessed_at skew check. Deliberately distinct from AcdpError::InvalidLogProof: that indicts the log (tree membership, history consistency, the registry’s checkpoint signature); this indicts a witness’s attestation — an independent verdict over an independent signer (RFC-ACDP-0015 §10). Permanent — a bad cosignature will not verify on retry (HTTP 502 on the wire: the cosignature came from an upstream party — a registry aggregating on a caller’s behalf or a resolver validating a witness’s cosignatures). A cosignature that verifies but is merely stale is consumer freshness policy (§8.1), never this code.

§

ImmutableField(String)

Wire code: immutable_field (RFC-ACDP-0013 §6, §10 — 0.3.0; activated from the v0.1.0 reservation). A lifecycle (or future mutation) endpoint request attempted to supply or alter immutable body content. Bodies are immutable; lifecycle endpoints mutate registry state only. Permanent (HTTP 400).

§

InvalidLifecycleTransition(String)

Wire code: invalid_lifecycle_transition (RFC-ACDP-0013 §6 step 4, §10 — 0.3.0). The requested lifecycle transition conflicts with the context’s current retraction state (retract of an already-retracted context; republish of a never-retracted one). A state conflict like the 409 arm of superseded_target; retryable only after the state changes (HTTP 409).

§

RegistryInternal(String)

Wire code: internal_error.

§

Registry(WireError)

Catch-all for WireError codes that have no typed variant in this version of the library. Forward-compatible: registries may emit reserved codes (unsupported_embedding_model) that future ACDP versions add.

§

Serialization(String)

JSON (de)serialization failed.

§

Http(String)

HTTP transport error.

Implementations§

Source§

impl AcdpError

Source

pub fn is_transient(&self) -> bool

Whether this error is plausibly transient and worth retrying with the same request body (and, if applicable, the same Idempotency-Key).

Returned by AcdpError::is_transient only for variants whose wire codes the spec marks retryable: key_resolution_unreachable (RFC-ACDP-0001 §5.11), rate_limited (RFC-ACDP-0008 §4.3), cross_registry_resolution_failed (RFC-ACDP-0006 §7), and internal_error (RFC-ACDP-0007 §5). Generic Http transport errors are conservatively treated as transient since they usually mean DNS or TCP-level glitches.

All cryptographic, schema, and authorization errors are NOT transient: a malformed body or invalid signature will not magically validate on retry.

AcdpError::RevocationDiscoveryFailed is a wrapper, not a wire code, so it is exempted from the list above and instead delegates to its own source — a retry-aware caller unwrapping the wrapper still gets the right answer.

Source

pub fn from_wire_error(wire: WireError) -> Self

Map a wire-protocol [crate::wire_error::WireError] into a typed AcdpError.

Codes the library does not yet recognize are returned as AcdpError::Registry for forward compatibility.

Trait Implementations§

Source§

impl Clone for AcdpError

Source§

fn clone(&self) -> AcdpError

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for AcdpError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for AcdpError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for AcdpError

Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0:

use the Display impl or to_string()

1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0:

replaced by Error::source, which can support downcasting

Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<Error> for AcdpError

Source§

fn from(e: Error) -> Self

Converts to this type from the input type.
Source§

impl From<Error> for AcdpError

Source§

fn from(e: Error) -> Self

Converts to this type from the input type.
Source§

impl From<Error> for AcdpError

Available on crate feature reqwest only.
Source§

fn from(e: Error) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T> ToString for T
where T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more