ferrispot/client/
unscoped.rs

1use log::warn;
2use reqwest::{Method, StatusCode};
3
4use super::{request_builder::RequestBuilder, API_USER_PROFILE_ENDPOINT};
5#[cfg(feature = "async")]
6use crate::client::request_builder::AsyncResponseHandler;
7#[cfg(feature = "sync")]
8use crate::client::request_builder::SyncResponseHandler;
9use crate::{
10    client::{
11        object,
12        request_builder::{BaseRequestBuilderContainer, CatalogItemRequestBuilder, SearchBuilder},
13        API_SEARCH_ENDPOINT, API_TRACKS_ENDPOINT,
14    },
15    error::Error,
16    model::{
17        id::{Id, IdTrait, TrackId, UserId},
18        track::FullTrack,
19        user::PublicUser,
20    },
21};
22
23/// All unscoped Spotify endpoints. The functions in this trait do not require user authentication to use. All Spotify
24/// clients implement this trait.
25pub trait UnscopedClient
26where
27    Self: crate::private::Sealed + Clone + Sized,
28{
29    /// Get Spotify catalog information for a single track identified by its unique Spotify ID.
30    ///
31    /// An optional market country may be specified with the [`market`-function in the request builder this function
32    /// returns](CatalogItemRequestBuilder::market). Only content that is available in that market will be returned and
33    /// [track relinking](crate::model::track#track-equality-and-track-relinking) may be applied.
34    fn track<'a>(&'a self, track: Id<'a, TrackId>) -> CatalogItemRequestBuilder<Self, FullTrack> {
35        let mut builder = CatalogItemRequestBuilder::new(
36            Method::GET,
37            format!("{}/{}", API_TRACKS_ENDPOINT, track.as_str()),
38            self.clone(),
39        );
40
41        #[cfg(feature = "async")]
42        {
43            builder = builder.with_async_response_handler(track_response_handler_async_fn(track.as_owned()));
44        }
45
46        #[cfg(feature = "sync")]
47        {
48            builder = builder.with_sync_response_handler(track_response_handler_sync_fn(track.as_owned()));
49        }
50
51        builder
52    }
53
54    /// Get Spotify catalog information for multiple tracks based on their Spotify IDs.
55    ///
56    /// Up to 50 IDs may be given. In case some IDs cannot be found, they will be omitted from the result.
57    ///
58    /// An optional market country may be specified with the [`market`-function in the request builder this function
59    /// returns](CatalogItemRequestBuilder::market). Only content that is available in that market will be returned and
60    /// [track relinking](crate::model::track#track-equality-and-track-relinking) may be applied.
61    fn tracks<'a, I>(&'a self, tracks: I) -> CatalogItemRequestBuilder<Self, object::TracksResponse, Vec<FullTrack>>
62    where
63        I: IntoIterator<Item = Id<'a, TrackId>>,
64    {
65        CatalogItemRequestBuilder::new(Method::GET, API_TRACKS_ENDPOINT, self.clone()).append_query(
66            object::TRACKS_IDS_QUERY,
67            tracks
68                .into_iter()
69                .map(|id| id.as_str().to_owned())
70                .collect::<Vec<_>>()
71                .join(","),
72        )
73    }
74
75    /// Get Spotify catalog information about albums, artists, playlists, tracks, shows or episodes that match a keyword
76    /// string.
77    ///
78    /// This function returns a [SearchBuilder](self::SearchBuilder) that you can use to configure the various search
79    /// parameters and finally send the search query and get the results back.
80    fn search<S>(&self, query: S) -> SearchBuilder<Self>
81    where
82        S: Into<String>,
83    {
84        SearchBuilder::new(Method::GET, API_SEARCH_ENDPOINT, self.clone()).query(query.into())
85    }
86
87    /// Get public information about a Spotify user.
88    fn user_profile<'a>(&'a self, user_id: Id<'a, UserId>) -> RequestBuilder<Self, PublicUser> {
89        RequestBuilder::new(
90            Method::GET,
91            format!("{API_USER_PROFILE_ENDPOINT}/{}", user_id.as_str()),
92            self.clone(),
93        )
94    }
95}
96
97#[cfg(feature = "async")]
98fn track_response_handler_async_fn(track_id: Id<'static, TrackId>) -> AsyncResponseHandler {
99    Box::new(move |response| {
100        Box::pin(async move {
101            match response.status() {
102                StatusCode::OK => Ok(response),
103
104                StatusCode::NOT_FOUND => {
105                    warn!("Got 404 Not Found to track call");
106                    Err(Error::NonexistentTrack(track_id))
107                }
108
109                other => Err(Error::UnhandledSpotifyResponseStatusCode(other.as_u16())),
110            }
111        })
112    })
113}
114
115#[cfg(feature = "sync")]
116fn track_response_handler_sync_fn(track_id: Id<'static, TrackId>) -> SyncResponseHandler {
117    Box::new(move |response| match response.status() {
118        StatusCode::OK => Ok(response),
119
120        StatusCode::NOT_FOUND => {
121            warn!("Got 404 Not Found to track call");
122            Err(Error::NonexistentTrack(track_id))
123        }
124
125        other => Err(Error::UnhandledSpotifyResponseStatusCode(other.as_u16())),
126    })
127}