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