sharesight_types/
types_prelude.rs

1use serde::de::Unexpected;
2use serde_with::{DeserializeAs, SerializeAs};
3
4pub use std::fmt;
5
6pub use chrono::{DateTime, FixedOffset, NaiveDate};
7pub use serde::de::{DeserializeOwned, Visitor};
8pub use serde::{Deserialize, Serialize};
9pub use serde_with::{serde_as, DefaultOnNull, DisplayFromStr, PickFirst};
10
11pub use crate::codes::*;
12
13#[cfg(all(feature = "rust_decimal", not(feature = "bigdecimal")))]
14pub type Number = rust_decimal::Decimal;
15#[cfg(all(feature = "bigdecimal", not(feature = "rust_decimal")))]
16pub type Number = bigdecimal::BigDecimal;
17#[cfg(all(not(feature = "bigdecimal"), not(feature = "rust_decimal")))]
18pub type Number = f64;
19#[cfg(all(feature = "rust_decimal", feature = "bigdecimal"))]
20compile_error!(
21    "sharesight: Features rust_decimal and bigdecimal are mutually exclusive. Pick one."
22);
23
24pub enum ApiHttpMethod {
25    Get,
26    Post,
27    Patch,
28    Put,
29    Delete,
30}
31
32pub trait ApiEndpoint<'a> {
33    const URL_PATH: &'static str;
34    const HTTP_METHOD: ApiHttpMethod;
35    const VERSION: &'static str;
36
37    type UrlDisplay: 'a + fmt::Display;
38    type Parameters: Serialize;
39    type Success: DeserializeOwned;
40
41    fn url_path(parameters: &'a Self::Parameters) -> Self::UrlDisplay;
42
43    fn url(api_host: &'a str, parameters: &'a Self::Parameters) -> ApiUrl<'a, Self> {
44        ApiUrl(api_host, parameters, Self::VERSION)
45    }
46}
47
48pub struct ApiUrl<'a, T: ApiEndpoint<'a> + ?Sized>(&'a str, &'a T::Parameters, &'a str);
49
50impl<'a, T: ApiEndpoint<'a>> fmt::Display for ApiUrl<'a, T> {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        let Self(api_host, parameters, version) = self;
53
54        let version = if version.starts_with("2.1.") {
55            "v2.1"
56        } else if version.starts_with("2.") {
57            "v2"
58        } else {
59            "v3"
60        };
61        write!(
62            f,
63            "https://{}/api/{}{}",
64            api_host,
65            version,
66            T::url_path(parameters)
67        )
68    }
69}
70
71pub struct DeserializeDate;
72
73impl<'de> DeserializeAs<'de, NaiveDate> for DeserializeDate {
74    fn deserialize_as<D>(deserializer: D) -> Result<NaiveDate, D::Error>
75    where
76        D: serde::Deserializer<'de>,
77    {
78        let s = String::deserialize(deserializer).map_err(serde::de::Error::custom)?;
79        NaiveDate::parse_from_str(&s, "%Y-%m-%d")
80            .or_else(|_| NaiveDate::parse_from_str(&s, "%d %b %Y"))
81            .map_err(serde::de::Error::custom)
82    }
83}
84
85impl<T: serde::Serialize> SerializeAs<T> for DeserializeDate {
86    fn serialize_as<S>(source: &T, serializer: S) -> Result<S::Ok, S::Error>
87    where
88        S: serde::Serializer,
89    {
90        source.serialize(serializer)
91    }
92}
93
94pub struct DeserializeNumber;
95
96impl<'de> DeserializeAs<'de, Number> for DeserializeNumber {
97    fn deserialize_as<D>(deserializer: D) -> Result<Number, D::Error>
98    where
99        D: serde::Deserializer<'de>,
100    {
101        deserializer.deserialize_any(NumberVisitor)
102    }
103}
104
105impl<T: serde::Serialize> SerializeAs<T> for DeserializeNumber {
106    fn serialize_as<S>(source: &T, serializer: S) -> Result<S::Ok, S::Error>
107    where
108        S: serde::Serializer,
109    {
110        source.serialize(serializer)
111    }
112}
113
114pub struct NumberVisitor;
115
116impl<'de> Visitor<'de> for NumberVisitor {
117    type Value = Number;
118
119    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
120        formatter.write_str("a sharesight api number")
121    }
122
123    fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
124    where
125        E: serde::de::Error,
126    {
127        #[cfg(any(feature = "rust_decimal", feature = "bigdecimal"))]
128        let result = v
129            .try_into()
130            .map_err(|_| serde::de::Error::invalid_type(Unexpected::Float(v), &self));
131        #[cfg(not(any(feature = "rust_decimal", feature = "bigdecimal")))]
132        let result = Ok(v);
133
134        result
135    }
136
137    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
138    where
139        E: serde::de::Error,
140    {
141        #[cfg(all(feature = "rust_decimal", not(feature = "bigdecimal")))]
142        let infinities = Some((Number::MAX, Number::MIN));
143        #[cfg(all(feature = "bigdecimal", not(feature = "rust_decimal")))]
144        let infinities = None;
145        #[cfg(all(not(feature = "rust_decimal"), not(feature = "bigdecimal")))]
146        let infinities = Some((Number::INFINITY, Number::NEG_INFINITY));
147
148        match (v, infinities) {
149            ("Infinity", Some((infinity, _))) => Ok(infinity),
150            ("-Infinity", Some((_, neg_infinity))) => Ok(neg_infinity),
151            _ => Err(serde::de::Error::invalid_type(Unexpected::Str(v), &self)),
152        }
153    }
154}
155
156#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
157#[serde(untagged)]
158pub enum IdOrName {
159    Id(i64),
160    Name(String),
161}
162
163impl IdOrName {
164    pub fn id(&self) -> Option<i64> {
165        match self {
166            IdOrName::Id(id) => Some(*id),
167            IdOrName::Name(_) => None,
168        }
169    }
170
171    pub fn name(&self) -> Option<String> {
172        match self {
173            IdOrName::Name(name) => Some(name.to_string()),
174            IdOrName::Id(_) => None,
175        }
176    }
177}
178
179#[cfg(test)]
180mod id_or_name_tests {
181    use super::IdOrName;
182    use serde::{
183        de::{value::Error, IntoDeserializer},
184        Deserialize,
185    };
186
187    #[test]
188    fn serialize() -> Result<(), serde_json::Error> {
189        use serde_json::{json, to_value};
190
191        assert_eq!(json!("name"), to_value(IdOrName::Name("name".to_string()))?);
192        assert_eq!(json!(0), to_value(IdOrName::Id(0))?);
193
194        Ok(())
195    }
196
197    #[test]
198    fn deserialize() -> Result<(), Error> {
199        assert_eq!(
200            IdOrName::Id(0),
201            IdOrName::deserialize(0.into_deserializer())?
202        );
203
204        assert_eq!(
205            IdOrName::Name("name".to_string()),
206            IdOrName::deserialize("name".to_string().into_deserializer())?
207        );
208
209        Ok(())
210    }
211}