use serde_json::Value;
use uuid::Uuid;
use super::constants::DEFAULT_RENDER_API_BASE_URL;
use super::error::ProvisioningError;
use super::payload_json::{
extract_connection_uri, extract_render_connection_uri, extract_render_service_id,
};
use super::types::{
RenderConnectionParams, RenderPostgresCreateParams, RenderPostgresCreateResult,
};
pub async fn create_render_postgres_service(
params: RenderPostgresCreateParams,
) -> Result<RenderPostgresCreateResult, ProvisioningError> {
if params.api_key.trim().is_empty() {
return Err(ProvisioningError::InvalidInput(
"render api_key must not be empty".to_string(),
));
}
let base: String = params
.api_base_url
.unwrap_or_else(|| DEFAULT_RENDER_API_BASE_URL.to_string());
let url = format!("{}/postgres", base.trim_end_matches('/'));
let payload = if let Some(payload) = params.service_payload {
payload
} else {
let owner_id = params
.owner_id
.filter(|value| !value.trim().is_empty())
.ok_or_else(|| {
ProvisioningError::InvalidInput(
"render owner_id must be provided when service_payload is omitted".to_string(),
)
})?;
let service_name = params
.service_name
.filter(|value| !value.trim().is_empty())
.unwrap_or_else(|| format!("athena-{}", Uuid::new_v4().simple()));
let plan = params
.plan
.filter(|value| !value.trim().is_empty())
.unwrap_or_else(|| "basic-256mb".to_string());
let region = params
.region
.filter(|value| !value.trim().is_empty())
.unwrap_or_else(|| "oregon".to_string());
let postgres_version = params
.postgres_version
.filter(|value| !value.trim().is_empty())
.unwrap_or_else(|| "16".to_string());
let disk_size_gb = params.disk_size_gb.unwrap_or(1).max(1);
serde_json::json!({
"name": service_name,
"ownerId": owner_id,
"plan": plan,
"region": region,
"postgresVersion": postgres_version,
"diskSizeGB": disk_size_gb,
})
};
let response = reqwest::Client::new()
.post(url)
.bearer_auth(params.api_key)
.json(&payload)
.send()
.await
.map_err(|err| {
ProvisioningError::Execution(format!("render api request failed: {}", err))
})?;
let status = response.status();
let body: Value = response.json().await.map_err(|err| {
ProvisioningError::Execution(format!("failed to parse render api response: {}", err))
})?;
if !status.is_success() {
return Err(ProvisioningError::Execution(format!(
"render api returned status {}: {}",
status, body
)));
}
let service_id = extract_render_service_id(&body).ok_or_else(|| {
ProvisioningError::Execution(format!(
"render service create response missing service id: {}",
body
))
})?;
Ok(RenderPostgresCreateResult {
service_id,
raw: body,
})
}
pub async fn fetch_render_connection_uri(
params: RenderConnectionParams,
) -> Result<String, ProvisioningError> {
if params.api_key.trim().is_empty() {
return Err(ProvisioningError::InvalidInput(
"render api_key must not be empty".to_string(),
));
}
if params.service_id.trim().is_empty() {
return Err(ProvisioningError::InvalidInput(
"render service_id must not be empty".to_string(),
));
}
let base: String = params
.api_base_url
.unwrap_or_else(|| DEFAULT_RENDER_API_BASE_URL.to_string());
let base: &str = base.trim_end_matches('/');
let service_url: String = format!("{}/postgres/{}", base, params.service_id);
let connection_url: String = format!("{}/postgres/{}/connection-info", base, params.service_id);
let service_body: reqwest::Response = reqwest::Client::new()
.get(service_url)
.bearer_auth(¶ms.api_key)
.send()
.await
.map_err(|err| {
ProvisioningError::Execution(format!("render api request failed: {}", err))
})?;
let service_status = service_body.status();
let service_body: Value = service_body.json().await.map_err(|err| {
ProvisioningError::Execution(format!("failed to parse render api response: {}", err))
})?;
if !service_status.is_success() {
return Err(ProvisioningError::Execution(format!(
"render api returned status {} for postgres service: {}",
service_status, service_body
)));
}
if let Some(uri) = extract_connection_uri(&service_body) {
return Ok(uri);
}
if let Some(uri) = extract_render_connection_uri(&service_body) {
return Ok(uri);
}
let connection_body = reqwest::Client::new()
.get(connection_url)
.bearer_auth(params.api_key)
.send()
.await
.map_err(|err| {
ProvisioningError::Execution(format!("render api request failed: {}", err))
})?;
let connection_status = connection_body.status();
let connection_body: Value = connection_body.json().await.map_err(|err| {
ProvisioningError::Execution(format!("failed to parse render api response: {}", err))
})?;
if !connection_status.is_success() {
return Err(ProvisioningError::Execution(format!(
"render api returned status {} for connection-info: {}",
connection_status, connection_body
)));
}
extract_render_connection_uri(&connection_body)
.or_else(|| extract_connection_uri(&connection_body))
.ok_or_else(|| {
ProvisioningError::Execution(format!(
"render api response did not include a postgres connection URI: {}",
connection_body
))
})
}