1use serde::{de, Deserialize};
2use serde_with::{serde_as, DeserializeAs, DisplayFromStr};
3
4use crate::{Genre, OpenOpusError, OpenOpusResult, Status, ID};
5
6#[derive(Debug, Deserialize)]
7struct Works {
8 status: Status,
9 works: Option<Vec<Work>>,
10}
11
12#[serde_as]
13#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
14pub struct Work {
15 pub title: String,
16 pub subtitle: String,
17 #[serde(rename = "searchterms")]
18 pub search_terms: String,
19 #[serde_as(as = "IntStrBool")]
20 pub popular: bool,
21 #[serde_as(as = "IntStrBool")]
22 pub recommended: bool,
23 #[serde_as(as = "DisplayFromStr")]
24 id: ID,
25 pub genre: Genre,
26}
27
28impl Work {
29 async fn list_common(url: &str) -> OpenOpusResult<Vec<Self>> {
30 let result = reqwest::get(url).await?.json::<Works>().await?;
31
32 dbg!(&result);
33
34 match result.status {
35 Status::Ok(_) => Ok(result.works.unwrap()),
36 Status::Err(err) => Err(OpenOpusError::OpenOpusAPIError(err.error)),
37 }
38 }
39
40 pub(crate) async fn list_by_composer_id_and_genre(
41 composer_id: ID,
42 genre: Genre,
43 ) -> OpenOpusResult<Vec<Self>> {
44 Self::list_common(&format!(
45 "https://api.openopus.org/work/list/composer/{}/genre/{}.json",
46 composer_id,
47 genre.into_url_str()
48 ))
49 .await
50 }
51
52 pub(crate) async fn saerch_with_composer_id_and_genre(
53 composer_id: ID,
54 genre: Genre,
55 word: &str,
56 ) -> OpenOpusResult<Vec<Self>> {
57 Self::list_common(&format!(
58 "https://api.openopus.org/work/list/composer/{}/genre/{}/search/{}.json",
59 composer_id,
60 genre.into_url_str(),
61 word
62 ))
63 .await
64 }
65}
66
67struct IntStrBool;
68
69impl<'de> DeserializeAs<'de, bool> for IntStrBool {
70 fn deserialize_as<D>(deserializer: D) -> Result<bool, D::Error>
71 where
72 D: serde::Deserializer<'de>,
73 {
74 let s = String::deserialize(deserializer).map_err(de::Error::custom)?;
75 Ok(match s.as_ref() {
76 "0" => false,
77 "1" => true,
78 _ => {
79 return Err(de::Error::custom(
80 r#"Invalid string: {}. should be "0" or "1""#,
81 ))
82 }
83 })
84 }
85}
86
87#[cfg(test)]
88mod test {
89 use super::*;
90
91 #[tokio::test]
92 async fn test_list_by_composer_id() -> anyhow::Result<()> {
93 let _ = Work::list_by_composer_id_and_genre(130, Genre::All).await?;
94 Ok(())
95 }
96
97 #[tokio::test]
98 async fn test_list_by_composer_id_and_genre() -> anyhow::Result<()> {
99 let _ = Work::list_by_composer_id_and_genre(2, Genre::Orchestral).await?;
100 Ok(())
101 }
102
103 #[tokio::test]
104 async fn test_saerch_with_composer_id() -> anyhow::Result<()> {
105 let _ = Work::saerch_with_composer_id_and_genre(196, Genre::All, "Sonata").await?;
106 Ok(())
107 }
108
109 #[tokio::test]
110 async fn test_saerch_with_composer_id_and_genre() -> anyhow::Result<()> {
111 let _ =
112 Work::saerch_with_composer_id_and_genre(145, Genre::Chamber, "Cello Sonata").await?;
113 Ok(())
114 }
115}