use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use super::common::OrderType;
use super::user::User;
#[derive(Debug, Clone, Deserialize)]
pub struct Order {
pub id: String,
#[serde(rename = "type")]
pub order_type: OrderType,
pub platinum: u32,
pub quantity: u32,
#[serde(rename = "itemId")]
pub item_id: String,
pub visible: bool,
#[serde(rename = "createdAt")]
pub created_at: DateTime<Utc>,
#[serde(rename = "updatedAt")]
pub updated_at: DateTime<Utc>,
#[serde(rename = "perTrade", default)]
pub per_trade: Option<u32>,
#[serde(default)]
pub subtype: Option<String>,
#[serde(default)]
pub rank: Option<u8>,
#[serde(default)]
pub charges: Option<u8>,
#[serde(rename = "amberStars", default)]
pub amber_stars: Option<u8>,
#[serde(rename = "cyanStars", default)]
pub cyan_stars: Option<u8>,
#[serde(default)]
pub group: Option<String>,
}
impl Order {
pub fn is_buy(&self) -> bool {
matches!(self.order_type, OrderType::Buy)
}
pub fn is_sell(&self) -> bool {
matches!(self.order_type, OrderType::Sell)
}
pub fn total_value(&self) -> u64 {
self.platinum as u64 * self.quantity as u64
}
pub fn is_mod_order(&self) -> bool {
self.rank.is_some()
}
pub fn is_sculpture_order(&self) -> bool {
self.amber_stars.is_some() || self.cyan_stars.is_some()
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct OrderListing {
#[serde(flatten)]
pub order: Order,
pub user: User,
}
impl OrderListing {
pub fn is_user_available(&self) -> bool {
self.user.is_available()
}
pub fn into_order(self) -> Order {
self.order
}
}
impl std::ops::Deref for OrderListing {
type Target = Order;
fn deref(&self) -> &Self::Target {
&self.order
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct OwnedOrderId(String);
impl OwnedOrderId {
pub fn from_raw(id: impl Into<String>) -> Self {
Self(id.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn into_inner(self) -> String {
self.0
}
}
impl AsRef<str> for OwnedOrderId {
fn as_ref(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for OwnedOrderId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<String> for OwnedOrderId {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for OwnedOrderId {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
#[derive(Debug, Clone)]
pub struct OwnedOrder {
id: OwnedOrderId,
pub order: Order,
}
impl OwnedOrder {
pub(crate) fn new(order: Order) -> Self {
Self {
id: OwnedOrderId(order.id.clone()),
order,
}
}
pub fn id(&self) -> &OwnedOrderId {
&self.id
}
pub fn order_type(&self) -> OrderType {
self.order.order_type
}
pub fn platinum(&self) -> u32 {
self.order.platinum
}
pub fn quantity(&self) -> u32 {
self.order.quantity
}
pub fn item_id(&self) -> &str {
&self.order.item_id
}
pub fn is_visible(&self) -> bool {
self.order.visible
}
pub fn created_at(&self) -> DateTime<Utc> {
self.order.created_at
}
pub fn updated_at(&self) -> DateTime<Utc> {
self.order.updated_at
}
pub fn into_order(self) -> Order {
self.order
}
}
impl std::ops::Deref for OwnedOrder {
type Target = Order;
fn deref(&self) -> &Self::Target {
&self.order
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct TopOrders {
pub buy: Vec<OrderListing>,
pub sell: Vec<OrderListing>,
}
impl TopOrders {
pub fn all(&self) -> impl Iterator<Item = &OrderListing> {
self.buy.iter().chain(self.sell.iter())
}
pub fn best_buy_price(&self) -> Option<u32> {
self.buy.first().map(|o| o.order.platinum)
}
pub fn best_sell_price(&self) -> Option<u32> {
self.sell.first().map(|o| o.order.platinum)
}
pub fn spread(&self) -> Option<i32> {
match (self.best_sell_price(), self.best_buy_price()) {
(Some(sell), Some(buy)) => Some(sell as i32 - buy as i32),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_order() -> Order {
Order {
id: "test-id".to_string(),
order_type: OrderType::Sell,
platinum: 100,
quantity: 5,
item_id: "item-123".to_string(),
visible: true,
created_at: Utc::now(),
updated_at: Utc::now(),
per_trade: None,
subtype: None,
rank: None,
charges: None,
amber_stars: None,
cyan_stars: None,
group: None,
}
}
#[test]
fn test_order_is_sell() {
let order = make_order();
assert!(order.is_sell());
assert!(!order.is_buy());
}
#[test]
fn test_order_total_value() {
let order = make_order();
assert_eq!(order.total_value(), 500);
}
#[test]
fn test_owned_order_id() {
let id = OwnedOrderId::from_raw("test-id");
assert_eq!(id.as_str(), "test-id");
assert_eq!(format!("{}", id), "test-id");
}
#[test]
fn test_owned_order_id_serialization() {
let id = OwnedOrderId::from_raw("test-id");
let json = serde_json::to_string(&id).unwrap();
assert_eq!(json, "\"test-id\"");
let restored: OwnedOrderId = serde_json::from_str(&json).unwrap();
assert_eq!(restored, id);
}
#[test]
fn test_owned_order() {
let order = make_order();
let owned = OwnedOrder::new(order);
assert_eq!(owned.id().as_str(), "test-id");
assert_eq!(owned.platinum(), 100);
assert_eq!(owned.quantity(), 5);
}
#[test]
fn test_owned_order_deref() {
let order = make_order();
let owned = OwnedOrder::new(order);
assert!(owned.is_sell());
assert_eq!(owned.total_value(), 500);
}
}