open_opus/
work.rs

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}