Skip to main content

vn_core/model/
visual_novel.rs

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 {}