open_opus/
composer.rs

1use chrono::NaiveDate;
2use serde::Deserialize;
3use serde_with::{serde_as, DisplayFromStr};
4
5use crate::{Epoch, Genre, OpenOpusError, OpenOpusResult, Status, Work, ID};
6
7#[derive(Debug, Deserialize)]
8struct Composers {
9    status: Status,
10    composers: Option<Vec<Composer>>,
11}
12
13#[serde_as]
14#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
15pub struct Composer {
16    #[serde_as(as = "DisplayFromStr")]
17    pub id: ID,
18    pub name: String,
19    pub complete_name: String,
20    pub birth: Option<NaiveDate>,
21    pub death: Option<NaiveDate>,
22    pub epoch: Epoch,
23    pub portrait: String,
24}
25
26impl Composer {
27    async fn list_common(url: &str) -> OpenOpusResult<Vec<Self>> {
28        let result = reqwest::get(url).await?.json::<Composers>().await?;
29
30        dbg!(&result);
31
32        match result.status {
33            Status::Ok(_) => Ok(result.composers.unwrap()),
34            Status::Err(err) => Err(OpenOpusError::OpenOpusAPIError(err.error)),
35        }
36    }
37
38    /// List popular composers
39    /// GET /composer/list/pop.json
40    pub async fn list_popular() -> OpenOpusResult<Vec<Self>> {
41        Self::list_common("https://api.openopus.org/composer/list/pop.json").await
42    }
43
44    /// List essential composers
45    /// GET /composer/list/rec.json
46    pub async fn list_essential() -> OpenOpusResult<Vec<Self>> {
47        Self::list_common("https://api.openopus.org/composer/list/rec.json").await
48    }
49
50    /// List composers by first letter
51    /// GET /composer/list/name/<first letter>.json
52    pub async fn list_by_first_letter(first_letter: char) -> OpenOpusResult<Vec<Self>> {
53        Self::list_common(&format!(
54            "https://api.openopus.org/composer/list/name/{}.json",
55            first_letter
56        ))
57        .await
58    }
59
60    /// List composers by period
61    /// GET /composer/list/epoch/<period>>.json
62    pub async fn list_by_period(epoch: Epoch) -> OpenOpusResult<Vec<Self>> {
63        Self::list_common(
64            &format!(
65                "https://api.openopus.org/composer/list/epoch/{}.json",
66                epoch.into_url_str()
67            )
68            .to_string(),
69        )
70        .await
71    }
72
73    /// Search composers by name
74    /// GET /composer/list/search/<search word>.json
75    pub async fn search(word: &str) -> OpenOpusResult<Vec<Self>> {
76        Self::list_common(
77            &format!(
78                "https://api.openopus.org/composer/list/search/{}.json",
79                word
80            )
81            .to_string(),
82        )
83        .await
84    }
85
86    /// List composers by ID
87    /// GET /composer/list/ids/<id>.json
88    pub async fn get_by_id(id: ID) -> OpenOpusResult<Self> {
89        Self::list_common(
90            &format!("https://api.openopus.org/composer/list/ids/{}.json", id).to_string(),
91        )
92        .await
93        .map(|v| v.into_iter().next().unwrap())
94    }
95
96    /// List genres by composer ID
97    /// GET /genre/list/composer/<composer id>.json
98    pub async fn genres(&self) -> OpenOpusResult<Vec<Genre>> {
99        Genre::list_by_composer_id(self.id).await
100    }
101
102    /// List works by composer ID
103    /// GET /work/list/composer/<composer id>/genre/all.json
104    pub async fn works(&self) -> OpenOpusResult<Vec<Work>> {
105        Work::list_by_composer_id_and_genre(self.id, Genre::All).await
106    }
107
108    /// List popular works by composer ID
109    /// GET /work/list/composer/<composer id>/genre/Popular.json
110    pub async fn popular_works(&self) -> OpenOpusResult<Vec<Work>> {
111        Work::list_by_composer_id_and_genre(self.id, Genre::Popular).await
112    }
113
114    /// List essential works by composer ID
115    /// GET /work/list/composer/<composer id>/genre/Recommended.json
116    pub async fn recommended_works(&self) -> OpenOpusResult<Vec<Work>> {
117        Work::list_by_composer_id_and_genre(self.id, Genre::Recommended).await
118    }
119
120    /// List works by composer ID and genre
121    /// GET /work/list/composer/<composer id>/genre/<genre>.json
122    pub async fn works_by_genre(&self, genre: Genre) -> OpenOpusResult<Vec<Work>> {
123        Work::list_by_composer_id_and_genre(self.id, genre).await
124    }
125
126    // Search works by composer ID and title
127    // GET /work/list/composer/<composer id>/genre/all/search/<search word>.json
128    pub async fn search_works(&self, search_word: &str) -> OpenOpusResult<Vec<Work>> {
129        Work::saerch_with_composer_id_and_genre(self.id, Genre::All, search_word).await
130    }
131
132    /// Search works by composer ID, genre and title
133    /// GET /work/list/composer/<composer id>/genre/<genre>/search/<search word>.json
134    pub async fn search_works_with_genre(
135        &self,
136        search_word: &str,
137        genre: Genre,
138    ) -> OpenOpusResult<Vec<Work>> {
139        Work::saerch_with_composer_id_and_genre(self.id, genre, search_word).await
140    }
141}
142
143#[cfg(test)]
144mod test {
145    use std::{thread, time::Duration};
146
147    use super::*;
148
149    #[tokio::test]
150    async fn test_list_popular() -> anyhow::Result<()> {
151        let _ = Composer::list_popular().await?;
152        Ok(())
153    }
154
155    #[tokio::test]
156    async fn test_list_essential() -> anyhow::Result<()> {
157        let _ = Composer::list_essential().await?;
158        Ok(())
159    }
160
161    #[tokio::test]
162    async fn test_filter_by_first_letter() -> anyhow::Result<()> {
163        let _ = Composer::list_by_first_letter('A').await?;
164        Ok(())
165    }
166
167    #[tokio::test]
168    async fn test_filter_by_period() -> anyhow::Result<()> {
169        use strum::IntoEnumIterator;
170
171        for epoch in Epoch::iter() {
172            let _ = Composer::list_by_period(epoch).await?;
173
174            thread::sleep(Duration::from_millis(100));
175        }
176
177        Ok(())
178    }
179
180    #[tokio::test]
181    async fn test_search() -> anyhow::Result<()> {
182        let _ = Composer::search("bruc").await?;
183        Ok(())
184    }
185
186    #[tokio::test]
187    async fn test_get_by_id() -> anyhow::Result<()> {
188        let _ = Composer::get_by_id(186).await?;
189        Ok(())
190    }
191
192    #[tokio::test]
193    async fn test_get_by_id_not_exists() -> anyhow::Result<()> {
194        assert!(matches!(
195            Composer::get_by_id(999999999).await,
196            Err(OpenOpusError::OpenOpusAPIError(_))
197        ));
198        Ok(())
199    }
200}