opentalk_keycloak_admin/
lib.rs1use ::reqwest::{Client, RequestBuilder, Response};
6use snafu::Snafu;
7use url::Url;
8
9mod authorized_client;
10pub mod reqwest;
11
12pub mod users;
13
14pub type Result<T, E = Error> = std::result::Result<T, E>;
15
16pub use authorized_client::AuthorizedClient;
17
18#[derive(Debug, Snafu)]
19pub enum Error {
20 #[snafu(display("Reqwest error: {}", source), context(false))]
21 Reqwest { source: ::reqwest::Error },
22 #[snafu(display("Failed to serialize body: {source}\n {body}"))]
23 InvalidBody {
24 body: String,
25 source: serde_json::Error,
26 },
27 #[snafu(display("Keycloak error response: {status} - {response}"))]
28 KeyCloak { status: u16, response: String },
29 #[snafu(display("Invalid credentials"))]
30 InvalidCredentials,
31 #[snafu(display("Given base URL is not a base: {}", url))]
32 NotBaseUrl { url: Url },
33 #[snafu(display("The OIDC metadata couldn't be fetched from this URL: {}", url))]
34 OidcMetadataError { url: Url },
35}
36
37impl Error {
38 async fn from_keycloak_response(response: Response, message: &str) -> Self {
40 let status = response.status();
41 let error_response = response
42 .text()
43 .await
44 .unwrap_or_else(|_| "<failed to receive body>".to_string());
45 log::error!(
46 "{} (status: {}, response: '{}'",
47 message,
48 status.as_u16(),
49 error_response,
50 );
51 Self::KeyCloak {
52 status: status.as_u16(),
53 response: error_response,
54 }
55 }
56}
57
58pub struct KeycloakAdminClient {
60 api_base_url: Url,
61
62 authorized_client: AuthorizedClient,
63
64 dump_failed_responses: bool,
65}
66
67impl KeycloakAdminClient {
68 pub fn new(api_base_url: Url, authorized_client: AuthorizedClient) -> Result<Self, Error> {
70 Self::with_dump_flag(api_base_url, authorized_client, false)
71 }
72
73 pub fn with_dump_flag(
74 api_base_url: Url,
75 authorized_client: AuthorizedClient,
76 dump_failed_responses: bool,
77 ) -> Result<Self, Error> {
78 if api_base_url.cannot_be_a_base() {
79 return Err(Error::NotBaseUrl { url: api_base_url });
80 }
81
82 Ok(Self {
83 api_base_url,
84 authorized_client,
85 dump_failed_responses,
86 })
87 }
88
89 fn api_url<I>(&self, path_segments: I) -> Result<Url>
90 where
91 I: IntoIterator,
92 I::Item: AsRef<str>,
93 {
94 build_url(self.api_base_url.clone(), path_segments)
95 }
96
97 async fn send_authorized(&self, f: impl Fn(&Client) -> RequestBuilder) -> Result<Response> {
98 self.authorized_client.send_authorized(f).await
99 }
100}
101
102fn build_url<I>(base_url: Url, path_segments: I) -> Result<Url>
104where
105 I: IntoIterator,
106 I::Item: AsRef<str>,
107{
108 let err_url = base_url.clone();
109 let mut url = base_url;
110 url.path_segments_mut()
111 .map_err(|_| Error::NotBaseUrl { url: err_url })?
112 .extend(path_segments);
113 Ok(url)
114}