eventuary_core/
organization.rs1use std::fmt;
2
3use serde::{Deserialize, Serialize};
4
5use crate::error::{Error, Result};
6
7#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
8#[serde(try_from = "String", into = "String")]
9pub struct OrganizationId(String);
10
11impl OrganizationId {
12 pub const PLATFORM: &'static str = "_platform";
13
14 pub fn new(s: impl Into<String>) -> Result<Self> {
15 let s = s.into();
16 if s.is_empty() {
17 return Err(Error::InvalidOrganization("must not be empty".into()));
18 }
19 if !s
20 .chars()
21 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_' || c == '-')
22 {
23 return Err(Error::InvalidOrganization(format!("invalid: {s}")));
24 }
25 Ok(Self(s))
26 }
27
28 pub fn platform() -> Self {
29 Self(Self::PLATFORM.to_owned())
30 }
31
32 pub fn as_str(&self) -> &str {
33 &self.0
34 }
35}
36
37impl fmt::Display for OrganizationId {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 write!(f, "{}", self.0)
40 }
41}
42
43impl TryFrom<String> for OrganizationId {
44 type Error = Error;
45 fn try_from(s: String) -> Result<Self> {
46 Self::new(s)
47 }
48}
49
50impl From<OrganizationId> for String {
51 fn from(o: OrganizationId) -> Self {
52 o.0
53 }
54}
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59
60 #[test]
61 fn platform_sentinel_is_constructible() {
62 let p = OrganizationId::platform();
63 assert_eq!(p.as_str(), "_platform");
64 }
65
66 #[test]
67 fn platform_sentinel_round_trips_through_validation() {
68 let parsed = OrganizationId::new("_platform").unwrap();
69 assert_eq!(parsed, OrganizationId::platform());
70 }
71}