Skip to main content

blazing_agi/
lib.rs

1//! `blazing_agi` is a fast, ergonomic and correct `FastAGI` Server.
2//!
3//! `blazing_agi` requires the use of [tokio]. Executor independence is currently not a goal.
4//! `blazing_agi` does not currently contain definitions for all AGI commands. Please file an issue
5//! or a PR if you want one added.
6//!
7//! To get started, consider this "Hello World" example:
8//! ```ignore
9//! use blazing_agi::{command::{verbose::Verbose}, router::Router, serve};
10//! use blazing_agi_macros::create_handler;
11//! use tokio::net::TcpListener;
12//!
13//! // The create_handler macro is used to turn an async fn into a handler.
14//! // Make sure to use the same signature as here (including the variable names, but not the function
15//! // name)
16//! #[create_handler]
17//! async fn foo(connection: &mut Connection, request: &AGIRequest) -> Result<(), AGIError> {
18//!     connection.send_command(Verbose::new("Hello There".to_owned())).await?;
19//!     Ok(())
20//! }
21//!
22//! #[tokio::main]
23//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
24//!     // Create the router from the handlers you have defined
25//!     let router = Router::new()
26//!         .route("/script", foo);
27//!     let listener = TcpListener::bind("0.0.0.0:4573").await?;
28//!     // Start serving the Router
29//!     serve::serve(listener, router).await?;
30//!     Ok(())
31//! }
32//! ```
33//! You can find a more elaborate example in `examples/layer-agi-digest.rs`.
34//!
35//! In general, `blazing_agi` works by defining [`AGIHandler`] (read: scripts). You then combine them
36//! into [`Router`](crate::router::Router)s. They define which requested uri is handled by which
37//! handler.
38//! An [`AGIHandler`] takes:
39//! - a &mut [`Connection`] - this is a wrapper around a tokio [`TcpStream`](tokio::net::TcpStream), which handles sending
40//!   Commands and parsing the response
41//! - a & [`AGIRequest`] - this contains the data send in the initial request made by the client
42//!   (asterisk).
43//!
44//! An [`AGIHandler`] can then use the [`Connection::send_command`] function to send commands to
45//! the client.
46//! When it is done, the Handler simply returns `Ok(())` to signal that the
47//! execution was successful and the stream can be terminated.
48//! If an error is encountered that the Handler does not want to handle, it can be bubbled up as
49//! [`AGIError`], which tells the runtime that something went wrong - the stream is also closed.
50use std::collections::HashMap;
51
52use agiparse::{AGIMessage, AGIParseError, AGIStatusGeneric, AGIVariableDump};
53use connection::Connection;
54use handler::AGIHandler;
55
56mod agiparse;
57pub mod command;
58pub mod connection;
59pub mod handler;
60pub mod layer;
61pub mod router;
62pub mod serve;
63
64/// Contains all the ways in which serving a `FastAGI` Request can fail.
65#[derive(Debug)]
66pub enum AGIError {
67    /// Handlers may use this to bubble up errors if they want.
68    InnerError(Box<dyn std::error::Error>),
69    /// A special case:
70    /// This is raised when the client (asterisk) made a well-formed request
71    /// with incorrect data (such as Unauth etc) - the handler asks the router to break
72    /// communication with the client.
73    ClientSideError(String),
74    /// Expected a 200-response, but got something else.
75    Not200(u16),
76    /// agi:// was not chosen as the schema.
77    WrongSchema(String),
78    /// A handler expected (param 1) custom arguments, but only (param 2) were actually passed.
79    NotEnoughCustomVariables(u8, u8),
80    /// Unable to spawn a [`TcpListener`](tokio::net::TcpListener).
81    CannotSpawnListener,
82    /// Unable to send a command.
83    CannotSendCommand(tokio::io::Error),
84    /// Unable to parse an incoming packet.
85    ParseError(AGIParseError),
86    /// A parsable message came in. We expected a Status, but got something else.
87    NotAStatus(Box<AGIMessage>),
88    /// The generic AGI status could be read, the expected return type is known, but the response
89    /// actually received is not parsable as the special response type expected.
90    AGIStatusUnspecializable(AGIStatusGeneric, &'static str),
91}
92impl core::fmt::Display for AGIError {
93    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
94        match self {
95            Self::WrongSchema(x) => {
96                write!(f, "The schema {x} is not known")
97            }
98            Self::NotEnoughCustomVariables(x, y) => {
99                write!(
100                    f,
101                    "There are only {x} custom variables, but {y} are required"
102                )
103            }
104            Self::CannotSpawnListener => {
105                write!(f, "Unable to spawn the TCP listener")
106            }
107            Self::CannotSendCommand(x) => {
108                write!(f, "Unable to send an AGI command: {x}")
109            }
110            Self::ParseError(x) => {
111                write!(f, "Unable to parse packet: {x}")
112            }
113            Self::NotAStatus(x) => {
114                write!(f, "Sent a Command, but the response was not a Status: {x}")
115            }
116            Self::InnerError(x) => {
117                write!(f, "InnerError: {x}")
118            }
119            Self::Not200(x) => {
120                write!(f, "Handler expected 200-response, but got {x}")
121            }
122            Self::ClientSideError(x) => {
123                write!(f, "Error on the Client side: {x}")
124            }
125            Self::AGIStatusUnspecializable(x, y) => {
126                write!(f, "I am unable to specialize {x} as a response to {y}")
127            }
128        }
129    }
130}
131impl std::error::Error for AGIError {}
132
133/// The Data sent with the request.
134#[derive(Debug, PartialEq)]
135pub struct AGIRequest {
136    /// The individual variables that asterisk sent.
137    pub variables: AGIVariableDump,
138    /// The pathsegments of the request uri that were captured in :capture segments.
139    pub captures: HashMap<String, String>,
140    /// The pathsegments of the request uri that were captured in * segments.
141    pub wildcards: Option<String>,
142}