Skip to main content

roas_arazzo_executor/
lib.rs

1//! Executes OpenAPI Arazzo workflows.
2//!
3//! An Arazzo description is a program: ordered steps that call API
4//! operations, assert on the responses, name outputs, and branch on
5//! success or failure. [`roas-arazzo`](https://crates.io/crates/roas-arazzo)
6//! parses and validates one; this crate runs it.
7//!
8//! ```no_run
9//! # use roas_arazzo::v1_1::Description;
10//! # use roas_arazzo_executor::{Options, execute};
11//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
12//! # let description: Description = serde_json::from_str("{}")?;
13//! # let mut client = roas_arazzo_executor::testing::Fake::default();
14//! let options = Options::new().workflow("buyPet");
15//! let report = execute(&description, &options, &mut client)?;
16//! println!("{report}");
17//! # Ok(()) }
18//! ```
19//!
20//! ## No IO of its own
21//!
22//! The engine decides *what* to send and asks a client to send it, so
23//! the same engine runs under a blocking client, an async one, or a fake
24//! that never touches a network. Implement [`HttpClient`] (or
25//! [`AsyncHttpClient`]), or enable the `reqwest` feature for ready-made
26//! ones.
27//!
28//! Source descriptions are loaded the same way: fetching them is IO, so
29//! the caller supplies the parsed documents through
30//! [`Options::source`].
31//!
32//! ## What it does not do yet
33//!
34//! AsyncAPI steps (`channelPath` / `action`), XPath criteria and
35//! selectors, `inputs` schema validation, and parallel `dependsOn`
36//! execution. Each is reported where it is met rather than passed over,
37//! so a run never looks successful because something was skipped.
38
39mod criterion;
40mod expression;
41mod http;
42mod operation;
43mod report;
44mod run;
45mod select;
46
47pub mod testing;
48
49#[cfg(feature = "reqwest")]
50mod client;
51
52pub use criterion::CriterionError;
53pub use expression::ExpressionError;
54pub use http::{
55    AsyncHttpClient, ClientError, HttpClient, HttpRequest, HttpResponse, SendFuture, SleepFuture,
56};
57pub use report::{
58    CriterionOutcome, ExecutionError, ExecutionReport, Outcome, Performed, StepRecord,
59};
60pub use run::{Options, Progress, Run};
61pub use select::SelectError;
62
63#[cfg(feature = "reqwest")]
64pub use client::Client;
65
66use roas_arazzo::v1_1::Description;
67
68/// Run a workflow, performing every request with `client`.
69///
70/// The workflow is [`Options::workflow`], or the first one in the
71/// description. The report says what each step did; a step that fails
72/// its criteria is part of the report, not an error.
73///
74/// # Errors
75///
76/// [`ExecutionError`] when the run cannot continue: an unknown workflow,
77/// an operation that cannot be resolved, an expression that names
78/// nothing, a client failure, or a limit reached.
79pub fn execute<C: HttpClient + ?Sized>(
80    description: &Description,
81    options: &Options,
82    client: &mut C,
83) -> Result<ExecutionReport, ExecutionError> {
84    let mut run = Run::start(description, options)?;
85    loop {
86        match run.advance()? {
87            Progress::Send(request) => {
88                let response = client.send(&request).map_err(ExecutionError::from)?;
89                run.supply(response)?;
90            }
91            Progress::Wait(duration) => std::thread::sleep(duration),
92            Progress::Done(report) => return Ok(*report),
93        }
94    }
95}
96
97/// Run a workflow, performing every request with an async `client`.
98///
99/// The same engine as [`execute`]; only the waiting differs.
100///
101/// # Errors
102///
103/// As [`execute`].
104pub async fn execute_async<C: AsyncHttpClient + ?Sized>(
105    description: &Description,
106    options: &Options,
107    client: &mut C,
108) -> Result<ExecutionReport, ExecutionError> {
109    let mut run = Run::start(description, options)?;
110    loop {
111        match run.advance()? {
112            Progress::Send(request) => {
113                let response = client.send(&request).await.map_err(ExecutionError::from)?;
114                run.supply(response)?;
115            }
116            Progress::Wait(duration) => client.sleep(duration).await,
117            Progress::Done(report) => return Ok(*report),
118        }
119    }
120}
121
122/// Run an Arazzo v1.0 description, upconverting it to v1.1 first.
123///
124/// # Errors
125///
126/// As [`execute`].
127#[cfg(feature = "v1_0")]
128pub fn execute_v1_0<C: HttpClient + ?Sized>(
129    description: &roas_arazzo::v1_0::Description,
130    options: &Options,
131    client: &mut C,
132) -> Result<ExecutionReport, ExecutionError> {
133    execute(&Description::from(description.clone()), options, client)
134}