architect_api/utils/
dir.rs

1use anyhow::{bail, Result};
2#[cfg(feature = "tokio-postgres")]
3use bytes::BytesMut;
4use rust_decimal::Decimal;
5use rust_decimal_macros::dec;
6use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8#[cfg(feature = "tokio-postgres")]
9use std::error::Error;
10use std::{
11    ops::{Deref, Mul},
12    str::FromStr,
13};
14
15/// An order side/direction or a trade execution side/direction.
16/// In GraphQL these are serialized as "buy" or "sell".
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
18#[cfg_attr(feature = "juniper", derive(juniper::GraphQLScalar))]
19#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
20#[cfg_attr(
21    feature = "sqlx",
22    sqlx(type_name = "TEXT", rename_all = "SCREAMING_SNAKE_CASE")
23)]
24#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
25pub enum Dir {
26    #[serde(alias = "Buy", alias = "buy", alias = "BUY")]
27    Buy,
28    #[serde(alias = "Sell", alias = "sell", alias = "SELL")]
29    Sell,
30}
31
32impl Mul<Dir> for Dir {
33    type Output = Dir;
34
35    fn mul(self, rhs: Dir) -> Dir {
36        match (self, rhs) {
37            (Dir::Buy, Dir::Buy) => Dir::Buy,
38            (Dir::Sell, Dir::Sell) => Dir::Buy,
39            (Dir::Buy, Dir::Sell) => Dir::Sell,
40            (Dir::Sell, Dir::Buy) => Dir::Sell,
41        }
42    }
43}
44
45#[cfg(feature = "rusqlite")]
46impl rusqlite::ToSql for Dir {
47    fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
48        use rusqlite::types::{ToSqlOutput, ValueRef};
49        let value_ref = match self {
50            Self::Buy => ValueRef::Text("BUY".as_ref()),
51            Self::Sell => ValueRef::Text("SELL".as_ref()),
52        };
53        Ok(ToSqlOutput::Borrowed(value_ref))
54    }
55}
56
57#[cfg(feature = "tokio-postgres")]
58impl tokio_postgres::types::ToSql for Dir {
59    tokio_postgres::types::to_sql_checked!();
60
61    fn to_sql(
62        &self,
63        ty: &tokio_postgres::types::Type,
64        out: &mut BytesMut,
65    ) -> std::result::Result<tokio_postgres::types::IsNull, Box<dyn Error + Sync + Send>>
66    {
67        let value_repr = match self {
68            Self::Buy => "BUY",
69            Self::Sell => "SELL",
70        };
71        value_repr.to_sql(ty, out)
72    }
73
74    fn accepts(ty: &tokio_postgres::types::Type) -> bool {
75        String::accepts(ty)
76    }
77}
78
79#[cfg(feature = "juniper")]
80impl Dir {
81    #[allow(clippy::wrong_self_convention)]
82    fn to_output<S: juniper::ScalarValue>(&self) -> juniper::Value<S> {
83        juniper::Value::scalar(self.to_str_lowercase().to_string())
84    }
85
86    fn from_input<S>(v: &juniper::InputValue<S>) -> std::result::Result<Self, String>
87    where
88        S: juniper::ScalarValue,
89    {
90        v.as_string_value()
91            .map(Self::from_str_lowercase)
92            .ok_or_else(|| format!("Expected `String`, found: {v}"))?
93            .map_err(|e| e.to_string())
94    }
95
96    fn parse_token<S>(value: juniper::ScalarToken<'_>) -> juniper::ParseScalarResult<S>
97    where
98        S: juniper::ScalarValue,
99    {
100        <String as juniper::ParseScalarValue<S>>::from_str(value)
101    }
102}
103
104impl FromStr for Dir {
105    type Err = anyhow::Error;
106
107    fn from_str(s: &str) -> Result<Self, Self::Err> {
108        match s {
109            "buy" | "Buy" | "BUY" => Ok(Self::Buy),
110            "sell" | "Sell" | "SELL" => Ok(Self::Sell),
111            _ => Err(anyhow::anyhow!("invalid format: {s}")),
112        }
113    }
114}
115
116impl Dir {
117    /// flip the direction Buy -> Sell, Sell -> Buy
118    pub fn flip(self) -> Self {
119        match self {
120            Self::Buy => Self::Sell,
121            Self::Sell => Self::Buy,
122        }
123    }
124
125    pub fn to_str_uppercase(&self) -> &'static str {
126        match self {
127            Self::Buy => "BUY",
128            Self::Sell => "SELL",
129        }
130    }
131
132    pub fn from_str_uppercase(s: &str) -> Result<Self> {
133        match s {
134            "BUY" => Ok(Self::Buy),
135            "SELL" => Ok(Self::Sell),
136            _ => bail!("invalid format: {}", s),
137        }
138    }
139
140    pub fn to_str_lowercase(&self) -> &'static str {
141        match self {
142            Self::Buy => "buy",
143            Self::Sell => "sell",
144        }
145    }
146
147    pub fn from_str_lowercase(s: &str) -> Result<Self> {
148        match s {
149            "buy" => Ok(Self::Buy),
150            "sell" => Ok(Self::Sell),
151            _ => bail!("invalid format: {}", s),
152        }
153    }
154
155    pub fn position_sign(&self) -> Decimal {
156        match self {
157            Self::Buy => dec!(1),
158            Self::Sell => dec!(-1),
159        }
160    }
161
162    #[inline]
163    pub fn sign(value: Decimal) -> Option<Dir> {
164        if value.is_sign_positive() {
165            return Some(Dir::Buy);
166        }
167        if value.is_zero() {
168            return None;
169        }
170        Some(Dir::Sell)
171    }
172}
173
174/// Wrapper around `Dir` that serializes to a single uppercase character.
175#[derive(Debug, Clone, Copy, PartialEq, Eq, JsonSchema)]
176pub struct DirAsCharUpper(Dir);
177
178impl Deref for DirAsCharUpper {
179    type Target = Dir;
180
181    fn deref(&self) -> &Self::Target {
182        &self.0
183    }
184}
185
186impl From<Dir> for DirAsCharUpper {
187    fn from(dir: Dir) -> Self {
188        Self(dir)
189    }
190}
191
192impl From<DirAsCharUpper> for Dir {
193    fn from(val: DirAsCharUpper) -> Self {
194        val.0
195    }
196}
197
198impl Serialize for DirAsCharUpper {
199    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
200    where
201        S: serde::Serializer,
202    {
203        serializer.serialize_char(match **self {
204            Dir::Buy => 'B',
205            Dir::Sell => 'S',
206        })
207    }
208}
209
210struct DirAsCharUpperVisitor;
211
212impl serde::de::Visitor<'_> for DirAsCharUpperVisitor {
213    type Value = DirAsCharUpper;
214
215    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
216        formatter.write_str("a single uppercase character")
217    }
218}
219
220impl<'de> Deserialize<'de> for DirAsCharUpper {
221    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
222    where
223        D: serde::Deserializer<'de>,
224    {
225        deserializer.deserialize_char(DirAsCharUpperVisitor)
226    }
227}