Skip to main content

sl_types/
key.rs

1//! Second Life key (UUID) related types
2
3use uuid::{Uuid, uuid};
4
5#[cfg(feature = "chumsky")]
6use chumsky::{
7    IterParser as _, Parser,
8    prelude::{just, one_of},
9};
10
11/// parse a UUID
12///
13/// # Errors
14///
15/// returns an error if the string could not be parsed
16#[cfg(feature = "chumsky")]
17#[must_use]
18pub fn uuid_parser<'src>()
19-> impl Parser<'src, &'src str, uuid::Uuid, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
20    one_of("0123456789abcdef")
21        .repeated()
22        .exactly(8)
23        .collect::<String>()
24        .then_ignore(just('-'))
25        .then(
26            one_of("0123456789abcdef")
27                .repeated()
28                .exactly(4)
29                .collect::<String>(),
30        )
31        .then_ignore(just('-'))
32        .then(
33            one_of("0123456789abcdef")
34                .repeated()
35                .exactly(4)
36                .collect::<String>(),
37        )
38        .then_ignore(just('-'))
39        .then(
40            one_of("0123456789abcdef")
41                .repeated()
42                .exactly(4)
43                .collect::<String>(),
44        )
45        .then_ignore(just('-'))
46        .then(
47            one_of("0123456789abcdef")
48                .repeated()
49                .exactly(12)
50                .collect::<String>(),
51        )
52        .try_map(|((((a, b), c), d), e), span: chumsky::span::SimpleSpan| {
53            uuid::Uuid::parse_str(&format!("{a}-{b}-{c}-{d}-{e}"))
54                .map_err(|e| chumsky::error::Rich::custom(span, format!("{e:?}")))
55        })
56}
57
58/// represents a general Second Life key without any knowledge about the type
59/// of entity this represents
60#[derive(Debug, Clone, PartialEq, Eq)]
61pub struct Key(pub Uuid);
62
63impl std::fmt::Display for Key {
64    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65        write!(f, "{}", self.0)
66    }
67}
68
69/// parse a Key
70///
71/// # Errors
72///
73/// returns an error if the string could not be parsed
74#[cfg(feature = "chumsky")]
75#[must_use]
76#[expect(
77    clippy::module_name_repetitions,
78    reason = "the parser is going to be used outside this module"
79)]
80pub fn key_parser<'src>()
81-> impl Parser<'src, &'src str, Key, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
82    uuid_parser().map(Key)
83}
84
85/// the null key used by Second Life in many places to represent the absence
86/// of a key value
87pub const NULL_KEY: Key = Key(uuid!("00000000-0000-0000-0000-000000000000"));
88
89/// the key used by the Second Life system to send combat logs to the COMBAT_CHANNEL
90pub const COMBAT_LOG_ID: Key = Key(uuid!("45e0fcfa-2268-4490-a51c-3e51bdfe80d1"));
91
92/// represents a Second Life key for an agent (avatar)
93#[derive(Debug, Clone, PartialEq, Eq)]
94#[expect(
95    clippy::module_name_repetitions,
96    reason = "the type is going to be used outside this module"
97)]
98pub struct AgentKey(pub Key);
99
100impl std::fmt::Display for AgentKey {
101    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102        write!(f, "{}", self.0)
103    }
104}
105
106impl From<AgentKey> for Key {
107    fn from(val: AgentKey) -> Self {
108        val.0
109    }
110}
111
112/// parse an AgentKey
113///
114/// # Errors
115///
116/// returns an error if the string could not be parsed
117#[cfg(feature = "chumsky")]
118#[must_use]
119pub fn agent_key_parser<'src>()
120-> impl Parser<'src, &'src str, AgentKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
121    key_parser().map(AgentKey)
122}
123
124/// parse a viewer URI that is either an /about or /inspect URL for an avatar
125/// and return the AgentKey
126///
127/// # Errors
128///
129/// returns an error if the string could not be parsed
130#[cfg(feature = "chumsky")]
131#[must_use]
132pub fn app_agent_uri_as_agent_key_parser<'src>()
133-> impl Parser<'src, &'src str, AgentKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
134    crate::viewer_uri::viewer_app_agent_uri_parser().try_map(|uri, span| match uri {
135        crate::viewer_uri::ViewerUri::AgentAbout(agent_key)
136        | crate::viewer_uri::ViewerUri::AgentInspect(agent_key) => Ok(agent_key),
137        _ => Err(chumsky::error::Rich::custom(
138            span,
139            "Unexpected type of Agent viewer URI",
140        )),
141    })
142}
143
144/// represents a Second Life key for a classified ad
145#[derive(Debug, Clone, PartialEq, Eq)]
146#[expect(
147    clippy::module_name_repetitions,
148    reason = "the type is going to be used outside this module"
149)]
150pub struct ClassifiedKey(pub Key);
151
152impl std::fmt::Display for ClassifiedKey {
153    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154        write!(f, "{}", self.0)
155    }
156}
157
158impl From<ClassifiedKey> for Key {
159    fn from(val: ClassifiedKey) -> Self {
160        val.0
161    }
162}
163
164/// parse a ClassifiedKey
165///
166/// # Errors
167///
168/// returns an error if the string could not be parsed
169#[cfg(feature = "chumsky")]
170#[must_use]
171pub fn classified_key_parser<'src>()
172-> impl Parser<'src, &'src str, ClassifiedKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
173{
174    key_parser().map(ClassifiedKey)
175}
176
177/// represents a Second Life key for an event
178#[derive(Debug, Clone, PartialEq, Eq)]
179#[expect(
180    clippy::module_name_repetitions,
181    reason = "the type is going to be used outside this module"
182)]
183pub struct EventKey(pub Key);
184
185impl std::fmt::Display for EventKey {
186    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
187        write!(f, "{}", self.0)
188    }
189}
190
191impl From<EventKey> for Key {
192    fn from(val: EventKey) -> Self {
193        val.0
194    }
195}
196
197/// parse an EventKey
198///
199/// # Errors
200///
201/// returns an error if the string could not be parsed
202#[cfg(feature = "chumsky")]
203#[must_use]
204pub fn event_key_parser<'src>()
205-> impl Parser<'src, &'src str, EventKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
206    key_parser().map(EventKey)
207}
208
209/// represents a Second Life key for an experience
210#[derive(Debug, Clone, PartialEq, Eq)]
211#[expect(
212    clippy::module_name_repetitions,
213    reason = "the type is going to be used outside this module"
214)]
215pub struct ExperienceKey(pub Key);
216
217impl std::fmt::Display for ExperienceKey {
218    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
219        write!(f, "{}", self.0)
220    }
221}
222
223impl From<ExperienceKey> for Key {
224    fn from(val: ExperienceKey) -> Self {
225        val.0
226    }
227}
228
229/// parse an ExperienceKey
230///
231/// # Errors
232///
233/// returns an error if the string could not be parsed
234#[cfg(feature = "chumsky")]
235#[must_use]
236pub fn experience_key_parser<'src>()
237-> impl Parser<'src, &'src str, ExperienceKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
238{
239    key_parser().map(ExperienceKey)
240}
241
242/// represents a Second Life key for an agent who is a friend
243#[derive(Debug, Clone, PartialEq, Eq)]
244#[expect(
245    clippy::module_name_repetitions,
246    reason = "the type is going to be used outside this module"
247)]
248pub struct FriendKey(pub Key);
249
250impl std::fmt::Display for FriendKey {
251    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
252        write!(f, "{}", self.0)
253    }
254}
255
256impl From<FriendKey> for Key {
257    fn from(val: FriendKey) -> Self {
258        val.0
259    }
260}
261
262impl From<FriendKey> for AgentKey {
263    fn from(val: FriendKey) -> Self {
264        Self(val.0)
265    }
266}
267
268/// parse a FriendKey
269///
270/// # Errors
271///
272/// returns an error if the string could not be parsed
273#[cfg(feature = "chumsky")]
274#[must_use]
275pub fn friend_key_parser<'src>()
276-> impl Parser<'src, &'src str, FriendKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
277    key_parser().map(FriendKey)
278}
279
280/// represents a Second Life key for a group
281#[derive(Debug, Clone, PartialEq, Eq)]
282#[expect(
283    clippy::module_name_repetitions,
284    reason = "the type is going to be used outside this module"
285)]
286pub struct GroupKey(pub Key);
287
288impl std::fmt::Display for GroupKey {
289    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
290        write!(f, "{}", self.0)
291    }
292}
293
294impl From<GroupKey> for Key {
295    fn from(val: GroupKey) -> Self {
296        val.0
297    }
298}
299
300/// parse a GroupKey
301///
302/// # Errors
303///
304/// returns an error if the string could not be parsed
305#[cfg(feature = "chumsky")]
306#[must_use]
307pub fn group_key_parser<'src>()
308-> impl Parser<'src, &'src str, GroupKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
309    key_parser().map(GroupKey)
310}
311
312/// parse a viewer URI that is either an /about or /inspect URL for a group
313/// and return the GroupKey
314///
315/// # Errors
316///
317/// returns an error if the string could not be parsed
318#[cfg(feature = "chumsky")]
319#[must_use]
320pub fn app_group_uri_as_group_key_parser<'src>()
321-> impl Parser<'src, &'src str, GroupKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
322    crate::viewer_uri::viewer_app_group_uri_parser().try_map(|uri, span| match uri {
323        crate::viewer_uri::ViewerUri::GroupAbout(group_key)
324        | crate::viewer_uri::ViewerUri::GroupInspect(group_key) => Ok(group_key),
325        _ => Err(chumsky::error::Rich::custom(
326            span,
327            "Unexpected type of group viewer URI",
328        )),
329    })
330}
331
332/// represents a Second Life key for an inventory item
333#[derive(Debug, Clone, PartialEq, Eq)]
334#[expect(
335    clippy::module_name_repetitions,
336    reason = "the type is going to be used outside this module"
337)]
338pub struct InventoryKey(pub Key);
339
340impl std::fmt::Display for InventoryKey {
341    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
342        write!(f, "{}", self.0)
343    }
344}
345
346impl From<InventoryKey> for Key {
347    fn from(val: InventoryKey) -> Self {
348        val.0
349    }
350}
351
352/// parse an InventoryKey
353///
354/// # Errors
355///
356/// returns an error if the string could not be parsed
357#[cfg(feature = "chumsky")]
358#[must_use]
359pub fn inventory_key_parser<'src>()
360-> impl Parser<'src, &'src str, InventoryKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
361{
362    key_parser().map(InventoryKey)
363}
364
365/// represents a Second Life key for an object
366#[derive(Debug, Clone, PartialEq, Eq)]
367#[expect(
368    clippy::module_name_repetitions,
369    reason = "the type is going to be used outside this module"
370)]
371pub struct ObjectKey(pub Key);
372
373impl std::fmt::Display for ObjectKey {
374    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
375        write!(f, "{}", self.0)
376    }
377}
378
379impl From<ObjectKey> for Key {
380    fn from(val: ObjectKey) -> Self {
381        val.0
382    }
383}
384
385/// parse an ObjectKey
386///
387/// # Errors
388///
389/// returns an error if the string could not be parsed
390#[cfg(feature = "chumsky")]
391#[must_use]
392pub fn object_key_parser<'src>()
393-> impl Parser<'src, &'src str, ObjectKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
394    key_parser().map(ObjectKey)
395}
396
397/// represents a Second Life key for a parcel
398#[derive(Debug, Clone, PartialEq, Eq)]
399#[expect(
400    clippy::module_name_repetitions,
401    reason = "the type is going to be used outside this module"
402)]
403pub struct ParcelKey(pub Key);
404
405impl std::fmt::Display for ParcelKey {
406    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
407        write!(f, "{}", self.0)
408    }
409}
410
411impl From<ParcelKey> for Key {
412    fn from(val: ParcelKey) -> Self {
413        val.0
414    }
415}
416
417/// parse a ParcelKey
418///
419/// # Errors
420///
421/// returns an error if the string could not be parsed
422#[cfg(feature = "chumsky")]
423#[must_use]
424pub fn parcel_key_parser<'src>()
425-> impl Parser<'src, &'src str, ParcelKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
426    key_parser().map(ParcelKey)
427}
428
429/// represents a Second Life key for a texture
430#[derive(Debug, Clone, PartialEq, Eq)]
431#[expect(
432    clippy::module_name_repetitions,
433    reason = "the type is going to be used outside this module"
434)]
435pub struct TextureKey(pub Key);
436
437impl std::fmt::Display for TextureKey {
438    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
439        write!(f, "{}", self.0)
440    }
441}
442
443impl From<TextureKey> for Key {
444    fn from(val: TextureKey) -> Self {
445        val.0
446    }
447}
448
449/// parse a TextureKey
450///
451/// # Errors
452///
453/// returns an error if the string could not be parsed
454#[cfg(feature = "chumsky")]
455#[must_use]
456pub fn texture_key_parser<'src>()
457-> impl Parser<'src, &'src str, TextureKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
458    key_parser().map(TextureKey)
459}
460
461/// represents a Second Life key for an inventory folder
462#[derive(Debug, Clone, PartialEq, Eq)]
463#[expect(
464    clippy::module_name_repetitions,
465    reason = "the type is going to be used outside this module"
466)]
467pub struct InventoryFolderKey(pub Key);
468
469impl std::fmt::Display for InventoryFolderKey {
470    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
471        write!(f, "{}", self.0)
472    }
473}
474
475impl From<InventoryFolderKey> for Key {
476    fn from(val: InventoryFolderKey) -> Self {
477        val.0
478    }
479}
480
481/// parse an InventoryFolderKey
482///
483/// # Errors
484///
485/// returns an error if the string could not be parsed
486#[cfg(feature = "chumsky")]
487#[must_use]
488pub fn inventory_folder_key_parser<'src>() -> impl Parser<
489    'src,
490    &'src str,
491    InventoryFolderKey,
492    chumsky::extra::Err<chumsky::error::Rich<'src, char>>,
493> {
494    key_parser().map(InventoryFolderKey)
495}
496
497/// represents s Second Life key for an owner (e.g. of an object)
498#[derive(Debug, Clone, PartialEq, Eq, strum::EnumIs)]
499#[expect(
500    clippy::module_name_repetitions,
501    reason = "the type is going to be used outside this module"
502)]
503pub enum OwnerKey {
504    /// the owner is an agent
505    Agent(AgentKey),
506    /// the owner is a group
507    Group(GroupKey),
508}
509
510impl std::fmt::Display for OwnerKey {
511    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
512        match self {
513            Self::Agent(agent_key) => write!(f, "{agent_key}"),
514            Self::Group(group_key) => write!(f, "{group_key}"),
515        }
516    }
517}
518
519/// error when the owner is a group while trying to convert an OwnerKey to an AgentKey
520#[derive(Debug, Clone)]
521pub struct OwnerIsGroupError(GroupKey);
522
523impl std::fmt::Display for OwnerIsGroupError {
524    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
525        write!(f, "The owner is not an agent but the group {}", self.0)
526    }
527}
528
529impl TryInto<AgentKey> for OwnerKey {
530    type Error = OwnerIsGroupError;
531
532    fn try_into(self) -> Result<AgentKey, Self::Error> {
533        match self {
534            Self::Agent(agent_key) => Ok(agent_key),
535            Self::Group(group_key) => Err(OwnerIsGroupError(group_key)),
536        }
537    }
538}
539
540/// error when the owner is an agent while trying to convert an OwnerKey to a GroupKey
541#[derive(Debug, Clone)]
542pub struct OwnerIsAgentError(AgentKey);
543
544impl std::fmt::Display for OwnerIsAgentError {
545    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
546        write!(f, "The owner is not a group but the agent {}", self.0)
547    }
548}
549
550impl TryInto<GroupKey> for OwnerKey {
551    type Error = OwnerIsAgentError;
552
553    fn try_into(self) -> Result<GroupKey, Self::Error> {
554        match self {
555            Self::Agent(agent_key) => Err(OwnerIsAgentError(agent_key)),
556            Self::Group(group_key) => Ok(group_key),
557        }
558    }
559}
560
561impl From<OwnerKey> for Key {
562    fn from(val: OwnerKey) -> Self {
563        match val {
564            OwnerKey::Agent(agent_key) => agent_key.into(),
565            OwnerKey::Group(group_key) => group_key.into(),
566        }
567    }
568}
569
570/// parse a viewer URI that is either an /about or /inspect URL for an agent group
571/// or for a group and return the OwnerKey
572///
573/// # Errors
574///
575/// returns an error if the string could not be parsed
576#[cfg(feature = "chumsky")]
577#[must_use]
578pub fn app_agent_or_group_uri_as_owner_key_parser<'src>()
579-> impl Parser<'src, &'src str, OwnerKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
580    app_agent_uri_as_agent_key_parser()
581        .map(OwnerKey::Agent)
582        .or(app_group_uri_as_group_key_parser().map(OwnerKey::Group))
583}