Line data Source code
1 : use compute_api::responses::GenericAPIError;
2 : use hyper::{Method, StatusCode};
3 : use serde::de::DeserializeOwned;
4 : use serde::{Deserialize, Serialize};
5 : use thiserror::Error;
6 :
7 : use crate::url::ApiUrl;
8 : use crate::{http, DbName, RoleName};
9 :
10 : pub struct ComputeCtlApi {
11 : pub(crate) api: http::Endpoint,
12 : }
13 :
14 : #[derive(Serialize, Debug)]
15 : pub struct ExtensionInstallRequest {
16 : pub extension: &'static str,
17 : pub database: DbName,
18 : pub version: &'static str,
19 : }
20 :
21 : #[derive(Serialize, Debug)]
22 : pub struct SetRoleGrantsRequest {
23 : pub database: DbName,
24 : pub schema: &'static str,
25 : pub privileges: Vec<Privilege>,
26 : pub role: RoleName,
27 : }
28 :
29 0 : #[derive(Clone, Debug, Deserialize)]
30 : pub struct ExtensionInstallResponse {}
31 :
32 0 : #[derive(Clone, Debug, Deserialize)]
33 : pub struct SetRoleGrantsResponse {}
34 :
35 0 : #[derive(Debug, Serialize, Deserialize, Clone, Copy)]
36 : #[serde(rename_all = "UPPERCASE")]
37 : pub enum Privilege {
38 : Usage,
39 : }
40 :
41 0 : #[derive(Error, Debug)]
42 : pub enum ComputeCtlError {
43 : #[error("connection error: {0}")]
44 : ConnectionError(#[source] reqwest_middleware::Error),
45 : #[error("request error [{status}]: {body:?}")]
46 : RequestError {
47 : status: StatusCode,
48 : body: Option<GenericAPIError>,
49 : },
50 : #[error("response parsing error: {0}")]
51 : ResponseError(#[source] reqwest::Error),
52 : }
53 :
54 : impl ComputeCtlApi {
55 0 : pub async fn install_extension(
56 0 : &self,
57 0 : req: &ExtensionInstallRequest,
58 0 : ) -> Result<ExtensionInstallResponse, ComputeCtlError> {
59 0 : self.generic_request(req, Method::POST, |url| {
60 0 : url.path_segments_mut().push("extensions");
61 0 : })
62 0 : .await
63 0 : }
64 :
65 0 : pub async fn grant_role(
66 0 : &self,
67 0 : req: &SetRoleGrantsRequest,
68 0 : ) -> Result<SetRoleGrantsResponse, ComputeCtlError> {
69 0 : self.generic_request(req, Method::POST, |url| {
70 0 : url.path_segments_mut().push("grants");
71 0 : })
72 0 : .await
73 0 : }
74 :
75 0 : async fn generic_request<Req, Resp>(
76 0 : &self,
77 0 : req: &Req,
78 0 : method: Method,
79 0 : url: impl for<'a> FnOnce(&'a mut ApiUrl),
80 0 : ) -> Result<Resp, ComputeCtlError>
81 0 : where
82 0 : Req: Serialize,
83 0 : Resp: DeserializeOwned,
84 0 : {
85 0 : let resp = self
86 0 : .api
87 0 : .request_with_url(method, url)
88 0 : .json(req)
89 0 : .send()
90 0 : .await
91 0 : .map_err(ComputeCtlError::ConnectionError)?;
92 :
93 0 : let status = resp.status();
94 0 : if status.is_client_error() || status.is_server_error() {
95 0 : let body = resp.json().await.ok();
96 0 : return Err(ComputeCtlError::RequestError { status, body });
97 0 : }
98 0 :
99 0 : resp.json().await.map_err(ComputeCtlError::ResponseError)
100 0 : }
101 : }
|