atento_core/lib.rs
1#![warn(
2    clippy::all,
3    clippy::pedantic,
4    clippy::unwrap_used,
5    clippy::expect_used
6)]
7#![allow(
8    clippy::missing_errors_doc,
9    clippy::missing_panics_doc,
10    clippy::module_name_repetitions
11)]
12
13//! # Atento Core - Chain Execution Engine
14//!
15//! Atento Core is a powerful Rust library for defining and executing sequential chained scripts
16//! with multi-interpreter support, robust error handling, and advanced variable passing capabilities.
17//!
18//! ## Key Features
19//!
20//! - **Multi-Interpreter Support**: Execute scripts in Bash, Batch, `PowerShell`, Pwsh, and Python
21//! - **Sequential Execution**: Guaranteed step order with dependency management
22//! - **Variable Passing**: Global parameters and step-to-step output chaining
23//! - **Type Safety**: Strongly typed parameters (string, int, float, bool, datetime)
24//! - **Cross-Platform**: Works reliably on Linux, macOS, and Windows
25//! - **Secure Execution**: Temporary file isolation and proper permission handling
26//! - **Embedded Logging**: Captures stdout, stderr, and errors inline in JSON results
27//! - **No Telemetry**: Never collects usage stats or requires licensing checks
28//!
29//! ## Quick Start
30//!
31//! ```rust,no_run
32//! use atento_core;
33//!
34//! fn main() -> Result<(), Box<dyn std::error::Error>> {
35//!     // Run a chain from a YAML file
36//!     atento_core::run("chain.yaml")?;
37//!     Ok(())
38//! }
39//! ```
40//!
41//! ## Chain Structure
42//!
43//! Chains are defined in YAML format with the following structure:
44//!
45//! ```yaml
46//! name: "Example Chain"
47//! timeout: 300  # Global timeout in seconds
48//!
49//! parameters:
50//!   project_name:
51//!     type: string
52//!     value: "my-project"
53//!   build_number:
54//!     type: int
55//!     value: 42
56//!
57//! steps:
58//!   setup:
59//!     name: "Setup Environment"
60//!     type: bash  # Interpreter: bash, batch, powershell, pwsh, python
61//!     timeout: 60
62//!     script: |
63//!       echo "Setting up {{ inputs.project }}"
64//!       echo "BUILD_DIR=/tmp/build-{{ inputs.build_num }}"
65//!     inputs:
66//!       project:
67//!         ref: parameters.project_name
68//!       build_num:
69//!         ref: parameters.build_number
70//!     outputs:
71//!       build_directory:
72//!         pattern: "BUILD_DIR=(.*)"
73//!
74//!   build:
75//!     name: "Build Project"
76//!     type: python
77//!     script: |
78//!       import os
79//!       build_dir = "{{ inputs.build_dir }}"
80//!       print(f"Building in {build_dir}")
81//!       print("BUILD_SUCCESS=true")
82//!     inputs:
83//!       build_dir:
84//!         ref: steps.setup.outputs.build_directory
85//!     outputs:
86//!       status:
87//!         pattern: "BUILD_SUCCESS=(.*)"
88//!
89//! results:
90//!   build_status:
91//!     ref: steps.build.outputs.status
92//!   workspace:
93//!     ref: steps.setup.outputs.build_directory
94//! ```
95//!
96//! ## Supported Interpreters
97//!
98//! | Type | Description | Platform |
99//! |------|-------------|----------|
100//! | `bash` | Bash shell scripts | Unix/Linux/macOS |
101//! | `batch` | Windows batch files | Windows |
102//! | `powershell` | `PowerShell` (Windows) | Windows |
103//! | `pwsh` | `PowerShell` Core | Cross-platform |
104//! | `python` | Python scripts | Cross-platform |
105//! | `python3` | Python3 scripts | Cross-platform |
106//!
107//! ## Variable Substitution
108//!
109//! Use `{{ inputs.variable_name }}` syntax in scripts to substitute input values:
110//!
111//! ```yaml
112//! script: |
113//!   echo "Processing {{ inputs.filename }} in {{ inputs.directory }}"
114//!   cp "{{ inputs.source }}" "{{ inputs.destination }}"
115//! ```
116//!
117//! ## Output Extraction
118//!
119//! Capture values from command output using regex patterns with capture groups:
120//!
121//! ```yaml
122//! outputs:
123//!   version:
124//!     pattern: "Version: ([0-9]+\.[0-9]+\.[0-9]+)"
125//!   status:
126//!     pattern: "Status: (SUCCESS|FAILED)"
127//! ```
128//!
129//! ## Error Handling
130//!
131//! The library provides comprehensive error handling for:
132//! - File I/O operations
133//! - YAML parsing errors
134//! - Chain validation failures
135//! - Script execution timeouts
136//! - Type conversion errors
137//! - Unresolved variable references
138//!
139//! ## Example Usage
140//!
141//! ```no_run
142//! # use atento_core::{Chain, AtentoError};
143//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
144//! // Load and validate a chain
145//! let yaml_content = std::fs::read_to_string("chain.yaml")?;
146//! let chain: Chain = serde_yaml::from_str(&yaml_content)?;
147//!
148//! // Validate the chain structure
149//! chain.validate()?;
150//!
151//! // Execute the chain
152//! let result = chain.run();
153//!
154//! // Serialize results to JSON
155//! let json_output = serde_json::to_string_pretty(&result)?;
156//! println!("{}", json_output);
157//! # Ok(())
158//! # }
159//! ```
160
161use std::path::Path;
162
163mod chain;
164mod data_type;
165mod errors;
166mod executor;
167mod input;
168mod interpreter;
169mod output;
170mod parameter;
171mod result_ref;
172mod runner;
173mod step;
174
175#[cfg(test)]
176mod tests;
177
178// Re-export main types for library users
179pub use chain::{Chain, ChainResult};
180pub use data_type::DataType;
181pub use errors::{AtentoError, Result};
182pub use interpreter::{Interpreter, default_interpreters};
183pub use step::{Step, StepResult};
184
185/// Runs a chain from a YAML file.
186///
187/// # Arguments
188/// * `filename` - Path to the chain YAML file
189///
190/// # Errors
191/// Returns an error if:
192/// - The file cannot be read
193/// - The YAML cannot be parsed
194/// - The chain validation fails
195/// - The chain execution fails
196/// - The results cannot be serialized to JSON
197pub fn run(filename: &str) -> Result<()> {
198    let path = Path::new(filename);
199
200    let contents = std::fs::read_to_string(path).map_err(|e| AtentoError::Io {
201        path: filename.to_string(),
202        source: e,
203    })?;
204
205    let chain: Chain = serde_yaml::from_str(&contents).map_err(|e| AtentoError::YamlParse {
206        context: filename.to_string(),
207        source: e,
208    })?;
209
210    chain.validate()?; // Already returns Result<(), AtentoError>
211
212    let result = chain.run(); // Returns ChainResult
213
214    let json = serde_json::to_string_pretty(&result)?; // From trait converts to AtentoError
215
216    println!("{json}");
217
218    if result.errors.is_empty() {
219        Ok(())
220    } else {
221        Err(AtentoError::Execution(
222            "Chain completed with errors".to_string(),
223        ))
224    }
225}