Files
attune/crates/api/src/dto/workflow.rs
David Culbreth b5d6bb2243
Some checks failed
CI / Rustfmt (push) Failing after 25s
CI / Clippy (push) Failing after 2m3s
CI / Cargo Audit & Deny (push) Successful in 33s
CI / Web Blocking Checks (push) Failing after 26s
CI / Security Blocking Checks (push) Successful in 8s
CI / Security Advisory Checks (push) Has been cancelled
CI / Web Advisory Checks (push) Has been cancelled
CI / Tests (push) Has been cancelled
more polish on workflows
2026-03-11 11:21:28 -05:00

349 lines
11 KiB
Rust

//! Workflow DTOs for API requests and responses
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use utoipa::{IntoParams, ToSchema};
use validator::Validate;
/// Request DTO for saving a workflow file to disk and syncing to DB
#[derive(Debug, Clone, Deserialize, Validate, ToSchema)]
pub struct SaveWorkflowFileRequest {
/// Workflow name (becomes filename: {name}.workflow.yaml)
#[validate(length(min = 1, max = 255))]
#[schema(example = "deploy_app")]
pub name: String,
/// Human-readable label
#[validate(length(min = 1, max = 255))]
#[schema(example = "Deploy Application")]
pub label: String,
/// Workflow description
#[schema(example = "Deploys an application to the target environment")]
pub description: Option<String>,
/// Workflow version (semantic versioning recommended)
#[validate(length(min = 1, max = 50))]
#[schema(example = "1.0.0")]
pub version: String,
/// Pack reference this workflow belongs to
#[validate(length(min = 1, max = 255))]
#[schema(example = "core")]
pub pack_ref: String,
/// The full workflow definition as JSON (will be serialized to YAML on disk)
#[schema(value_type = Object)]
pub definition: JsonValue,
/// Parameter schema (flat format with inline required/secret)
#[schema(value_type = Object, nullable = true)]
pub param_schema: Option<JsonValue>,
/// Output schema (flat format)
#[schema(value_type = Object, nullable = true)]
pub out_schema: Option<JsonValue>,
/// Tags for categorization
#[schema(example = json!(["deployment", "automation"]))]
pub tags: Option<Vec<String>>,
}
/// Request DTO for creating a new workflow
#[derive(Debug, Clone, Deserialize, Validate, ToSchema)]
pub struct CreateWorkflowRequest {
/// Unique reference identifier (e.g., "core.notify_on_failure", "slack.incident_workflow")
#[validate(length(min = 1, max = 255))]
#[schema(example = "slack.incident_workflow")]
pub r#ref: String,
/// Pack reference this workflow belongs to
#[validate(length(min = 1, max = 255))]
#[schema(example = "slack")]
pub pack_ref: String,
/// Human-readable label
#[validate(length(min = 1, max = 255))]
#[schema(example = "Incident Response Workflow")]
pub label: String,
/// Workflow description
#[schema(example = "Automated incident response workflow with notifications and approvals")]
pub description: Option<String>,
/// Workflow version (semantic versioning recommended)
#[validate(length(min = 1, max = 50))]
#[schema(example = "1.0.0")]
pub version: String,
/// Parameter schema (StackStorm-style) defining expected inputs with inline required/secret
#[schema(value_type = Object, example = json!({"severity": {"type": "string", "description": "Incident severity", "required": true}, "channel": {"type": "string", "description": "Notification channel"}}))]
pub param_schema: Option<JsonValue>,
/// Output schema (flat format) defining expected outputs with inline required/secret
#[schema(value_type = Object, example = json!({"incident_id": {"type": "string", "description": "Unique incident identifier", "required": true}}))]
pub out_schema: Option<JsonValue>,
/// Workflow definition (complete workflow YAML structure as JSON)
#[schema(value_type = Object)]
pub definition: JsonValue,
/// Tags for categorization and search
#[schema(example = json!(["incident", "slack", "approval"]))]
pub tags: Option<Vec<String>>,
}
/// Request DTO for updating a workflow
#[derive(Debug, Clone, Deserialize, Validate, ToSchema)]
pub struct UpdateWorkflowRequest {
/// Human-readable label
#[validate(length(min = 1, max = 255))]
#[schema(example = "Incident Response Workflow (Updated)")]
pub label: Option<String>,
/// Workflow description
#[schema(example = "Enhanced incident response workflow with additional automation")]
pub description: Option<String>,
/// Workflow version
#[validate(length(min = 1, max = 50))]
#[schema(example = "1.1.0")]
pub version: Option<String>,
/// Parameter schema (StackStorm-style with inline required/secret)
#[schema(value_type = Object, nullable = true)]
pub param_schema: Option<JsonValue>,
/// Output schema
#[schema(value_type = Object, nullable = true)]
pub out_schema: Option<JsonValue>,
/// Workflow definition
#[schema(value_type = Object, nullable = true)]
pub definition: Option<JsonValue>,
/// Tags
#[schema(example = json!(["incident", "slack", "approval", "automation"]))]
pub tags: Option<Vec<String>>,
}
/// Response DTO for workflow information
#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct WorkflowResponse {
/// Workflow ID
#[schema(example = 1)]
pub id: i64,
/// Unique reference identifier
#[schema(example = "slack.incident_workflow")]
pub r#ref: String,
/// Pack ID
#[schema(example = 1)]
pub pack: i64,
/// Pack reference
#[schema(example = "slack")]
pub pack_ref: String,
/// Human-readable label
#[schema(example = "Incident Response Workflow")]
pub label: String,
/// Workflow description
#[schema(example = "Automated incident response workflow with notifications and approvals")]
pub description: Option<String>,
/// Workflow version
#[schema(example = "1.0.0")]
pub version: String,
/// Parameter schema (StackStorm-style with inline required/secret)
#[schema(value_type = Object, nullable = true)]
pub param_schema: Option<JsonValue>,
/// Output schema
#[schema(value_type = Object, nullable = true)]
pub out_schema: Option<JsonValue>,
/// Workflow definition
#[schema(value_type = Object)]
pub definition: JsonValue,
/// Tags
#[schema(example = json!(["incident", "slack", "approval"]))]
pub tags: Vec<String>,
/// Creation timestamp
#[schema(example = "2024-01-13T10:30:00Z")]
pub created: DateTime<Utc>,
/// Last update timestamp
#[schema(example = "2024-01-13T10:30:00Z")]
pub updated: DateTime<Utc>,
}
/// Simplified workflow response (for list endpoints)
#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct WorkflowSummary {
/// Workflow ID
#[schema(example = 1)]
pub id: i64,
/// Unique reference identifier
#[schema(example = "slack.incident_workflow")]
pub r#ref: String,
/// Pack reference
#[schema(example = "slack")]
pub pack_ref: String,
/// Human-readable label
#[schema(example = "Incident Response Workflow")]
pub label: String,
/// Workflow description
#[schema(example = "Automated incident response workflow with notifications and approvals")]
pub description: Option<String>,
/// Workflow version
#[schema(example = "1.0.0")]
pub version: String,
/// Tags
#[schema(example = json!(["incident", "slack", "approval"]))]
pub tags: Vec<String>,
/// Creation timestamp
#[schema(example = "2024-01-13T10:30:00Z")]
pub created: DateTime<Utc>,
/// Last update timestamp
#[schema(example = "2024-01-13T10:30:00Z")]
pub updated: DateTime<Utc>,
}
/// Convert from WorkflowDefinition model to WorkflowResponse
impl From<attune_common::models::workflow::WorkflowDefinition> for WorkflowResponse {
fn from(workflow: attune_common::models::workflow::WorkflowDefinition) -> Self {
Self {
id: workflow.id,
r#ref: workflow.r#ref,
pack: workflow.pack,
pack_ref: workflow.pack_ref,
label: workflow.label,
description: workflow.description,
version: workflow.version,
param_schema: workflow.param_schema,
out_schema: workflow.out_schema,
definition: workflow.definition,
tags: workflow.tags,
created: workflow.created,
updated: workflow.updated,
}
}
}
/// Convert from WorkflowDefinition model to WorkflowSummary
impl From<attune_common::models::workflow::WorkflowDefinition> for WorkflowSummary {
fn from(workflow: attune_common::models::workflow::WorkflowDefinition) -> Self {
Self {
id: workflow.id,
r#ref: workflow.r#ref,
pack_ref: workflow.pack_ref,
label: workflow.label,
description: workflow.description,
version: workflow.version,
tags: workflow.tags,
created: workflow.created,
updated: workflow.updated,
}
}
}
/// Query parameters for workflow search and filtering
#[derive(Debug, Clone, Deserialize, Validate, IntoParams)]
pub struct WorkflowSearchParams {
/// Filter by tag(s) - comma-separated list
#[param(example = "incident,approval")]
pub tags: Option<String>,
/// Search term for label/description (case-insensitive)
#[param(example = "incident")]
pub search: Option<String>,
/// Filter by pack reference
#[param(example = "core")]
pub pack_ref: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_workflow_request_validation() {
let req = CreateWorkflowRequest {
r#ref: "".to_string(), // Invalid: empty
pack_ref: "test-pack".to_string(),
label: "Test Workflow".to_string(),
description: Some("Test description".to_string()),
version: "1.0.0".to_string(),
param_schema: None,
out_schema: None,
definition: serde_json::json!({"tasks": []}),
tags: None,
};
assert!(req.validate().is_err());
}
#[test]
fn test_create_workflow_request_valid() {
let req = CreateWorkflowRequest {
r#ref: "test.workflow".to_string(),
pack_ref: "test-pack".to_string(),
label: "Test Workflow".to_string(),
description: Some("Test description".to_string()),
version: "1.0.0".to_string(),
param_schema: None,
out_schema: None,
definition: serde_json::json!({"tasks": []}),
tags: Some(vec!["test".to_string()]),
};
assert!(req.validate().is_ok());
}
#[test]
fn test_update_workflow_request_all_none() {
let req = UpdateWorkflowRequest {
label: None,
description: None,
version: None,
param_schema: None,
out_schema: None,
definition: None,
tags: None,
};
// Should be valid even with all None values
assert!(req.validate().is_ok());
}
#[test]
fn test_workflow_search_params() {
let params = WorkflowSearchParams {
tags: Some("incident,approval".to_string()),
search: Some("response".to_string()),
pack_ref: Some("core".to_string()),
};
assert!(params.validate().is_ok());
}
}