drogue_client/discovery/v1/
client.rs

1use super::data::*;
2use crate::core::CoreClient;
3use crate::error::ClientError;
4use crate::openid::{NoTokenProvider, TokenProvider};
5use std::fmt::Debug;
6use std::sync::Arc;
7use tracing::instrument;
8use url::Url;
9
10/// A client to discover available drogue-cloud endpoints and their URL.
11#[derive(Clone, Debug)]
12pub struct Client {
13    client: reqwest::Client,
14    api_url: Url,
15    token_provider: Arc<dyn TokenProvider>,
16}
17
18type ClientResult<T> = Result<T, ClientError>;
19
20impl CoreClient for Client {
21    fn client(&self) -> &reqwest::Client {
22        &self.client
23    }
24
25    fn token_provider(&self) -> &dyn TokenProvider {
26        self.token_provider.as_ref()
27    }
28}
29
30impl Client {
31    /// Create a new unauthenticated client instance.
32    pub fn new_anonymous(client: reqwest::Client, api_url: Url) -> Self {
33        Self {
34            client,
35            api_url,
36            token_provider: Arc::new(NoTokenProvider),
37        }
38    }
39
40    /// Create a new authenticated client instance.
41    pub fn new_authenticated(
42        client: reqwest::Client,
43        api_url: Url,
44        token_provider: impl TokenProvider + 'static,
45    ) -> Self {
46        Self {
47            client,
48            api_url,
49            token_provider: Arc::new(token_provider),
50        }
51    }
52
53    fn url(&self, authenticated: bool) -> ClientResult<Url> {
54        let mut url = self.api_url.clone();
55
56        {
57            let mut path = url
58                .path_segments_mut()
59                .map_err(|_| ClientError::Request("Failed to get paths".into()))?;
60
61            if authenticated {
62                path.extend(&["api", "console", "v1alpha1", "info"]);
63            } else {
64                path.extend(&[".well-known", "drogue-endpoints"]);
65            }
66        }
67
68        Ok(url)
69    }
70
71    /// Fetch drogue's well known endpoint to retrieve a list of accessible endpoints.
72    /// This endpoint does not require authentication, therefore the returned list of endpoint is not complete.
73    #[instrument]
74    pub async fn get_public_endpoints(&self) -> ClientResult<Option<Endpoints>> {
75        let req = self.client().get(self.url(false)?);
76
77        Self::read_response(req.send().await?).await
78    }
79
80    /// Fetch drogue full list of accessible endpoints.
81    #[instrument]
82    pub async fn get_authenticated_endpoints(&self) -> ClientResult<Option<Endpoints>> {
83        self.read(self.url(true)?).await
84    }
85
86    /// Fetch drogue-cloud running version.
87    #[instrument]
88    pub async fn get_drogue_cloud_version(&self) -> ClientResult<Option<DrogueVersion>> {
89        let url = self.api_url.join(".well-known/drogue-version")?;
90        let req = self.client().get(url);
91
92        Self::read_response(req.send().await?).await
93    }
94
95    /// Fetch drogue-cloud Single Sign On provider URL.
96    #[instrument]
97    pub async fn get_sso_url(&self) -> ClientResult<Option<Url>> {
98        self.get_authenticated_endpoints().await.map(|r| {
99            r.and_then(|endpoints| {
100                endpoints
101                    .issuer_url
102                    .and_then(|url| Url::parse(url.as_str()).ok())
103            })
104        })
105    }
106}
107
108#[cfg(test)]
109mod test {
110    use crate::discovery::v1::Client;
111    use url::Url;
112
113    #[tokio::test]
114    async fn test_get_drogue_version() {
115        let client: Client = Client::new_anonymous(
116            reqwest::Client::new(),
117            Url::parse("https://api.sandbox.drogue.cloud").unwrap(),
118        );
119
120        let version = client.get_drogue_cloud_version().await;
121        assert!(version.is_ok());
122        let version = version.unwrap();
123
124        assert!(version.is_some());
125        let version = version.unwrap();
126        assert!(!version.version.is_empty());
127    }
128
129    #[tokio::test]
130    async fn test_get_drogue_public_endpoints() {
131        let client: Client = Client::new_anonymous(
132            reqwest::Client::new(),
133            Url::parse("https://api.sandbox.drogue.cloud").unwrap(),
134        );
135
136        let endpoints = client.get_public_endpoints().await;
137        assert!(endpoints.is_ok());
138        let endpoints = endpoints.unwrap();
139
140        assert!(endpoints.is_some());
141        let endpoints = endpoints.unwrap();
142
143        assert!(endpoints.issuer_url.is_some());
144        assert!(endpoints.api.is_some());
145        assert!(endpoints.registry.is_some());
146        assert!(endpoints.sso.is_some());
147        assert!(endpoints.http.is_some());
148        assert!(endpoints.mqtt.is_some());
149        assert!(endpoints.kafka_bootstrap_servers.is_some());
150        assert!(endpoints.mqtt_integration.is_some());
151    }
152}