1 use std
::collections
::HashMap
;
2 use std
::future
::Future
;
4 use serde
::{Deserialize, Serialize}
;
11 pub use proxmox_login
::tfa
::TfaChallenge
;
12 pub use proxmox_login
::{Authentication, Ticket}
;
15 pub use auth
::{AuthenticationKind, Token}
;
17 #[cfg(feature = "hyper-client")]
19 #[cfg(feature = "hyper-client")]
20 pub use client
::{Client, TlsOptions}
;
22 /// HTTP client backend trait. This should be implemented for a HTTP client capable of making
23 /// *authenticated* API requests to a proxmox HTTP API.
24 pub trait HttpApiClient
{
25 /// An API call should return a status code and the raw body.
26 type ResponseFuture
<'a
>: Future
<Output
= Result
<HttpApiResponse
, Error
>> + 'a
30 /// `GET` request with a path and query component (no hostname).
32 /// For this request, authentication headers should be set!
33 fn get
<'a
>(&'a
self, path_and_query
: &'a
str) -> Self::ResponseFuture
<'a
>;
35 /// `POST` request with a path and query component (no hostname), and a serializable body.
37 /// The body should be serialized to json and sent with `Content-type: applicaion/json`.
39 /// For this request, authentication headers should be set!
40 fn post
<'a
, T
>(&'a
self, path_and_query
: &'a
str, params
: &T
) -> Self::ResponseFuture
<'a
>
42 T
: ?Sized
+ Serialize
;
44 /// `PUT` request with a path and query component (no hostname), and a serializable body.
46 /// The body should be serialized to json and sent with `Content-type: applicaion/json`.
48 /// For this request, authentication headers should be set!
49 fn put
<'a
, T
>(&'a
self, path_and_query
: &'a
str, params
: &T
) -> Self::ResponseFuture
<'a
>
51 T
: ?Sized
+ Serialize
;
53 /// `PUT` request with a path and query component (no hostname), no request body.
55 /// For this request, authentication headers should be set!
56 fn put_without_body
<'a
>(&'a
self, path_and_query
: &'a
str) -> Self::ResponseFuture
<'a
>;
58 /// `DELETE` request with a path and query component (no hostname).
60 /// For this request, authentication headers should be set!
61 fn delete
<'a
>(&'a
self, path_and_query
: &'a
str) -> Self::ResponseFuture
<'a
>;
64 /// A response from the HTTP API as required by the [`HttpApiClient`] trait.
65 pub struct HttpApiResponse
{
67 pub content_type
: Option
<String
>,
71 impl HttpApiResponse
{
72 /// Expect a JSON response as returend by the `extjs` formatter.
73 pub fn expect_json
<T
>(self) -> Result
<ApiResponseData
<T
>, Error
>
75 T
: for<'de
> Deserialize
<'de
>,
77 self.assert_json_content_type()?
;
79 serde_json
::from_slice
::<RawApiResponse
<T
>>(&self.body
)
80 .map_err(|err
| Error
::bad_api("failed to parse api response", err
))?
84 fn assert_json_content_type(&self) -> Result
<(), Error
> {
88 .and_then(|v
| v
.split('
;'
).next())
90 Some("application/json") => Ok(()),
91 Some(other
) => Err(Error
::BadApi(
92 format
!("expected json body, got {other}",),
95 None
=> Err(Error
::BadApi(
96 "expected json body, but no Content-Type was sent".to_string(),
102 /// Expect that the API call did *not* return any data in the `data` field.
103 pub fn nodata(self) -> Result
<(), Error
> {
104 let response
= serde_json
::from_slice
::<RawApiResponse
<()>>(&self.body
)
105 .map_err(|err
| Error
::bad_api("failed to parse api response", err
))?
;
107 if response
.data
.is_some() {
108 Err(Error
::UnexpectedData
)
110 response
.check_nodata()?
;
116 /// API responses can have additional *attributes* added to their data.
117 pub struct ApiResponseData
<T
> {
118 pub attribs
: HashMap
<String
, Value
>,
122 #[derive(serde::Deserialize)]
123 struct RawApiResponse
<T
> {
124 #[serde(default, deserialize_with = "proxmox_login::parse::deserialize_u16")]
126 message
: Option
<String
>,
127 #[serde(default, deserialize_with = "proxmox_login::parse::deserialize_bool")]
128 success
: Option
<bool
>,
132 errors
: HashMap
<String
, String
>,
134 #[serde(default, flatten)]
135 attribs
: HashMap
<String
, Value
>,
138 impl<T
> RawApiResponse
<T
> {
139 fn check_success(mut self) -> Result
<Self, Error
> {
140 if self.success
== Some(true) {
144 let status
= http
::StatusCode
::from_u16(self.status
.unwrap_or(400))
145 .unwrap_or(http
::StatusCode
::BAD_REQUEST
);
146 let mut message
= self
149 .unwrap_or_else(|| "no message provided".to_string());
150 for (param
, error
) in self.errors
{
152 let _
= write
!(message
, "\n{param}: {error}");
155 Err(Error
::api(status
, message
))
158 fn check(self) -> Result
<ApiResponseData
<T
>, Error
> {
159 let this
= self.check_success()?
;
164 .ok_or_else(|| Error
::BadApi("api returned no data".to_string(), None
))?
,
165 attribs
: this
.attribs
,
169 fn check_nodata(self) -> Result
<ApiResponseData
<()>, Error
> {
170 let this
= self.check_success()?
;
174 attribs
: this
.attribs
,
179 impl<'c
, C
> HttpApiClient
for &'c C
183 type ResponseFuture
<'a
> = C
::ResponseFuture
<'a
>
187 fn get
<'a
>(&'a
self, path_and_query
: &'a
str) -> Self::ResponseFuture
<'a
> {
188 C
::get(self, path_and_query
)
191 fn post
<'a
, T
>(&'a
self, path_and_query
: &'a
str, params
: &T
) -> Self::ResponseFuture
<'a
>
193 T
: ?Sized
+ Serialize
,
195 C
::post(self, path_and_query
, params
)
198 fn put
<'a
, T
>(&'a
self, path_and_query
: &'a
str, params
: &T
) -> Self::ResponseFuture
<'a
>
200 T
: ?Sized
+ Serialize
,
202 C
::put(self, path_and_query
, params
)
205 fn put_without_body
<'a
>(&'a
self, path_and_query
: &'a
str) -> Self::ResponseFuture
<'a
> {
206 C
::put_without_body(self, path_and_query
)
209 fn delete
<'a
>(&'a
self, path_and_query
: &'a
str) -> Self::ResponseFuture
<'a
> {
210 C
::delete(self, path_and_query
)
214 impl<C
> HttpApiClient
for std
::sync
::Arc
<C
>
218 type ResponseFuture
<'a
> = C
::ResponseFuture
<'a
>
222 fn get
<'a
>(&'a
self, path_and_query
: &'a
str) -> Self::ResponseFuture
<'a
> {
223 C
::get(self, path_and_query
)
226 fn post
<'a
, T
>(&'a
self, path_and_query
: &'a
str, params
: &T
) -> Self::ResponseFuture
<'a
>
228 T
: ?Sized
+ Serialize
,
230 C
::post(self, path_and_query
, params
)
233 fn put
<'a
, T
>(&'a
self, path_and_query
: &'a
str, params
: &T
) -> Self::ResponseFuture
<'a
>
235 T
: ?Sized
+ Serialize
,
237 C
::put(self, path_and_query
, params
)
240 fn put_without_body
<'a
>(&'a
self, path_and_query
: &'a
str) -> Self::ResponseFuture
<'a
> {
241 C
::put_without_body(self, path_and_query
)
244 fn delete
<'a
>(&'a
self, path_and_query
: &'a
str) -> Self::ResponseFuture
<'a
> {
245 C
::delete(self, path_and_query
)
249 impl<C
> HttpApiClient
for std
::rc
::Rc
<C
>
253 type ResponseFuture
<'a
> = C
::ResponseFuture
<'a
>
257 fn get
<'a
>(&'a
self, path_and_query
: &'a
str) -> Self::ResponseFuture
<'a
> {
258 C
::get(self, path_and_query
)
261 fn post
<'a
, T
>(&'a
self, path_and_query
: &'a
str, params
: &T
) -> Self::ResponseFuture
<'a
>
263 T
: ?Sized
+ Serialize
,
265 C
::post(self, path_and_query
, params
)
268 fn put
<'a
, T
>(&'a
self, path_and_query
: &'a
str, params
: &T
) -> Self::ResponseFuture
<'a
>
270 T
: ?Sized
+ Serialize
,
272 C
::put(self, path_and_query
, params
)
275 fn put_without_body
<'a
>(&'a
self, path_and_query
: &'a
str) -> Self::ResponseFuture
<'a
> {
276 C
::put_without_body(self, path_and_query
)
279 fn delete
<'a
>(&'a
self, path_and_query
: &'a
str) -> Self::ResponseFuture
<'a
> {
280 C
::delete(self, path_and_query
)