1use super::character::Character;
2use super::producer::Producer;
3use super::release::ExternalLink;
4use super::schema::Language;
5use super::staff::Staff;
6use super::tag::Tag;
7use super::{QueryField, SortQueryBy};
8use crate::{impl_id_newtype, impl_into_field_set};
9use regex::Regex;
10use serde::{Deserialize, Deserializer, Serialize};
11use std::result::Result as StdResult;
12use std::sync::{Arc, LazyLock};
13use strum::{Display, EnumIs, EnumString, VariantArray};
14
15static ID_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^v\d+$").unwrap());
16
17#[remain::sorted]
18#[derive(Clone, Debug, Deserialize, Serialize)]
19#[cfg_attr(feature = "specta", derive(specta::Type))]
20pub struct VisualNovel {
21 pub aliases: Option<Vec<String>>,
22 pub alttitle: Option<String>,
23 pub average: Option<f32>,
24 pub description: Option<String>,
25 pub developers: Option<Vec<VisualNovelDeveloper>>,
26 pub devstatus: Option<VisualNovelDevStatus>,
27 pub editions: Option<Vec<VisualNovelEdition>>,
28 pub extlinks: Option<Vec<ExternalLink>>,
29 pub id: VisualNovelId,
30 pub image: Option<VisualNovelImage>,
31 pub languages: Option<Vec<Language>>,
32 pub length: Option<VisualNovelLength>,
33 pub length_minutes: Option<u32>,
34 pub length_votes: Option<u32>,
35 pub olang: Option<Language>,
36 pub platforms: Option<Vec<String>>,
37 pub rating: Option<f32>,
38 pub relations: Option<Vec<VisualNovelRelation>>,
39 pub released: Option<String>,
40 pub screenshots: Option<Vec<VisualNovelScreenShot>>,
41 pub staff: Option<Vec<VisualNovelStaff>>,
42 pub tags: Option<Vec<VisualNovelTag>>,
43 pub title: Option<String>,
44 pub titles: Option<Vec<VisualNovelTitle>>,
45 pub va: Option<Vec<VisualNovelVoiceActor>>,
46 pub votecount: Option<u32>,
47}
48
49impl From<VisualNovel> for VisualNovelId {
50 fn from(v: VisualNovel) -> Self {
51 v.id
52 }
53}
54
55#[derive(
56 Debug,
57 Deserialize,
58 Serialize,
59 PartialEq,
60 Eq,
61 PartialOrd,
62 Ord,
63 Hash,
64 derive_more::Display,
65 derive_more::Into,
66)]
67#[cfg_attr(feature = "specta", derive(specta::Type))]
68pub struct VisualNovelId(#[cfg_attr(feature = "specta", specta(type = String))] Arc<str>);
69
70impl VisualNovelId {
71 pub const PREFIX: char = 'v';
72}
73
74impl_id_newtype!(VisualNovel, VisualNovelId, ID_REGEX);
75
76#[non_exhaustive]
77#[remain::sorted]
78#[derive(Copy, Clone, Debug, Serialize, PartialEq, Eq, Hash, Display, EnumIs)]
79#[cfg_attr(feature = "specta", derive(specta::Type))]
80pub enum VisualNovelDevStatus {
81 Cancelled,
82 Finished,
83 InDevelopment,
84}
85
86impl<'de> Deserialize<'de> for VisualNovelDevStatus {
87 fn deserialize<D>(deserializer: D) -> StdResult<Self, D::Error>
88 where
89 D: Deserializer<'de>,
90 {
91 use serde::de::Error;
92 match u8::deserialize(deserializer)? {
93 0 => Ok(Self::Finished),
94 1 => Ok(Self::InDevelopment),
95 2 => Ok(Self::Cancelled),
96 _ => Err(Error::custom("invalid devstatus")),
97 }
98 }
99}
100
101#[remain::sorted]
102#[derive(Clone, Debug, Deserialize, Serialize)]
103#[cfg_attr(feature = "specta", derive(specta::Type))]
104pub struct VisualNovelDeveloper {
105 #[serde(flatten)]
106 pub producer: Producer,
107}
108
109impl From<VisualNovelDeveloper> for Producer {
110 fn from(v: VisualNovelDeveloper) -> Self {
111 v.producer
112 }
113}
114
115#[remain::sorted]
116#[derive(Clone, Debug, Deserialize, Serialize)]
117#[cfg_attr(feature = "specta", derive(specta::Type))]
118pub struct VisualNovelEdition {
119 pub eid: Option<u32>,
120 pub lang: Option<Language>,
121 pub name: Option<String>,
122 pub official: Option<bool>,
123}
124
125#[remain::sorted]
126#[derive(Clone, Debug, Deserialize, Serialize)]
127#[cfg_attr(feature = "specta", derive(specta::Type))]
128pub struct VisualNovelImage {
129 pub dims: Option<[u32; 2]>,
130 pub id: Option<String>,
131 pub sexual: Option<f32>,
132 pub thumbnail: Option<String>,
133 pub thumbnail_dims: Option<[u32; 2]>,
134 pub url: Option<String>,
135 pub violence: Option<f32>,
136 pub votecount: Option<u32>,
137}
138
139#[non_exhaustive]
140#[derive(Copy, Clone, Debug, Serialize, PartialEq, Eq, Hash, Display, EnumIs)]
141#[cfg_attr(feature = "specta", derive(specta::Type))]
142pub enum VisualNovelLength {
143 VeryShort,
144 Short,
145 Average,
146 Long,
147 VeryLong,
148}
149
150impl<'de> Deserialize<'de> for VisualNovelLength {
151 fn deserialize<D>(deserializer: D) -> StdResult<Self, D::Error>
152 where
153 D: Deserializer<'de>,
154 {
155 use serde::de::Error;
156 match u8::deserialize(deserializer)? {
157 1 => Ok(Self::VeryShort),
158 2 => Ok(Self::Short),
159 3 => Ok(Self::Average),
160 4 => Ok(Self::Long),
161 5 => Ok(Self::VeryLong),
162 _ => Err(D::Error::custom("invalid visual novel length")),
163 }
164 }
165}
166
167#[remain::sorted]
168#[derive(Clone, Debug, Deserialize, Serialize)]
169#[cfg_attr(feature = "specta", derive(specta::Type))]
170pub struct VisualNovelRelation {
171 pub relation: Option<String>,
172 pub relation_official: Option<bool>,
173 #[serde(flatten)]
174 pub visual_novel: VisualNovel,
175}
176
177impl From<VisualNovelRelation> for VisualNovel {
178 fn from(v: VisualNovelRelation) -> Self {
179 v.visual_novel
180 }
181}
182
183#[remain::sorted]
184#[derive(Clone, Debug, Deserialize, Serialize)]
185#[cfg_attr(feature = "specta", derive(specta::Type))]
186pub struct VisualNovelScreenShot {
187 #[serde(flatten)]
188 pub image: VisualNovelImage,
189}
190
191impl From<VisualNovelScreenShot> for VisualNovelImage {
192 fn from(v: VisualNovelScreenShot) -> Self {
193 v.image
194 }
195}
196
197#[remain::sorted]
198#[derive(Clone, Debug, Deserialize, Serialize)]
199#[cfg_attr(feature = "specta", derive(specta::Type))]
200pub struct VisualNovelStaff {
201 pub eid: Option<u32>,
202 pub note: Option<String>,
203 pub role: Option<String>,
204 #[serde(flatten)]
205 pub staff: Staff,
206}
207
208impl From<VisualNovelStaff> for Staff {
209 fn from(v: VisualNovelStaff) -> Self {
210 v.staff
211 }
212}
213
214#[remain::sorted]
215#[derive(Clone, Debug, Deserialize, Serialize)]
216#[cfg_attr(feature = "specta", derive(specta::Type))]
217pub struct VisualNovelTag {
218 pub lie: Option<bool>,
219 pub rating: Option<f32>,
220 pub spoiler: Option<u8>,
221 #[serde(flatten)]
222 pub tag: Tag,
223}
224
225impl From<VisualNovelTag> for Tag {
226 fn from(v: VisualNovelTag) -> Self {
227 v.tag
228 }
229}
230
231#[remain::sorted]
232#[derive(Clone, Debug, Deserialize, Serialize)]
233#[cfg_attr(feature = "specta", derive(specta::Type))]
234pub struct VisualNovelTitle {
235 pub lang: Option<Language>,
236 pub latin: Option<String>,
237 pub main: Option<bool>,
238 pub official: Option<bool>,
239 pub title: Option<String>,
240}
241
242#[remain::sorted]
243#[derive(Clone, Debug, Deserialize, Serialize)]
244#[cfg_attr(feature = "specta", derive(specta::Type))]
245pub struct VisualNovelVoiceActor {
246 pub character: Option<Character>,
247 pub note: Option<String>,
248 pub staff: Option<Staff>,
249}
250
251#[non_exhaustive]
252#[remain::sorted]
253#[derive(Clone, Copy, Debug, Deserialize, Serialize, Display, EnumString, VariantArray)]
254#[cfg_attr(feature = "specta", derive(specta::Type))]
255pub enum VisualNovelField {
256 #[serde(rename = "aliases")]
257 #[strum(serialize = "aliases")]
258 Aliases,
259
260 #[serde(rename = "alttitle")]
261 #[strum(serialize = "alttitle")]
262 AltTitle,
263
264 #[serde(rename = "average")]
265 #[strum(serialize = "average")]
266 Average,
267
268 #[serde(rename = "description")]
269 #[strum(serialize = "description")]
270 Description,
271
272 #[serde(rename = "developers.aliases")]
273 #[strum(serialize = "developers.aliases")]
274 DevelopersAliases,
275
276 #[serde(rename = "developers.description")]
277 #[strum(serialize = "developers.description")]
278 DevelopersDescription,
279
280 #[serde(rename = "developers.id")]
281 #[strum(serialize = "developers.id")]
282 DevelopersId,
283
284 #[serde(rename = "developers.lang")]
285 #[strum(serialize = "developers.lang")]
286 DevelopersLang,
287
288 #[serde(rename = "developers.name")]
289 #[strum(serialize = "developers.name")]
290 DevelopersName,
291
292 #[serde(rename = "developers.original")]
293 #[strum(serialize = "developers.original")]
294 DevelopersOriginal,
295
296 #[serde(rename = "developers.type")]
297 #[strum(serialize = "developers.type")]
298 DevelopersType,
299
300 #[serde(rename = "devstatus")]
301 #[strum(serialize = "devstatus")]
302 DevStatus,
303
304 #[serde(rename = "editions.eid")]
305 #[strum(serialize = "editions.eid")]
306 EditionsEid,
307
308 #[serde(rename = "editions.lang")]
309 #[strum(serialize = "editions.lang")]
310 EditionsLang,
311
312 #[serde(rename = "editions.name")]
313 #[strum(serialize = "editions.name")]
314 EditionsName,
315
316 #[serde(rename = "editions.official")]
317 #[strum(serialize = "editions.official")]
318 EditionsOfficial,
319
320 #[serde(rename = "extlinks.id")]
321 #[strum(serialize = "extlinks.id")]
322 ExtlinksId,
323
324 #[serde(rename = "extlinks.label")]
325 #[strum(serialize = "extlinks.label")]
326 ExtlinksLabel,
327
328 #[serde(rename = "extlinks.name")]
329 #[strum(serialize = "extlinks.name")]
330 ExtlinksName,
331
332 #[serde(rename = "extlinks.url")]
333 #[strum(serialize = "extlinks.url")]
334 ExtlinksUrl,
335
336 #[serde(rename = "id")]
337 #[strum(serialize = "id")]
338 Id,
339
340 #[serde(rename = "image.dims")]
341 #[strum(serialize = "image.dims")]
342 ImageDims,
343
344 #[serde(rename = "image.id")]
345 #[strum(serialize = "image.id")]
346 ImageId,
347
348 #[serde(rename = "image.sexual")]
349 #[strum(serialize = "image.sexual")]
350 ImageSexual,
351
352 #[serde(rename = "image.thumbnail")]
353 #[strum(serialize = "image.thumbnail")]
354 ImageThumbnail,
355
356 #[serde(rename = "image.thumbnail_dims")]
357 #[strum(serialize = "image.thumbnail_dims")]
358 ImageThumbnailDims,
359
360 #[serde(rename = "image.url")]
361 #[strum(serialize = "image.url")]
362 ImageUrl,
363
364 #[serde(rename = "image.violence")]
365 #[strum(serialize = "image.violence")]
366 ImageViolence,
367
368 #[serde(rename = "image.votecount")]
369 #[strum(serialize = "image.votecount")]
370 ImageVoteCount,
371
372 #[serde(rename = "languages")]
373 #[strum(serialize = "languages")]
374 Languages,
375
376 #[serde(rename = "length")]
377 #[strum(serialize = "length")]
378 Length,
379
380 #[serde(rename = "length_minutes")]
381 #[strum(serialize = "length_minutes")]
382 LengthMinutes,
383
384 #[serde(rename = "length_votes")]
385 #[strum(serialize = "length_votes")]
386 LengthVotes,
387
388 #[serde(rename = "olang")]
389 #[strum(serialize = "olang")]
390 OLang,
391
392 #[serde(rename = "platforms")]
393 #[strum(serialize = "platforms")]
394 Platforms,
395
396 #[serde(rename = "rating")]
397 #[strum(serialize = "rating")]
398 Rating,
399
400 #[serde(rename = "relations.id")]
401 #[strum(serialize = "relations.id")]
402 RelationsId,
403
404 #[serde(rename = "relations.relation")]
405 #[strum(serialize = "relations.relation")]
406 RelationsRelation,
407
408 #[serde(rename = "relations.relation_official")]
409 #[strum(serialize = "relations.relation_official")]
410 RelationsRelationOfficial,
411
412 #[serde(rename = "released")]
413 #[strum(serialize = "released")]
414 Released,
415
416 #[serde(rename = "screenshots.dims")]
417 #[strum(serialize = "screenshots.dims")]
418 ScreenshotsDims,
419
420 #[serde(rename = "screenshots.id")]
421 #[strum(serialize = "screenshots.id")]
422 ScreenshotsId,
423
424 #[serde(rename = "screenshots.sexual")]
425 #[strum(serialize = "screenshots.sexual")]
426 ScreenshotsSexual,
427
428 #[serde(rename = "screenshots.thumbnail")]
429 #[strum(serialize = "screenshots.thumbnail")]
430 ScreenshotsThumbnail,
431
432 #[serde(rename = "screenshots.thumbnail_dims")]
433 #[strum(serialize = "screenshots.thumbnail_dims")]
434 ScreenshotsThumbnailDims,
435
436 #[serde(rename = "screenshots.url")]
437 #[strum(serialize = "screenshots.url")]
438 ScreenshotsUrl,
439
440 #[serde(rename = "screenshots.violence")]
441 #[strum(serialize = "screenshots.violence")]
442 ScreenshotsViolence,
443
444 #[serde(rename = "screenshots.votecount")]
445 #[strum(serialize = "screenshots.votecount")]
446 ScreenshotsVoteCount,
447
448 #[serde(rename = "staff.eid")]
449 #[strum(serialize = "staff.eid")]
450 StaffEid,
451
452 #[serde(rename = "staff.note")]
453 #[strum(serialize = "staff.note")]
454 StaffNote,
455
456 #[serde(rename = "staff.role")]
457 #[strum(serialize = "staff.role")]
458 StaffRole,
459
460 #[serde(rename = "tags.id")]
461 #[strum(serialize = "tags.id")]
462 TagsId,
463
464 #[serde(rename = "tags.lie")]
465 #[strum(serialize = "tags.lie")]
466 TagsLie,
467
468 #[serde(rename = "tags.rating")]
469 #[strum(serialize = "tags.rating")]
470 TagsRating,
471
472 #[serde(rename = "tags.spoiler")]
473 #[strum(serialize = "tags.spoiler")]
474 TagsSpoiler,
475
476 #[serde(rename = "title")]
477 #[strum(serialize = "title")]
478 Title,
479
480 #[serde(rename = "titles.lang")]
481 #[strum(serialize = "titles.lang")]
482 TitlesLang,
483
484 #[serde(rename = "titles.latin")]
485 #[strum(serialize = "titles.latin")]
486 TitlesLatin,
487
488 #[serde(rename = "titles.main")]
489 #[strum(serialize = "titles.main")]
490 TitlesMain,
491
492 #[serde(rename = "titles.official")]
493 #[strum(serialize = "titles.official")]
494 TitlesOfficial,
495
496 #[serde(rename = "titles.title")]
497 #[strum(serialize = "titles.title")]
498 TitlesTitle,
499
500 #[serde(rename = "va.character.id")]
501 #[strum(serialize = "va.character.id")]
502 VaCharacterId,
503
504 #[serde(rename = "va.note")]
505 #[strum(serialize = "va.note")]
506 VaNote,
507
508 #[serde(rename = "va.staff.id")]
509 #[strum(serialize = "va.staff.id")]
510 VaStaffId,
511
512 #[serde(rename = "votecount")]
513 #[strum(serialize = "votecount")]
514 VoteCount,
515}
516
517impl QueryField for VisualNovelField {}
518
519impl_into_field_set!(VisualNovelField);
520
521#[non_exhaustive]
522#[remain::sorted]
523#[derive(Clone, Copy, Debug, Deserialize, Serialize, Display, EnumString)]
524#[cfg_attr(feature = "specta", derive(specta::Type))]
525pub enum SortVisualNovelBy {
526 #[serde(rename = "id")]
527 #[strum(serialize = "id")]
528 Id,
529
530 #[serde(rename = "rating")]
531 #[strum(serialize = "rating")]
532 Rating,
533
534 #[serde(rename = "released")]
535 #[strum(serialize = "released")]
536 Released,
537
538 #[serde(rename = "searchrank")]
539 #[strum(serialize = "searchrank")]
540 SearchRank,
541
542 #[serde(rename = "title")]
543 #[strum(serialize = "title")]
544 Title,
545
546 #[serde(rename = "votecount")]
547 #[strum(serialize = "votecount")]
548 VoteCount,
549}
550
551impl SortQueryBy for SortVisualNovelBy {}