flawless_utils/
server.rs

1use flawless::workflow::WorkflowTypeAlias;
2use reqwest::{
3    multipart::{Form, Part},
4    Client,
5};
6use serde::{Deserialize, Serialize};
7
8use crate::WorkflowRef;
9
10/// Represents an instance of a flawless server.
11#[derive(Debug, Clone, Deserialize)]
12pub struct Server {
13    pub(crate) url: String,
14    pub(crate) auth_token: Option<String>,
15}
16
17impl Server {
18    /// Creates a new server.
19    pub fn new<A: ToString>(url: A, auth_token: Option<&str>) -> Self {
20        Server { url: url.to_string(), auth_token: auth_token.map(|t| t.to_string()) }
21    }
22
23    /// Deploys a flawless module.
24    pub async fn deploy(&self, module: &'static [u8]) -> Result<DeployedModule, String> {
25        let deploy_url = format!("{}/api/module/deploy", self.url);
26
27        let client = Client::new();
28        let module = Part::stream(module).mime_str("application/wasm").unwrap();
29        let form = Form::new().part("module", module);
30
31        let mut request = client.post(deploy_url).multipart(form);
32
33        if let Some(auth_token) = &self.auth_token {
34            request = request.header("Authorization", &format!("Bearer {}", auth_token));
35        }
36
37        match request.send().await {
38            Ok(response) => {
39                let response = response.text().await.map_err(|_err| "Unable to read response".to_string())?;
40                let response: DeployResponse = serde_json::from_str(&response)
41                    .map_err(|err| format!("Failed to parse flawless response as json. {err}"))?;
42                match response {
43                    DeployResponse::Ok(mut deployed_module) => {
44                        deployed_module.server = Some(self.clone());
45                        Ok(deployed_module)
46                    }
47                    DeployResponse::Error(err) => Err(err),
48                }
49            }
50            Err(err) => Err(format!("Failed to send request to `{}`. {err}", self.url)),
51        }
52    }
53}
54
55#[derive(Debug, Deserialize)]
56pub enum DeployResponse {
57    #[serde(rename = "ok")]
58    Ok(DeployedModule),
59    #[serde(rename = "error")]
60    Error(String),
61}
62
63#[derive(Debug, Deserialize)]
64pub struct DeployedModule {
65    pub(crate) server: Option<Server>,
66    name: String,
67    version: String,
68}
69
70impl DeployedModule {
71    /// Start workflow using a `WorkflowTypeAlias`.
72    ///
73    /// The `#[workflow]` macro will automatically create a type alias for a workflow function. The type alias
74    /// will have the same name and visibility as the workflow function.
75    ///
76    /// # Example
77    ///
78    /// ```rust,ignore
79    /// let workflow = module.start<workflow_function>(arg).await.unwrap();
80    /// ```
81    pub async fn start<W>(&self, input: W::InputArg) -> Result<WorkflowRef, String>
82    where
83        W: WorkflowTypeAlias,
84    {
85        self.start_by_name(W::NAME, input).await
86    }
87
88    /// Start workflow by name.
89    pub async fn start_by_name<T>(&self, name: &str, input: T) -> Result<WorkflowRef, String>
90    where
91        T: Serialize,
92    {
93        let start_url = format!("{}/api/workflow/start", self.server.as_ref().unwrap().url);
94        let input = serde_json::to_string(&input).map_err(|err| err.to_string())?;
95        let workflow_start = WorkflowStart {
96            module: self.name.to_string(),
97            version: self.version.to_string(),
98            workflow: name.to_string(),
99            input,
100        };
101
102        let client = Client::new();
103        let mut request = client.post(start_url).json(&workflow_start);
104
105        if let Some(auth_token) = &self.server.as_ref().unwrap().auth_token {
106            request = request.header("Authorization", &format!("Bearer {}", auth_token));
107        }
108
109        match request.send().await {
110            Ok(response) => {
111                let response = response.text().await.map_err(|_err| "Unable to read response".to_string())?;
112                let response: WorkflowStartResponse = serde_json::from_str(&response)
113                    .map_err(|err| format!("Failed to parse flawless response as json. {err}"))?;
114                match response {
115                    WorkflowStartResponse::Ok(mut workflow) => {
116                        workflow.server = self.server.clone();
117                        Ok(workflow)
118                    }
119                    WorkflowStartResponse::Error(err) => Err(err),
120                }
121            }
122            Err(err) => {
123                Err(format!("Failed to send request to `{}`. {err}", self.server.as_ref().unwrap().url))
124            }
125        }
126    }
127}
128
129#[derive(Debug, Serialize)]
130struct WorkflowStart<T> {
131    module: String,
132    version: String,
133    workflow: String,
134    input: T,
135}
136
137#[derive(Debug, Deserialize)]
138enum WorkflowStartResponse {
139    #[serde(rename = "ok")]
140    Ok(WorkflowRef),
141    #[serde(rename = "error")]
142    Error(String),
143}