Skip to main content

vn_core/model/
mod.rs

1pub mod auth_info;
2pub mod character;
3pub mod producer;
4pub mod release;
5pub mod schema;
6pub mod staff;
7pub mod stats;
8pub mod tag;
9pub mod r#trait;
10pub mod user;
11pub mod visual_novel;
12
13pub mod prelude {
14  pub use super::auth_info::{AuthInfo, TokenPermission};
15  pub use super::character::{
16    Character, CharacterBirthday, CharacterField, CharacterId, CharacterImage, CharacterSex,
17    CharacterSexValue, CharacterTrait, CharacterVisualNovel, SortCharacterBy,
18  };
19  pub use super::producer::{Producer, ProducerField, ProducerId, ProducerType, SortProducerBy};
20  pub use super::release::{
21    ExternalLink, Release, ReleaseField, ReleaseId, ReleaseImage, ReleaseImageType,
22    ReleaseLanguage, ReleaseMedia, ReleaseProducer, ReleaseResolution, ReleaseType,
23    ReleaseVisualNovel, ReleaseVoiced, SortReleaseBy,
24  };
25  pub use super::schema::{Language, Schema};
26  pub use super::staff::{SortStaffBy, Staff, StaffAlias, StaffField, StaffGender, StaffId};
27  pub use super::stats::Stats;
28  pub use super::tag::{SortTagBy, Tag, TagCategory, TagField, TagId};
29  pub use super::r#trait::{SortTraitBy, Trait, TraitField, TraitId};
30  pub use super::user::{User, UserField, UserId, UserUrlQuery, Users};
31  pub use super::visual_novel::{
32    SortVisualNovelBy, VisualNovel, VisualNovelDevStatus, VisualNovelDeveloper, VisualNovelEdition,
33    VisualNovelField, VisualNovelId, VisualNovelImage, VisualNovelLength, VisualNovelRelation,
34    VisualNovelScreenShot, VisualNovelStaff, VisualNovelTag, VisualNovelTitle,
35    VisualNovelVoiceActor,
36  };
37  pub use super::{Response, VndbId};
38}
39
40use crate::error::{Error, Result};
41use serde::{Deserialize, Serialize};
42use serde_json::Value as JsonValue;
43use std::collections::VecDeque;
44use std::fmt;
45use std::str::FromStr;
46use strum::EnumIs;
47use url::Url;
48
49#[remain::sorted]
50#[derive(Clone, Debug, Deserialize, Serialize)]
51#[cfg_attr(feature = "specta", derive(specta::Type))]
52pub struct Response<T> {
53  pub compact_filters: Option<String>,
54  pub count: Option<u32>,
55  pub more: bool,
56  pub normalized_filters: Option<JsonValue>,
57  pub results: VecDeque<T>,
58}
59
60impl<T> IntoIterator for Response<T> {
61  type Item = T;
62  type IntoIter = std::collections::vec_deque::IntoIter<Self::Item>;
63
64  fn into_iter(self) -> Self::IntoIter {
65    self.results.into_iter()
66  }
67}
68
69#[remain::sorted]
70#[derive(Clone, Debug, Deserialize, Serialize, EnumIs)]
71#[cfg_attr(feature = "specta", derive(specta::Type))]
72#[serde(tag = "kind", rename_all = "kebab-case")]
73pub enum VndbId {
74  Character(character::CharacterId),
75  Producer(producer::ProducerId),
76  Release(release::ReleaseId),
77  Staff(staff::StaffId),
78  Tag(tag::TagId),
79  Trait(r#trait::TraitId),
80  User(user::UserId),
81  VisualNovel(visual_novel::VisualNovelId),
82}
83
84impl VndbId {
85  pub fn new(id: impl AsRef<str>) -> Option<Self> {
86    id.as_ref().parse().ok()
87  }
88
89  pub fn from_url(url: &Url) -> Option<Self> {
90    if url.host_str()?.contains("vndb.org") {
91      url.path_segments()?.find_map(Self::new)
92    } else {
93      None
94    }
95  }
96
97  /// # Safety
98  ///
99  /// Calling this function with a URL that doesn't contain a valid id is undefined behavior.
100  pub unsafe fn from_url_unchecked(url: &Url) -> Self {
101    unsafe { Self::from_url(url).unwrap_unchecked() }
102  }
103}
104
105impl FromStr for VndbId {
106  type Err = Error;
107
108  fn from_str(s: &str) -> Result<Self> {
109    let prefix = s
110      .chars()
111      .next()
112      .ok_or_else(|| Error::InvalidId(s.to_owned()))?;
113
114    match prefix {
115      character::CharacterId::PREFIX => Ok(Self::Character(s.parse()?)),
116      producer::ProducerId::PREFIX => Ok(Self::Producer(s.parse()?)),
117      release::ReleaseId::PREFIX => Ok(Self::Release(s.parse()?)),
118      staff::StaffId::PREFIX => Ok(Self::Staff(s.parse()?)),
119      tag::TagId::PREFIX => Ok(Self::Tag(s.parse()?)),
120      r#trait::TraitId::PREFIX => Ok(Self::Trait(s.parse()?)),
121      user::UserId::PREFIX => Ok(Self::User(s.parse()?)),
122      visual_novel::VisualNovelId::PREFIX => Ok(Self::VisualNovel(s.parse()?)),
123      _ => Err(Error::InvalidId(s.to_owned())),
124    }
125  }
126}
127
128impl TryFrom<&str> for VndbId {
129  type Error = Error;
130
131  fn try_from(value: &str) -> Result<Self> {
132    value.parse()
133  }
134}
135
136impl fmt::Display for VndbId {
137  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138    match self {
139      Self::Character(id) => id.fmt(f),
140      Self::Producer(id) => id.fmt(f),
141      Self::Release(id) => id.fmt(f),
142      Self::Staff(id) => id.fmt(f),
143      Self::Tag(id) => id.fmt(f),
144      Self::Trait(id) => id.fmt(f),
145      Self::User(id) => id.fmt(f),
146      Self::VisualNovel(id) => id.fmt(f),
147    }
148  }
149}
150
151pub trait QueryField: fmt::Display + sealed::Sealed {}
152
153pub trait SortQueryBy: fmt::Display + sealed::Sealed {}
154
155mod sealed {
156  pub trait Sealed {}
157
158  // Field
159  impl Sealed for super::character::CharacterField {}
160  impl Sealed for super::producer::ProducerField {}
161  impl Sealed for super::release::ReleaseField {}
162  impl Sealed for super::staff::StaffField {}
163  impl Sealed for super::tag::TagField {}
164  impl Sealed for super::r#trait::TraitField {}
165  impl Sealed for super::user::UserField {}
166  impl Sealed for super::visual_novel::VisualNovelField {}
167
168  // Sort
169  impl Sealed for super::character::SortCharacterBy {}
170  impl Sealed for super::producer::SortProducerBy {}
171  impl Sealed for super::release::SortReleaseBy {}
172  impl Sealed for super::staff::SortStaffBy {}
173  impl Sealed for super::tag::SortTagBy {}
174  impl Sealed for super::r#trait::SortTraitBy {}
175  impl Sealed for super::visual_novel::SortVisualNovelBy {}
176}