use std::fmt::{Display, Formatter};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum RejectScope {
Order,
Account,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum RejectCode {
MissingRequiredField,
InvalidFieldFormat,
InvalidFieldValue,
UnsupportedOrderType,
UnsupportedTimeInForce,
UnsupportedOrderAttribute,
DuplicateClientOrderId,
TooLateToEnter,
ExchangeClosed,
UnknownInstrument,
UnknownAccount,
UnknownVenue,
UnknownClearingAccount,
UnknownCollateralAsset,
InsufficientFunds,
InsufficientMargin,
InsufficientPosition,
CreditLimitExceeded,
RiskLimitExceeded,
OrderExceedsLimit,
OrderQtyExceedsLimit,
OrderNotionalExceedsLimit,
PositionLimitExceeded,
ConcentrationLimitExceeded,
LeverageLimitExceeded,
RateLimitExceeded,
PnlKillSwitchTriggered,
AccountBlocked,
AccountNotAuthorized,
ComplianceRestriction,
InstrumentRestricted,
JurisdictionRestriction,
WashTradePrevention,
SelfMatchPrevention,
ShortSaleRestriction,
RiskConfigurationMissing,
ReferenceDataUnavailable,
OrderValueCalculationFailed,
SystemUnavailable,
Other,
}
impl RejectCode {
pub const fn as_str(self) -> &'static str {
match self {
Self::MissingRequiredField => "MissingRequiredField",
Self::InvalidFieldFormat => "InvalidFieldFormat",
Self::InvalidFieldValue => "InvalidFieldValue",
Self::UnsupportedOrderType => "UnsupportedOrderType",
Self::UnsupportedTimeInForce => "UnsupportedTimeInForce",
Self::UnsupportedOrderAttribute => "UnsupportedOrderAttribute",
Self::DuplicateClientOrderId => "DuplicateClientOrderId",
Self::TooLateToEnter => "TooLateToEnter",
Self::ExchangeClosed => "ExchangeClosed",
Self::UnknownInstrument => "UnknownInstrument",
Self::UnknownAccount => "UnknownAccount",
Self::UnknownVenue => "UnknownVenue",
Self::UnknownClearingAccount => "UnknownClearingAccount",
Self::UnknownCollateralAsset => "UnknownCollateralAsset",
Self::InsufficientFunds => "InsufficientFunds",
Self::InsufficientMargin => "InsufficientMargin",
Self::InsufficientPosition => "InsufficientPosition",
Self::CreditLimitExceeded => "CreditLimitExceeded",
Self::RiskLimitExceeded => "RiskLimitExceeded",
Self::OrderExceedsLimit => "OrderExceedsLimit",
Self::OrderQtyExceedsLimit => "OrderQtyExceedsLimit",
Self::OrderNotionalExceedsLimit => "OrderNotionalExceedsLimit",
Self::PositionLimitExceeded => "PositionLimitExceeded",
Self::ConcentrationLimitExceeded => "ConcentrationLimitExceeded",
Self::LeverageLimitExceeded => "LeverageLimitExceeded",
Self::RateLimitExceeded => "RateLimitExceeded",
Self::PnlKillSwitchTriggered => "PnlKillSwitchTriggered",
Self::AccountBlocked => "AccountBlocked",
Self::AccountNotAuthorized => "AccountNotAuthorized",
Self::ComplianceRestriction => "ComplianceRestriction",
Self::InstrumentRestricted => "InstrumentRestricted",
Self::JurisdictionRestriction => "JurisdictionRestriction",
Self::WashTradePrevention => "WashTradePrevention",
Self::SelfMatchPrevention => "SelfMatchPrevention",
Self::ShortSaleRestriction => "ShortSaleRestriction",
Self::RiskConfigurationMissing => "RiskConfigurationMissing",
Self::ReferenceDataUnavailable => "ReferenceDataUnavailable",
Self::OrderValueCalculationFailed => "OrderValueCalculationFailed",
Self::SystemUnavailable => "SystemUnavailable",
Self::Other => "Other",
}
}
}
impl Display for RejectCode {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter.write_str(self.as_str())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct Reject {
pub code: RejectCode,
pub reason: String,
pub details: String,
pub policy: &'static str,
pub scope: RejectScope,
}
impl Display for Reject {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
write!(
formatter,
"[{}] {}: {}",
self.policy, self.reason, self.details
)
}
}
impl std::error::Error for Reject {}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Rejects(Vec<Reject>);
impl Rejects {
pub(crate) fn new(rejects: Vec<Reject>) -> Self {
Self(rejects)
}
}
impl std::ops::Deref for Rejects {
type Target = Vec<Reject>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Display for Rejects {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
for (i, reject) in self.0.iter().enumerate() {
if i > 0 {
write!(formatter, "; ")?;
}
Display::fmt(reject, formatter)?;
}
Ok(())
}
}
impl std::error::Error for Rejects {}
impl Reject {
pub fn new(
policy: &'static str,
scope: RejectScope,
code: RejectCode,
reason: impl Into<String>,
details: impl Into<String>,
) -> Self {
Self {
code,
reason: reason.into(),
details: details.into(),
policy,
scope,
}
}
}
#[cfg(test)]
mod tests {
use super::RejectCode;
#[test]
fn reject_code_as_str_and_display_are_stable_for_all_values() {
let cases = [
(RejectCode::MissingRequiredField, "MissingRequiredField"),
(RejectCode::InvalidFieldFormat, "InvalidFieldFormat"),
(RejectCode::InvalidFieldValue, "InvalidFieldValue"),
(RejectCode::UnsupportedOrderType, "UnsupportedOrderType"),
(RejectCode::UnsupportedTimeInForce, "UnsupportedTimeInForce"),
(
RejectCode::UnsupportedOrderAttribute,
"UnsupportedOrderAttribute",
),
(RejectCode::DuplicateClientOrderId, "DuplicateClientOrderId"),
(RejectCode::TooLateToEnter, "TooLateToEnter"),
(RejectCode::ExchangeClosed, "ExchangeClosed"),
(RejectCode::UnknownInstrument, "UnknownInstrument"),
(RejectCode::UnknownAccount, "UnknownAccount"),
(RejectCode::UnknownVenue, "UnknownVenue"),
(RejectCode::UnknownClearingAccount, "UnknownClearingAccount"),
(RejectCode::UnknownCollateralAsset, "UnknownCollateralAsset"),
(RejectCode::InsufficientFunds, "InsufficientFunds"),
(RejectCode::InsufficientMargin, "InsufficientMargin"),
(RejectCode::InsufficientPosition, "InsufficientPosition"),
(RejectCode::CreditLimitExceeded, "CreditLimitExceeded"),
(RejectCode::RiskLimitExceeded, "RiskLimitExceeded"),
(RejectCode::OrderExceedsLimit, "OrderExceedsLimit"),
(RejectCode::OrderQtyExceedsLimit, "OrderQtyExceedsLimit"),
(
RejectCode::OrderNotionalExceedsLimit,
"OrderNotionalExceedsLimit",
),
(RejectCode::PositionLimitExceeded, "PositionLimitExceeded"),
(
RejectCode::ConcentrationLimitExceeded,
"ConcentrationLimitExceeded",
),
(RejectCode::LeverageLimitExceeded, "LeverageLimitExceeded"),
(RejectCode::RateLimitExceeded, "RateLimitExceeded"),
(RejectCode::PnlKillSwitchTriggered, "PnlKillSwitchTriggered"),
(RejectCode::AccountBlocked, "AccountBlocked"),
(RejectCode::AccountNotAuthorized, "AccountNotAuthorized"),
(RejectCode::ComplianceRestriction, "ComplianceRestriction"),
(RejectCode::InstrumentRestricted, "InstrumentRestricted"),
(
RejectCode::JurisdictionRestriction,
"JurisdictionRestriction",
),
(RejectCode::WashTradePrevention, "WashTradePrevention"),
(RejectCode::SelfMatchPrevention, "SelfMatchPrevention"),
(RejectCode::ShortSaleRestriction, "ShortSaleRestriction"),
(
RejectCode::RiskConfigurationMissing,
"RiskConfigurationMissing",
),
(
RejectCode::ReferenceDataUnavailable,
"ReferenceDataUnavailable",
),
(
RejectCode::OrderValueCalculationFailed,
"OrderValueCalculationFailed",
),
(RejectCode::SystemUnavailable, "SystemUnavailable"),
(RejectCode::Other, "Other"),
];
for (code, expected_name) in cases {
assert_eq!(code.as_str(), expected_name);
assert_eq!(code.to_string(), expected_name);
}
}
#[test]
fn reject_display_formats_policy_reason_and_details() {
let reject = super::Reject::new(
"TestPolicy",
super::RejectScope::Order,
RejectCode::Other,
"something went wrong",
"extra info",
);
assert_eq!(
reject.to_string(),
"[TestPolicy] something went wrong: extra info"
);
}
#[test]
fn rejects_deref_gives_slice_access() {
let rejects = super::Rejects::new(vec![super::Reject::new(
"P",
super::RejectScope::Order,
RejectCode::Other,
"r",
"d",
)]);
assert_eq!(rejects.len(), 1);
assert_eq!(rejects[0].policy, "P");
}
#[test]
fn rejects_display_single() {
let rejects = super::Rejects::new(vec![super::Reject::new(
"P",
super::RejectScope::Order,
RejectCode::Other,
"reason",
"details",
)]);
assert_eq!(rejects.to_string(), "[P] reason: details");
}
#[test]
fn rejects_display_multiple_joined_with_separator() {
let rejects = super::Rejects::new(vec![
super::Reject::new(
"P1",
super::RejectScope::Order,
RejectCode::Other,
"r1",
"d1",
),
super::Reject::new(
"P2",
super::RejectScope::Account,
RejectCode::Other,
"r2",
"d2",
),
]);
assert_eq!(rejects.to_string(), "[P1] r1: d1; [P2] r2: d2");
}
#[test]
fn rejects_display_empty_produces_empty_string() {
let rejects = super::Rejects::new(vec![]);
assert_eq!(rejects.to_string(), "");
}
#[test]
fn rejects_display_propagates_error_from_reject_fmt() {
use std::fmt;
use std::fmt::Write;
struct ImmediateFailWriter;
impl Write for ImmediateFailWriter {
fn write_str(&mut self, _: &str) -> fmt::Result {
Err(fmt::Error)
}
}
let rejects = super::Rejects::new(vec![super::Reject::new(
"P",
super::RejectScope::Order,
RejectCode::Other,
"r",
"d",
)]);
assert!(fmt::write(&mut ImmediateFailWriter, format_args!("{rejects}")).is_err());
}
#[test]
fn rejects_display_propagates_write_error() {
use std::fmt;
use std::fmt::Write;
struct FailOnSeparator;
impl Write for FailOnSeparator {
fn write_str(&mut self, s: &str) -> fmt::Result {
if s == "; " {
Err(fmt::Error)
} else {
Ok(())
}
}
}
let rejects = super::Rejects::new(vec![
super::Reject::new("P", super::RejectScope::Order, RejectCode::Other, "r", "d"),
super::Reject::new("P", super::RejectScope::Order, RejectCode::Other, "r", "d"),
]);
assert!(fmt::write(&mut FailOnSeparator, format_args!("{rejects}")).is_err());
}
}