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