pragma_common/pair/
mod.rs1use std::str::FromStr;
2
3const STABLE_SUFFIXES: [&str; 4] = ["USDT", "USDC", "USD", "DAI"];
4
5#[derive(Default, Debug, Clone, Eq, PartialEq, Hash)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize,))]
12#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
13pub struct Pair {
14 pub base: String,
15 pub quote: String,
16}
17
18impl Pair {
19 pub fn create_routed_pair(base_pair: &Self, quote_pair: &Self) -> Self {
23 Self {
24 base: base_pair.base.clone(),
25 quote: quote_pair.base.clone(),
26 }
27 }
28
29 pub fn from_currencies(base: &str, quote: &str) -> Self {
31 Self {
32 base: base.to_uppercase(),
33 quote: quote.to_uppercase(),
34 }
35 }
36
37 pub fn from_stable_pair(pair: &str) -> Option<Self> {
40 let pair = pair.to_uppercase();
41 let normalized = pair.replace(['-', '_', '/'], "");
42
43 for stable in STABLE_SUFFIXES {
44 if let Some(base) = normalized.strip_suffix(stable) {
45 return Some(Self {
46 base: base.to_string(),
47 quote: "USD".to_string(),
48 });
49 }
50 }
51 None
52 }
53
54 pub fn as_tuple(&self) -> (String, String) {
56 (self.base.clone(), self.quote.clone())
57 }
58
59 pub fn format_with_separator(&self, separator: &str) -> String {
61 format!("{}{}{}", self.base, separator, self.quote)
62 }
63
64 pub fn to_pair_id(&self) -> String {
66 self.format_with_separator("/")
67 }
68}
69
70impl std::fmt::Display for Pair {
71 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72 write!(f, "{}/{}", self.base, self.quote)
73 }
74}
75
76impl From<Pair> for String {
77 fn from(pair: Pair) -> Self {
78 format!("{0}/{1}", pair.base, pair.quote)
79 }
80}
81
82impl From<&str> for Pair {
83 fn from(pair_id: &str) -> Self {
84 let normalized = pair_id.replace(['-', '_'], "/");
85 let parts: Vec<&str> = normalized.split('/').collect();
86 Self {
87 base: parts[0].trim().to_uppercase(),
88 quote: parts[1].trim().to_uppercase(),
89 }
90 }
91}
92
93impl From<String> for Pair {
94 fn from(pair_id: String) -> Self {
95 Self::from(pair_id.as_str())
96 }
97}
98
99impl FromStr for Pair {
100 type Err = ();
101
102 fn from_str(s: &str) -> Result<Self, Self::Err> {
103 Ok(Self::from(s))
104 }
105}
106
107impl From<(String, String)> for Pair {
108 fn from(pair: (String, String)) -> Self {
109 Self {
110 base: pair.0.to_uppercase(),
111 quote: pair.1.to_uppercase(),
112 }
113 }
114}
115
116#[macro_export]
117macro_rules! pair {
118 ($pair_str:expr) => {{
119 #[allow(dead_code)]
120 const fn validate_pair(s: &str) -> bool {
121 let mut count = 0;
122 let chars = s.as_bytes();
123 let mut i = 0;
124 while i < chars.len() {
125 if chars[i] == b'/' || chars[i] == b'-' || chars[i] == b'_' {
126 count += 1;
127 }
128 i += 1;
129 }
130 count == 1
131 }
132 const _: () = {
133 assert!(
134 validate_pair($pair_str),
135 "Invalid pair format. Expected format: BASE/QUOTE, BASE-QUOTE, or BASE_QUOTE"
136 );
137 };
138 let normalized = $pair_str.replace('-', "/").replace('_', "/");
139 let parts: Vec<&str> = normalized.split('/').collect();
140 Pair {
141 base: parts[0].trim().to_uppercase(),
142 quote: parts[1].trim().to_uppercase(),
143 }
144 }};
145}