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}