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