1use crate::validation::Validate;
7use crate::{Error, Result};
8use rust_decimal::Decimal;
9use serde::{Deserialize, Deserializer, Serialize, Serializer};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum TradeCloseoutUnits {
17 All,
19 Units(Decimal),
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum PositionCloseoutUnits {
29 All,
31 None,
33 Units(Decimal),
35}
36
37impl Serialize for TradeCloseoutUnits {
38 fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
39 match self {
40 Self::All => serializer.serialize_str("ALL"),
41 Self::Units(value) => serializer.collect_str(value),
42 }
43 }
44}
45
46impl<'de> Deserialize<'de> for TradeCloseoutUnits {
47 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
48 let value = String::deserialize(deserializer)?;
49 match value.as_str() {
50 "ALL" => Ok(Self::All),
51 _ => Decimal::from_str_exact(&value)
52 .map(Self::Units)
53 .map_err(serde::de::Error::custom),
54 }
55 }
56}
57
58impl Serialize for PositionCloseoutUnits {
59 fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
60 match self {
61 Self::All => serializer.serialize_str("ALL"),
62 Self::None => serializer.serialize_str("NONE"),
63 Self::Units(value) => serializer.collect_str(value),
64 }
65 }
66}
67
68impl<'de> Deserialize<'de> for PositionCloseoutUnits {
69 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
70 let value = String::deserialize(deserializer)?;
71 match value.as_str() {
72 "ALL" => Ok(Self::All),
73 "NONE" => Ok(Self::None),
74 _ => Decimal::from_str_exact(&value)
75 .map(Self::Units)
76 .map_err(serde::de::Error::custom),
77 }
78 }
79}
80
81impl Validate for TradeCloseoutUnits {
82 fn validate(&self) -> Result<()> {
83 match self {
84 Self::All => Ok(()),
85 Self::Units(value) => positive(*value),
86 }
87 }
88}
89
90impl Validate for PositionCloseoutUnits {
91 fn validate(&self) -> Result<()> {
92 match self {
93 Self::All | Self::None => Ok(()),
94 Self::Units(value) => positive(*value),
95 }
96 }
97}
98
99fn positive(value: Decimal) -> Result<()> {
100 if value <= Decimal::ZERO {
101 return Err(Error::InvalidInput("close units must be positive".into()));
102 }
103 Ok(())
104}
105
106#[cfg(test)]
107mod tests {
108 use super::{PositionCloseoutUnits, TradeCloseoutUnits};
109 use crate::validation::Validate;
110 use rust_decimal::Decimal;
111
112 #[test]
113 fn exact_closeout_quantities_reject_rounding() -> Result<(), Box<dyn std::error::Error>> {
114 let wire = "\"0.1234567890123456789012345678\"";
115 let quantity: TradeCloseoutUnits = serde_json::from_str(wire)?;
116 assert_eq!(serde_json::to_string(&quantity)?, wire);
117 assert!(
118 serde_json::from_str::<TradeCloseoutUnits>("\"0.12345678901234567890123456789\"")
119 .is_err()
120 );
121 assert!(
122 serde_json::from_str::<PositionCloseoutUnits>("\"0.12345678901234567890123456789\"")
123 .is_err()
124 );
125 assert!(serde_json::from_str::<TradeCloseoutUnits>("\"NONE\"").is_err());
126 Ok(())
127 }
128
129 #[test]
130 fn invalid_outgoing_quantity_keeps_decoded_evidence() -> Result<(), Box<dyn std::error::Error>>
131 {
132 let quantity: TradeCloseoutUnits = serde_json::from_str("\"-1.25\"")?;
133 assert!(quantity.validate().is_err());
134 assert_eq!(serde_json::to_string(&quantity)?, "\"-1.25\"");
135 assert!(
136 PositionCloseoutUnits::Units(Decimal::ZERO)
137 .validate()
138 .is_err()
139 );
140 assert!(PositionCloseoutUnits::All.validate().is_ok());
141 assert!(PositionCloseoutUnits::None.validate().is_ok());
142 Ok(())
143 }
144}