flawless-utils 1.0.0-beta.2

Utility tools for integrating flawless workflows into Rust projects.
Documentation
use flawless::workflow::WorkflowTypeAlias;
use reqwest::{
    multipart::{Form, Part},
    Client,
};
use serde::{Deserialize, Serialize};

use crate::WorkflowRef;

/// Represents an instance of a flawless server.
#[derive(Debug, Clone, Deserialize)]
pub struct Server {
    pub(crate) url: String,
    pub(crate) auth_token: Option<String>,
}

impl Server {
    /// Creates a new server.
    pub fn new<A: ToString>(url: A, auth_token: Option<&str>) -> Self {
        Server { url: url.to_string(), auth_token: auth_token.map(|t| t.to_string()) }
    }

    /// Deploys a flawless module.
    pub async fn deploy(&self, module: &'static [u8]) -> Result<DeployedModule, String> {
        let deploy_url = format!("{}/api/module/deploy", self.url);

        let client = Client::new();
        let module = Part::stream(module).mime_str("application/wasm").unwrap();
        let form = Form::new().part("module", module);

        let mut request = client.post(deploy_url).multipart(form);

        if let Some(auth_token) = &self.auth_token {
            request = request.header("Authorization", &format!("Bearer {}", auth_token));
        }

        match request.send().await {
            Ok(response) => {
                let response = response.text().await.map_err(|_err| format!("Unable to read response"))?;
                let response: DeployResponse = serde_json::from_str(&response)
                    .map_err(|err| format!("Failed to parse flawless response as json. {err}"))?;
                match response {
                    DeployResponse::Ok(mut deployed_module) => {
                        deployed_module.server = Some(self.clone());
                        Ok(deployed_module)
                    }
                    DeployResponse::Error(err) => Err(err),
                }
            }
            Err(err) => Err(format!("Failed to send request to `{}`. {err}", self.url)),
        }
    }
}

#[derive(Debug, Deserialize)]
pub enum DeployResponse {
    #[serde(rename = "ok")]
    Ok(DeployedModule),
    #[serde(rename = "error")]
    Error(String),
}

#[derive(Debug, Deserialize)]
pub struct DeployedModule {
    pub(crate) server: Option<Server>,
    name: String,
    version: String,
}

impl DeployedModule {
    /// Start workflow using a `WorkflowTypeAlias`.
    ///
    /// The `#[workflow]` macro will automatically create a type alias for a workflow function. The type alias
    /// will have the same name and visibility as the workflow function.
    ///
    /// # Example
    ///
    /// ```rust,ignore
    /// let workflow = module.start<workflow_function>(arg).await.unwrap();
    /// ```
    pub async fn start<W>(&self, input: W::InputArg) -> Result<WorkflowRef, String>
    where
        W: WorkflowTypeAlias,
    {
        self.start_by_name(W::NAME, input).await
    }

    /// Start workflow by name.
    pub async fn start_by_name<T>(&self, name: &str, input: T) -> Result<WorkflowRef, String>
    where
        T: Serialize,
    {
        let start_url = format!("{}/api/workflow/start", self.server.as_ref().unwrap().url);
        let input = serde_json::to_string(&input).map_err(|err| err.to_string())?;
        let workflow_start = WorkflowStart {
            module: self.name.to_string(),
            version: self.version.to_string(),
            workflow: name.to_string(),
            input,
        };

        let client = Client::new();
        let mut request = client.post(start_url).json(&workflow_start);

        if let Some(auth_token) = &self.server.as_ref().unwrap().auth_token {
            request = request.header("Authorization", &format!("Bearer {}", auth_token));
        }

        match request.send().await {
            Ok(response) => {
                let response = response.text().await.map_err(|_err| format!("Unable to read response"))?;
                let response: WorkflowStartResponse = serde_json::from_str(&response)
                    .map_err(|err| format!("Failed to parse flawless response as json. {err}"))?;
                match response {
                    WorkflowStartResponse::Ok(mut workflow) => {
                        workflow.server = self.server.clone();
                        Ok(workflow)
                    }
                    WorkflowStartResponse::Error(err) => Err(err),
                }
            }
            Err(err) => {
                Err(format!("Failed to send request to `{}`. {err}", self.server.as_ref().unwrap().url))
            }
        }
    }
}

#[derive(Debug, Serialize)]
struct WorkflowStart<T> {
    module: String,
    version: String,
    workflow: String,
    input: T,
}

#[derive(Debug, Deserialize)]
enum WorkflowStartResponse {
    #[serde(rename = "ok")]
    Ok(WorkflowRef),
    #[serde(rename = "error")]
    Error(String),
}