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}