Skip to main content

im_core/
ids.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
4pub struct IdentityId(String);
5
6#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
7pub struct Did(String);
8
9#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
10pub struct Handle(String);
11
12#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
13pub struct PeerRef(String);
14
15#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
16pub struct GroupRef(String);
17
18#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
19pub struct MessageId(String);
20
21#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
22pub struct ThreadId(String);
23
24#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
25pub struct Cursor(String);
26
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
28pub struct Page<T> {
29    pub items: Vec<T>,
30    pub next_cursor: Option<Cursor>,
31    pub has_more: bool,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
35pub struct PageLimit(pub u32);
36
37impl IdentityId {
38    pub fn parse(input: impl AsRef<str>) -> crate::ImResult<Self> {
39        parse_non_empty("identity_id", input).map(Self)
40    }
41
42    pub fn as_str(&self) -> &str {
43        &self.0
44    }
45}
46
47impl Did {
48    pub fn parse(input: impl AsRef<str>) -> crate::ImResult<Self> {
49        let value = parse_non_empty("did", input)?;
50        if !value.starts_with("did:") {
51            return Err(crate::ImError::invalid_input(
52                Some("did".to_string()),
53                "DID must start with did:",
54            ));
55        }
56        Ok(Self(value))
57    }
58
59    pub fn as_str(&self) -> &str {
60        &self.0
61    }
62}
63
64impl Handle {
65    pub fn parse(input: impl AsRef<str>, default_domain: &str) -> crate::ImResult<Self> {
66        let value = parse_non_empty("handle", input)?;
67        let normalized = if value.contains('@') || value.contains('.') || default_domain.is_empty()
68        {
69            value
70        } else {
71            format!("{value}.{default_domain}")
72        };
73        Ok(Self(
74            normalized.trim_start_matches('@').to_ascii_lowercase(),
75        ))
76    }
77
78    pub fn as_str(&self) -> &str {
79        &self.0
80    }
81}
82
83impl PeerRef {
84    pub fn parse(input: impl AsRef<str>, default_domain: &str) -> crate::ImResult<Self> {
85        let value = parse_non_empty("peer", input)?;
86        if value.starts_with("did:") {
87            return Ok(Self(value));
88        }
89        Ok(Self(
90            Handle::parse(value, default_domain)?.as_str().to_string(),
91        ))
92    }
93
94    pub fn as_str(&self) -> &str {
95        &self.0
96    }
97}
98
99impl GroupRef {
100    pub fn parse(input: impl AsRef<str>) -> crate::ImResult<Self> {
101        parse_non_empty("group", input).map(Self)
102    }
103
104    pub fn as_str(&self) -> &str {
105        &self.0
106    }
107}
108
109impl MessageId {
110    pub fn parse(input: impl AsRef<str>) -> crate::ImResult<Self> {
111        parse_non_empty("message_id", input).map(Self)
112    }
113
114    pub fn as_str(&self) -> &str {
115        &self.0
116    }
117}
118
119impl ThreadId {
120    pub fn parse(input: impl AsRef<str>) -> crate::ImResult<Self> {
121        parse_non_empty("thread_id", input).map(Self)
122    }
123
124    pub fn as_str(&self) -> &str {
125        &self.0
126    }
127}
128
129impl Cursor {
130    pub fn parse(input: impl AsRef<str>) -> crate::ImResult<Self> {
131        parse_non_empty("cursor", input).map(Self)
132    }
133
134    pub fn as_str(&self) -> &str {
135        &self.0
136    }
137}
138
139impl PageLimit {
140    pub fn new(value: u32) -> crate::ImResult<Self> {
141        if value == 0 {
142            return Err(crate::ImError::invalid_input(
143                Some("limit".to_string()),
144                "limit must be greater than zero",
145            ));
146        }
147        Ok(Self(value.min(100)))
148    }
149}
150
151fn parse_non_empty(field: &'static str, input: impl AsRef<str>) -> crate::ImResult<String> {
152    let value = input.as_ref().trim();
153    if value.is_empty() {
154        return Err(crate::ImError::invalid_input(
155            Some(field.to_string()),
156            format!("{field} must not be empty"),
157        ));
158    }
159    Ok(value.to_string())
160}