athena_rs 3.3.0

Database gateway API
Documentation
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,
};

/// Create a Render Postgres service using the Render REST API.
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,
    })
}

/// Resolve a Render Postgres connection URI from Render REST API.
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(&params.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
            ))
        })
}