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