hyperactor/reference/
name.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9//! Names in hyperactor.
10
11use rand::RngCore as _;
12use serde::Deserialize;
13use serde::Serialize;
14
15use crate::reference::lex::Lexer;
16use crate::reference::lex::ParseError;
17use crate::reference::lex::Token;
18
19/// So-called ["Flickr base 58"](https://www.flickr.com/groups/api/discuss/72157616713786392/)
20/// as this alphabet was used in Flickr URLs. It has nice properties: 1) characters are all
21/// URL safe, and characters which are easily confused (e.g., I and l) are removed.
22pub(crate) const FLICKR_BASE_58: &str =
23    "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ";
24
25/// An id that uniquely identifies an object.
26/// UIDs are are displayed in, and parsed from the
27/// ["Flickr base 58"](https://www.flickr.com/groups/api/discuss/72157616713786392/)
28/// representation.
29#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
30pub struct Uid(u64);
31
32impl Uid {
33    /// Generate a new uid.
34    pub fn generate() -> Self {
35        Self(rand::thread_rng().next_u64())
36    }
37
38    pub(crate) fn zero() -> Self {
39        Self(0)
40    }
41}
42
43impl From<u64> for Uid {
44    fn from(value: u64) -> Self {
45        Self(value)
46    }
47}
48
49impl std::fmt::Display for Uid {
50    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51        let mut num = self.0;
52        let base = FLICKR_BASE_58.len() as u64;
53        let mut result = String::with_capacity(12);
54
55        for _pos in 0..12 {
56            let remainder = (num % base) as usize;
57            num /= base;
58            let c = FLICKR_BASE_58.chars().nth(remainder).unwrap();
59            result.push(c);
60        }
61        debug_assert_eq!(num, 0);
62
63        let result = result.chars().rev().collect::<String>();
64        write!(f, "{}", result)
65    }
66}
67
68impl std::str::FromStr for Uid {
69    type Err = ParseError;
70
71    fn from_str(s: &str) -> Result<Self, Self::Err> {
72        let mut lexer = Lexer::new(s);
73        let uid = lexer.next_or_eof().into_uid()?;
74        lexer.expect(Token::Eof)?;
75        Ok(uid)
76    }
77}
78
79/// A Unicode XID identifier.
80/// See [Unicode Standard Annex #31](https://unicode.org/reports/tr31/).
81#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
82pub struct Ident(String);
83
84impl Ident {
85    /// Create a new identifier, returning the original string if it
86    /// is not a valid unicode identifier.
87    pub fn new(value: String) -> Result<Self, String> {
88        let mut chars = value.chars();
89        match chars.next() {
90            None => return Err(value),
91            Some(ch) if !unicode_ident::is_xid_start(ch) && ch != '_' => return Err(value),
92            Some(_) => (),
93        }
94        if chars.all(unicode_ident::is_xid_continue) {
95            Ok(Self(value))
96        } else {
97            Err(value)
98        }
99    }
100}
101
102impl std::fmt::Display for Ident {
103    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104        self.0.fmt(f)
105    }
106}
107
108impl std::str::FromStr for Ident {
109    type Err = String;
110
111    fn from_str(s: &str) -> Result<Self, Self::Err> {
112        Ident::new(s.to_string())
113    }
114}
115
116/// A namespaced object name, comprising a namespace, base, and uid.
117/// Each namespace contains a set of related objects; the base name is
118/// a user-defined name used to indicate the purpose of the named object,
119/// and the UID uniquely identifies it. Taken together, each name is
120/// global and unique. This facilitates identifiers that are "self-contained",
121/// not requiring additional context when they appear in situ.
122///
123/// Both the namespace and the base must be valid Idents.
124///
125/// Names have a concrete syntax: `namespace:base-uid`, shown and parsed
126/// by `Display` and `FromStr` respectively.
127#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
128pub struct Name {
129    namespace: Ident,
130    base: Ident,
131    uid: Uid,
132}
133
134impl Name {
135    /// Create a new name. Users should typically generate
136    /// a new name with [`Name::generate`].
137    pub fn new(namespace: Ident, base: Ident, uid: Uid) -> Self {
138        Self {
139            namespace,
140            base,
141            uid,
142        }
143    }
144
145    /// Generate a new name with the provided namespace and base.
146    pub fn generate(namespace: Ident, base: Ident) -> Self {
147        Self {
148            namespace,
149            base,
150            uid: Uid::generate(),
151        }
152    }
153}
154
155impl std::fmt::Display for Name {
156    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157        write!(f, "{}:{}-{}", self.namespace, self.base, self.uid)
158    }
159}
160
161impl std::str::FromStr for Name {
162    type Err = ParseError;
163
164    fn from_str(s: &str) -> Result<Self, Self::Err> {
165        let mut lexer = Lexer::new(s);
166
167        let namespace = lexer.next_or_eof().into_ident()?;
168        lexer.expect(Token::Colon)?;
169        let base = lexer.next_or_eof().into_ident()?;
170        let uid = lexer.next_or_eof().into_uid()?;
171        lexer.expect(Token::Eof)?;
172
173        Ok(Name {
174            namespace,
175            base,
176            uid,
177        })
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184
185    #[test]
186    fn test_generate() {
187        assert_ne!(
188            Name::generate("namespace".parse().unwrap(), "base".parse().unwrap(),),
189            Name::generate("namespace".parse().unwrap(), "base".parse().unwrap(),),
190        );
191    }
192
193    #[test]
194    fn test_roundtrip() {
195        let name = Name::new(
196            "namespace".parse().unwrap(),
197            "base".parse().unwrap(),
198            Uid::zero(),
199        );
200        assert_eq!(name.to_string(), "namespace:base-111111111111".to_string());
201        assert_eq!(name.to_string().parse::<Name>().unwrap(), name);
202    }
203
204    #[test]
205    fn test_invalid() {
206        assert_eq!(
207            "namespace:base-lllll".parse::<Name>().unwrap_err(),
208            ParseError::Expected(Token::Uid(Uid::zero()), Token::Error("lllll".to_string()))
209        );
210    }
211}