xmtp_proto/types/
app_version.rs

1use tonic::metadata::{Ascii, MetadataValue, errors::InvalidMetadataValue};
2
3/// An Inbox-App specific version
4#[derive(Clone, PartialEq, Eq)]
5pub struct AppVersion(String);
6
7impl From<String> for AppVersion {
8    fn from(value: String) -> Self {
9        AppVersion(value)
10    }
11}
12
13impl From<&str> for AppVersion {
14    fn from(value: &str) -> Self {
15        AppVersion(value.to_string())
16    }
17}
18
19impl From<&String> for AppVersion {
20    fn from(value: &String) -> Self {
21        AppVersion(value.to_string())
22    }
23}
24
25impl Default for AppVersion {
26    fn default() -> Self {
27        Self("0.0.0".to_string())
28    }
29}
30
31impl TryFrom<AppVersion> for MetadataValue<Ascii> {
32    type Error = InvalidMetadataValue;
33
34    fn try_from(value: AppVersion) -> Result<Self, Self::Error> {
35        MetadataValue::try_from(value.0)
36    }
37}
38
39impl TryFrom<&AppVersion> for MetadataValue<Ascii> {
40    type Error = InvalidMetadataValue;
41
42    fn try_from(value: &AppVersion) -> Result<Self, Self::Error> {
43        MetadataValue::try_from(&value.0)
44    }
45}
46
47impl std::fmt::Display for AppVersion {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        write!(f, "{}", self.0)
50    }
51}
52
53impl std::fmt::Debug for AppVersion {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        f.debug_tuple("AppVersion").field(&self.0).finish()
56    }
57}
58
59impl PartialEq<String> for AppVersion {
60    fn eq(&self, other: &String) -> bool {
61        self.0 == *other
62    }
63}
64
65impl PartialEq<AppVersion> for String {
66    fn eq(&self, other: &AppVersion) -> bool {
67        other.0 == *self
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    use rstest::rstest;
75    use tonic::metadata::MetadataValue;
76
77    #[rstest]
78    #[case("1.2.3")]
79    #[case("2.0.1")]
80    #[case("")]
81    #[case("v1.0.0")]
82    #[xmtp_common::test]
83    async fn test_from_conversions(#[case] version: &str) {
84        assert_eq!(AppVersion::from(version).to_string(), version);
85        assert_eq!(AppVersion::from(version.to_string()).to_string(), version);
86    }
87
88    #[rstest]
89    #[case("1.0.0", true)]
90    #[case("", true)]
91    #[case("1.0.0\u{1F600}", true)] // emoji - apparently allowed
92    #[xmtp_common::test]
93    async fn test_metadata_value_conversion(#[case] version: &str, #[case] should_succeed: bool) {
94        assert_eq!(
95            TryInto::<MetadataValue<Ascii>>::try_into(AppVersion::from(version).clone()).is_ok(),
96            should_succeed
97        );
98
99        if should_succeed && version.is_ascii() {
100            assert_eq!(
101                TryInto::<MetadataValue<Ascii>>::try_into(AppVersion::from(version).clone())
102                    .unwrap()
103                    .to_str()
104                    .unwrap(),
105                version
106            );
107        }
108    }
109
110    #[rstest]
111    #[case("1.0.0-alpha")]
112    #[case("2.1.3-beta.1")]
113    #[case("3.0.0-rc.2+build.123")]
114    #[case("v1.2.3")]
115    #[case("1.0.0+build.1")]
116    #[xmtp_common::test]
117    async fn test_complex_versions(#[case] version: &str) {
118        assert_eq!(AppVersion::from(version).to_string(), version);
119        assert!(
120            TryInto::<MetadataValue<Ascii>>::try_into(&AppVersion::from(version)).is_ok(),
121            "Version {} should be valid ASCII",
122            version
123        );
124    }
125}