fn0-deploy 0.1.10

Deploy client for fn0 cloud
Documentation
use crate::credentials;
use anyhow::{Result, anyhow};
use serde::{Deserialize, Serialize};

pub struct AdminRunOutput {
    pub status: u16,
    pub content_type: Option<String>,
    pub body: Vec<u8>,
}

#[derive(Serialize)]
struct AdminRunInput<'a> {
    project_id: &'a str,
    task: &'a str,
    input: serde_json::Value,
}

#[derive(Deserialize)]
#[serde(tag = "t", rename_all_fields = "camelCase")]
enum AdminRunResponse {
    Ok {
        status: u16,
        body: serde_json::Value,
    },
    NotLoggedIn,
    NotFound,
    Forbidden,
    NotDeployed,
    UpstreamError {
        status: u16,
        body: serde_json::Value,
    },
    InternalError {
        reason: String,
    },
}

pub async fn admin_run(
    project_id: &str,
    task: &str,
    input_body: Vec<u8>,
    timeout_secs: u64,
) -> Result<AdminRunOutput> {
    let creds = credentials::require()?;

    let input: serde_json::Value = if input_body.is_empty() {
        serde_json::Value::Null
    } else {
        serde_json::from_slice(&input_body)
            .map_err(|e| anyhow!("admin run input is not valid JSON: {e}"))?
    };

    let url = format!(
        "{}/__forte_action/admin_run",
        creds.control_url.trim_end_matches('/')
    );

    let client = reqwest::Client::builder()
        .timeout(std::time::Duration::from_secs(timeout_secs))
        .build()?;

    let raw: AdminRunResponse = client
        .post(&url)
        .bearer_auth(&creds.token)
        .json(&AdminRunInput {
            project_id,
            task,
            input,
        })
        .send()
        .await?
        .error_for_status()
        .map_err(|e| anyhow!("admin run control call failed: {e}"))?
        .json()
        .await?;

    match raw {
        AdminRunResponse::Ok { status, body } => Ok(AdminRunOutput {
            status,
            content_type: Some("application/json".to_string()),
            body: serde_json::to_vec(&body)?,
        }),
        AdminRunResponse::UpstreamError { status, body } => Ok(AdminRunOutput {
            status,
            content_type: Some("application/json".to_string()),
            body: serde_json::to_vec(&body)?,
        }),
        AdminRunResponse::NotLoggedIn => {
            Err(anyhow!("control rejected token; run `fn0 login` again."))
        }
        AdminRunResponse::NotFound => Err(anyhow!("project '{project_id}' not found.")),
        AdminRunResponse::Forbidden => Err(anyhow!(
            "project '{project_id}' is not owned by the signed-in user."
        )),
        AdminRunResponse::NotDeployed => Err(anyhow!(
            "project '{project_id}' has no deployed version yet."
        )),
        AdminRunResponse::InternalError { reason } => {
            Err(anyhow!("control admin_run internal error: {reason}"))
        }
    }
}