architect_api/orderflow/
order_id.rs

1use anyhow::{bail, Result};
2use base64::{engine::general_purpose::URL_SAFE, Engine as _};
3#[cfg(feature = "tokio-postgres")]
4use bytes::BytesMut;
5use schemars::JsonSchema;
6use serde_with::{DeserializeFromStr, SerializeDisplay};
7#[cfg(feature = "tokio-postgres")]
8use std::error::Error;
9use std::{fmt, str::FromStr};
10use uuid::Uuid;
11
12/// System-unique, persistent order identifiers
13/// <!-- py: type=string -->
14#[derive(
15    Debug,
16    Clone,
17    Copy,
18    Hash,
19    PartialEq,
20    Eq,
21    PartialOrd,
22    Ord,
23    SerializeDisplay,
24    DeserializeFromStr,
25    JsonSchema,
26    bytemuck::Pod,
27    bytemuck::Zeroable,
28)]
29#[cfg_attr(feature = "juniper", derive(juniper::GraphQLScalar))]
30#[repr(C)]
31pub struct OrderId {
32    pub seqid: Uuid,
33    pub seqno: u64,
34}
35
36impl OrderId {
37    /// For use in tests and non-effecting operations only!
38    /// For production use, use an OrderIdAllocator from the `sdk` crate.
39    pub fn nil(seqno: u64) -> Self {
40        Self { seqid: Uuid::nil(), seqno }
41    }
42
43    pub fn random() -> Self {
44        Self { seqid: Uuid::new_v4(), seqno: 0 }
45    }
46
47    pub fn exchange(namespace: &Uuid, exchange_id: impl AsRef<[u8]>) -> Self {
48        let seqid = Uuid::new_v5(namespace, exchange_id.as_ref());
49        Self { seqid, seqno: 0 }
50    }
51
52    pub fn as_bytes(&self) -> &[u8] {
53        bytemuck::bytes_of(self)
54    }
55
56    pub fn try_from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self> {
57        match bytemuck::try_from_bytes(bytes.as_ref()) {
58            Ok(oid) => Ok(*oid),
59            Err(e) => bail!("casting bytes to OrderId: {e}"),
60        }
61    }
62
63    pub fn encode_base64(&self) -> String {
64        URL_SAFE.encode(bytemuck::bytes_of(self))
65    }
66
67    pub fn decode_base64(s: impl AsRef<[u8]>) -> Result<Self> {
68        let bytes = URL_SAFE.decode(s.as_ref())?;
69        Ok(Self::try_from_bytes(bytes)?)
70    }
71}
72
73impl FromStr for OrderId {
74    type Err = anyhow::Error;
75
76    fn from_str(s: &str) -> Result<Self, Self::Err> {
77        match s.split_once(':') {
78            Some((seqid_s, seqno_s)) => {
79                let seqid = Uuid::from_str(seqid_s)?;
80                let seqno = u64::from_str(seqno_s)?;
81                Ok(Self { seqid, seqno })
82            }
83            None => Ok(Self { seqid: Uuid::nil(), seqno: u64::from_str(s)? }),
84        }
85    }
86}
87
88impl fmt::Display for OrderId {
89    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90        if self.seqid.is_nil() {
91            write!(f, "{}", self.seqno)
92        } else {
93            write!(f, "{}:{}", self.seqid, self.seqno)
94        }
95    }
96}
97
98#[cfg(feature = "rusqlite")]
99impl rusqlite::ToSql for OrderId {
100    fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
101        use rusqlite::types::{ToSqlOutput, Value};
102        let val = Value::Text(self.to_string());
103        Ok(ToSqlOutput::Owned(val))
104    }
105}
106
107#[cfg(feature = "tokio-postgres")]
108impl tokio_postgres::types::ToSql for OrderId {
109    tokio_postgres::types::to_sql_checked!();
110
111    fn to_sql(
112        &self,
113        ty: &tokio_postgres::types::Type,
114        out: &mut BytesMut,
115    ) -> std::result::Result<tokio_postgres::types::IsNull, Box<dyn Error + Sync + Send>>
116    {
117        self.to_string().to_sql(ty, out)
118    }
119
120    fn accepts(ty: &tokio_postgres::types::Type) -> bool {
121        String::accepts(ty)
122    }
123}
124
125#[cfg(feature = "sqlx")]
126impl<'a, DB: sqlx::Database> sqlx::Encode<'a, DB> for OrderId
127where
128    String: sqlx::Encode<'a, DB>,
129{
130    fn encode_by_ref(
131        &self,
132        buf: &mut <DB as sqlx::Database>::ArgumentBuffer<'a>,
133    ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
134        let value = self.to_string();
135        <String as sqlx::Encode<DB>>::encode(value, buf)
136    }
137}
138
139#[cfg(feature = "sqlx")]
140impl<'a, DB: sqlx::Database> sqlx::Decode<'a, DB> for OrderId
141where
142    &'a str: sqlx::Decode<'a, DB>,
143{
144    fn decode(
145        value: <DB as sqlx::Database>::ValueRef<'a>,
146    ) -> Result<Self, sqlx::error::BoxDynError> {
147        let value = <&str as sqlx::Decode<DB>>::decode(value)?;
148        Ok(value.parse()?)
149    }
150}
151
152#[cfg(feature = "sqlx")]
153impl<DB: sqlx::Database> sqlx::Type<DB> for OrderId
154where
155    for<'a> &'a str: sqlx::Type<DB>,
156{
157    fn type_info() -> <DB as sqlx::Database>::TypeInfo {
158        <&str as sqlx::Type<DB>>::type_info()
159    }
160}
161
162#[cfg(feature = "juniper")]
163impl OrderId {
164    #[allow(clippy::wrong_self_convention)]
165    fn to_output<S: juniper::ScalarValue>(&self) -> juniper::Value<S> {
166        juniper::Value::scalar(self.to_string())
167    }
168
169    fn from_input<S>(v: &juniper::InputValue<S>) -> Result<Self, String>
170    where
171        S: juniper::ScalarValue,
172    {
173        v.as_string_value()
174            .map(Self::from_str)
175            .ok_or_else(|| format!("Expected `String`, found: {v}"))?
176            .map_err(|e| e.to_string())
177    }
178
179    fn parse_token<S>(value: juniper::ScalarToken<'_>) -> juniper::ParseScalarResult<S>
180    where
181        S: juniper::ScalarValue,
182    {
183        <String as juniper::ParseScalarValue<S>>::from_str(value)
184    }
185}
186
187impl rand::distr::Distribution<OrderId> for rand::distr::StandardUniform {
188    fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> OrderId {
189        OrderId { seqid: Uuid::new_v4(), seqno: rng.random() }
190    }
191}