mas_data_model/compat/
device.rs1use oauth2_types::scope::ScopeToken;
8use rand::{
9 RngCore,
10 distributions::{Alphanumeric, DistString},
11};
12use serde::{Deserialize, Serialize};
13use thiserror::Error;
14
15static GENERATED_DEVICE_ID_LENGTH: usize = 10;
16static DEVICE_SCOPE_PREFIX: &str = "urn:matrix:org.matrix.msc2967.client:device:";
17
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
19#[serde(transparent)]
20pub struct Device {
21 id: String,
22}
23
24#[derive(Debug, Error)]
25pub enum ToScopeTokenError {
26 #[error("Device ID contains characters that can't be encoded in a scope")]
27 InvalidCharacters,
28}
29
30impl Device {
31 pub fn to_scope_token(&self) -> Result<ScopeToken, ToScopeTokenError> {
38 format!("{DEVICE_SCOPE_PREFIX}{}", self.id)
39 .parse()
40 .map_err(|_| ToScopeTokenError::InvalidCharacters)
41 }
42
43 #[must_use]
47 pub fn from_scope_token(token: &ScopeToken) -> Option<Self> {
48 let id = token.as_str().strip_prefix(DEVICE_SCOPE_PREFIX)?;
49 Some(Device::from(id.to_owned()))
50 }
51
52 pub fn generate<R: RngCore + ?Sized>(rng: &mut R) -> Self {
54 let id: String = Alphanumeric.sample_string(rng, GENERATED_DEVICE_ID_LENGTH);
55 Self { id }
56 }
57
58 #[must_use]
60 pub fn as_str(&self) -> &str {
61 &self.id
62 }
63}
64
65impl From<String> for Device {
66 fn from(id: String) -> Self {
67 Self { id }
68 }
69}
70
71impl From<Device> for String {
72 fn from(device: Device) -> Self {
73 device.id
74 }
75}
76
77impl std::fmt::Display for Device {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 f.write_str(&self.id)
80 }
81}
82
83#[cfg(test)]
84mod test {
85 use oauth2_types::scope::OPENID;
86
87 use crate::Device;
88
89 #[test]
90 fn test_device_id_to_from_scope_token() {
91 let device = Device::from("AABBCCDDEE".to_owned());
92 let scope_token = device.to_scope_token().unwrap();
93 assert_eq!(
94 scope_token.as_str(),
95 "urn:matrix:org.matrix.msc2967.client:device:AABBCCDDEE"
96 );
97 assert_eq!(Device::from_scope_token(&scope_token), Some(device));
98 assert_eq!(Device::from_scope_token(&OPENID), None);
99 }
100}