1use super::QueryField;
2use crate::http::UrlQueryParams;
3use crate::{
4 impl_id_newtype, impl_into_field_set, impl_string_set, impl_string_set_from_newtype,
5 impl_string_set_from_numeric,
6};
7use regex::Regex;
8use serde::{Deserialize, Deserializer, Serialize};
9use std::collections::{HashMap, HashSet};
10use std::ops::{Deref, DerefMut};
11use std::result::Result as StdResult;
12use std::sync::{Arc, LazyLock};
13use strum::{Display, EnumString, VariantArray};
14
15static ID_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^u\d+$").unwrap());
16
17#[remain::sorted]
18#[derive(Clone, Debug, Deserialize, Serialize)]
19#[cfg_attr(feature = "specta", derive(specta::Type))]
20pub struct User {
21 pub id: UserId,
22 pub lengthvotes: Option<u32>,
23 pub lengthvotes_sum: Option<u32>,
24 pub username: String,
25}
26
27impl From<User> for UserId {
28 fn from(u: User) -> Self {
29 u.id
30 }
31}
32
33#[derive(
34 Debug,
35 Deserialize,
36 Serialize,
37 PartialEq,
38 Eq,
39 PartialOrd,
40 Ord,
41 Hash,
42 derive_more::Display,
43 derive_more::Into,
44)]
45#[cfg_attr(feature = "specta", derive(specta::Type))]
46pub struct UserId(#[cfg_attr(feature = "specta", specta(type = String))] Arc<str>);
47
48impl UserId {
49 pub const PREFIX: char = 'u';
50}
51
52impl_id_newtype!(User, UserId, ID_REGEX);
53
54#[derive(Clone, Debug, Default, Serialize)]
55#[cfg_attr(feature = "specta", derive(specta::Type))]
56pub struct Users(HashMap<String, User>);
57
58impl<'de> Deserialize<'de> for Users {
59 fn deserialize<D>(deserializer: D) -> StdResult<Self, D::Error>
60 where
61 D: Deserializer<'de>,
62 {
63 type Optional = HashMap<String, Option<User>>;
64 Ok(Users(
65 Optional::deserialize(deserializer)?
66 .into_iter()
67 .filter_map(|(k, v)| v.map(|v| (k, v)))
68 .collect(),
69 ))
70 }
71}
72
73impl Users {
74 pub fn into_inner(self) -> HashMap<String, User> {
75 self.0
76 }
77}
78
79impl Deref for Users {
80 type Target = HashMap<String, User>;
81
82 fn deref(&self) -> &Self::Target {
83 &self.0
84 }
85}
86
87impl DerefMut for Users {
88 fn deref_mut(&mut self) -> &mut Self::Target {
89 &mut self.0
90 }
91}
92
93#[non_exhaustive]
94#[remain::sorted]
95#[derive(Clone, Copy, Debug, Deserialize, Serialize, Display, EnumString, VariantArray)]
96#[cfg_attr(feature = "specta", derive(specta::Type))]
97pub enum UserField {
98 #[serde(rename = "lengthvotes")]
99 #[strum(serialize = "lengthvotes")]
100 LengthVotes,
101
102 #[serde(rename = "lengthvotes_sum")]
103 #[strum(serialize = "lengthvotes_sum")]
104 LengthVotesSum,
105}
106
107impl QueryField for UserField {}
108
109impl_into_field_set!(UserField);
110
111#[derive(Clone, Debug, Default)]
113pub struct UserUrlQuery(HashSet<String>);
114
115impl UserUrlQuery {
116 pub const URL_QUERY_PARAM: &'static str = "q";
117
118 pub fn into_query(self) -> UrlQueryParams {
119 UrlQueryParams(
120 self
121 .0
122 .into_iter()
123 .map(|user| (Self::URL_QUERY_PARAM, user))
124 .collect(),
125 )
126 }
127}
128
129impl_string_set!(UserUrlQuery);
130impl_string_set_from_numeric!(UserId::PREFIX, UserUrlQuery);
131impl_string_set_from_newtype!(UserUrlQuery, UserId);