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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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
478impl std::fmt::Display for OwnerKey {
479    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
480        match self {
481            Self::Agent(agent_key) => write!(f, "{agent_key}"),
482            Self::Group(group_key) => write!(f, "{group_key}"),
483        }
484    }
485}
486
487/// error when the owner is a group while trying to convert an OwnerKey to an AgentKey
488#[derive(Debug, Clone)]
489pub struct OwnerIsGroupError(GroupKey);
490
491impl std::fmt::Display for OwnerIsGroupError {
492    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
493        write!(f, "The owner is not an agent but the group {}", self.0)
494    }
495}
496
497impl TryInto<AgentKey> for OwnerKey {
498    type Error = OwnerIsGroupError;
499
500    fn try_into(self) -> Result<AgentKey, Self::Error> {
501        match self {
502            Self::Agent(agent_key) => Ok(agent_key),
503            Self::Group(group_key) => Err(OwnerIsGroupError(group_key)),
504        }
505    }
506}
507
508/// error when the owner is an agent while trying to convert an OwnerKey to a GroupKey
509#[derive(Debug, Clone)]
510pub struct OwnerIsAgentError(AgentKey);
511
512impl std::fmt::Display for OwnerIsAgentError {
513    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
514        write!(f, "The owner is not a group but the agent {}", self.0)
515    }
516}
517
518impl TryInto<GroupKey> for OwnerKey {
519    type Error = OwnerIsAgentError;
520
521    fn try_into(self) -> Result<GroupKey, Self::Error> {
522        match self {
523            Self::Agent(agent_key) => Err(OwnerIsAgentError(agent_key)),
524            Self::Group(group_key) => Ok(group_key),
525        }
526    }
527}
528
529impl From<OwnerKey> for Key {
530    fn from(val: OwnerKey) -> Self {
531        match val {
532            OwnerKey::Agent(agent_key) => agent_key.into(),
533            OwnerKey::Group(group_key) => group_key.into(),
534        }
535    }
536}
537
538/// parse a viewer URI that is either an /about or /inspect URL for an agent group
539/// or for a group and return the OwnerKey
540///
541/// # Errors
542///
543/// returns an error if the string could not be parsed
544#[cfg(feature = "chumsky")]
545#[must_use]
546pub fn app_agent_or_group_uri_as_owner_key_parser<'src>()
547-> impl Parser<'src, &'src str, OwnerKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
548    app_agent_uri_as_agent_key_parser()
549        .map(OwnerKey::Agent)
550        .or(app_group_uri_as_group_key_parser().map(OwnerKey::Group))
551}
552
553impl Key {
554    /// The wrapped raw UUID.
555    #[must_use]
556    pub const fn uuid(&self) -> Uuid {
557        self.0
558    }
559}
560
561impl From<Uuid> for Key {
562    fn from(value: Uuid) -> Self {
563        Self(value)
564    }
565}
566
567impl AgentKey {
568    /// The wrapped raw UUID.
569    #[must_use]
570    pub const fn uuid(&self) -> Uuid {
571        self.0.0
572    }
573}
574
575impl From<Uuid> for AgentKey {
576    fn from(value: Uuid) -> Self {
577        Self(Key(value))
578    }
579}
580
581impl ClassifiedKey {
582    /// The wrapped raw UUID.
583    #[must_use]
584    pub const fn uuid(&self) -> Uuid {
585        self.0.0
586    }
587}
588
589impl From<Uuid> for ClassifiedKey {
590    fn from(value: Uuid) -> Self {
591        Self(Key(value))
592    }
593}
594
595impl ExperienceKey {
596    /// The wrapped raw UUID.
597    #[must_use]
598    pub const fn uuid(&self) -> Uuid {
599        self.0.0
600    }
601}
602
603impl From<Uuid> for ExperienceKey {
604    fn from(value: Uuid) -> Self {
605        Self(Key(value))
606    }
607}
608
609impl FriendKey {
610    /// The wrapped raw UUID.
611    #[must_use]
612    pub const fn uuid(&self) -> Uuid {
613        self.0.0
614    }
615}
616
617impl From<Uuid> for FriendKey {
618    fn from(value: Uuid) -> Self {
619        Self(Key(value))
620    }
621}
622
623impl GroupKey {
624    /// The wrapped raw UUID.
625    #[must_use]
626    pub const fn uuid(&self) -> Uuid {
627        self.0.0
628    }
629}
630
631impl From<Uuid> for GroupKey {
632    fn from(value: Uuid) -> Self {
633        Self(Key(value))
634    }
635}
636
637impl InventoryKey {
638    /// The wrapped raw UUID.
639    #[must_use]
640    pub const fn uuid(&self) -> Uuid {
641        self.0.0
642    }
643}
644
645impl From<Uuid> for InventoryKey {
646    fn from(value: Uuid) -> Self {
647        Self(Key(value))
648    }
649}
650
651impl ObjectKey {
652    /// The wrapped raw UUID.
653    #[must_use]
654    pub const fn uuid(&self) -> Uuid {
655        self.0.0
656    }
657}
658
659impl From<Uuid> for ObjectKey {
660    fn from(value: Uuid) -> Self {
661        Self(Key(value))
662    }
663}
664
665impl ParcelKey {
666    /// The wrapped raw UUID.
667    #[must_use]
668    pub const fn uuid(&self) -> Uuid {
669        self.0.0
670    }
671}
672
673impl From<Uuid> for ParcelKey {
674    fn from(value: Uuid) -> Self {
675        Self(Key(value))
676    }
677}
678
679impl TextureKey {
680    /// The wrapped raw UUID.
681    #[must_use]
682    pub const fn uuid(&self) -> Uuid {
683        self.0.0
684    }
685}
686
687impl From<Uuid> for TextureKey {
688    fn from(value: Uuid) -> Self {
689        Self(Key(value))
690    }
691}
692
693impl InventoryFolderKey {
694    /// The wrapped raw UUID.
695    #[must_use]
696    pub const fn uuid(&self) -> Uuid {
697        self.0.0
698    }
699}
700
701impl From<Uuid> for InventoryFolderKey {
702    fn from(value: Uuid) -> Self {
703        Self(Key(value))
704    }
705}
706
707impl OwnerKey {
708    /// The wrapped raw UUID, regardless of whether the owner is an agent or a
709    /// group.
710    #[must_use]
711    pub const fn uuid(&self) -> Uuid {
712        match self {
713            Self::Agent(agent_key) => agent_key.0.0,
714            Self::Group(group_key) => group_key.0.0,
715        }
716    }
717}
718
719/// represents a Second Life key for a *role* within a group (e.g. the "Owners"
720/// role, or the nil-keyed default "Everyone" role), as distinct from the
721/// group's own [`GroupKey`]
722#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
723#[expect(
724    clippy::module_name_repetitions,
725    reason = "the type is going to be used outside this module"
726)]
727pub struct GroupRoleKey(pub Key);
728
729impl std::fmt::Display for GroupRoleKey {
730    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
731        write!(f, "{}", self.0)
732    }
733}
734
735impl From<GroupRoleKey> for Key {
736    fn from(val: GroupRoleKey) -> Self {
737        val.0
738    }
739}
740
741impl GroupRoleKey {
742    /// The wrapped raw UUID.
743    #[must_use]
744    pub const fn uuid(&self) -> Uuid {
745        self.0.0
746    }
747}
748
749impl From<Uuid> for GroupRoleKey {
750    fn from(value: Uuid) -> Self {
751        Self(Key(value))
752    }
753}
754
755/// parse a GroupRoleKey
756///
757/// # Errors
758///
759/// returns an error if the string could not be parsed
760#[cfg(feature = "chumsky")]
761#[must_use]
762pub fn group_role_key_parser<'src>()
763-> impl Parser<'src, &'src str, GroupRoleKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
764{
765    key_parser().map(GroupRoleKey)
766}
767
768/// represents a Second Life key for a profile **pick** (the viewer's
769/// `LLPickData::mPickID`) — a profile-listed place. The picks-side parallel of
770/// [`ClassifiedKey`], kept distinct so the two cannot be transposed.
771#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
772#[expect(
773    clippy::module_name_repetitions,
774    reason = "the type is going to be used outside this module"
775)]
776pub struct PickKey(pub Key);
777
778impl std::fmt::Display for PickKey {
779    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
780        write!(f, "{}", self.0)
781    }
782}
783
784impl From<PickKey> for Key {
785    fn from(val: PickKey) -> Self {
786        val.0
787    }
788}
789
790impl PickKey {
791    /// The wrapped raw UUID.
792    #[must_use]
793    pub const fn uuid(&self) -> Uuid {
794        self.0.0
795    }
796}
797
798impl From<Uuid> for PickKey {
799    fn from(value: Uuid) -> Self {
800        Self(Key(value))
801    }
802}
803
804/// parse a PickKey
805///
806/// # Errors
807///
808/// returns an error if the string could not be parsed
809#[cfg(feature = "chumsky")]
810#[must_use]
811pub fn pick_key_parser<'src>()
812-> impl Parser<'src, &'src str, PickKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
813    key_parser().map(PickKey)
814}
815
816/// represents a Second Life key for a group **notice** (the viewer's group-notice
817/// `mNoticeID`) — one posting in a group's notice list.
818#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
819#[expect(
820    clippy::module_name_repetitions,
821    reason = "the type is going to be used outside this module"
822)]
823pub struct GroupNoticeKey(pub Key);
824
825impl std::fmt::Display for GroupNoticeKey {
826    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
827        write!(f, "{}", self.0)
828    }
829}
830
831impl From<GroupNoticeKey> for Key {
832    fn from(val: GroupNoticeKey) -> Self {
833        val.0
834    }
835}
836
837impl GroupNoticeKey {
838    /// The wrapped raw UUID.
839    #[must_use]
840    pub const fn uuid(&self) -> Uuid {
841        self.0.0
842    }
843}
844
845impl From<Uuid> for GroupNoticeKey {
846    fn from(value: Uuid) -> Self {
847        Self(Key(value))
848    }
849}
850
851/// parse a GroupNoticeKey
852///
853/// # Errors
854///
855/// returns an error if the string could not be parsed
856#[cfg(feature = "chumsky")]
857#[must_use]
858pub fn group_notice_key_parser<'src>()
859-> impl Parser<'src, &'src str, GroupNoticeKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
860{
861    key_parser().map(GroupNoticeKey)
862}
863
864/// represents a Second Life key for a *mesh* asset — the UUID of a mesh asset,
865/// as carried in the sculpt/mesh block of a prim whose shape comes from a mesh
866/// rather than a sculpt texture. A mesh asset is emphatically not a
867/// [`TextureKey`].
868#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
869#[expect(
870    clippy::module_name_repetitions,
871    reason = "the type is going to be used outside this module"
872)]
873pub struct MeshKey(pub Key);
874
875impl std::fmt::Display for MeshKey {
876    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
877        write!(f, "{}", self.0)
878    }
879}
880
881impl From<MeshKey> for Key {
882    fn from(val: MeshKey) -> Self {
883        val.0
884    }
885}
886
887impl MeshKey {
888    /// The wrapped raw UUID.
889    #[must_use]
890    pub const fn uuid(&self) -> Uuid {
891        self.0.0
892    }
893}
894
895impl From<Uuid> for MeshKey {
896    fn from(value: Uuid) -> Self {
897        Self(Key(value))
898    }
899}
900
901/// parse a MeshKey
902///
903/// # Errors
904///
905/// returns an error if the string could not be parsed
906#[cfg(feature = "chumsky")]
907#[must_use]
908pub fn mesh_key_parser<'src>()
909-> impl Parser<'src, &'src str, MeshKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
910    key_parser().map(MeshKey)
911}
912
913/// a key that is either an **agent** ([`AgentKey`]) or an in-world **object**
914/// ([`ObjectKey`]), as selected by a separate source-type discriminator (an
915/// avatar or a prim can be the source of chat, sounds, effects, …)
916#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::EnumIs)]
917#[expect(
918    clippy::module_name_repetitions,
919    reason = "the type is going to be used outside this module"
920)]
921pub enum AgentOrObjectKey {
922    /// the key is an agent
923    Agent(AgentKey),
924    /// the key is an in-world object
925    Object(ObjectKey),
926}
927
928impl AgentOrObjectKey {
929    /// The wrapped raw UUID, regardless of whether the key is an agent or an
930    /// object.
931    #[must_use]
932    pub const fn uuid(&self) -> Uuid {
933        match self {
934            Self::Agent(agent_key) => agent_key.0.0,
935            Self::Object(object_key) => object_key.0.0,
936        }
937    }
938}
939
940impl std::fmt::Display for AgentOrObjectKey {
941    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
942        match self {
943            Self::Agent(agent_key) => write!(f, "{agent_key}"),
944            Self::Object(object_key) => write!(f, "{object_key}"),
945        }
946    }
947}
948
949impl From<AgentOrObjectKey> for Key {
950    fn from(val: AgentOrObjectKey) -> Self {
951        match val {
952            AgentOrObjectKey::Agent(agent_key) => agent_key.into(),
953            AgentOrObjectKey::Object(object_key) => object_key.into(),
954        }
955    }
956}
957
958/// a key that is either an inventory **item** ([`InventoryKey`]) or a whole
959/// inventory **folder/category** ([`InventoryFolderKey`]), as selected by a
960/// separate asset-type discriminator
961#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::EnumIs)]
962#[expect(
963    clippy::module_name_repetitions,
964    reason = "the type is going to be used outside this module"
965)]
966pub enum InventoryItemOrFolderKey {
967    /// the key refers to a single inventory item
968    Item(InventoryKey),
969    /// the key refers to a whole inventory folder/category
970    Folder(InventoryFolderKey),
971}
972
973impl InventoryItemOrFolderKey {
974    /// The wrapped raw UUID, regardless of whether it refers to an item or a
975    /// folder.
976    #[must_use]
977    pub const fn uuid(&self) -> Uuid {
978        match self {
979            Self::Item(item_key) => item_key.0.0,
980            Self::Folder(folder_key) => folder_key.0.0,
981        }
982    }
983}
984
985impl std::fmt::Display for InventoryItemOrFolderKey {
986    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
987        match self {
988            Self::Item(item_key) => write!(f, "{item_key}"),
989            Self::Folder(folder_key) => write!(f, "{folder_key}"),
990        }
991    }
992}
993
994impl From<InventoryItemOrFolderKey> for Key {
995    fn from(val: InventoryItemOrFolderKey) -> Self {
996        match val {
997            InventoryItemOrFolderKey::Item(item_key) => item_key.into(),
998            InventoryItemOrFolderKey::Folder(folder_key) => folder_key.into(),
999        }
1000    }
1001}
1002
1003/// the asset backing a prim's sculpt/mesh shape: either a sculpt **texture**
1004/// ([`TextureKey`]) or a **mesh** asset ([`MeshKey`]), as selected by the prim's
1005/// sculpt-type discriminator
1006#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::EnumIs)]
1007#[expect(
1008    clippy::module_name_repetitions,
1009    reason = "the type is going to be used outside this module"
1010)]
1011pub enum SculptOrMeshKey {
1012    /// the prim is a sculpty: the key is a sculpt texture
1013    Sculpt(TextureKey),
1014    /// the prim is a mesh: the key is a mesh asset
1015    Mesh(MeshKey),
1016}
1017
1018impl SculptOrMeshKey {
1019    /// The wrapped raw UUID, regardless of whether it is a sculpt texture or a
1020    /// mesh asset.
1021    #[must_use]
1022    pub const fn uuid(&self) -> Uuid {
1023        match self {
1024            Self::Sculpt(texture_key) => texture_key.0.0,
1025            Self::Mesh(mesh_key) => mesh_key.0.0,
1026        }
1027    }
1028}
1029
1030impl std::fmt::Display for SculptOrMeshKey {
1031    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1032        match self {
1033            Self::Sculpt(texture_key) => write!(f, "{texture_key}"),
1034            Self::Mesh(mesh_key) => write!(f, "{mesh_key}"),
1035        }
1036    }
1037}
1038
1039impl From<SculptOrMeshKey> for Key {
1040    fn from(val: SculptOrMeshKey) -> Self {
1041        match val {
1042            SculptOrMeshKey::Sculpt(texture_key) => texture_key.into(),
1043            SculptOrMeshKey::Mesh(mesh_key) => mesh_key.into(),
1044        }
1045    }
1046}