Skip to main content

ocm_types/
common.rs

1// SPDX-FileCopyrightText: 2026 Matthias Kraus <info@opengeomesh.org>
2//
3// SPDX-License-Identifier: LGPL-3.0-or-later
4
5use std::fmt::Display;
6
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize)]
10#[serde(rename_all = "kebab-case")]
11pub enum ShareType {
12    #[default]
13    User,
14    Group,
15    Federation,
16}
17
18/// The OCM Address identifies a user or group "at" an OCM Server.
19/// The OCM Address contains a server specific Party identifier, a host
20/// locating the OCM Server and an optional port. The OCM Address is not a
21/// URI as it does not have scheme and the identifier may contain reserved
22/// characters.
23#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize)]
24#[serde(try_from = "String", into = "String")]
25pub struct OcmAddress {
26    address: String,
27    separator_index: usize
28}
29
30impl OcmAddress {
31    /// Returns the OCM Server specific identifier of a Sending or Receiving Party
32    pub fn get_identifier(&self) -> &str {
33        self.address.split_at(self.separator_index).0
34    }
35
36    /// Returns the address of the OCM Server where the Party identified by the OCM
37    /// Address is located. This can be used to Discover the OCM Server of the Party.
38    pub fn get_server_url(&self) -> &str {
39        self.address.split_at(self.separator_index+1).1
40    }
41}
42
43impl TryFrom<&str> for OcmAddress {
44    type Error = &'static str;
45
46    fn try_from(value: &str) -> Result<Self, Self::Error> {
47        value.to_string().try_into()
48    }
49}
50
51impl TryFrom<String> for OcmAddress {
52    type Error = &'static str;
53
54    fn try_from(value: String) -> Result<Self, Self::Error> {
55        if value.is_empty() {
56            Err("OCM Address may not be empty")?
57        }
58
59        let separator_index = value.rfind('@')
60            .ok_or("Missing '@' separator in OCM Address")?;
61
62        if separator_index == 0 {
63            Err("UserId before the '@' character in OCM Address may not be empty")?
64        }
65        
66        if separator_index == value.len() - 1 {
67            Err("OCM Server FQDN after the '@' character in OCM Address may not be empty")?
68        }
69
70        let host = value.split_at(separator_index+1).1;
71
72        if host.contains('/') {
73            Err("OCM Address may not contain a path or scheme")?
74        };
75        Ok(Self {
76            address: value,
77            separator_index
78        })
79    }
80}
81
82impl Display for OcmAddress {
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        f.write_str(&self.address)?;
85        Ok(())
86    }
87}
88
89impl From<OcmAddress> for String {
90    fn from(val: OcmAddress) -> Self {
91        val.address
92    }
93}
94
95impl AsRef<str> for OcmAddress {
96    fn as_ref(&self) -> &str {
97        &self.address
98    }
99}
100
101#[cfg(test)]
102mod test {
103    use super::OcmAddress;
104
105
106    #[test]
107    fn invalid_ocm_addresses() {
108        OcmAddress::try_from("").expect_err("OCM Address may not be empty");
109        OcmAddress::try_from("user@https://example.org").expect_err("Scheme is not allowed in server address part");
110        OcmAddress::try_from("user@example.org/subfolder").expect_err("Scheme is not allowed in server address part");
111        OcmAddress::try_from("@example.org").expect_err("Empty user Id must be rejected");
112        OcmAddress::try_from("user@").expect_err("Empty Server FQDN must be rejected");
113        OcmAddress::try_from("@").expect_err("Empty User Id and Empty Server FQDN must be rejected");
114    }
115
116    #[test]
117    fn valid_ocm_addresses() {
118        let address = OcmAddress::try_from("asdf@user@example.org").expect("asdf@user@example.org should be accepted");
119        assert_eq!(("asdf@user", "example.org"), (address.get_identifier(), address.get_server_url()));
120        
121        let address = OcmAddress::try_from("🤡asdf@user@example.org:8080").expect("🤡asdf@user@example.org:8080 should be accepted");
122        assert_eq!(("🤡asdf@user", "example.org:8080"), (address.get_identifier(), address.get_server_url()));
123        
124        let address = OcmAddress::try_from("asdf@user@127.0.0.1").expect("asdf@user@127.0.0.1 should be accepted");
125        assert_eq!(("asdf@user", "127.0.0.1"), (address.get_identifier(), address.get_server_url()));
126        
127        let address = OcmAddress::try_from("asdf@user@[fe80::cc7f:2f7d:80f6:3876%en0]").expect("asdf@user@[fe80::cc7f:2f7d:80f6:3876%en0] should be accepted");
128        assert_eq!(("asdf@user", "[fe80::cc7f:2f7d:80f6:3876%en0]"), (address.get_identifier(), address.get_server_url()));
129        
130    }
131}