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 *mesh* asset — the UUID of a mesh asset,
769/// as carried in the sculpt/mesh block of a prim whose shape comes from a mesh
770/// rather than a sculpt texture. A mesh asset is emphatically not a
771/// [`TextureKey`].
772#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
773#[expect(
774    clippy::module_name_repetitions,
775    reason = "the type is going to be used outside this module"
776)]
777pub struct MeshKey(pub Key);
778
779impl std::fmt::Display for MeshKey {
780    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
781        write!(f, "{}", self.0)
782    }
783}
784
785impl From<MeshKey> for Key {
786    fn from(val: MeshKey) -> Self {
787        val.0
788    }
789}
790
791impl MeshKey {
792    /// The wrapped raw UUID.
793    #[must_use]
794    pub const fn uuid(&self) -> Uuid {
795        self.0.0
796    }
797}
798
799impl From<Uuid> for MeshKey {
800    fn from(value: Uuid) -> Self {
801        Self(Key(value))
802    }
803}
804
805/// parse a MeshKey
806///
807/// # Errors
808///
809/// returns an error if the string could not be parsed
810#[cfg(feature = "chumsky")]
811#[must_use]
812pub fn mesh_key_parser<'src>()
813-> impl Parser<'src, &'src str, MeshKey, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
814    key_parser().map(MeshKey)
815}
816
817/// a key that is either an **agent** ([`AgentKey`]) or an in-world **object**
818/// ([`ObjectKey`]), as selected by a separate source-type discriminator (an
819/// avatar or a prim can be the source of chat, sounds, effects, …)
820#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::EnumIs)]
821#[expect(
822    clippy::module_name_repetitions,
823    reason = "the type is going to be used outside this module"
824)]
825pub enum AgentOrObjectKey {
826    /// the key is an agent
827    Agent(AgentKey),
828    /// the key is an in-world object
829    Object(ObjectKey),
830}
831
832impl AgentOrObjectKey {
833    /// The wrapped raw UUID, regardless of whether the key is an agent or an
834    /// object.
835    #[must_use]
836    pub const fn uuid(&self) -> Uuid {
837        match self {
838            Self::Agent(agent_key) => agent_key.0.0,
839            Self::Object(object_key) => object_key.0.0,
840        }
841    }
842}
843
844impl std::fmt::Display for AgentOrObjectKey {
845    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
846        match self {
847            Self::Agent(agent_key) => write!(f, "{agent_key}"),
848            Self::Object(object_key) => write!(f, "{object_key}"),
849        }
850    }
851}
852
853impl From<AgentOrObjectKey> for Key {
854    fn from(val: AgentOrObjectKey) -> Self {
855        match val {
856            AgentOrObjectKey::Agent(agent_key) => agent_key.into(),
857            AgentOrObjectKey::Object(object_key) => object_key.into(),
858        }
859    }
860}
861
862/// a key that is either an inventory **item** ([`InventoryKey`]) or a whole
863/// inventory **folder/category** ([`InventoryFolderKey`]), as selected by a
864/// separate asset-type discriminator
865#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::EnumIs)]
866#[expect(
867    clippy::module_name_repetitions,
868    reason = "the type is going to be used outside this module"
869)]
870pub enum InventoryItemOrFolderKey {
871    /// the key refers to a single inventory item
872    Item(InventoryKey),
873    /// the key refers to a whole inventory folder/category
874    Folder(InventoryFolderKey),
875}
876
877impl InventoryItemOrFolderKey {
878    /// The wrapped raw UUID, regardless of whether it refers to an item or a
879    /// folder.
880    #[must_use]
881    pub const fn uuid(&self) -> Uuid {
882        match self {
883            Self::Item(item_key) => item_key.0.0,
884            Self::Folder(folder_key) => folder_key.0.0,
885        }
886    }
887}
888
889impl std::fmt::Display for InventoryItemOrFolderKey {
890    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
891        match self {
892            Self::Item(item_key) => write!(f, "{item_key}"),
893            Self::Folder(folder_key) => write!(f, "{folder_key}"),
894        }
895    }
896}
897
898impl From<InventoryItemOrFolderKey> for Key {
899    fn from(val: InventoryItemOrFolderKey) -> Self {
900        match val {
901            InventoryItemOrFolderKey::Item(item_key) => item_key.into(),
902            InventoryItemOrFolderKey::Folder(folder_key) => folder_key.into(),
903        }
904    }
905}
906
907/// the asset backing a prim's sculpt/mesh shape: either a sculpt **texture**
908/// ([`TextureKey`]) or a **mesh** asset ([`MeshKey`]), as selected by the prim's
909/// sculpt-type discriminator
910#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::EnumIs)]
911#[expect(
912    clippy::module_name_repetitions,
913    reason = "the type is going to be used outside this module"
914)]
915pub enum SculptOrMeshKey {
916    /// the prim is a sculpty: the key is a sculpt texture
917    Sculpt(TextureKey),
918    /// the prim is a mesh: the key is a mesh asset
919    Mesh(MeshKey),
920}
921
922impl SculptOrMeshKey {
923    /// The wrapped raw UUID, regardless of whether it is a sculpt texture or a
924    /// mesh asset.
925    #[must_use]
926    pub const fn uuid(&self) -> Uuid {
927        match self {
928            Self::Sculpt(texture_key) => texture_key.0.0,
929            Self::Mesh(mesh_key) => mesh_key.0.0,
930        }
931    }
932}
933
934impl std::fmt::Display for SculptOrMeshKey {
935    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
936        match self {
937            Self::Sculpt(texture_key) => write!(f, "{texture_key}"),
938            Self::Mesh(mesh_key) => write!(f, "{mesh_key}"),
939        }
940    }
941}
942
943impl From<SculptOrMeshKey> for Key {
944    fn from(val: SculptOrMeshKey) -> Self {
945        match val {
946            SculptOrMeshKey::Sculpt(texture_key) => texture_key.into(),
947            SculptOrMeshKey::Mesh(mesh_key) => mesh_key.into(),
948        }
949    }
950}