1use super::release::Release;
2use super::r#trait::Trait;
3use super::visual_novel::VisualNovel;
4use super::{QueryField, SortQueryBy};
5use crate::{impl_id_newtype, impl_into_field_set};
6use regex::Regex;
7use serde::{Deserialize, Deserializer, Serialize};
8use std::result::Result as StdResult;
9use std::sync::{Arc, LazyLock};
10use strum::{Display, EnumIs, EnumString, VariantArray};
11
12static ID_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^c\d+$").unwrap());
13
14#[remain::sorted]
15#[derive(Clone, Debug, Deserialize, Serialize)]
16#[cfg_attr(feature = "specta", derive(specta::Type))]
17pub struct Character {
18 pub age: Option<u32>,
19 pub aliases: Option<Vec<String>>,
20 pub birthday: Option<CharacterBirthday>,
21 pub blood_type: Option<String>,
22 pub bust: Option<u32>,
23 pub cup: Option<String>,
24 pub description: Option<String>,
25 pub gender: Option<CharacterGender>,
26 pub height: Option<u32>,
27 pub hips: Option<u32>,
28 pub id: CharacterId,
29 pub image: Option<CharacterImage>,
30 pub name: Option<String>,
31 pub original: Option<String>,
32 pub sex: Option<CharacterSex>,
33 pub traits: Option<Vec<CharacterTrait>>,
34 pub vns: Option<Vec<CharacterVisualNovel>>,
35 pub waist: Option<u32>,
36 pub weight: Option<u32>,
37}
38
39impl From<Character> for CharacterId {
40 fn from(c: Character) -> Self {
41 c.id
42 }
43}
44
45#[derive(
46 Debug,
47 Deserialize,
48 Serialize,
49 PartialEq,
50 Eq,
51 PartialOrd,
52 Ord,
53 Hash,
54 derive_more::Display,
55 derive_more::Into,
56)]
57#[cfg_attr(feature = "specta", derive(specta::Type))]
58pub struct CharacterId(#[cfg_attr(feature = "specta", specta(type = String))] Arc<str>);
59
60impl CharacterId {
61 pub const PREFIX: char = 'c';
62}
63
64impl_id_newtype!(Character, CharacterId, ID_REGEX);
65
66#[remain::sorted]
67#[derive(Clone, Debug, Serialize)]
68#[cfg_attr(feature = "specta", derive(specta::Type))]
69pub struct CharacterBirthday {
70 pub day: u32,
71 pub month: u32,
72}
73
74impl<'de> Deserialize<'de> for CharacterBirthday {
75 fn deserialize<D>(deserializer: D) -> StdResult<Self, D::Error>
76 where
77 D: Deserializer<'de>,
78 {
79 let date: [u32; 2] = Deserialize::deserialize(deserializer)?;
80 Ok(Self { day: date[1], month: date[0] })
81 }
82}
83
84#[remain::sorted]
85#[derive(Clone, Debug, Serialize)]
86#[cfg_attr(feature = "specta", derive(specta::Type))]
87pub struct CharacterGender {
88 pub apparent: Option<CharacterGenderValue>,
89 pub real: Option<CharacterGenderValue>,
90}
91
92impl<'de> Deserialize<'de> for CharacterGender {
93 fn deserialize<D>(deserializer: D) -> StdResult<Self, D::Error>
94 where
95 D: Deserializer<'de>,
96 {
97 type Value = Option<CharacterGenderValue>;
98 let array: [Value; 2] = Deserialize::deserialize(deserializer)?;
99 Ok(Self { apparent: array[0], real: array[1] })
100 }
101}
102
103#[remain::sorted]
104#[derive(
105 Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash, Display, EnumIs, EnumString,
106)]
107#[cfg_attr(feature = "specta", derive(specta::Type))]
108pub enum CharacterGenderValue {
109 #[serde(rename = "a")]
110 #[strum(serialize = "a")]
111 Ambiguous,
112
113 #[serde(rename = "f")]
114 #[strum(serialize = "f")]
115 Female,
116
117 #[serde(rename = "m")]
118 #[strum(serialize = "m")]
119 Male,
120
121 #[serde(rename = "o")]
122 #[strum(serialize = "o")]
123 NonBinary,
124}
125
126#[remain::sorted]
127#[derive(Clone, Debug, Deserialize, Serialize)]
128#[cfg_attr(feature = "specta", derive(specta::Type))]
129pub struct CharacterImage {
130 pub dims: Option<[u32; 2]>,
131 pub id: Option<String>,
132 pub sexual: Option<f32>,
133 pub url: Option<String>,
134 pub violence: Option<f32>,
135 pub votecount: Option<u32>,
136}
137
138#[remain::sorted]
139#[derive(Clone, Debug, Serialize)]
140#[cfg_attr(feature = "specta", derive(specta::Type))]
141pub struct CharacterSex {
142 pub apparent: Option<CharacterSexValue>,
143 pub real: Option<CharacterSexValue>,
144}
145
146impl<'de> Deserialize<'de> for CharacterSex {
147 fn deserialize<D>(deserializer: D) -> StdResult<Self, D::Error>
148 where
149 D: Deserializer<'de>,
150 {
151 type Value = Option<CharacterSexValue>;
152 let array: [Value; 2] = Deserialize::deserialize(deserializer)?;
153 Ok(Self { apparent: array[0], real: array[1] })
154 }
155}
156
157#[remain::sorted]
158#[derive(
159 Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash, Display, EnumIs, EnumString,
160)]
161#[cfg_attr(feature = "specta", derive(specta::Type))]
162pub enum CharacterSexValue {
163 #[serde(rename = "b")]
164 #[strum(serialize = "b")]
165 Both,
166
167 #[serde(rename = "f")]
168 #[strum(serialize = "f")]
169 Female,
170
171 #[serde(rename = "m")]
172 #[strum(serialize = "m")]
173 Male,
174
175 #[serde(rename = "n")]
176 #[strum(serialize = "n")]
177 None,
178}
179
180#[remain::sorted]
181#[derive(Clone, Debug, Deserialize, Serialize)]
182#[cfg_attr(feature = "specta", derive(specta::Type))]
183pub struct CharacterTrait {
184 pub lie: Option<bool>,
185 #[serde(flatten)]
186 pub r#trait: Trait,
187 pub spoiler: Option<u8>,
188}
189
190impl From<CharacterTrait> for Trait {
191 fn from(ct: CharacterTrait) -> Self {
192 ct.r#trait
193 }
194}
195
196#[remain::sorted]
197#[derive(Clone, Debug, Deserialize, Serialize)]
198#[cfg_attr(feature = "specta", derive(specta::Type))]
199pub struct CharacterVisualNovel {
200 pub release: Option<Release>,
201 pub role: Option<String>,
202 pub spoiler: Option<u8>,
203 #[serde(flatten)]
204 pub visual_novel: VisualNovel,
205}
206
207impl From<CharacterVisualNovel> for VisualNovel {
208 fn from(cvn: CharacterVisualNovel) -> Self {
209 cvn.visual_novel
210 }
211}
212
213#[non_exhaustive]
214#[remain::sorted]
215#[derive(Clone, Copy, Debug, Deserialize, Serialize, Display, EnumString, VariantArray)]
216#[cfg_attr(feature = "specta", derive(specta::Type))]
217pub enum CharacterField {
218 #[serde(rename = "age")]
219 #[strum(serialize = "age")]
220 Age,
221
222 #[serde(rename = "aliases")]
223 #[strum(serialize = "aliases")]
224 Aliases,
225
226 #[serde(rename = "birthday")]
227 #[strum(serialize = "birthday")]
228 Birthday,
229
230 #[serde(rename = "blood_type")]
231 #[strum(serialize = "blood_type")]
232 BloodType,
233
234 #[serde(rename = "bust")]
235 #[strum(serialize = "bust")]
236 Bust,
237
238 #[serde(rename = "cup")]
239 #[strum(serialize = "cup")]
240 Cup,
241
242 #[serde(rename = "description")]
243 #[strum(serialize = "description")]
244 Description,
245
246 #[serde(rename = "gender")]
247 #[strum(serialize = "gender")]
248 Gender,
249
250 #[serde(rename = "height")]
251 #[strum(serialize = "height")]
252 Height,
253
254 #[serde(rename = "hips")]
255 #[strum(serialize = "hips")]
256 Hips,
257
258 #[serde(rename = "id")]
259 #[strum(serialize = "id")]
260 Id,
261
262 #[serde(rename = "image.id")]
263 #[strum(serialize = "image.id")]
264 ImageId,
265
266 #[serde(rename = "image.url")]
267 #[strum(serialize = "image.url")]
268 ImageUrl,
269
270 #[serde(rename = "name")]
271 #[strum(serialize = "name")]
272 Name,
273
274 #[serde(rename = "original")]
275 #[strum(serialize = "original")]
276 Original,
277
278 #[serde(rename = "sex")]
279 #[strum(serialize = "sex")]
280 Sex,
281
282 #[serde(rename = "traits.id")]
283 #[strum(serialize = "traits.id")]
284 TraitId,
285
286 #[serde(rename = "traits.lie")]
287 #[strum(serialize = "traits.lie")]
288 TraitLie,
289
290 #[serde(rename = "traits.spoiler")]
291 #[strum(serialize = "traits.spoiler")]
292 TraitSpoiler,
293
294 #[serde(rename = "vns.aliases")]
295 #[strum(serialize = "vns.aliases")]
296 VisualNovelAliases,
297
298 #[serde(rename = "vns.alttitle")]
299 #[strum(serialize = "vns.alttitle")]
300 VisualNovelAltTitle,
301
302 #[serde(rename = "vns.id")]
303 #[strum(serialize = "vns.id")]
304 VisualNovelId,
305
306 #[serde(rename = "vns.release.id")]
307 #[strum(serialize = "vns.release.id")]
308 VisualNovelReleaseId,
309
310 #[serde(rename = "vns.role")]
311 #[strum(serialize = "vns.role")]
312 VisualNovelRole,
313
314 #[serde(rename = "vns.spoiler")]
315 #[strum(serialize = "vns.spoiler")]
316 VisualNovelSpoiler,
317
318 #[serde(rename = "vns.title")]
319 #[strum(serialize = "vns.title")]
320 VisualNovelTitle,
321
322 #[serde(rename = "waist")]
323 #[strum(serialize = "waist")]
324 Waist,
325
326 #[serde(rename = "weight")]
327 #[strum(serialize = "weight")]
328 Weight,
329}
330
331impl QueryField for CharacterField {}
332
333impl_into_field_set!(CharacterField);
334
335#[non_exhaustive]
336#[remain::sorted]
337#[derive(Clone, Copy, Debug, Deserialize, Serialize, Display, EnumString)]
338#[cfg_attr(feature = "specta", derive(specta::Type))]
339pub enum SortCharacterBy {
340 #[serde(rename = "id")]
341 #[strum(serialize = "id")]
342 Id,
343
344 #[serde(rename = "name")]
345 #[strum(serialize = "name")]
346 Name,
347
348 #[serde(rename = "searchrank")]
349 #[strum(serialize = "searchrank")]
350 SearchRank,
351}
352
353impl SortQueryBy for SortCharacterBy {}