use log::warn;
use crate::errors::Error;
use crate::messages::{is_informational_code, routes_by_request_id, IncomingMessages, Notice, ResponseMessage};
use super::RoutedItem;
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum RoutingDecision {
ByRequestId(i32),
ByOrderId(i32),
ByMessageType(IncomingMessages),
SharedMessage(IncomingMessages),
Error(DecodedError),
Shutdown,
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct DecodedError {
pub request_id: i32,
pub error_code: i32,
pub error_message: String,
pub error_time: Option<i64>,
pub advanced_order_reject_json: String,
}
impl Default for DecodedError {
fn default() -> Self {
Self {
request_id: UNSPECIFIED_REQUEST_ID,
error_code: 0,
error_message: String::new(),
error_time: None,
advanced_order_reject_json: String::new(),
}
}
}
pub(crate) fn decode_error_envelope(raw_bytes: &[u8]) -> Option<DecodedError> {
let envelope: crate::proto::ErrorMessage = prost::Message::decode(raw_bytes).ok()?;
Some(DecodedError {
request_id: envelope.id.unwrap_or(UNSPECIFIED_REQUEST_ID),
error_code: envelope.error_code.unwrap_or(0),
error_message: envelope.error_msg.unwrap_or_default(),
error_time: envelope.error_time,
advanced_order_reject_json: envelope.advanced_order_reject_json.unwrap_or_default(),
})
}
fn is_order_message(message_type: IncomingMessages) -> bool {
matches!(
message_type,
IncomingMessages::OrderStatus
| IncomingMessages::OrderBound
| IncomingMessages::OpenOrder
| IncomingMessages::OpenOrderEnd
| IncomingMessages::CompletedOrder
| IncomingMessages::CompletedOrdersEnd
| IncomingMessages::ExecutionData
| IncomingMessages::ExecutionDataEnd
| IncomingMessages::CommissionsReport
)
}
fn is_shared_message(message_type: IncomingMessages) -> bool {
matches!(
message_type,
IncomingMessages::ManagedAccounts | IncomingMessages::NextValidId | IncomingMessages::CurrentTime
)
}
pub(crate) fn determine_routing(message: &ResponseMessage) -> RoutingDecision {
let message_type = message.message_type();
if message_type == IncomingMessages::Shutdown {
return RoutingDecision::Shutdown;
}
if message_type == IncomingMessages::Error {
let raw_bytes = message.raw_bytes();
let decoded = raw_bytes.and_then(decode_error_envelope).unwrap_or_else(|| {
if let Some(bytes) = raw_bytes {
warn!(
"error frame did not decode as an ErrorMessage proto ({} bytes); routing it with default fields",
bytes.len()
);
}
DecodedError::default()
});
return RoutingDecision::Error(decoded);
}
if is_order_message(message_type) {
return RoutingDecision::ByOrderId(message.order_id().unwrap_or(-1));
}
if is_shared_message(message_type) {
return RoutingDecision::SharedMessage(message_type);
}
if let Some(request_id) = message.request_id() {
return RoutingDecision::ByRequestId(request_id);
}
RoutingDecision::ByMessageType(message_type)
}
fn routable_to_request_id_subscription(message_type: IncomingMessages) -> bool {
is_order_message(message_type) || routes_by_request_id(message_type)
}
pub(crate) fn first_unroutable_by_request_id(message_types: &[IncomingMessages]) -> Option<IncomingMessages> {
message_types.iter().copied().find(|&kind| !routable_to_request_id_subscription(kind))
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum OrderRoutingStrategy {
OrderUpdateOnly,
ExecutionData,
ExecutionDataEnd,
OrderOrShared,
ByExecutionId,
SharedOnly,
ByOrderId,
}
pub(crate) fn order_routing_strategy(message_type: IncomingMessages) -> OrderRoutingStrategy {
match message_type {
IncomingMessages::OrderBound => OrderRoutingStrategy::OrderUpdateOnly,
IncomingMessages::ExecutionData => OrderRoutingStrategy::ExecutionData,
IncomingMessages::ExecutionDataEnd => OrderRoutingStrategy::ExecutionDataEnd,
IncomingMessages::OpenOrder | IncomingMessages::OrderStatus => OrderRoutingStrategy::OrderOrShared,
IncomingMessages::CommissionsReport => OrderRoutingStrategy::ByExecutionId,
IncomingMessages::CompletedOrder | IncomingMessages::OpenOrderEnd | IncomingMessages::CompletedOrdersEnd => OrderRoutingStrategy::SharedOnly,
_ => OrderRoutingStrategy::ByOrderId,
}
}
pub(crate) const UNSPECIFIED_REQUEST_ID: i32 = -1;
pub(crate) fn order_update_notice(payload: &DecodedError, id_owned_by_data_request: bool) -> Option<Notice> {
(payload.request_id != UNSPECIFIED_REQUEST_ID && !id_owned_by_data_request).then(|| Notice::from(payload.clone()))
}
#[derive(Debug)]
pub(crate) enum ErrorDisposition {
NoticeOnly(Notice),
NoticeAndFailOneShots(Notice, Error),
Route(i32, RoutedItem),
}
pub(crate) fn classify_error(payload: DecodedError) -> ErrorDisposition {
let request_id = payload.request_id;
let is_informational = is_informational_code(payload.error_code, &payload.error_message);
if request_id == UNSPECIFIED_REQUEST_ID {
let notice = Notice::from(payload.clone());
if is_informational {
ErrorDisposition::NoticeOnly(notice)
} else {
ErrorDisposition::NoticeAndFailOneShots(notice, Error::from(payload))
}
} else {
let item = if is_informational {
RoutedItem::Notice(Notice::from(payload))
} else {
RoutedItem::Error(Error::from(payload))
};
ErrorDisposition::Route(request_id, item)
}
}
#[cfg(test)]
#[path = "routing_tests.rs"]
mod tests;