//! Pack DTOs for API requests and responses use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_json::Value as JsonValue; use utoipa::ToSchema; use validator::Validate; /// Request DTO for creating a new pack #[derive(Debug, Clone, Deserialize, Validate, ToSchema)] pub struct CreatePackRequest { /// Unique reference identifier (e.g., "core", "aws", "slack") #[validate(length(min = 1, max = 255))] #[schema(example = "slack")] pub r#ref: String, /// Human-readable label #[validate(length(min = 1, max = 255))] #[schema(example = "Slack Integration")] pub label: String, /// Pack description #[schema(example = "Integration with Slack for messaging and notifications")] pub description: Option, /// Pack version (semver format recommended) #[validate(length(min = 1, max = 50))] #[schema(example = "1.0.0")] pub version: String, /// Configuration schema (flat format with inline required/secret per parameter) #[serde(default = "default_empty_object")] #[schema(value_type = Object, example = json!({"api_token": {"type": "string", "description": "API authentication key", "required": true, "secret": true}}))] pub conf_schema: JsonValue, /// Pack configuration values #[serde(default = "default_empty_object")] #[schema(value_type = Object, example = json!({"api_token": "xoxb-..."}))] pub config: JsonValue, /// Pack metadata #[serde(default = "default_empty_object")] #[schema(value_type = Object, example = json!({"author": "Attune Team"}))] pub meta: JsonValue, /// Tags for categorization #[serde(default)] #[schema(example = json!(["messaging", "collaboration"]))] pub tags: Vec, /// Runtime dependencies (e.g., shell, python, nodejs) #[serde(default)] #[schema(example = json!(["shell", "python"]))] pub runtime_deps: Vec, /// Pack dependencies (refs of required packs) #[serde(default)] #[schema(example = json!(["core"]))] pub dependencies: Vec, /// Whether this is a standard/built-in pack #[serde(default)] #[schema(example = false)] pub is_standard: bool, } /// Request DTO for registering a pack from local filesystem #[derive(Debug, Clone, Deserialize, Validate, ToSchema)] pub struct RegisterPackRequest { /// Local filesystem path to the pack directory #[validate(length(min = 1))] #[schema(example = "/path/to/packs/mypack")] pub path: String, /// Skip running pack tests during registration #[serde(default)] #[schema(example = false)] pub skip_tests: bool, /// Force registration even if tests fail #[serde(default)] #[schema(example = false)] pub force: bool, } /// Request DTO for installing a pack from remote source #[derive(Debug, Clone, Deserialize, Validate, ToSchema)] pub struct InstallPackRequest { /// Repository URL or source location #[validate(length(min = 1))] #[schema(example = "https://github.com/attune/pack-slack.git")] pub source: String, /// Git branch, tag, or commit reference #[schema(example = "main")] pub ref_spec: Option, /// Skip running pack tests during installation #[serde(default)] #[schema(example = false)] pub skip_tests: bool, /// Skip dependency validation (not recommended) #[serde(default)] #[schema(example = false)] pub skip_deps: bool, } /// Response for pack install/register operations with test results #[derive(Debug, Clone, Serialize, ToSchema)] pub struct PackInstallResponse { /// The installed/registered pack pub pack: PackResponse, /// Test execution result (if tests were run) pub test_result: Option, /// Whether tests were skipped pub tests_skipped: bool, } /// Request DTO for updating a pack #[derive(Debug, Clone, Deserialize, Validate, ToSchema)] pub struct UpdatePackRequest { /// Human-readable label #[validate(length(min = 1, max = 255))] #[schema(example = "Slack Integration v2")] pub label: Option, /// Pack description #[schema(example = "Enhanced Slack integration with new features")] pub description: Option, /// Pack version #[validate(length(min = 1, max = 50))] #[schema(example = "2.0.0")] pub version: Option, /// Configuration schema #[schema(value_type = Object, nullable = true)] pub conf_schema: Option, /// Pack configuration values #[schema(value_type = Object, nullable = true)] pub config: Option, /// Pack metadata #[schema(value_type = Object, nullable = true)] pub meta: Option, /// Tags for categorization #[schema(example = json!(["messaging", "collaboration", "webhooks"]))] pub tags: Option>, /// Runtime dependencies (e.g., shell, python, nodejs) #[schema(example = json!(["shell", "python"]))] pub runtime_deps: Option>, /// Pack dependencies (refs of required packs) #[schema(example = json!(["core", "http"]))] pub dependencies: Option>, /// Whether this is a standard pack #[schema(example = false)] pub is_standard: Option, } #[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] #[serde(tag = "op", content = "value", rename_all = "snake_case")] pub enum PackDescriptionPatch { Set(String), Clear, } /// Response DTO for pack information #[derive(Debug, Clone, Serialize, ToSchema)] pub struct PackResponse { /// Pack ID #[schema(example = 1)] pub id: i64, /// Unique reference identifier #[schema(example = "slack")] pub r#ref: String, /// Human-readable label #[schema(example = "Slack Integration")] pub label: String, /// Pack description #[schema(example = "Integration with Slack for messaging and notifications")] pub description: Option, /// Pack version #[schema(example = "1.0.0")] pub version: String, /// Configuration schema #[schema(value_type = Object)] pub conf_schema: JsonValue, /// Pack configuration #[schema(value_type = Object)] pub config: JsonValue, /// Pack metadata #[schema(value_type = Object)] pub meta: JsonValue, /// Tags #[schema(example = json!(["messaging", "collaboration"]))] pub tags: Vec, /// Runtime dependencies (e.g., shell, python, nodejs) #[schema(example = json!(["shell", "python"]))] pub runtime_deps: Vec, /// Pack dependencies (refs of required packs) #[schema(example = json!(["core"]))] pub dependencies: Vec, /// Is standard pack #[schema(example = false)] pub is_standard: bool, /// Creation timestamp #[schema(example = "2024-01-13T10:30:00Z")] pub created: DateTime, /// Last update timestamp #[schema(example = "2024-01-13T10:30:00Z")] pub updated: DateTime, } /// Simplified pack response (for list endpoints) #[derive(Debug, Clone, Serialize, ToSchema)] pub struct PackSummary { /// Pack ID #[schema(example = 1)] pub id: i64, /// Unique reference identifier #[schema(example = "slack")] pub r#ref: String, /// Human-readable label #[schema(example = "Slack Integration")] pub label: String, /// Pack description #[schema(example = "Integration with Slack for messaging and notifications")] pub description: Option, /// Pack version #[schema(example = "1.0.0")] pub version: String, /// Tags #[schema(example = json!(["messaging", "collaboration"]))] pub tags: Vec, /// Is standard pack #[schema(example = false)] pub is_standard: bool, /// Creation timestamp #[schema(example = "2024-01-13T10:30:00Z")] pub created: DateTime, /// Last update timestamp #[schema(example = "2024-01-13T10:30:00Z")] pub updated: DateTime, } /// Convert from Pack model to PackResponse impl From for PackResponse { fn from(pack: attune_common::models::Pack) -> Self { Self { id: pack.id, r#ref: pack.r#ref, label: pack.label, description: pack.description, version: pack.version, conf_schema: pack.conf_schema, config: pack.config, meta: pack.meta, tags: pack.tags, runtime_deps: pack.runtime_deps, dependencies: pack.dependencies, is_standard: pack.is_standard, created: pack.created, updated: pack.updated, } } } /// Convert from Pack model to PackSummary impl From for PackSummary { fn from(pack: attune_common::models::Pack) -> Self { Self { id: pack.id, r#ref: pack.r#ref, label: pack.label, description: pack.description, version: pack.version, tags: pack.tags, is_standard: pack.is_standard, created: pack.created, updated: pack.updated, } } } /// Response for pack workflow sync operation #[derive(Debug, Clone, Serialize, ToSchema)] pub struct PackWorkflowSyncResponse { /// Pack reference pub pack_ref: String, /// Number of workflows loaded from filesystem pub loaded_count: usize, /// Number of workflows registered/updated in database pub registered_count: usize, /// Individual workflow registration results pub workflows: Vec, /// Any errors encountered during sync pub errors: Vec, } /// Individual workflow sync result #[derive(Debug, Clone, Serialize, ToSchema)] pub struct WorkflowSyncResult { /// Workflow reference name pub ref_name: String, /// Whether the workflow was created (false = updated) pub created: bool, /// Workflow definition ID pub workflow_def_id: i64, /// Any warnings during registration pub warnings: Vec, } /// Response for pack workflow validation operation #[derive(Debug, Clone, Serialize, ToSchema)] pub struct PackWorkflowValidationResponse { /// Pack reference pub pack_ref: String, /// Number of workflows validated pub validated_count: usize, /// Number of workflows with errors pub error_count: usize, /// Validation errors by workflow reference pub errors: std::collections::HashMap>, } /// Request DTO for downloading packs #[derive(Debug, Clone, Deserialize, Validate, ToSchema)] pub struct DownloadPacksRequest { /// List of pack sources (git URLs, HTTP URLs, or registry refs) #[validate(length(min = 1))] #[schema(example = json!(["https://github.com/attune/pack-slack.git", "aws@2.0.0"]))] pub packs: Vec, /// Destination directory for downloaded packs #[validate(length(min = 1))] #[schema(example = "/tmp/attune-packs")] pub destination_dir: String, /// Pack registry URL for resolving references #[schema(example = "https://registry.attune.io/index.json")] pub registry_url: Option, /// Git reference (branch, tag, or commit) for git sources #[schema(example = "v1.0.0")] pub ref_spec: Option, /// Download timeout in seconds #[serde(default = "default_download_timeout")] #[schema(example = 300)] pub timeout: u64, /// Verify SSL certificates #[serde(default = "default_true")] #[schema(example = true)] pub verify_ssl: bool, } /// Response DTO for download packs operation #[derive(Debug, Clone, Serialize, ToSchema)] pub struct DownloadPacksResponse { /// Successfully downloaded packs pub downloaded_packs: Vec, /// Failed pack downloads pub failed_packs: Vec, /// Total number of packs requested pub total_count: usize, /// Number of successful downloads pub success_count: usize, /// Number of failed downloads pub failure_count: usize, } /// Information about a downloaded pack #[derive(Debug, Clone, Serialize, ToSchema)] pub struct DownloadedPack { /// Original source pub source: String, /// Source type (git, http, registry) pub source_type: String, /// Local path to downloaded pack pub pack_path: String, /// Pack reference from pack.yaml pub pack_ref: String, /// Pack version from pack.yaml pub pack_version: String, /// Git commit hash (for git sources) pub git_commit: Option, /// Directory checksum pub checksum: Option, } /// Information about a failed pack download #[derive(Debug, Clone, Serialize, ToSchema)] pub struct FailedPack { /// Pack source that failed pub source: String, /// Error message pub error: String, } /// Request DTO for getting pack dependencies #[derive(Debug, Clone, Deserialize, Validate, ToSchema)] pub struct GetPackDependenciesRequest { /// List of pack directory paths to analyze #[validate(length(min = 1))] #[schema(example = json!(["/tmp/attune-packs/slack"]))] pub pack_paths: Vec, /// Skip pack.yaml validation #[serde(default)] #[schema(example = false)] pub skip_validation: bool, } /// Response DTO for get pack dependencies operation #[derive(Debug, Clone, Serialize, ToSchema)] pub struct GetPackDependenciesResponse { /// All dependencies found pub dependencies: Vec, /// Runtime requirements by pack pub runtime_requirements: std::collections::HashMap, /// Dependencies not yet installed pub missing_dependencies: Vec, /// Packs that were analyzed pub analyzed_packs: Vec, /// Errors encountered during analysis pub errors: Vec, } /// Pack dependency information #[derive(Debug, Clone, Serialize, ToSchema)] pub struct PackDependency { /// Pack reference pub pack_ref: String, /// Version specification pub version_spec: String, /// Pack that requires this dependency pub required_by: String, /// Whether dependency is already installed pub already_installed: bool, } /// Runtime requirements for a pack #[derive(Debug, Clone, Serialize, ToSchema)] pub struct RuntimeRequirements { /// Pack reference pub pack_ref: String, /// Python requirements pub python: Option, /// Node.js requirements pub nodejs: Option, } /// Python runtime requirements #[derive(Debug, Clone, Serialize, ToSchema)] pub struct PythonRequirements { /// Python version requirement pub version: Option, /// Path to requirements.txt pub requirements_file: Option, } /// Node.js runtime requirements #[derive(Debug, Clone, Serialize, ToSchema)] pub struct NodeJsRequirements { /// Node.js version requirement pub version: Option, /// Path to package.json pub package_file: Option, } /// Information about an analyzed pack #[derive(Debug, Clone, Serialize, ToSchema)] pub struct AnalyzedPack { /// Pack reference pub pack_ref: String, /// Pack directory path pub pack_path: String, /// Whether pack has dependencies pub has_dependencies: bool, /// Number of dependencies pub dependency_count: usize, } /// Dependency analysis error #[derive(Debug, Clone, Serialize, ToSchema)] pub struct DependencyError { /// Pack path where error occurred pub pack_path: String, /// Error message pub error: String, } /// Request DTO for building pack environments #[derive(Debug, Clone, Deserialize, Validate, ToSchema)] pub struct BuildPackEnvsRequest { /// List of pack directory paths #[validate(length(min = 1))] #[schema(example = json!(["/tmp/attune-packs/slack"]))] pub pack_paths: Vec, /// Base directory for permanent pack storage #[schema(example = "/opt/attune/packs")] pub packs_base_dir: Option, /// Python version to use #[serde(default = "default_python_version")] #[schema(example = "3.11")] pub python_version: String, /// Node.js version to use #[serde(default = "default_nodejs_version")] #[schema(example = "20")] pub nodejs_version: String, /// Skip building Python environments #[serde(default)] #[schema(example = false)] pub skip_python: bool, /// Skip building Node.js environments #[serde(default)] #[schema(example = false)] pub skip_nodejs: bool, /// Force rebuild of existing environments #[serde(default)] #[schema(example = false)] pub force_rebuild: bool, /// Timeout in seconds for building each environment #[serde(default = "default_build_timeout")] #[schema(example = 600)] pub timeout: u64, } /// Response DTO for build pack environments operation #[derive(Debug, Clone, Serialize, ToSchema)] pub struct BuildPackEnvsResponse { /// Successfully built environments pub built_environments: Vec, /// Failed environment builds pub failed_environments: Vec, /// Summary statistics pub summary: BuildSummary, } /// Information about a built environment #[derive(Debug, Clone, Serialize, ToSchema)] pub struct BuiltEnvironment { /// Pack reference pub pack_ref: String, /// Pack directory path pub pack_path: String, /// Built environments pub environments: Environments, /// Build duration in milliseconds pub duration_ms: u64, } /// Environment details #[derive(Debug, Clone, Serialize, ToSchema)] pub struct Environments { /// Python environment pub python: Option, /// Node.js environment pub nodejs: Option, } /// Python environment details #[derive(Debug, Clone, Serialize, ToSchema)] pub struct PythonEnvironment { /// Path to virtualenv pub virtualenv_path: String, /// Whether requirements were installed pub requirements_installed: bool, /// Number of packages installed pub package_count: usize, /// Python version used pub python_version: String, } /// Node.js environment details #[derive(Debug, Clone, Serialize, ToSchema)] pub struct NodeJsEnvironment { /// Path to node_modules pub node_modules_path: String, /// Whether dependencies were installed pub dependencies_installed: bool, /// Number of packages installed pub package_count: usize, /// Node.js version used pub nodejs_version: String, } /// Failed environment build #[derive(Debug, Clone, Serialize, ToSchema)] pub struct FailedEnvironment { /// Pack reference pub pack_ref: String, /// Pack directory path pub pack_path: String, /// Runtime that failed pub runtime: String, /// Error message pub error: String, } /// Build summary statistics #[derive(Debug, Clone, Serialize, ToSchema)] pub struct BuildSummary { /// Total packs processed pub total_packs: usize, /// Successfully built pub success_count: usize, /// Failed builds pub failure_count: usize, /// Python environments built pub python_envs_built: usize, /// Node.js environments built pub nodejs_envs_built: usize, /// Total duration in milliseconds pub total_duration_ms: u64, } /// Request DTO for registering multiple packs #[derive(Debug, Clone, Deserialize, Validate, ToSchema)] pub struct RegisterPacksRequest { /// List of pack directory paths to register #[validate(length(min = 1))] #[schema(example = json!(["/tmp/attune-packs/slack"]))] pub pack_paths: Vec, /// Base directory for permanent storage #[schema(example = "/opt/attune/packs")] pub packs_base_dir: Option, /// Skip schema validation #[serde(default)] #[schema(example = false)] pub skip_validation: bool, /// Skip running pack tests #[serde(default)] #[schema(example = false)] pub skip_tests: bool, /// Force registration (replace if exists) #[serde(default)] #[schema(example = false)] pub force: bool, } /// Response DTO for register packs operation #[derive(Debug, Clone, Serialize, ToSchema)] pub struct RegisterPacksResponse { /// Successfully registered packs pub registered_packs: Vec, /// Failed pack registrations pub failed_packs: Vec, /// Summary statistics pub summary: RegistrationSummary, } /// Information about a registered pack #[derive(Debug, Clone, Serialize, ToSchema)] pub struct RegisteredPack { /// Pack reference pub pack_ref: String, /// Pack database ID pub pack_id: i64, /// Pack version pub pack_version: String, /// Permanent storage path pub storage_path: String, /// Registered components by type pub components_registered: ComponentCounts, /// Test results pub test_result: Option, /// Validation results pub validation_results: ValidationResults, } /// Component counts #[derive(Debug, Clone, Serialize, ToSchema)] pub struct ComponentCounts { /// Number of actions pub actions: usize, /// Number of sensors pub sensors: usize, /// Number of triggers pub triggers: usize, /// Number of rules pub rules: usize, /// Number of workflows pub workflows: usize, /// Number of policies pub policies: usize, } /// Test result #[derive(Debug, Clone, Serialize, ToSchema)] pub struct TestResult { /// Test status pub status: String, /// Total number of tests pub total_tests: usize, /// Number passed pub passed: usize, /// Number failed pub failed: usize, } /// Validation results #[derive(Debug, Clone, Serialize, ToSchema)] pub struct ValidationResults { /// Whether validation passed pub valid: bool, /// Validation errors pub errors: Vec, } /// Failed pack registration #[derive(Debug, Clone, Serialize, ToSchema)] pub struct FailedPackRegistration { /// Pack reference pub pack_ref: String, /// Pack path pub pack_path: String, /// Error message pub error: String, /// Error stage pub error_stage: String, } /// Registration summary #[derive(Debug, Clone, Serialize, ToSchema)] pub struct RegistrationSummary { /// Total packs processed pub total_packs: usize, /// Successfully registered pub success_count: usize, /// Failed registrations pub failure_count: usize, /// Total components registered pub total_components: usize, /// Duration in milliseconds pub duration_ms: u64, } fn default_empty_object() -> JsonValue { serde_json::json!({}) } fn default_download_timeout() -> u64 { 300 } fn default_build_timeout() -> u64 { 600 } fn default_python_version() -> String { "3.11".to_string() } fn default_nodejs_version() -> String { "20".to_string() } fn default_true() -> bool { true } #[cfg(test)] mod tests { use super::*; #[test] fn test_create_pack_request_defaults() { let json = r#"{ "ref": "test-pack", "label": "Test Pack", "version": "1.0.0" }"#; let req: CreatePackRequest = serde_json::from_str(json).unwrap(); assert_eq!(req.r#ref, "test-pack"); assert_eq!(req.label, "Test Pack"); assert_eq!(req.version, "1.0.0"); assert!(req.tags.is_empty()); assert!(req.runtime_deps.is_empty()); assert!(req.dependencies.is_empty()); assert!(!req.is_standard); } #[test] fn test_create_pack_request_validation() { let req = CreatePackRequest { r#ref: "".to_string(), // Invalid: empty label: "Test".to_string(), version: "1.0.0".to_string(), description: None, conf_schema: default_empty_object(), config: default_empty_object(), meta: default_empty_object(), tags: vec![], runtime_deps: vec![], dependencies: vec![], is_standard: false, }; assert!(req.validate().is_err()); } }