Skip to main content

sl_types/
search.rs

1//! Search related types
2
3#[cfg(feature = "chumsky")]
4use chumsky::{Parser, prelude::just};
5
6/// Search categories
7#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, strum::EnumIs)]
8#[expect(
9    clippy::module_name_repetitions,
10    reason = "the type is used outside this module"
11)]
12pub enum SearchCategory {
13    /// search in all categories
14    All,
15    /// search for an avatar
16    People,
17    /// search for a parcel
18    Places,
19    /// search for an event
20    Events,
21    /// search for a group
22    Groups,
23    /// search the wiki
24    Wiki,
25    /// search the destination guide
26    Destinations,
27    /// search the classifieds
28    Classifieds,
29}
30
31/// parse a search category
32///
33/// # Errors
34///
35/// returns an error if the string could not be parsed
36#[cfg(feature = "chumsky")]
37#[must_use]
38#[expect(
39    clippy::module_name_repetitions,
40    reason = "the parser is used outside this module"
41)]
42pub fn search_category_parser<'src>()
43-> impl Parser<'src, &'src str, SearchCategory, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
44{
45    just("all")
46        .to(SearchCategory::All)
47        .or(just("people").to(SearchCategory::People))
48        .or(just("places").to(SearchCategory::Places))
49        .or(just("events").to(SearchCategory::Events))
50        .or(just("groups").to(SearchCategory::Groups))
51        .or(just("wiki").to(SearchCategory::Wiki))
52        .or(just("destinations").to(SearchCategory::Destinations))
53        .or(just("classifieds").to(SearchCategory::Classifieds))
54}
55
56impl std::fmt::Display for SearchCategory {
57    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58        match self {
59            Self::All => write!(f, "all"),
60            Self::People => write!(f, "people"),
61            Self::Places => write!(f, "places"),
62            Self::Events => write!(f, "events"),
63            Self::Groups => write!(f, "groups"),
64            Self::Wiki => write!(f, "wiki"),
65            Self::Destinations => write!(f, "destinations"),
66            Self::Classifieds => write!(f, "classifieds"),
67        }
68    }
69}
70
71/// Error deserializing SearchCategory from String
72#[derive(Debug, Clone)]
73#[expect(
74    clippy::module_name_repetitions,
75    reason = "the type is used outside this module"
76)]
77pub struct SearchCategoryParseError {
78    /// the value that could not be parsed
79    value: String,
80}
81
82impl std::fmt::Display for SearchCategoryParseError {
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        write!(f, "Could not parse as SearchCategory: {}", self.value)
85    }
86}
87
88impl std::str::FromStr for SearchCategory {
89    type Err = SearchCategoryParseError;
90
91    fn from_str(s: &str) -> Result<Self, Self::Err> {
92        match s {
93            "all" => Ok(Self::All),
94            "people" => Ok(Self::People),
95            "places" => Ok(Self::Places),
96            "events" => Ok(Self::Events),
97            "groups" => Ok(Self::Groups),
98            "wiki" => Ok(Self::Wiki),
99            "destinations" => Ok(Self::Destinations),
100            "classifieds" => Ok(Self::Classifieds),
101            _ => Err(SearchCategoryParseError {
102                value: s.to_owned(),
103            }),
104        }
105    }
106}
107
108/// The id of an in-world scheduled **event** in the Second Life *events
109/// directory* (Search → Events) — the numeric handle an events-directory search
110/// result carries and the `secondlife:///app/event/<id>/about` viewer URI
111/// references.
112///
113/// Unlike the UUID-based [keys](crate::key) this is a 32-bit integer (the
114/// reference viewer parses the app-URI id with `asInteger()` and the
115/// events-directory messages carry it as a `U32`). It is a newtype rather than a
116/// bare `u32` so an events-directory id can't be transposed with any other
117/// 32-bit field.
118#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
119pub struct EventId(pub u32);
120
121impl EventId {
122    /// Builds an event-directory id from its raw `u32` value.
123    #[must_use]
124    pub const fn new(id: u32) -> Self {
125        Self(id)
126    }
127
128    /// Returns the raw `u32` value.
129    #[must_use]
130    pub const fn get(self) -> u32 {
131        self.0
132    }
133}
134
135impl std::fmt::Display for EventId {
136    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137        write!(f, "{}", self.0)
138    }
139}
140
141/// parse an event id
142///
143/// "12345"
144///
145/// # Errors
146///
147/// returns an error if the string could not be parsed
148#[cfg(feature = "chumsky")]
149#[must_use]
150pub fn event_id_parser<'src>()
151-> impl Parser<'src, &'src str, EventId, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
152    crate::utils::u32_parser().map(EventId)
153}
154
155/// A classified-ad search category — the listing's classified-directory
156/// classification (a [`ClassifiedInfo`]/`DirClassifiedQuery` `Category`). The
157/// wire value is a `u32` (the viewer's classified category combo; `0` for "any
158/// category").
159///
160/// Not to be confused with a parcel's land classification or with
161/// [`SearchCategory`] (the Search-floater tab, a viewer-UI concept).
162///
163/// [`ClassifiedInfo`]: https://wiki.secondlife.com/wiki/ClassifiedInfoReply
164#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
165#[non_exhaustive]
166pub enum ClassifiedCategory {
167    /// Any category (`0`); the "any" filter in a query, or an unset listing.
168    #[default]
169    AnyCategory,
170    /// Shopping (`1`).
171    Shopping,
172    /// Land rental (`2`).
173    LandRental,
174    /// Property rental (`3`).
175    PropertyRental,
176    /// A special attraction (`4`).
177    SpecialAttraction,
178    /// New products (`5`).
179    NewProducts,
180    /// Employment (`6`).
181    Employment,
182    /// Wanted (`7`).
183    Wanted,
184    /// A service (`8`).
185    Service,
186    /// Personal (`9`).
187    Personal,
188    /// An unrecognised category value, preserved verbatim. Has no named textual
189    /// form (its [`Display`](std::fmt::Display) is the raw number).
190    Unknown(u32),
191}
192
193impl ClassifiedCategory {
194    /// Classifies a classified-category wire value.
195    #[must_use]
196    pub const fn from_u32(value: u32) -> Self {
197        match value {
198            0 => Self::AnyCategory,
199            1 => Self::Shopping,
200            2 => Self::LandRental,
201            3 => Self::PropertyRental,
202            4 => Self::SpecialAttraction,
203            5 => Self::NewProducts,
204            6 => Self::Employment,
205            7 => Self::Wanted,
206            8 => Self::Service,
207            9 => Self::Personal,
208            other => Self::Unknown(other),
209        }
210    }
211
212    /// The wire value for this category.
213    #[must_use]
214    pub const fn to_u32(self) -> u32 {
215        match self {
216            Self::AnyCategory => 0,
217            Self::Shopping => 1,
218            Self::LandRental => 2,
219            Self::PropertyRental => 3,
220            Self::SpecialAttraction => 4,
221            Self::NewProducts => 5,
222            Self::Employment => 6,
223            Self::Wanted => 7,
224            Self::Service => 8,
225            Self::Personal => 9,
226            Self::Unknown(value) => value,
227        }
228    }
229}
230
231impl std::fmt::Display for ClassifiedCategory {
232    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
233        match self {
234            Self::AnyCategory => write!(f, "any"),
235            Self::Shopping => write!(f, "shopping"),
236            Self::LandRental => write!(f, "land rental"),
237            Self::PropertyRental => write!(f, "property rental"),
238            Self::SpecialAttraction => write!(f, "special attraction"),
239            Self::NewProducts => write!(f, "new products"),
240            Self::Employment => write!(f, "employment"),
241            Self::Wanted => write!(f, "wanted"),
242            Self::Service => write!(f, "service"),
243            Self::Personal => write!(f, "personal"),
244            Self::Unknown(value) => write!(f, "{value}"),
245        }
246    }
247}
248
249/// Error deserializing `ClassifiedCategory` from a string
250#[derive(Debug, Clone)]
251pub struct ClassifiedCategoryParseError {
252    /// the value that could not be parsed
253    value: String,
254}
255
256impl std::fmt::Display for ClassifiedCategoryParseError {
257    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
258        write!(f, "Could not parse as ClassifiedCategory: {}", self.value)
259    }
260}
261
262impl std::str::FromStr for ClassifiedCategory {
263    type Err = ClassifiedCategoryParseError;
264
265    fn from_str(s: &str) -> Result<Self, Self::Err> {
266        match s {
267            "any" => Ok(Self::AnyCategory),
268            "shopping" => Ok(Self::Shopping),
269            "land rental" => Ok(Self::LandRental),
270            "property rental" => Ok(Self::PropertyRental),
271            "special attraction" => Ok(Self::SpecialAttraction),
272            "new products" => Ok(Self::NewProducts),
273            "employment" => Ok(Self::Employment),
274            "wanted" => Ok(Self::Wanted),
275            "service" => Ok(Self::Service),
276            "personal" => Ok(Self::Personal),
277            _ => Err(ClassifiedCategoryParseError {
278                value: s.to_owned(),
279            }),
280        }
281    }
282}
283
284/// parse a classified-ad category (the named variants only; the
285/// [`Unknown`](ClassifiedCategory::Unknown) catch-all has no textual form)
286///
287/// # Errors
288///
289/// returns an error if the string could not be parsed
290#[cfg(feature = "chumsky")]
291#[must_use]
292pub fn classified_category_parser<'src>() -> impl Parser<
293    'src,
294    &'src str,
295    ClassifiedCategory,
296    chumsky::extra::Err<chumsky::error::Rich<'src, char>>,
297> {
298    just("any")
299        .to(ClassifiedCategory::AnyCategory)
300        .or(just("shopping").to(ClassifiedCategory::Shopping))
301        .or(just("land rental").to(ClassifiedCategory::LandRental))
302        .or(just("property rental").to(ClassifiedCategory::PropertyRental))
303        .or(just("special attraction").to(ClassifiedCategory::SpecialAttraction))
304        .or(just("new products").to(ClassifiedCategory::NewProducts))
305        .or(just("employment").to(ClassifiedCategory::Employment))
306        .or(just("wanted").to(ClassifiedCategory::Wanted))
307        .or(just("service").to(ClassifiedCategory::Service))
308        .or(just("personal").to(ClassifiedCategory::Personal))
309}