1use serde::{Deserialize, Deserializer, Serialize, Serializer};
4use std::{fmt, str::FromStr};
5
6#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
8#[error("invalid OANDA identifier")]
9pub struct InvalidId;
10
11macro_rules! id_type {
12 ($name:ident) => {
13 #[doc = concat!("Validated OANDA ", stringify!($name), ".")]
14 #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
15 pub struct $name(String);
16
17 impl $name {
18 pub fn new(value: impl Into<String>) -> Result<Self, InvalidId> {
24 let value = value.into();
25 if value.is_empty() || value.chars().any(char::is_control) {
26 return Err(InvalidId);
27 }
28 Ok(Self(value))
29 }
30
31 pub fn as_str(&self) -> &str {
33 &self.0
34 }
35 }
36
37 impl fmt::Debug for $name {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 f.debug_tuple(stringify!($name)).field(&self.0).finish()
40 }
41 }
42 impl fmt::Display for $name {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 f.write_str(&self.0)
45 }
46 }
47 impl FromStr for $name {
48 type Err = InvalidId;
49 fn from_str(s: &str) -> Result<Self, Self::Err> {
50 Self::new(s)
51 }
52 }
53 impl Serialize for $name {
54 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
55 serializer.serialize_str(&self.0)
56 }
57 }
58 impl<'de> Deserialize<'de> for $name {
59 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
60 let value = String::deserialize(deserializer)?;
61 Self::new(value).map_err(serde::de::Error::custom)
62 }
63 }
64 };
65}
66
67id_type!(AccountID);
68id_type!(OrderID);
69id_type!(TradeID);
70id_type!(TransactionID);
71id_type!(InstrumentName);
72id_type!(OrderSpecifier);
73id_type!(TradeSpecifier);
74id_type!(ClientID);
75id_type!(ClientTag);
76id_type!(ClientComment);
77id_type!(RequestID);
78id_type!(ClientRequestID);
79id_type!(Currency);
80id_type!(CandleSpecification);
81id_type!(PricingComponent);