#[non_exhaustive]pub enum Error {
Config(ConfigError),
Transport(TransportError),
RateLimited {
retry_after: Duration,
},
Rejected {
code: String,
message: String,
},
Unknown,
Unsupported {
capability: Cow<'static, str>,
},
NotReconciled,
Degraded,
ShuttingDown,
}Expand description
What can go wrong in a connector call.
The texts printed by Display are the ones on the errors page of the book (docs/src/errors.md),
with what they mean and what to do. A test keeps the two in step.
Each variant says what happened and what the caller does next. Error::class groups them.
Reading calls (order book, history, portfolio) return only the variants that make sense for a
read: Error::Config, Error::Transport, Error::RateLimited, Error::Rejected and
Error::Unsupported. The gate variants (Error::NotReconciled, Error::Degraded,
Error::ShuttingDown) come from trading commands.
§Examples
use std::time::Duration;
use brokapi_core::{Error, ErrorClass};
/// What a caller does with an error of a reading call.
fn next_step(e: &Error) -> &'static str {
match e {
Error::RateLimited { retry_after } if *retry_after < Duration::from_secs(5) => "wait and repeat",
Error::Transport(_) => "repeat later",
Error::Unsupported { .. } => "ask the broker another way, or skip",
_ => "give up and report",
}
}
let e = Error::RateLimited { retry_after: Duration::from_secs(1) };
assert_eq!(e.class(), ErrorClass::Retryable);
assert_eq!(next_step(&e), "wait and repeat");Variants (Non-exhaustive)§
This enum is marked as non-exhaustive
Config(ConfigError)
The setup is wrong: a bad config value, or an input that cannot work with this broker (for
example an InstrumentId whose NativeId is of
another broker’s kind).
The broker refusing the credentials (a wrong or expired token, an authentication that failed) is this variant too, at every broker: fix the token, a repeat will not help.
Nothing was sent. The call will fail the same way until the config or the input is fixed.
Transport(TransportError)
The network or the broker’s endpoint failed in a way that is not an answer: connect, TLS,
timeout of a read, a stream cut, an unreadable reply. (A refused authentication is
Error::Config.)
For a reading call, repeat it later. For a trading command the connector returns
Error::Unknown instead whenever the command might have reached the broker, so
Transport on a command means the connector has no sign that it did (for example the
authentication failed). Even then, look at the order before you send a command again, and use
a new cid for a new place.
RateLimited
A rate limit stopped the call: the local limiter of the connector or the broker’s own answer (HTTP 429 or the equivalent).
Wait retry_after, then repeat. A command refused by the local limiter was not sent. A
connector may still have used up the cid (see its docs), so for a place the safe rule is a
new cid. The library does not wait and repeat for you.
Fields
Rejected
The broker (or the connector, before sending) said no to this request, and nothing was done.
The request is wrong or not allowed: a price outside the band, no money, an unknown order, an order that is already filled, a missing price on a limit order. Change the request. Do not repeat it as it is.
code is a short machine-readable label that the connector sets (for example validation,
missing_price, invalid_qty, not_found, already_filled, duplicate_place, http_400). message is
the broker’s text or the connector’s explanation, for a human. The set of codes belongs to the
connector and is not fixed across brokers, except NOT_FOUND (the same at every broker: use
Error::is_not_found) and the codes the shared set of checks relies on (duplicate_place,
invalid_qty). Do not parse message.
Fields
Unknown
A command (place, cancel, replace, place_stop, cancel_stop) was sent, and no answer says whether the broker did it: a timeout, a connection cut after sending, an HTTP 5xx, an answer that cannot be read.
The order may exist, may be cancelled, or may never have arrived. The library NEVER repeats the command, because a repeated place could create a second order. Do the same:
- Do not send the command again.
- Ask the broker:
OrderExecutor::order_statewith thecid, orOrderExecutor::active_orders, or look for the fills inExecutionSource::executions. - Only when the broker has no such order, place a new one under a NEW
cid.
Never turned into Error::Transport or Error::Rejected: those would say “nothing
happened”, and here that is not known.
Unsupported
This connector or this broker does not have the capability, so the call was not made.
Returned at once, without a network call. capability is a short name of what is missing
(for example stop_order_stream). The library does not emulate a missing stream by polling.
If you need the data, poll yourself. See also UserDataCaps and
MarketDataCaps, which declare what a connector can do.
Fields
NotReconciled
The trading gate is closed: this connector has not run OrderExecutor::reconcile
successfully yet.
Trading commands wait for the first reconcile, so that the connector knows which orders are
already at the broker before it puts a new one. Call reconcile with your ledger (an empty
slice if you have none), then repeat the command. Nothing was sent.
Degraded
The trading gate is closed because the connector lost events it could not afford to lose: a
Backpressure::Lossless subscription overflowed (an
Event::Overflow was sent).
The local state of orders may have a hole. Drain your event channel, then call
OrderExecutor::reconcile: a successful reconcile opens the gate again.
Nothing was sent.
ShuttingDown
The connector is shutting down and takes no new trading commands.
Final for this connector object. Orders already at the exchange are not touched by a shutdown. Start a new connector if you need to trade. Nothing was sent.
Implementations§
Source§impl Error
impl Error
Sourcepub fn not_found(message: impl Into<String>) -> Self
pub fn not_found(message: impl Into<String>) -> Self
A Error::Rejected with the code NOT_FOUND: the broker does not know the order, the cid or
the instrument. Every connector uses this one for “not found”.
Sourcepub fn is_not_found(&self) -> bool
pub fn is_not_found(&self) -> bool
Whether this is the “not found” refusal (Error::Rejected with the code NOT_FOUND).
§Examples
use brokapi_core::Error;
assert!(Error::not_found("x").is_not_found());
assert!(!Error::Unknown.is_not_found());
assert!(!Error::Rejected { code: "validation".into(), message: String::new() }.is_not_found());Sourcepub fn class(&self) -> ErrorClass
pub fn class(&self) -> ErrorClass
The coarse class of this error: may the call be tried again, or not.
| Variant | Class |
|---|---|
Error::Transport, Error::RateLimited | ErrorClass::Retryable |
Error::Unknown | ErrorClass::Unknown |
| all others | ErrorClass::Fatal |
The class says what is possible in principle, and it is the class of a read. A generic retry
loop must not use it for a trading command: Error::Transport is Retryable here, and a
command that failed with it may still have reached the broker. For a command ask
retryable_for_command. The library itself never repeats a
command, whatever the class.
§Examples
use brokapi_core::{Error, ErrorClass, TransportError};
let e = Error::Transport(TransportError::Message("timeout".into()));
assert_eq!(e.class(), ErrorClass::Retryable);Sourcepub fn retryable_for_command(&self) -> bool
pub fn retryable_for_command(&self) -> bool
May a trading command that failed with this error be sent again, after the wait the error names?
true only for Error::RateLimited: the broker (or the local limiter) refused before it acted,
so the same cancel can be sent again after retry_after. A place needs a new cid, because the
connector may have used the old one up. Everything else is false: Error::Transport and
Error::Unknown mean the command may have reached the broker, so look at the order first
(OrderExecutor::order_state), and the other variants
are final until the setup, the gate or the request changes.
Use this, not class, in the retry loop of a command.
§Examples
use std::time::Duration;
use brokapi_core::{Error, TransportError};
assert!(Error::RateLimited { retry_after: Duration::from_secs(1) }.retryable_for_command());
// A read may be repeated after this error, a command may not.
let cut = Error::Transport(TransportError::Message("connection reset".into()));
assert!(!cut.retryable_for_command());
assert!(!Error::Unknown.retryable_for_command());
assert!(!Error::Degraded.retryable_for_command());Trait Implementations§
Source§impl<'de> Deserialize<'de> for Error
impl<'de> Deserialize<'de> for Error
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Source§impl Error for Error
impl Error for Error
Source§fn source(&self) -> Option<&(dyn Error + 'static)>
fn source(&self) -> Option<&(dyn Error + 'static)>
1.0.0 · Source§fn description(&self) -> &str
fn description(&self) -> &str
use the Display impl or to_string()