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