http_endpoint_server_harness/
lib.rs

1//! HTTP Endpoint Server Harness
2//!
3//! A test harness for mocking HTTP endpoints with predefined responses.
4//! The server automatically shuts down once all handlers have been called.
5//!
6//! # Example
7//!
8//! ```rust,no_run
9//! use http_endpoint_server_harness::prelude::*;
10//! use std::net::SocketAddr;
11//!
12//! #[tokio::main]
13//! async fn main() -> Result<(), HarnessError> {
14//!     // Define a fixed address for the server
15//!     let addr: SocketAddr = "127.0.0.1:3000".parse().unwrap();
16//!
17//!     // Spawn a task to make HTTP requests
18//!     let requests_task = tokio::spawn(async move {
19//!         // Wait for server to be ready (in real code, add proper retry logic)
20//!         tokio::time::sleep(std::time::Duration::from_millis(100)).await;
21//!
22//!         let client = reqwest::Client::new();
23//!         let _response = client
24//!             .get(format!("http://{}/api/users", addr))
25//!             .send()
26//!             .await
27//!             .unwrap();
28//!     });
29//!
30//!     // Build and execute a scenario using the builder pattern
31//!     let collected = ScenarioBuilder::new()
32//!         .server(Axum::bind(addr))
33//!         .collector(DefaultCollector::new())
34//!         .endpoint(
35//!             Endpoint::new("/api/users", Method::Get)
36//!                 .with_handler(Handler::from_json(&json!({"id": 1})))
37//!         )
38//!         .build()
39//!         .execute()
40//!         .await?;
41//!
42//!     requests_task.await.unwrap();
43//!
44//!     // Server has automatically shut down, collected contains all requests
45//!     for req in &collected {
46//!         println!("Received: {} {}", req.method, req.path);
47//!     }
48//!
49//!     Ok(())
50//! }
51//! ```
52
53mod adapters;
54pub mod entities;
55pub mod error;
56pub mod use_cases;
57
58pub use error::HarnessError;
59
60#[cfg(feature = "axum")]
61pub use adapters::gateways::Axum;
62
63/// Default collector implementation that collects requests into a Vec
64pub struct DefaultCollector {
65    requests: std::sync::Mutex<Vec<entities::Request>>,
66}
67
68impl DefaultCollector {
69    pub fn new() -> Self {
70        Self {
71            requests: std::sync::Mutex::new(Vec::new()),
72        }
73    }
74}
75
76impl Default for DefaultCollector {
77    fn default() -> Self {
78        Self::new()
79    }
80}
81
82impl use_cases::ports::Collector for DefaultCollector {
83    type Output = Vec<entities::Request>;
84
85    fn collect(&self, request: entities::Request) {
86        if let Ok(mut requests) = self.requests.lock() {
87            requests.push(request);
88        }
89    }
90
91    fn into_output(self) -> Self::Output {
92        self.requests.into_inner().unwrap_or_default()
93    }
94}
95
96/// Prelude module for convenient imports
97pub mod prelude {
98    pub use crate::entities::{Endpoint, Handler, Method, Request, Response};
99    pub use crate::error::HarnessError;
100    pub use crate::use_cases::ports::Collector;
101    pub use crate::use_cases::ScenarioBuilder;
102    pub use crate::DefaultCollector;
103
104    #[cfg(feature = "axum")]
105    pub use crate::Axum;
106
107    pub use serde_json::json;
108}