Skip to main content

oparl_types/
email_address.rs

1// SPDX-FileCopyrightText: Politik im Blick developers
2// SPDX-FileCopyrightText: Wolfgang Silbermayr <wolfgang@silbermayr.at>
3//
4// SPDX-License-Identifier: AGPL-3.0-or-later OR EUPL-1.2
5
6use std::str::FromStr;
7
8use snafu::{ResultExt as _, Snafu};
9
10#[derive(
11    Debug,
12    Clone,
13    PartialEq,
14    Eq,
15    Hash,
16    derive_more::AsRef,
17    derive_more::Display,
18    derive_more::From,
19    derive_more::Into,
20    serde::Serialize,
21    serde::Deserialize,
22)]
23pub struct EmailAddress(email_address::EmailAddress);
24
25impl EmailAddress {
26    pub fn as_str(&self) -> &str {
27        self.0.as_str()
28    }
29}
30
31#[derive(Debug, Snafu)]
32pub enum TryFromEmailAddressError {
33    #[snafu(display("Failed to parse e-mail address: {source}"))]
34    Parse { source: email_address::Error },
35}
36
37impl FromStr for EmailAddress {
38    type Err = TryFromEmailAddressError;
39
40    fn from_str(s: &str) -> Result<Self, Self::Err> {
41        let email_address: email_address::EmailAddress = s.parse().context(ParseSnafu)?;
42        Ok(Self(email_address))
43    }
44}
45
46impl PartialOrd for EmailAddress {
47    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
48        Some(self.cmp(other))
49    }
50}
51
52impl Ord for EmailAddress {
53    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
54        self.as_str().cmp(other.as_str())
55    }
56}
57
58impl TryFrom<String> for EmailAddress {
59    type Error = TryFromEmailAddressError;
60
61    fn try_from(value: String) -> Result<Self, Self::Error> {
62        value.parse()
63    }
64}
65
66#[cfg(feature = "sea-orm")]
67mod sea_orm_impls {
68    use sea_orm::{
69        ActiveValue, ColumnType, DbErr, IntoActiveValue, TryGetError, TryGetable, Value,
70        sea_query::{ArrayType, Nullable, ValueType, ValueTypeErr},
71    };
72
73    use super::EmailAddress;
74
75    impl From<EmailAddress> for Value {
76        fn from(value: EmailAddress) -> Self {
77            value.as_str().into()
78        }
79    }
80
81    impl TryGetable for EmailAddress {
82        fn try_get_by<I: sea_orm::ColIdx>(
83            res: &sea_orm::QueryResult,
84            index: I,
85        ) -> Result<Self, TryGetError> {
86            let s = <String as TryGetable>::try_get_by(res, index)?;
87            s.try_into().map_err(|e| {
88                DbErr::TryIntoErr {
89                    from: "String",
90                    into: "EmailAddress",
91                    source: Box::new(e),
92                }
93                .into()
94            })
95        }
96    }
97
98    impl ValueType for EmailAddress {
99        fn try_from(v: Value) -> Result<Self, ValueTypeErr> {
100            let s = <String as ValueType>::try_from(v)?;
101            s.try_into().map_err(|_| ValueTypeErr)
102        }
103
104        fn type_name() -> String {
105            stringify!(String).to_owned()
106        }
107
108        fn array_type() -> ArrayType {
109            <String as ValueType>::array_type()
110        }
111
112        fn column_type() -> ColumnType {
113            <String as ValueType>::column_type()
114        }
115    }
116
117    impl Nullable for EmailAddress {
118        fn null() -> Value {
119            Value::String(None)
120        }
121    }
122
123    impl IntoActiveValue<EmailAddress> for EmailAddress {
124        fn into_active_value(self) -> ActiveValue<EmailAddress> {
125            ActiveValue::Set(self)
126        }
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use pretty_assertions::assert_eq;
133
134    use super::EmailAddress;
135
136    #[test]
137    fn from_str() {
138        assert_eq!(
139            EmailAddress(
140                "hello@example.com"
141                    .parse()
142                    .expect("value must be a parseable EmailAddress")
143            ),
144            "hello@example.com"
145                .parse()
146                .expect("value must be a parseable EmailAddress")
147        );
148    }
149}
150
151#[cfg(test)]
152mod serde_tests {
153    use pretty_assertions::assert_eq;
154    use serde_json::json;
155
156    use super::EmailAddress;
157
158    #[test]
159    fn serialize() {
160        assert_eq!(
161            json!(EmailAddress(
162                "hello@example.com"
163                    .parse()
164                    .expect("value must be a parseable EmailAddress")
165            )),
166            json!("hello@example.com")
167        );
168    }
169
170    #[test]
171    fn deserialize_good() {
172        let deserialized: EmailAddress = serde_json::from_value(json!("hello@example.com"))
173            .expect("value must be deserializable as EmailAddress");
174        assert_eq!(
175            deserialized,
176            EmailAddress(
177                "hello@example.com"
178                    .parse()
179                    .expect("value must be a parseable EmailAddress")
180            )
181        );
182    }
183
184    #[test]
185    fn deserialize_bad() {
186        assert!(serde_json::from_value::<EmailAddress>(json!("xyzabcd")).is_err());
187        assert!(serde_json::from_value::<EmailAddress>(json!(true)).is_err());
188        assert!(serde_json::from_value::<EmailAddress>(json!(123)).is_err());
189    }
190}