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(
61    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
62)]
63pub struct Key(pub Uuid);
64
65impl std::fmt::Display for Key {
66    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        write!(f, "{}", self.0)
68    }
69}
70
71/// parse a Key
72///
73/// # Errors
74///
75/// returns an error if the string could not be parsed
76#[cfg(feature = "chumsky")]
77#[must_use]
78#[expect(
79    clippy::module_name_repetitions,
80    reason = "the parser is going to be used outside this module"
81)]
82pub fn key_parser<'src>()
83-> impl Parser<'src, &'src str, Key, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
84    uuid_parser().map(Key)
85}
86
87/// the null key used by Second Life in many places to represent the absence
88/// of a key value
89pub const NULL_KEY: Key = Key(uuid!("00000000-0000-0000-0000-000000000000"));
90
91/// the key used by the Second Life system to send combat logs to the COMBAT_CHANNEL
92pub const COMBAT_LOG_ID: Key = Key(uuid!("45e0fcfa-2268-4490-a51c-3e51bdfe80d1"));
93
94/// represents a Second Life key for an agent (avatar)
95#[derive(
96    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
97)]
98#[expect(
99    clippy::module_name_repetitions,
100    reason = "the type is going to be used outside this module"
101)]
102pub struct AgentKey(pub Key);
103
104impl std::fmt::Display for AgentKey {
105    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106        write!(f, "{}", self.0)
107    }
108}
109
110impl From<AgentKey> for Key {
111    fn from(val: AgentKey) -> Self {
112        val.0
113    }
114}
115
116/// parse an AgentKey
117///
118/// # Errors
119///
120/// returns an error if the string could not be parsed
121#[cfg(feature = "chumsky")]
122#[must_use]
123pub fn agent_key_parser<'src>()
124-> impl Parser<'src, &'src str, AgentKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
125    key_parser().map(AgentKey)
126}
127
128/// parse a viewer URI that is either an /about or /inspect URL for an avatar
129/// and return the AgentKey
130///
131/// # Errors
132///
133/// returns an error if the string could not be parsed
134#[cfg(feature = "chumsky")]
135#[must_use]
136pub fn app_agent_uri_as_agent_key_parser<'src>()
137-> impl Parser<'src, &'src str, AgentKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
138    crate::viewer_uri::viewer_app_agent_uri_parser().try_map(|uri, span| match uri {
139        crate::viewer_uri::ViewerUri::AgentAbout(agent_key)
140        | crate::viewer_uri::ViewerUri::AgentInspect(agent_key) => Ok(agent_key),
141        _ => Err(chumsky::error::Rich::custom(
142            span,
143            "Unexpected type of Agent viewer URI",
144        )),
145    })
146}
147
148/// represents a Second Life key for a classified ad
149#[derive(
150    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
151)]
152#[expect(
153    clippy::module_name_repetitions,
154    reason = "the type is going to be used outside this module"
155)]
156pub struct ClassifiedKey(pub Key);
157
158impl std::fmt::Display for ClassifiedKey {
159    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160        write!(f, "{}", self.0)
161    }
162}
163
164impl From<ClassifiedKey> for Key {
165    fn from(val: ClassifiedKey) -> Self {
166        val.0
167    }
168}
169
170/// parse a ClassifiedKey
171///
172/// # Errors
173///
174/// returns an error if the string could not be parsed
175#[cfg(feature = "chumsky")]
176#[must_use]
177pub fn classified_key_parser<'src>()
178-> impl Parser<'src, &'src str, ClassifiedKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
179{
180    key_parser().map(ClassifiedKey)
181}
182
183/// represents a Second Life key for an experience
184#[derive(
185    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
186)]
187#[expect(
188    clippy::module_name_repetitions,
189    reason = "the type is going to be used outside this module"
190)]
191pub struct ExperienceKey(pub Key);
192
193impl std::fmt::Display for ExperienceKey {
194    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195        write!(f, "{}", self.0)
196    }
197}
198
199impl From<ExperienceKey> for Key {
200    fn from(val: ExperienceKey) -> Self {
201        val.0
202    }
203}
204
205/// parse an ExperienceKey
206///
207/// # Errors
208///
209/// returns an error if the string could not be parsed
210#[cfg(feature = "chumsky")]
211#[must_use]
212pub fn experience_key_parser<'src>()
213-> impl Parser<'src, &'src str, ExperienceKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
214{
215    key_parser().map(ExperienceKey)
216}
217
218/// represents a Second Life key for an agent who is a friend
219#[derive(
220    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
221)]
222#[expect(
223    clippy::module_name_repetitions,
224    reason = "the type is going to be used outside this module"
225)]
226pub struct FriendKey(pub Key);
227
228impl std::fmt::Display for FriendKey {
229    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
230        write!(f, "{}", self.0)
231    }
232}
233
234impl From<FriendKey> for Key {
235    fn from(val: FriendKey) -> Self {
236        val.0
237    }
238}
239
240impl From<FriendKey> for AgentKey {
241    fn from(val: FriendKey) -> Self {
242        Self(val.0)
243    }
244}
245
246/// parse a FriendKey
247///
248/// # Errors
249///
250/// returns an error if the string could not be parsed
251#[cfg(feature = "chumsky")]
252#[must_use]
253pub fn friend_key_parser<'src>()
254-> impl Parser<'src, &'src str, FriendKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
255    key_parser().map(FriendKey)
256}
257
258/// represents a Second Life key for a group
259#[derive(
260    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
261)]
262#[expect(
263    clippy::module_name_repetitions,
264    reason = "the type is going to be used outside this module"
265)]
266pub struct GroupKey(pub Key);
267
268impl std::fmt::Display for GroupKey {
269    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
270        write!(f, "{}", self.0)
271    }
272}
273
274impl From<GroupKey> for Key {
275    fn from(val: GroupKey) -> Self {
276        val.0
277    }
278}
279
280/// parse a GroupKey
281///
282/// # Errors
283///
284/// returns an error if the string could not be parsed
285#[cfg(feature = "chumsky")]
286#[must_use]
287pub fn group_key_parser<'src>()
288-> impl Parser<'src, &'src str, GroupKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
289    key_parser().map(GroupKey)
290}
291
292/// parse a viewer URI that is either an /about or /inspect URL for a group
293/// and return the GroupKey
294///
295/// # Errors
296///
297/// returns an error if the string could not be parsed
298#[cfg(feature = "chumsky")]
299#[must_use]
300pub fn app_group_uri_as_group_key_parser<'src>()
301-> impl Parser<'src, &'src str, GroupKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
302    crate::viewer_uri::viewer_app_group_uri_parser().try_map(|uri, span| match uri {
303        crate::viewer_uri::ViewerUri::GroupAbout(group_key)
304        | crate::viewer_uri::ViewerUri::GroupInspect(group_key) => Ok(group_key),
305        _ => Err(chumsky::error::Rich::custom(
306            span,
307            "Unexpected type of group viewer URI",
308        )),
309    })
310}
311
312/// represents a Second Life key for an inventory item
313#[derive(
314    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
315)]
316#[expect(
317    clippy::module_name_repetitions,
318    reason = "the type is going to be used outside this module"
319)]
320pub struct InventoryKey(pub Key);
321
322impl std::fmt::Display for InventoryKey {
323    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
324        write!(f, "{}", self.0)
325    }
326}
327
328impl From<InventoryKey> for Key {
329    fn from(val: InventoryKey) -> Self {
330        val.0
331    }
332}
333
334/// parse an InventoryKey
335///
336/// # Errors
337///
338/// returns an error if the string could not be parsed
339#[cfg(feature = "chumsky")]
340#[must_use]
341pub fn inventory_key_parser<'src>()
342-> impl Parser<'src, &'src str, InventoryKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
343{
344    key_parser().map(InventoryKey)
345}
346
347/// represents a Second Life key for an object
348#[derive(
349    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
350)]
351#[expect(
352    clippy::module_name_repetitions,
353    reason = "the type is going to be used outside this module"
354)]
355pub struct ObjectKey(pub Key);
356
357impl std::fmt::Display for ObjectKey {
358    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
359        write!(f, "{}", self.0)
360    }
361}
362
363impl From<ObjectKey> for Key {
364    fn from(val: ObjectKey) -> Self {
365        val.0
366    }
367}
368
369/// parse an ObjectKey
370///
371/// # Errors
372///
373/// returns an error if the string could not be parsed
374#[cfg(feature = "chumsky")]
375#[must_use]
376pub fn object_key_parser<'src>()
377-> impl Parser<'src, &'src str, ObjectKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
378    key_parser().map(ObjectKey)
379}
380
381/// represents a Second Life key for a parcel
382#[derive(
383    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
384)]
385#[expect(
386    clippy::module_name_repetitions,
387    reason = "the type is going to be used outside this module"
388)]
389pub struct ParcelKey(pub Key);
390
391impl std::fmt::Display for ParcelKey {
392    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
393        write!(f, "{}", self.0)
394    }
395}
396
397impl From<ParcelKey> for Key {
398    fn from(val: ParcelKey) -> Self {
399        val.0
400    }
401}
402
403/// parse a ParcelKey
404///
405/// # Errors
406///
407/// returns an error if the string could not be parsed
408#[cfg(feature = "chumsky")]
409#[must_use]
410pub fn parcel_key_parser<'src>()
411-> impl Parser<'src, &'src str, ParcelKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
412    key_parser().map(ParcelKey)
413}
414
415/// represents a Second Life key for a texture
416#[derive(
417    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
418)]
419#[expect(
420    clippy::module_name_repetitions,
421    reason = "the type is going to be used outside this module"
422)]
423pub struct TextureKey(pub Key);
424
425impl std::fmt::Display for TextureKey {
426    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
427        write!(f, "{}", self.0)
428    }
429}
430
431impl From<TextureKey> for Key {
432    fn from(val: TextureKey) -> Self {
433        val.0
434    }
435}
436
437/// parse a TextureKey
438///
439/// # Errors
440///
441/// returns an error if the string could not be parsed
442#[cfg(feature = "chumsky")]
443#[must_use]
444pub fn texture_key_parser<'src>()
445-> impl Parser<'src, &'src str, TextureKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
446    key_parser().map(TextureKey)
447}
448
449/// represents a Second Life key for an inventory folder
450#[derive(
451    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
452)]
453#[expect(
454    clippy::module_name_repetitions,
455    reason = "the type is going to be used outside this module"
456)]
457pub struct InventoryFolderKey(pub Key);
458
459impl std::fmt::Display for InventoryFolderKey {
460    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
461        write!(f, "{}", self.0)
462    }
463}
464
465impl From<InventoryFolderKey> for Key {
466    fn from(val: InventoryFolderKey) -> Self {
467        val.0
468    }
469}
470
471/// parse an InventoryFolderKey
472///
473/// # Errors
474///
475/// returns an error if the string could not be parsed
476#[cfg(feature = "chumsky")]
477#[must_use]
478pub fn inventory_folder_key_parser<'src>() -> impl Parser<
479    'src,
480    &'src str,
481    InventoryFolderKey,
482    chumsky::extra::Err<chumsky::error::Rich<'src, char>>,
483> {
484    key_parser().map(InventoryFolderKey)
485}
486
487/// represents s Second Life key for an owner (e.g. of an object)
488#[derive(
489    Debug, Clone, Copy, PartialEq, Eq, Hash, strum::EnumIs, serde::Serialize, serde::Deserialize,
490)]
491#[expect(
492    clippy::module_name_repetitions,
493    reason = "the type is going to be used outside this module"
494)]
495pub enum OwnerKey {
496    /// the owner is an agent
497    Agent(AgentKey),
498    /// the owner is a group
499    Group(GroupKey),
500}
501
502// Order by the underlying UUID, not by variant, so the two owner kinds
503// interleave in a single key space (an `Agent` and a `Group` sort purely by id,
504// matching how the wire treats the raw key). `uuid()` is defined further down.
505impl PartialOrd for OwnerKey {
506    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
507        Some(self.cmp(other))
508    }
509}
510
511impl Ord for OwnerKey {
512    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
513        self.uuid().cmp(&other.uuid())
514    }
515}
516
517impl std::fmt::Display for OwnerKey {
518    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
519        match self {
520            Self::Agent(agent_key) => write!(f, "{agent_key}"),
521            Self::Group(group_key) => write!(f, "{group_key}"),
522        }
523    }
524}
525
526/// error when the owner is a group while trying to convert an OwnerKey to an AgentKey
527#[derive(Debug, Clone)]
528pub struct OwnerIsGroupError(GroupKey);
529
530impl std::fmt::Display for OwnerIsGroupError {
531    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
532        write!(f, "The owner is not an agent but the group {}", self.0)
533    }
534}
535
536impl TryInto<AgentKey> for OwnerKey {
537    type Error = OwnerIsGroupError;
538
539    fn try_into(self) -> Result<AgentKey, Self::Error> {
540        match self {
541            Self::Agent(agent_key) => Ok(agent_key),
542            Self::Group(group_key) => Err(OwnerIsGroupError(group_key)),
543        }
544    }
545}
546
547/// error when the owner is an agent while trying to convert an OwnerKey to a GroupKey
548#[derive(Debug, Clone)]
549pub struct OwnerIsAgentError(AgentKey);
550
551impl std::fmt::Display for OwnerIsAgentError {
552    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
553        write!(f, "The owner is not a group but the agent {}", self.0)
554    }
555}
556
557impl TryInto<GroupKey> for OwnerKey {
558    type Error = OwnerIsAgentError;
559
560    fn try_into(self) -> Result<GroupKey, Self::Error> {
561        match self {
562            Self::Agent(agent_key) => Err(OwnerIsAgentError(agent_key)),
563            Self::Group(group_key) => Ok(group_key),
564        }
565    }
566}
567
568impl From<OwnerKey> for Key {
569    fn from(val: OwnerKey) -> Self {
570        match val {
571            OwnerKey::Agent(agent_key) => agent_key.into(),
572            OwnerKey::Group(group_key) => group_key.into(),
573        }
574    }
575}
576
577/// parse a viewer URI that is either an /about or /inspect URL for an agent group
578/// or for a group and return the OwnerKey
579///
580/// # Errors
581///
582/// returns an error if the string could not be parsed
583#[cfg(feature = "chumsky")]
584#[must_use]
585pub fn app_agent_or_group_uri_as_owner_key_parser<'src>()
586-> impl Parser<'src, &'src str, OwnerKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
587    app_agent_uri_as_agent_key_parser()
588        .map(OwnerKey::Agent)
589        .or(app_group_uri_as_group_key_parser().map(OwnerKey::Group))
590}
591
592impl Key {
593    /// The wrapped raw UUID.
594    #[must_use]
595    pub const fn uuid(&self) -> Uuid {
596        self.0
597    }
598}
599
600impl From<Uuid> for Key {
601    fn from(value: Uuid) -> Self {
602        Self(value)
603    }
604}
605
606impl AgentKey {
607    /// The wrapped raw UUID.
608    #[must_use]
609    pub const fn uuid(&self) -> Uuid {
610        self.0.0
611    }
612}
613
614impl From<Uuid> for AgentKey {
615    fn from(value: Uuid) -> Self {
616        Self(Key(value))
617    }
618}
619
620impl ClassifiedKey {
621    /// The wrapped raw UUID.
622    #[must_use]
623    pub const fn uuid(&self) -> Uuid {
624        self.0.0
625    }
626}
627
628impl From<Uuid> for ClassifiedKey {
629    fn from(value: Uuid) -> Self {
630        Self(Key(value))
631    }
632}
633
634impl ExperienceKey {
635    /// The wrapped raw UUID.
636    #[must_use]
637    pub const fn uuid(&self) -> Uuid {
638        self.0.0
639    }
640}
641
642impl From<Uuid> for ExperienceKey {
643    fn from(value: Uuid) -> Self {
644        Self(Key(value))
645    }
646}
647
648impl FriendKey {
649    /// The wrapped raw UUID.
650    #[must_use]
651    pub const fn uuid(&self) -> Uuid {
652        self.0.0
653    }
654}
655
656impl From<Uuid> for FriendKey {
657    fn from(value: Uuid) -> Self {
658        Self(Key(value))
659    }
660}
661
662impl GroupKey {
663    /// The wrapped raw UUID.
664    #[must_use]
665    pub const fn uuid(&self) -> Uuid {
666        self.0.0
667    }
668}
669
670impl From<Uuid> for GroupKey {
671    fn from(value: Uuid) -> Self {
672        Self(Key(value))
673    }
674}
675
676impl InventoryKey {
677    /// The wrapped raw UUID.
678    #[must_use]
679    pub const fn uuid(&self) -> Uuid {
680        self.0.0
681    }
682}
683
684impl From<Uuid> for InventoryKey {
685    fn from(value: Uuid) -> Self {
686        Self(Key(value))
687    }
688}
689
690impl ObjectKey {
691    /// The wrapped raw UUID.
692    #[must_use]
693    pub const fn uuid(&self) -> Uuid {
694        self.0.0
695    }
696}
697
698impl From<Uuid> for ObjectKey {
699    fn from(value: Uuid) -> Self {
700        Self(Key(value))
701    }
702}
703
704impl ParcelKey {
705    /// The wrapped raw UUID.
706    #[must_use]
707    pub const fn uuid(&self) -> Uuid {
708        self.0.0
709    }
710}
711
712impl From<Uuid> for ParcelKey {
713    fn from(value: Uuid) -> Self {
714        Self(Key(value))
715    }
716}
717
718impl TextureKey {
719    /// The wrapped raw UUID.
720    #[must_use]
721    pub const fn uuid(&self) -> Uuid {
722        self.0.0
723    }
724}
725
726impl From<Uuid> for TextureKey {
727    fn from(value: Uuid) -> Self {
728        Self(Key(value))
729    }
730}
731
732impl InventoryFolderKey {
733    /// The wrapped raw UUID.
734    #[must_use]
735    pub const fn uuid(&self) -> Uuid {
736        self.0.0
737    }
738}
739
740impl From<Uuid> for InventoryFolderKey {
741    fn from(value: Uuid) -> Self {
742        Self(Key(value))
743    }
744}
745
746impl OwnerKey {
747    /// The wrapped raw UUID, regardless of whether the owner is an agent or a
748    /// group.
749    #[must_use]
750    pub const fn uuid(&self) -> Uuid {
751        match self {
752            Self::Agent(agent_key) => agent_key.0.0,
753            Self::Group(group_key) => group_key.0.0,
754        }
755    }
756}
757
758/// represents a Second Life key for a *role* within a group (e.g. the "Owners"
759/// role, or the nil-keyed default "Everyone" role), as distinct from the
760/// group's own [`GroupKey`]
761#[derive(
762    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
763)]
764#[expect(
765    clippy::module_name_repetitions,
766    reason = "the type is going to be used outside this module"
767)]
768pub struct GroupRoleKey(pub Key);
769
770impl std::fmt::Display for GroupRoleKey {
771    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
772        write!(f, "{}", self.0)
773    }
774}
775
776impl From<GroupRoleKey> for Key {
777    fn from(val: GroupRoleKey) -> Self {
778        val.0
779    }
780}
781
782impl GroupRoleKey {
783    /// The wrapped raw UUID.
784    #[must_use]
785    pub const fn uuid(&self) -> Uuid {
786        self.0.0
787    }
788}
789
790impl From<Uuid> for GroupRoleKey {
791    fn from(value: Uuid) -> Self {
792        Self(Key(value))
793    }
794}
795
796/// parse a GroupRoleKey
797///
798/// # Errors
799///
800/// returns an error if the string could not be parsed
801#[cfg(feature = "chumsky")]
802#[must_use]
803pub fn group_role_key_parser<'src>()
804-> impl Parser<'src, &'src str, GroupRoleKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
805{
806    key_parser().map(GroupRoleKey)
807}
808
809/// represents a Second Life key for a profile **pick** (the viewer's
810/// `LLPickData::mPickID`) — a profile-listed place. The picks-side parallel of
811/// [`ClassifiedKey`], kept distinct so the two cannot be transposed.
812#[derive(
813    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
814)]
815#[expect(
816    clippy::module_name_repetitions,
817    reason = "the type is going to be used outside this module"
818)]
819pub struct PickKey(pub Key);
820
821impl std::fmt::Display for PickKey {
822    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
823        write!(f, "{}", self.0)
824    }
825}
826
827impl From<PickKey> for Key {
828    fn from(val: PickKey) -> Self {
829        val.0
830    }
831}
832
833impl PickKey {
834    /// The wrapped raw UUID.
835    #[must_use]
836    pub const fn uuid(&self) -> Uuid {
837        self.0.0
838    }
839}
840
841impl From<Uuid> for PickKey {
842    fn from(value: Uuid) -> Self {
843        Self(Key(value))
844    }
845}
846
847/// parse a PickKey
848///
849/// # Errors
850///
851/// returns an error if the string could not be parsed
852#[cfg(feature = "chumsky")]
853#[must_use]
854pub fn pick_key_parser<'src>()
855-> impl Parser<'src, &'src str, PickKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
856    key_parser().map(PickKey)
857}
858
859/// represents a Second Life key for a group **notice** (the viewer's group-notice
860/// `mNoticeID`) — one posting in a group's notice list.
861#[derive(
862    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
863)]
864#[expect(
865    clippy::module_name_repetitions,
866    reason = "the type is going to be used outside this module"
867)]
868pub struct GroupNoticeKey(pub Key);
869
870impl std::fmt::Display for GroupNoticeKey {
871    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
872        write!(f, "{}", self.0)
873    }
874}
875
876impl From<GroupNoticeKey> for Key {
877    fn from(val: GroupNoticeKey) -> Self {
878        val.0
879    }
880}
881
882impl GroupNoticeKey {
883    /// The wrapped raw UUID.
884    #[must_use]
885    pub const fn uuid(&self) -> Uuid {
886        self.0.0
887    }
888}
889
890impl From<Uuid> for GroupNoticeKey {
891    fn from(value: Uuid) -> Self {
892        Self(Key(value))
893    }
894}
895
896/// parse a GroupNoticeKey
897///
898/// # Errors
899///
900/// returns an error if the string could not be parsed
901#[cfg(feature = "chumsky")]
902#[must_use]
903pub fn group_notice_key_parser<'src>()
904-> impl Parser<'src, &'src str, GroupNoticeKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
905{
906    key_parser().map(GroupNoticeKey)
907}
908
909/// represents a Second Life key for a *mesh* asset — the UUID of a mesh asset,
910/// as carried in the sculpt/mesh block of a prim whose shape comes from a mesh
911/// rather than a sculpt texture. A mesh asset is emphatically not a
912/// [`TextureKey`].
913#[derive(
914    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
915)]
916#[expect(
917    clippy::module_name_repetitions,
918    reason = "the type is going to be used outside this module"
919)]
920pub struct MeshKey(pub Key);
921
922impl std::fmt::Display for MeshKey {
923    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
924        write!(f, "{}", self.0)
925    }
926}
927
928impl From<MeshKey> for Key {
929    fn from(val: MeshKey) -> Self {
930        val.0
931    }
932}
933
934impl MeshKey {
935    /// The wrapped raw UUID.
936    #[must_use]
937    pub const fn uuid(&self) -> Uuid {
938        self.0.0
939    }
940}
941
942impl From<Uuid> for MeshKey {
943    fn from(value: Uuid) -> Self {
944        Self(Key(value))
945    }
946}
947
948/// parse a MeshKey
949///
950/// # Errors
951///
952/// returns an error if the string could not be parsed
953#[cfg(feature = "chumsky")]
954#[must_use]
955pub fn mesh_key_parser<'src>()
956-> impl Parser<'src, &'src str, MeshKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
957    key_parser().map(MeshKey)
958}
959
960/// a key that is either an **agent** ([`AgentKey`]) or an in-world **object**
961/// ([`ObjectKey`]), as selected by a separate source-type discriminator (an
962/// avatar or a prim can be the source of chat, sounds, effects, …)
963#[derive(
964    Debug, Clone, Copy, PartialEq, Eq, Hash, strum::EnumIs, serde::Serialize, serde::Deserialize,
965)]
966#[expect(
967    clippy::module_name_repetitions,
968    reason = "the type is going to be used outside this module"
969)]
970pub enum AgentOrObjectKey {
971    /// the key is an agent
972    Agent(AgentKey),
973    /// the key is an in-world object
974    Object(ObjectKey),
975}
976
977impl AgentOrObjectKey {
978    /// The wrapped raw UUID, regardless of whether the key is an agent or an
979    /// object.
980    #[must_use]
981    pub const fn uuid(&self) -> Uuid {
982        match self {
983            Self::Agent(agent_key) => agent_key.0.0,
984            Self::Object(object_key) => object_key.0.0,
985        }
986    }
987}
988
989// Order by the underlying UUID, not by variant, so agents and objects share one
990// key space.
991impl PartialOrd for AgentOrObjectKey {
992    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
993        Some(self.cmp(other))
994    }
995}
996
997impl Ord for AgentOrObjectKey {
998    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
999        self.uuid().cmp(&other.uuid())
1000    }
1001}
1002
1003impl std::fmt::Display for AgentOrObjectKey {
1004    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1005        match self {
1006            Self::Agent(agent_key) => write!(f, "{agent_key}"),
1007            Self::Object(object_key) => write!(f, "{object_key}"),
1008        }
1009    }
1010}
1011
1012impl From<AgentOrObjectKey> for Key {
1013    fn from(val: AgentOrObjectKey) -> Self {
1014        match val {
1015            AgentOrObjectKey::Agent(agent_key) => agent_key.into(),
1016            AgentOrObjectKey::Object(object_key) => object_key.into(),
1017        }
1018    }
1019}
1020
1021/// a key that is either an inventory **item** ([`InventoryKey`]) or a whole
1022/// inventory **folder/category** ([`InventoryFolderKey`]), as selected by a
1023/// separate asset-type discriminator
1024#[derive(
1025    Debug, Clone, Copy, PartialEq, Eq, Hash, strum::EnumIs, serde::Serialize, serde::Deserialize,
1026)]
1027#[expect(
1028    clippy::module_name_repetitions,
1029    reason = "the type is going to be used outside this module"
1030)]
1031pub enum InventoryItemOrFolderKey {
1032    /// the key refers to a single inventory item
1033    Item(InventoryKey),
1034    /// the key refers to a whole inventory folder/category
1035    Folder(InventoryFolderKey),
1036}
1037
1038impl InventoryItemOrFolderKey {
1039    /// The wrapped raw UUID, regardless of whether it refers to an item or a
1040    /// folder.
1041    #[must_use]
1042    pub const fn uuid(&self) -> Uuid {
1043        match self {
1044            Self::Item(item_key) => item_key.0.0,
1045            Self::Folder(folder_key) => folder_key.0.0,
1046        }
1047    }
1048}
1049
1050// Order by the underlying UUID, not by variant, so items and folders share one
1051// key space.
1052impl PartialOrd for InventoryItemOrFolderKey {
1053    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
1054        Some(self.cmp(other))
1055    }
1056}
1057
1058impl Ord for InventoryItemOrFolderKey {
1059    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
1060        self.uuid().cmp(&other.uuid())
1061    }
1062}
1063
1064impl std::fmt::Display for InventoryItemOrFolderKey {
1065    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1066        match self {
1067            Self::Item(item_key) => write!(f, "{item_key}"),
1068            Self::Folder(folder_key) => write!(f, "{folder_key}"),
1069        }
1070    }
1071}
1072
1073impl From<InventoryItemOrFolderKey> for Key {
1074    fn from(val: InventoryItemOrFolderKey) -> Self {
1075        match val {
1076            InventoryItemOrFolderKey::Item(item_key) => item_key.into(),
1077            InventoryItemOrFolderKey::Folder(folder_key) => folder_key.into(),
1078        }
1079    }
1080}
1081
1082/// the asset backing a prim's sculpt/mesh shape: either a sculpt **texture**
1083/// ([`TextureKey`]) or a **mesh** asset ([`MeshKey`]), as selected by the prim's
1084/// sculpt-type discriminator
1085#[derive(
1086    Debug, Clone, Copy, PartialEq, Eq, Hash, strum::EnumIs, serde::Serialize, serde::Deserialize,
1087)]
1088#[expect(
1089    clippy::module_name_repetitions,
1090    reason = "the type is going to be used outside this module"
1091)]
1092pub enum SculptOrMeshKey {
1093    /// the prim is a sculpty: the key is a sculpt texture
1094    Sculpt(TextureKey),
1095    /// the prim is a mesh: the key is a mesh asset
1096    Mesh(MeshKey),
1097}
1098
1099impl SculptOrMeshKey {
1100    /// The wrapped raw UUID, regardless of whether it is a sculpt texture or a
1101    /// mesh asset.
1102    #[must_use]
1103    pub const fn uuid(&self) -> Uuid {
1104        match self {
1105            Self::Sculpt(texture_key) => texture_key.0.0,
1106            Self::Mesh(mesh_key) => mesh_key.0.0,
1107        }
1108    }
1109}
1110
1111// Order by the underlying UUID, not by variant, so sculpt textures and mesh
1112// assets share one key space.
1113impl PartialOrd for SculptOrMeshKey {
1114    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
1115        Some(self.cmp(other))
1116    }
1117}
1118
1119impl Ord for SculptOrMeshKey {
1120    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
1121        self.uuid().cmp(&other.uuid())
1122    }
1123}
1124
1125impl std::fmt::Display for SculptOrMeshKey {
1126    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1127        match self {
1128            Self::Sculpt(texture_key) => write!(f, "{texture_key}"),
1129            Self::Mesh(mesh_key) => write!(f, "{mesh_key}"),
1130        }
1131    }
1132}
1133
1134impl From<SculptOrMeshKey> for Key {
1135    fn from(val: SculptOrMeshKey) -> Self {
1136        match val {
1137            SculptOrMeshKey::Sculpt(texture_key) => texture_key.into(),
1138            SculptOrMeshKey::Mesh(mesh_key) => mesh_key.into(),
1139        }
1140    }
1141}