Skip to main content

fn0_deploy/
admin.rs

1use crate::credentials;
2use anyhow::{Result, anyhow};
3use serde::{Deserialize, Serialize};
4
5pub struct AdminRunOutput {
6    pub status: u16,
7    pub content_type: Option<String>,
8    pub body: Vec<u8>,
9}
10
11#[derive(Serialize)]
12struct AdminRunInput<'a> {
13    project_id: &'a str,
14    task: &'a str,
15    input: serde_json::Value,
16}
17
18#[derive(Deserialize)]
19#[serde(tag = "t", rename_all_fields = "camelCase")]
20enum AdminRunResponse {
21    Ok {
22        status: u16,
23        #[serde(default)]
24        content_type: Option<String>,
25        #[serde(default)]
26        body: String,
27    },
28    NotLoggedIn,
29    NotFound,
30    Forbidden,
31    NotDeployed,
32    UpstreamError {
33        status: u16,
34        #[serde(default)]
35        content_type: Option<String>,
36        #[serde(default)]
37        body: String,
38    },
39    InternalError {
40        reason: String,
41    },
42}
43
44pub async fn admin_run(
45    project_id: &str,
46    task: &str,
47    input_body: Vec<u8>,
48    timeout_secs: u64,
49) -> Result<AdminRunOutput> {
50    let creds = credentials::require()?;
51
52    let input: serde_json::Value = if input_body.is_empty() {
53        serde_json::Value::Null
54    } else {
55        serde_json::from_slice(&input_body)
56            .map_err(|e| anyhow!("admin run input is not valid JSON: {e}"))?
57    };
58
59    let url = format!(
60        "{}/__forte_action/admin_run",
61        creds.control_url.trim_end_matches('/')
62    );
63
64    let client = reqwest::Client::builder()
65        .timeout(std::time::Duration::from_secs(timeout_secs))
66        .build()?;
67
68    let raw: AdminRunResponse = client
69        .post(&url)
70        .bearer_auth(&creds.token)
71        .json(&AdminRunInput {
72            project_id,
73            task,
74            input,
75        })
76        .send()
77        .await?
78        .error_for_status()
79        .map_err(|e| anyhow!("admin run control call failed: {e}"))?
80        .json()
81        .await?;
82
83    match raw {
84        AdminRunResponse::Ok {
85            status,
86            content_type,
87            body,
88        } => Ok(AdminRunOutput {
89            status,
90            content_type,
91            body: body.into_bytes(),
92        }),
93        AdminRunResponse::UpstreamError {
94            status,
95            content_type,
96            body,
97        } => Ok(AdminRunOutput {
98            status,
99            content_type,
100            body: body.into_bytes(),
101        }),
102        AdminRunResponse::NotLoggedIn => {
103            Err(anyhow!("control rejected token; run `fn0 login` again."))
104        }
105        AdminRunResponse::NotFound => Err(anyhow!("project '{project_id}' not found.")),
106        AdminRunResponse::Forbidden => Err(anyhow!(
107            "project '{project_id}' is not owned by the signed-in user."
108        )),
109        AdminRunResponse::NotDeployed => Err(anyhow!(
110            "project '{project_id}' has no deployed version yet."
111        )),
112        AdminRunResponse::InternalError { reason } => {
113            Err(anyhow!("control admin_run internal error: {reason}"))
114        }
115    }
116}