1use super::producer::Producer;
2use super::schema::Language;
3use super::visual_novel::{VisualNovel, VisualNovelId, VisualNovelImage};
4use super::{QueryField, SortQueryBy};
5use crate::{impl_id_newtype, impl_into_field_set};
6use regex::Regex;
7use serde::{Deserialize, Deserializer, Serialize};
8use serde_json::Value as JsonValue;
9use std::sync::{Arc, LazyLock};
10use strum::{Display, EnumIs, EnumString, VariantArray};
11
12static ID_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^r\d+$").unwrap());
13
14#[remain::sorted]
15#[derive(Clone, Debug, Deserialize, Serialize)]
16#[cfg_attr(feature = "specta", derive(specta::Type))]
17pub struct Release {
18 pub alttitle: Option<String>,
19 pub catalog: Option<String>,
20 pub engine: Option<String>,
21 pub extlinks: Option<Vec<ExternalLink>>,
22 pub freeware: Option<bool>,
23 pub gtin: Option<String>,
24 pub has_ero: Option<bool>,
25 pub id: ReleaseId,
26 pub images: Option<Vec<ReleaseImage>>,
27 pub languages: Option<Vec<ReleaseLanguage>>,
28 pub media: Option<Vec<ReleaseMedia>>,
29 pub minage: Option<u32>,
30 pub notes: Option<String>,
31 pub official: Option<bool>,
32 pub patch: Option<bool>,
33 pub platforms: Option<Vec<String>>,
34 pub released: Option<String>,
35 pub resolution: Option<ReleaseResolution>,
36 pub title: Option<String>,
37 pub uncensored: Option<bool>,
38 pub voiced: Option<ReleaseVoiced>,
39}
40
41impl From<Release> for ReleaseId {
42 fn from(r: Release) -> Self {
43 r.id
44 }
45}
46
47#[derive(
48 Debug,
49 Deserialize,
50 Serialize,
51 PartialEq,
52 Eq,
53 PartialOrd,
54 Ord,
55 Hash,
56 derive_more::Display,
57 derive_more::Into,
58)]
59#[cfg_attr(feature = "specta", derive(specta::Type))]
60pub struct ReleaseId(#[cfg_attr(feature = "specta", specta(type = String))] Arc<str>);
61
62impl ReleaseId {
63 pub const PREFIX: char = 'r';
64}
65
66impl_id_newtype!(Release, ReleaseId, ID_REGEX);
67
68#[remain::sorted]
69#[derive(Clone, Debug, Deserialize, Serialize)]
70#[cfg_attr(feature = "specta", derive(specta::Type))]
71pub struct ReleaseImage {
72 #[serde(flatten)]
73 pub image: VisualNovelImage,
74 pub languages: Option<Vec<Language>>,
75 pub photo: Option<bool>,
76 pub r#type: Option<ReleaseImageType>,
77 pub vn: Option<VisualNovelId>,
78}
79
80impl From<ReleaseImage> for VisualNovelImage {
81 fn from(ri: ReleaseImage) -> Self {
82 ri.image
83 }
84}
85
86#[non_exhaustive]
87#[remain::sorted]
88#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash, Display, EnumIs)]
89#[cfg_attr(feature = "specta", derive(specta::Type))]
90pub enum ReleaseImageType {
91 Dig,
92 PkgBack,
93 PkgContent,
94 PkgFront,
95 PkgMed,
96 PkgSide,
97}
98
99#[remain::sorted]
100#[derive(Clone, Debug, Deserialize, Serialize)]
101#[cfg_attr(feature = "specta", derive(specta::Type))]
102pub struct ReleaseLanguage {
103 pub lang: Option<Language>,
104 pub latin: Option<String>,
105 pub main: Option<bool>,
106 pub mtl: Option<bool>,
107 pub title: Option<String>,
108}
109
110#[remain::sorted]
111#[derive(Clone, Debug, Deserialize, Serialize)]
112#[cfg_attr(feature = "specta", derive(specta::Type))]
113pub struct ReleaseMedia {
114 pub medium: Option<String>,
115 pub qty: Option<u32>,
116}
117
118#[remain::sorted]
119#[derive(Clone, Debug, Deserialize, Serialize)]
120#[cfg_attr(feature = "specta", derive(specta::Type))]
121pub struct ReleaseProducer {
122 pub developer: Option<bool>,
123 #[serde(flatten)]
124 pub producer: Producer,
125 pub publisher: Option<bool>,
126}
127
128impl From<ReleaseProducer> for Producer {
129 fn from(rp: ReleaseProducer) -> Self {
130 rp.producer
131 }
132}
133
134#[non_exhaustive]
135#[remain::sorted]
136#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Display, EnumIs)]
137#[cfg_attr(feature = "specta", derive(specta::Type))]
138#[serde(untagged)]
139pub enum ReleaseResolution {
140 NonStandard(String),
141 Standard([u32; 2]),
142}
143
144#[non_exhaustive]
145#[remain::sorted]
146#[derive(
147 Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash, Display, EnumIs, EnumString,
148)]
149#[cfg_attr(feature = "specta", derive(specta::Type))]
150pub enum ReleaseType {
151 #[serde(rename = "complete")]
152 #[strum(serialize = "complete")]
153 Complete,
154
155 #[serde(rename = "partial")]
156 #[strum(serialize = "partial")]
157 Partial,
158
159 #[serde(rename = "trial")]
160 #[strum(serialize = "trial")]
161 Trial,
162}
163
164#[remain::sorted]
165#[derive(Clone, Debug, Deserialize, Serialize)]
166#[cfg_attr(feature = "specta", derive(specta::Type))]
167pub struct ReleaseVisualNovel {
168 pub rtype: Option<ReleaseType>,
169 #[serde(flatten)]
170 pub visual_novel: VisualNovel,
171}
172
173impl From<ReleaseVisualNovel> for VisualNovel {
174 fn from(rvn: ReleaseVisualNovel) -> Self {
175 rvn.visual_novel
176 }
177}
178
179#[non_exhaustive]
180#[remain::sorted]
181#[derive(Copy, Clone, Debug, Serialize, PartialEq, Eq, Hash, Display, EnumIs)]
182#[cfg_attr(feature = "specta", derive(specta::Type))]
183pub enum ReleaseVoiced {
184 FullyVoiced,
185 NotVoiced,
186 OnlyEroScenes,
187 PartiallyVoiced,
188}
189
190impl<'de> Deserialize<'de> for ReleaseVoiced {
191 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
192 where
193 D: Deserializer<'de>,
194 {
195 use serde::de::Error;
196 match u8::deserialize(deserializer)? {
197 1 => Ok(ReleaseVoiced::NotVoiced),
198 2 => Ok(ReleaseVoiced::OnlyEroScenes),
199 3 => Ok(ReleaseVoiced::PartiallyVoiced),
200 4 => Ok(ReleaseVoiced::FullyVoiced),
201 _ => Err(D::Error::custom("Invalid release voiced value")),
202 }
203 }
204}
205
206#[remain::sorted]
207#[derive(Clone, Debug, Deserialize, Serialize)]
208#[cfg_attr(feature = "specta", derive(specta::Type))]
209pub struct ExternalLink {
210 pub id: Option<JsonValue>,
211 pub label: Option<String>,
212 pub name: Option<String>,
213 pub url: Option<String>,
214}
215
216#[non_exhaustive]
217#[remain::sorted]
218#[derive(Clone, Copy, Debug, Deserialize, Serialize, Display, EnumString, VariantArray)]
219#[cfg_attr(feature = "specta", derive(specta::Type))]
220pub enum ReleaseField {
221 #[serde(rename = "alttitle")]
222 #[strum(serialize = "alttitle")]
223 AltTitle,
224
225 #[serde(rename = "catalog")]
226 #[strum(serialize = "catalog")]
227 Catalog,
228
229 #[serde(rename = "engine")]
230 #[strum(serialize = "engine")]
231 Engine,
232
233 #[serde(rename = "extlinks.id")]
234 #[strum(serialize = "extlinks.id")]
235 ExtlinksId,
236
237 #[serde(rename = "extlinks.label")]
238 #[strum(serialize = "extlinks.label")]
239 ExtlinksLabel,
240
241 #[serde(rename = "extlinks.name")]
242 #[strum(serialize = "extlinks.name")]
243 ExtlinksName,
244
245 #[serde(rename = "extlinks.url")]
246 #[strum(serialize = "extlinks.url")]
247 ExtlinksUrl,
248
249 #[serde(rename = "freeware")]
250 #[strum(serialize = "freeware")]
251 Freeware,
252
253 #[serde(rename = "gtin")]
254 #[strum(serialize = "gtin")]
255 Gtin,
256
257 #[serde(rename = "has_ero")]
258 #[strum(serialize = "has_ero")]
259 HasEro,
260
261 #[serde(rename = "id")]
262 #[strum(serialize = "id")]
263 Id,
264
265 #[serde(rename = "images.id")]
266 #[strum(serialize = "images.id")]
267 ImagesId,
268
269 #[serde(rename = "images.languages")]
270 #[strum(serialize = "images.languages")]
271 ImagesLanguages,
272
273 #[serde(rename = "images.photo")]
274 #[strum(serialize = "images.photo")]
275 ImagesPhoto,
276
277 #[serde(rename = "images.type")]
278 #[strum(serialize = "images.type")]
279 ImagesType,
280
281 #[serde(rename = "images.url")]
282 #[strum(serialize = "images.url")]
283 ImagesUrl,
284
285 #[serde(rename = "images.vn")]
286 #[strum(serialize = "images.vn")]
287 ImagesVn,
288
289 #[serde(rename = "languages.lang")]
290 #[strum(serialize = "languages.lang")]
291 LanguagesLang,
292
293 #[serde(rename = "languages.latin")]
294 #[strum(serialize = "languages.latin")]
295 LanguagesLatin,
296
297 #[serde(rename = "languages.main")]
298 #[strum(serialize = "languages.main")]
299 LanguagesMain,
300
301 #[serde(rename = "languages.mtl")]
302 #[strum(serialize = "languages.mtl")]
303 LanguagesMtl,
304
305 #[serde(rename = "languages.title")]
306 #[strum(serialize = "languages.title")]
307 LanguagesTitle,
308
309 #[serde(rename = "media.medium")]
310 #[strum(serialize = "media.medium")]
311 MediaMedium,
312
313 #[serde(rename = "media.qty")]
314 #[strum(serialize = "media.qty")]
315 MediaQty,
316
317 #[serde(rename = "minage")]
318 #[strum(serialize = "minage")]
319 MinAge,
320
321 #[serde(rename = "notes")]
322 #[strum(serialize = "notes")]
323 Notes,
324
325 #[serde(rename = "official")]
326 #[strum(serialize = "official")]
327 Official,
328
329 #[serde(rename = "patch")]
330 #[strum(serialize = "patch")]
331 Patch,
332
333 #[serde(rename = "platforms")]
334 #[strum(serialize = "platforms")]
335 Platforms,
336
337 #[serde(rename = "producers.developer")]
338 #[strum(serialize = "producers.developer")]
339 ProducersDeveloper,
340
341 #[serde(rename = "producers.id")]
342 #[strum(serialize = "producers.id")]
343 ProducersId,
344
345 #[serde(rename = "producers.publisher")]
346 #[strum(serialize = "producers.publisher")]
347 ProducersPublisher,
348
349 #[serde(rename = "released")]
350 #[strum(serialize = "released")]
351 Released,
352
353 #[serde(rename = "resolution")]
354 #[strum(serialize = "resolution")]
355 Resolution,
356
357 #[serde(rename = "title")]
358 #[strum(serialize = "title")]
359 Title,
360
361 #[serde(rename = "uncensored")]
362 #[strum(serialize = "uncensored")]
363 Uncensored,
364
365 #[serde(rename = "vns.id")]
366 #[strum(serialize = "vns.id")]
367 VisualNovelId,
368
369 #[serde(rename = "vns.rtype")]
370 #[strum(serialize = "vns.rtype")]
371 VisualNovelRType,
372
373 #[serde(rename = "voiced")]
374 #[strum(serialize = "voiced")]
375 Voiced,
376}
377
378impl QueryField for ReleaseField {}
379
380impl_into_field_set!(ReleaseField);
381
382#[non_exhaustive]
383#[remain::sorted]
384#[derive(Clone, Copy, Debug, Deserialize, Serialize, Display, EnumString)]
385#[cfg_attr(feature = "specta", derive(specta::Type))]
386pub enum SortReleaseBy {
387 #[serde(rename = "id")]
388 #[strum(serialize = "id")]
389 Id,
390
391 #[serde(rename = "released")]
392 #[strum(serialize = "released")]
393 Released,
394
395 #[serde(rename = "searchrank")]
396 #[strum(serialize = "searchrank")]
397 SearchRank,
398
399 #[serde(rename = "title")]
400 #[strum(serialize = "title")]
401 Title,
402}
403
404impl SortQueryBy for SortReleaseBy {}