am_api/resource/search/
mod.rs

1//! Apple music search
2
3use crate::error::Error;
4use crate::request::context::{ContextContainer, RequestContext};
5use crate::resource::ErrorResponse;
6use crate::ApiClient;
7use async_stream::try_stream;
8use futures::Stream;
9use reqwest::Response;
10use serde::de::DeserializeOwned;
11use serde::{Deserialize, Serialize};
12use serde_json::Value;
13use std::fmt::{Debug, Formatter};
14use std::hash::{Hash, Hasher};
15use std::sync::Arc;
16
17pub mod catalog;
18pub mod library;
19
20/// Search relationship
21#[derive(Serialize, Deserialize, Clone)]
22#[serde(rename_all = "camelCase")]
23pub struct SearchRelationship<T> {
24    /// A relative location for the relationship
25    #[serde(default)]
26    pub href: Option<String>,
27    /// A relative cursor to fetch the next paginated collection of resources in the relationship if more exist
28    #[serde(default)]
29    pub next: Option<String>,
30    /// Associated data
31    #[serde(default = "Vec::default")]
32    pub data: Vec<T>,
33    /// Context
34    #[serde(skip, default)]
35    context: Option<Arc<RequestContext>>,
36}
37
38impl<T> SearchRelationship<T>
39where
40    T: Clone + DeserializeOwned + ContextContainer,
41{
42    /// Iterate this relationship
43    pub fn iter(&self, client: &ApiClient) -> impl Stream<Item = Result<T, Error>> {
44        let relationship = self.clone();
45        let client = client.clone();
46        let context = relationship
47            .context
48            .clone()
49            .expect("context should always exist on relationships");
50
51        try_stream! {
52            let mut relationship = Some(relationship);
53
54            while let Some(rel) = relationship {
55                for mut entry in rel.data {
56                    entry.set_context(context.clone());
57                    yield entry;
58                }
59
60                let Some(next) = rel.next.as_ref() else {
61                    return;
62                };
63
64                let response = client.get(next.as_str()).query(&context.query).send().await?;
65                relationship = Self::try_relationship_response(response).await?;
66            }
67        }
68    }
69
70    async fn try_relationship_response(response: Response) -> Result<Option<Self>, Error> {
71        if !response.status().is_success() {
72            let error_response: ErrorResponse = response.json().await?;
73            return Err(Error::MusicError(error_response));
74        }
75
76        let result: Value = response.json().await?;
77        result
78            .as_object()
79            .and_then(|e| e.get("results"))
80            .and_then(|e| e.as_object())
81            .and_then(|e| e.values().next())
82            .map(|e| serde_json::from_value(e.clone()))
83            .transpose()
84            .map_err(|e| e.into())
85    }
86}
87
88impl<T> ContextContainer for SearchRelationship<T>
89where
90    T: ContextContainer,
91{
92    fn set_context(&mut self, context: Arc<RequestContext>) {
93        self.context = Some(context.clone());
94        self.data.set_context(context.clone());
95    }
96}
97
98impl<T> Debug for SearchRelationship<T>
99where
100    T: Debug,
101{
102    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
103        f.debug_struct("SearchRelationship")
104            .field("href", &self.href)
105            .field("next", &self.next)
106            .field("data", &self.data)
107            .finish()
108    }
109}
110
111impl<T> PartialEq for SearchRelationship<T>
112where
113    T: PartialEq,
114{
115    fn eq(&self, other: &Self) -> bool {
116        self.href == other.href && self.next == other.next && self.data == other.data
117    }
118}
119
120impl<T> Eq for SearchRelationship<T> where T: PartialEq + Eq {}
121
122impl<T> Hash for SearchRelationship<T>
123where
124    T: Hash,
125{
126    fn hash<H: Hasher>(&self, state: &mut H) {
127        self.href.hash(state);
128        self.next.hash(state);
129        self.data.hash(state);
130    }
131}
132
133impl<T> Default for SearchRelationship<T> {
134    fn default() -> Self {
135        SearchRelationship {
136            href: None,
137            next: None,
138            data: Vec::default(),
139            context: None,
140        }
141    }
142}