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}