diff --git a/Cargo.toml b/Cargo.toml index 2b004f5..b87e3a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,9 +39,15 @@ tokio-tungstenite = { version = "0.26.1", features = ["rustls-tls-webpki-roots"] futures-util = { version = "0.3.31", optional = true } [dev-dependencies] +ajj = { path = "./", features = ["axum", "ws", "ipc"] } + tempfile = "3.15.0" tracing-subscriber = "0.3.19" axum = { version = "0.8.1", features = ["macros"] } +tower-http = { version = "0.6.2", features = ["cors"] } +tokio = { version = "1.43.0", features = ["full"] } +eyre = "0.6.12" + [features] default = ["axum", "ws", "ipc"] diff --git a/examples/cors.rs b/examples/cors.rs new file mode 100644 index 0000000..a55d6a5 --- /dev/null +++ b/examples/cors.rs @@ -0,0 +1,81 @@ +//! Basic example of using ajj with CORS. +//! +//! This example demonstrates how to set up a simple HTTP server using `axum` +//! and `tower_http` for CORS support. +//! +//! ## Inovking this example +//! +//! ```ignore +//! // Invoking with cors domains +//! cargo run --example cors -- "https://example.com,https://example.org" +//! // Invoking with wildcard +//! cargo run --example cors +//! ``` + +use axum::http::{HeaderValue, Method}; +use eyre::{ensure, Context}; +use std::{future::IntoFuture, net::SocketAddr}; +use tower_http::cors::{AllowOrigin, Any, CorsLayer}; + +fn get_allowed(cors: &str) -> eyre::Result { + // Wildcard `*` means any origin is allowed. + if cors == "*" { + return Ok(AllowOrigin::any()); + } + + // Check if the cors string contains a wildcard + ensure!( + !cors.split(',').any(|o| o == "*"), + "Wildcard '*' is not allowed in CORS domains" + ); + + // Split the cors string by commas and parse each domain into a HeaderValue + // Then convert the Vec into AllowOrigin + cors.split(',') + .map(|domain| { + // Parse each substring into a HeaderValue + // We print parsing errors to stderr, because this is an example :) + domain + .parse::() + .inspect_err(|e| eprintln!("Failed to parse domain {}: {}", domain, e)) + .wrap_err_with(|| format!("Invalid CORS domain: {}", domain)) + }) + .collect::, _>>() + .map(Into::into) +} + +fn make_cors(cors: &str) -> eyre::Result { + let origins = get_allowed(cors)?; + + Ok(CorsLayer::new() + .allow_methods([Method::GET, Method::POST]) + .allow_origin(origins) + .allow_headers(Any)) +} + +#[tokio::main] +async fn main() -> eyre::Result<()> { + let cors = std::env::args().nth(1).unwrap_or("*".to_string()); + + // Setting up an AJJ router is easy and fun! + let router = ajj::Router::<()>::new() + .route("helloWorld", || async { + tracing::info!("serving hello world"); + Ok::<_, ()>("Hello, world!") + }) + .route("addNumbers", |(a, b): (u32, u32)| async move { + tracing::info!("serving addNumbers"); + Ok::<_, ()>(a + b) + }) + // Convert to an axum router + .into_axum("/") + // And then layer on your CORS settings + .layer(make_cors(&cors)?); + + // Now we can serve the router on a TCP listener + let addr = SocketAddr::from(([127, 0, 0, 1], 0)); + let listener = tokio::net::TcpListener::bind(addr).await?; + + axum::serve(listener, router).into_future().await?; + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index cb67dc9..a5da1e6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -136,7 +136,6 @@ //! //! [`axum`]: https://docs.rs/axum/latest/axum/index.html //! [`axum::Router`]: https://docs.rs/axum/latest/axum/routing/struct.Router.html -//! [`ResponsePayload`]: alloy::rpc::json_rpc::ResponsePayload //! [`interprocess::local_socket::ListenerOptions`]: https://docs.rs/interprocess/latest/interprocess/local_socket/struct.ListenerOptions.html #![warn(