use anyhow::Context; use reqwest::{Body, Response}; use serde::de::DeserializeOwned; use crate::utils::HTTP_CLIENT; #[derive(Clone)] pub struct AuthedClient { pub base_internal_url: String, pub workspace: String, pub token: String, pub force_client: Option, } impl AuthedClient { pub fn new( base_internal_url: String, workspace: String, token: String, force_client: Option, ) -> AuthedClient { AuthedClient { base_internal_url, workspace, token, force_client } } pub async fn get(&self, url: &str, query: Vec<(&str, String)>) -> anyhow::Result { self.force_client .as_ref() .unwrap_or(&HTTP_CLIENT) .get(url) .query(&query) .header( reqwest::header::ACCEPT, reqwest::header::HeaderValue::from_static("application/json"), ) .header( reqwest::header::AUTHORIZATION, reqwest::header::HeaderValue::from_str(&format!("Bearer {}", self.token))?, ) .send() .await .map_err(|e| { tracing::error!("Error executing get request from authed http client to {url} with query {query:?}: {e}"); anyhow::anyhow!("Error executing get request from authed http client to {url} with query {query:?}: {e}") }) } pub async fn get_id_token(&self, audience: &str) -> anyhow::Result { let url = format!( "{}/api/w/{}/oidc/token/{}", self.base_internal_url, self.workspace, audience ); let response = self.get(&url, vec![]).await?; match response.status().as_u16() { 200u16 => Ok(response .json::() .await .context("decoding oidc token as json string")?), _ => Err(anyhow::anyhow!(response.text().await.unwrap_or_default())), } } pub async fn get_resource_value(&self, path: &str) -> anyhow::Result { let url = format!( "{}/api/w/{}/resources/get_value/{}", self.base_internal_url, self.workspace, path ); let response = self.get(&url, vec![]).await?; match response.status().as_u16() { 200u16 => Ok(response .json::() .await .context("decoding resource value as json")?), _ => Err(anyhow::anyhow!(response.text().await.unwrap_or_default())), } } pub async fn get_variable_value(&self, path: &str) -> anyhow::Result { let url = format!( "{}/api/w/{}/variables/get_value/{}", self.base_internal_url, self.workspace, path ); let response = self.get(&url, vec![]).await?; match response.status().as_u16() { 200u16 => Ok(response .json::() .await .context("decoding variable value as json")?), _ => Err(anyhow::anyhow!(response.text().await.unwrap_or_default())), } } pub async fn get_resource_value_interpolated( &self, path: &str, job_id: Option, ) -> anyhow::Result { let url = format!( "{}/api/w/{}/resources/get_value_interpolated/{}", self.base_internal_url, self.workspace, path ); let mut query = Vec::with_capacity(1usize); if let Some(v) = &job_id { query.push(("job_id", v.to_string())); } let response = self.get(&url, query).await?; match response.status().as_u16() { 200u16 => Ok(response .json::() .await .context("decoding interpolated resource value as json")?), _ => Err(anyhow::anyhow!(response.text().await.unwrap_or_default())), } } pub async fn get_completed_job_result( &self, path: &str, json_path: Option, ) -> anyhow::Result { let url = format!( "{}/api/w/{}/jobs_u/completed/get_result/{}", self.base_internal_url, self.workspace, path ); let query = if let Some(json_path) = json_path { vec![("json_path", json_path)] } else { vec![] }; let response = self.get(&url, query).await?; match response.status().as_u16() { 200u16 => Ok(response .json::() .await .context("decoding completed job result as json")?), _ => Err(anyhow::anyhow!(response.text().await.unwrap_or_default())), } } pub async fn get_result_by_id( &self, flow_job_id: &str, node_id: &str, json_path: Option, ) -> anyhow::Result { let url = format!( "{}/api/w/{}/jobs/result_by_id/{}/{}", self.base_internal_url, self.workspace, flow_job_id, node_id ); let query = if let Some(json_path) = json_path { vec![("json_path", json_path)] } else { vec![] }; let response = self.get(&url, query).await?; match response.status().as_u16() { 200u16 => Ok(response .json::() .await .context("decoding result by id as json")?), _ => Err(anyhow::anyhow!(response.text().await.unwrap_or_default())), } } pub async fn upload_s3_file( &self, workspace_id: &str, object_key: String, storage: Option, body: S, ) -> anyhow::Result<()> where S: futures::stream::TryStream + Send + 'static, S::Error: Into>, bytes::Bytes: From, { let mut query = vec![("file_key", object_key)]; if let Some(storage) = storage { query.push(("storage", storage)); } let response = self .force_client .as_ref() .unwrap_or(&HTTP_CLIENT) .post(format!( "{}/api/w/{}/job_helpers/upload_s3_file", self.base_internal_url, workspace_id )) .query(&query) .header( reqwest::header::ACCEPT, reqwest::header::HeaderValue::from_static("application/json"), ) .header( reqwest::header::AUTHORIZATION, reqwest::header::HeaderValue::from_str(&format!("Bearer {}", self.token)) .map_err(|e| anyhow::anyhow!(e.to_string()))?, ) .body(Body::wrap_stream(body)) .send() .await .context(format!("Sent upload_s3_file request",)) .map_err(|e| anyhow::anyhow!(e.to_string()))?; match response.status().as_u16() { 200u16 => Ok(()), _ => Err(anyhow::anyhow!(response.text().await.unwrap_or_default()))?, } } pub async fn download_s3_file( &self, workspace_id: &str, file_key: &str, storage: Option, ) -> anyhow::Result { let mut query = vec![("file_key", file_key.to_string())]; if let Some(storage) = storage { query.push(("storage", storage)); } let response = self .force_client .as_ref() .unwrap_or(&HTTP_CLIENT) .get(&format!( "{}/api/w/{}/job_helpers/download_s3_file", self.base_internal_url, workspace_id )) .query(&query) .header( reqwest::header::AUTHORIZATION, reqwest::header::HeaderValue::from_str(&format!("Bearer {}", self.token)) .map_err(|e| anyhow::anyhow!(e.to_string()))?, ) .send() .await .context("Failed to send download_s3_file request") .map_err(|e| anyhow::anyhow!(e.to_string()))?; match response.status().as_u16() { 200u16 => Ok(response .bytes() .await .context("Failed to read response bytes")?), _ => Err(anyhow::anyhow!(response.text().await.unwrap_or_default())), } } }