pub struct ApiError {
status: StatusCode,
body: ErrorBody,
headers: Vec<(HeaderName, HeaderValue)>,
}Expand description
A failure rendered as an HTTP status plus an ErrorBody.
Fields§
§status: StatusCode§body: ErrorBody§headers: Vec<(HeaderName, HeaderValue)>Extra response headers, such as WWW-Authenticate or DPoP-Nonce.
A client’s next move after AuthenticationRequired, ExpiredToken
and InvalidToken differs — ask for a credential, refresh, or give
up — and a header is how it learns which without parsing the message.
Empty for almost every error this crate raises, which is why it is a
field rather than a second constructor for every variant above.
Implementations§
Source§impl ApiError
impl ApiError
Sourcepub fn new(
status: StatusCode,
error: impl Into<String>,
message: impl Into<String>,
) -> Self
pub fn new( status: StatusCode, error: impl Into<String>, message: impl Into<String>, ) -> Self
Builds an error response from its parts.
Sourcepub fn with_header(self, name: HeaderName, value: impl Into<String>) -> Self
pub fn with_header(self, name: HeaderName, value: impl Into<String>) -> Self
Attaches a response header, such as WWW-Authenticate.
Sourcepub fn bad_request(message: impl Into<String>) -> Self
pub fn bad_request(message: impl Into<String>) -> Self
A 400 for a body that did not parse or did not make sense.
Sourcepub fn repo_not_found(repo: &str) -> Self
pub fn repo_not_found(repo: &str) -> Self
No repository here, under either a DID or a handle.
RepoNotFound is spelled as atproto’s own personal data server spells
it. The com.atproto.repo.* lexicons declare no error for it — only
RecordNotFound and InvalidSwap — but every com.atproto.sync.*
lexicon that can miss a repository declares this name, and the
reference implementation raises it from describeRepo,
deleteRecord and putRecord alike. Inventing a second spelling for
the same condition is the failure this constructor exists to avoid.
404 rather than the reference implementation’s 400. XRPC clients switch on the name and not the status, the name is the one they know, and “not found” is what the status field is for.
Sourcepub fn repo_mismatch(authenticated: &str, repo: &str) -> Self
pub fn repo_mismatch(authenticated: &str, repo: &str) -> Self
The authenticated agent is not the one the repository belongs to.
RepoMismatch, its own name rather than a reused one: an agent
token is real and unexpired — ApiError::new with
AuthenticationRequired, ExpiredToken or InvalidToken is what a
route answers before this is ever reached — and the caller needs to
know that what failed is whose repository this is, which no retry
of the same credential against the same repository can fix.
plan/auth-types.md treats “an agent never writes another agent’s
repository” as an invariant rather than a policy knob, and this is
where it is enforced: routes::write_record and its siblings compare
the DID auth::require_agent_token resolved against the repository
the request names, before any write reaches the registry.
Sourcepub fn account_mismatch(authenticated: &str, target: &str) -> Self
pub fn account_mismatch(authenticated: &str, target: &str) -> Self
The authenticated agent is not the account this admin route names.
AccountMismatch, its own name for the same reason Self::repo_mismatch
has one: an agent token is real and unexpired, and what failed is
whose account this is. Every account-lifecycle mutation is
self-service — an agent’s own credential, checked against the account
the request names, is the whole of what authorizes them; see
plan/auth-types.md. This is what that credential gets for naming
somebody else’s account.
Sourcepub fn rate_limited(message: impl Into<String>) -> Self
pub fn rate_limited(message: impl Into<String>) -> Self
A caller has exceeded a rate limit; see crate::rate_limit.
RateLimitExceeded names no error the lexicons declare — the same
position MethodNotImplemented’s own doc note (routes::unknown_route)
describes for a route that does not exist at all. 429 is the standard
HTTP status for this and every client is expected to already know it
even when the name is new to it.
Sourcepub fn account_cap_reached(cap: u64) -> Self
pub fn account_cap_reached(cap: u64) -> Self
This deployment already holds as many accounts as its operator has configured room for.
Its own name rather than Halted: that name says a stop was thrown,
by an operator or by this server when its operator’s record is
missing, and neither is true of a full deployment — an account
leaving, or an operator raising the cap, both clear this without
anyone having pulled anything. 503, the same posture
ProvisionError::WriteQueueFull takes: a statement about the
deployment, not about the caller.
Sourcepub fn not_ready(route: &str, snapshot: &LifecycleSnapshot) -> Self
pub fn not_ready(route: &str, snapshot: &LifecycleSnapshot) -> Self
This deployment has not finished being born, or has lost its public standing, and the route asked for is one it therefore defers.
503 with Retry-After, and an error name — ServerNotReady — no
lexicon declares, the same position RateLimitExceeded above is in.
The status and the header are what a caller actually reads, and both
are standard.
§What a relay is meant to conclude
Three conditions a crawler has to tell apart, and this is exactly one of them:
- Try later — this.
503, aRetry-After, a named reason. The server is fine and is not finished. Retry on the hint. - Broken — a refused connection, a
502from something in front, or a500with noRetry-After. Nothing emits a500for this condition, which is what makes the absence of the header meaningful. - Nothing here —
200with an empty list or stream. Fine, and holds nothing. Nothing to retry.
Never a 404: “this method does not exist” and “this method is not
ready” are facts a relay reacts to completely differently, and only
one of them is worth ever asking about again.
The message names the state, which for an unclaimed server is the
whole answer: nobody is currently answerable for this deployment, and
the fix is to run didbot operate.
Sourcepub fn record_not_found(uri: &str) -> Self
pub fn record_not_found(uri: &str) -> Self
The repository is here and the record is not.
Declared by com.atproto.repo.getRecord, which is the one route that
returns it: deleteRecord is specified to succeed when the record was
already absent.
Trait Implementations§
Source§impl From<&ProvisionError> for ApiError
impl From<&ProvisionError> for ApiError
Source§fn from(err: &ProvisionError) -> Self
fn from(err: &ProvisionError) -> Self
Maps a provisioning failure onto a status and an error name.
The Display text of the underlying error becomes the message. Every
error in the chain is written for the caller to read: a secret, a
signing key and the path to a file this deployment keeps are each kept
out of it, and a store refusal carries only the sizes and the sentence
that are about the request itself.
Source§impl From<Halted> for ApiError
The name a request gets when the e-stop has halted it.
impl From<Halted> for ApiError
The name a request gets when the e-stop has halted it.
Halted rather than a generic MethodNotImplemented or an ordinary 503:
atproto clients that do not know the name still see a clear
“unavailable”, and plan/e-stop.md’s own requirement is that the ones
that do know it can tell a halt apart from every other reason a request
might fail. The message says whose latch it is, because that decides
whether waiting helps: an operator’s stop stays until the operator
releases it, and this server’s own pause lifts when the operator’s record
is back. Either way, retrying before then does not.
impl Eq for ApiError
impl StructuralPartialEq for ApiError
Auto Trait Implementations§
impl Freeze for ApiError
impl RefUnwindSafe for ApiError
impl Send for ApiError
impl Sync for ApiError
impl Unpin for ApiError
impl UnsafeUnpin for ApiError
impl UnwindSafe for ApiError
Blanket Implementations§
§impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
§impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
§impl<T, S> Handler<IntoResponseHandler, S> for T
impl<T, S> Handler<IntoResponseHandler, S> for T
§fn call(
self,
_req: Request<Body>,
_state: S,
) -> <T as Handler<IntoResponseHandler, S>>::Future
fn call( self, _req: Request<Body>, _state: S, ) -> <T as Handler<IntoResponseHandler, S>>::Future
§fn layer<L>(self, layer: L) -> Layered<L, Self, T, S>where
L: Layer<HandlerService<Self, T, S>> + Clone,
<L as Layer<HandlerService<Self, T, S>>>::Service: Service<Request<Body>>,
fn layer<L>(self, layer: L) -> Layered<L, Self, T, S>where
L: Layer<HandlerService<Self, T, S>> + Clone,
<L as Layer<HandlerService<Self, T, S>>>::Service: Service<Request<Body>>,
tower::Layer] to the handler. Read more§fn with_state(self, state: S) -> HandlerService<Self, T, S>
fn with_state(self, state: S) -> HandlerService<Self, T, S>
Service] by providing the state§impl<H, T> HandlerWithoutStateExt<T> for Hwhere
H: Handler<T, ()>,
impl<H, T> HandlerWithoutStateExt<T> for Hwhere
H: Handler<T, ()>,
§fn into_service(self) -> HandlerService<H, T, ()>
fn into_service(self) -> HandlerService<H, T, ()>
Service] and no state.§fn into_make_service(self) -> IntoMakeService<HandlerService<H, T, ()>>
fn into_make_service(self) -> IntoMakeService<HandlerService<H, T, ()>>
MakeService and no state. Read more§fn into_make_service_with_connect_info<C>(
self,
) -> IntoMakeServiceWithConnectInfo<HandlerService<H, T, ()>, C>
fn into_make_service_with_connect_info<C>( self, ) -> IntoMakeServiceWithConnectInfo<HandlerService<H, T, ()>, C>
MakeService which stores information
about the incoming connection and has no state. Read more§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more