use std::{future::Future, pin::Pin};
use super::ProtocolAdapter;
#[derive(Debug, Clone)]
pub struct RestRoute {
pub method: String,
pub path: String,
pub handler: String,
}
impl RestRoute {
pub fn new(
method: impl Into<String>,
path: impl Into<String>,
handler: impl Into<String>,
) -> Self {
Self {
method: method.into(),
path: path.into(),
handler: handler.into(),
}
}
pub fn matches(&self, method: &str, path: &str) -> bool {
if self.method != method {
return false;
}
self.path == path
}
pub fn matches_path(&self, path: &str) -> bool {
let route_segments: Vec<&str> = self.path.split('/').filter(|s| !s.is_empty()).collect();
let path_segments: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
if route_segments.len() != path_segments.len() {
return false;
}
for (route_seg, path_seg) in route_segments.iter().zip(path_segments.iter()) {
if route_seg.starts_with(':') {
continue;
}
if route_seg != path_seg {
return false;
}
}
true
}
}
pub struct RestAdapter {
routes: Vec<RestRoute>,
}
impl RestAdapter {
pub fn new() -> Self {
Self { routes: Vec::new() }
}
pub fn route(&mut self, method: &str, path: &str, handler: &str) -> &mut Self {
self.routes.push(RestRoute::new(method, path, handler));
self
}
pub fn match_route(&self, method: &str, path: &str) -> Option<&RestRoute> {
self.routes
.iter()
.find(|r| r.method == method && r.matches_path(path))
}
pub fn parse_request(&self, request: &str) -> Result<(String, String, Option<String>), String> {
let parts: Vec<&str> = request.splitn(3, ' ').collect();
if parts.len() < 2 {
return Err("Invalid request format. Expected: METHOD /path [body]".to_string());
}
let method = parts[0].to_string();
let path = parts[1].to_string();
let body = parts.get(2).map(|s| s.to_string());
Ok((method, path, body))
}
pub fn format_response(&self, status: u16, body: &str) -> String {
format!("HTTP {} {}", status, body)
}
pub fn build_request(
&self,
method: &str,
path: &str,
_body: Option<&str>,
_headers: Option<&str>,
) -> RestRequest {
RestRequest {
method: method.to_string(),
path: path.to_string(),
}
}
}
impl Default for RestAdapter {
fn default() -> Self {
Self::new()
}
}
impl ProtocolAdapter for RestAdapter {
fn name(&self) -> &str {
"rest"
}
fn handle(
&self,
request: &str,
) -> Pin<Box<dyn Future<Output = Result<String, String>> + Send + '_>> {
let parse_result = self.parse_request(request);
let routes = self.routes.clone();
Box::pin(async move {
let (method, path, _body) = match parse_result {
Ok(parsed) => parsed,
Err(e) => {
let response = format!("HTTP 400 {}", e);
return Ok(response);
}
};
let matched_route = routes
.iter()
.find(|r| r.method == method && r.matches_path(&path));
match matched_route {
Some(route) => {
let response_body = format!(
"{{\"handler\":\"{}\",\"method\":\"{}\",\"path\":\"{}\"}}",
route.handler, method, path
);
let response = format!("HTTP 200 {}", response_body);
Ok(response)
}
None => {
let error = format!("{{\"error\":\"Not Found\",\"path\":\"{}\"}}", path);
let response = format!("HTTP 404 {}", error);
Ok(response)
}
}
})
}
}
#[derive(Debug, Clone)]
pub struct RestRequest {
pub method: String,
pub path: String,
}
#[derive(Debug, Clone)]
pub struct RestResponse {
status: u16,
body: String,
}
impl RestResponse {
pub fn new(status: u16, body: String) -> Self {
Self { status, body }
}
pub fn status(&self) -> u16 {
self.status
}
pub fn body(&self) -> &str {
&self.body
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rest_adapter_creation() {
let adapter = RestAdapter::new();
assert_eq!(adapter.name(), "rest");
}
#[test]
fn test_build_request() {
let adapter = RestAdapter::new();
let request = adapter.build_request("GET", "/users/42", None, None);
assert_eq!(request.method, "GET");
assert_eq!(request.path, "/users/42");
}
#[test]
fn test_route_registration() {
let mut adapter = RestAdapter::new();
adapter.route("GET", "/users", "list_users");
adapter.route("POST", "/users", "create_user");
assert_eq!(adapter.routes.len(), 2);
assert_eq!(adapter.routes[0].method, "GET");
assert_eq!(adapter.routes[0].path, "/users");
assert_eq!(adapter.routes[0].handler, "list_users");
}
#[test]
fn test_route_matching_exact() {
let mut adapter = RestAdapter::new();
adapter.route("GET", "/users", "list_users");
let matched = adapter.match_route("GET", "/users");
assert!(matched.is_some());
assert_eq!(matched.unwrap().handler, "list_users");
}
#[test]
fn test_route_matching_not_found() {
let mut adapter = RestAdapter::new();
adapter.route("GET", "/users", "list_users");
let matched = adapter.match_route("GET", "/posts");
assert!(matched.is_none());
}
#[test]
fn test_route_matching_wrong_method() {
let mut adapter = RestAdapter::new();
adapter.route("GET", "/users", "list_users");
let matched = adapter.match_route("POST", "/users");
assert!(matched.is_none());
}
#[test]
fn test_route_matching_with_params() {
let mut adapter = RestAdapter::new();
adapter.route("GET", "/users/:id", "get_user");
let matched = adapter.match_route("GET", "/users/42");
assert!(matched.is_some());
assert_eq!(matched.unwrap().handler, "get_user");
let matched2 = adapter.match_route("GET", "/users/123");
assert!(matched2.is_some());
}
#[test]
fn test_route_matching_params_wrong_length() {
let mut adapter = RestAdapter::new();
adapter.route("GET", "/users/:id", "get_user");
let matched = adapter.match_route("GET", "/users");
assert!(matched.is_none());
let matched2 = adapter.match_route("GET", "/users/42/posts");
assert!(matched2.is_none());
}
#[test]
fn test_parse_request_get() {
let adapter = RestAdapter::new();
let result = adapter.parse_request("GET /users");
assert!(result.is_ok());
let (method, path, body) = result.unwrap();
assert_eq!(method, "GET");
assert_eq!(path, "/users");
assert!(body.is_none());
}
#[test]
fn test_parse_request_post_with_body() {
let adapter = RestAdapter::new();
let result = adapter.parse_request("POST /users {\"name\":\"John\"}");
assert!(result.is_ok());
let (method, path, body) = result.unwrap();
assert_eq!(method, "POST");
assert_eq!(path, "/users");
assert_eq!(body.unwrap(), "{\"name\":\"John\"}");
}
#[test]
fn test_parse_request_invalid() {
let adapter = RestAdapter::new();
let result = adapter.parse_request("INVALID");
assert!(result.is_err());
assert!(result.unwrap_err().contains("Invalid request format"));
}
#[test]
fn test_format_response_200() {
let adapter = RestAdapter::new();
let response = adapter.format_response(200, "{\"success\":true}");
assert!(response.contains("HTTP 200"));
assert!(response.contains("{\"success\":true}"));
}
#[test]
fn test_format_response_404() {
let adapter = RestAdapter::new();
let response = adapter.format_response(404, "{\"error\":\"Not Found\"}");
assert!(response.contains("HTTP 404"));
assert!(response.contains("Not Found"));
}
#[tokio::test]
async fn test_handle_request_success() {
let mut adapter = RestAdapter::new();
adapter.route("GET", "/users", "list_users");
let result = adapter.handle("GET /users").await;
assert!(result.is_ok());
let response = result.unwrap();
assert!(response.contains("HTTP 200"));
assert!(response.contains("list_users"));
}
#[tokio::test]
async fn test_handle_request_not_found() {
let adapter = RestAdapter::new();
let result = adapter.handle("GET /users").await;
assert!(result.is_ok());
let response = result.unwrap();
assert!(response.contains("HTTP 404"));
assert!(response.contains("Not Found"));
}
#[tokio::test]
async fn test_handle_request_invalid() {
let adapter = RestAdapter::new();
let result = adapter.handle("INVALID").await;
assert!(result.is_ok());
let response = result.unwrap();
assert!(response.contains("HTTP 400"));
}
#[tokio::test]
async fn test_handle_request_with_params() {
let mut adapter = RestAdapter::new();
adapter.route("GET", "/users/:id", "get_user");
let result = adapter.handle("GET /users/42").await;
assert!(result.is_ok());
let response = result.unwrap();
assert!(response.contains("HTTP 200"));
assert!(response.contains("get_user"));
assert!(response.contains("/users/42"));
}
#[test]
fn test_rest_route_new() {
let route = RestRoute::new("GET", "/users", "list_users");
assert_eq!(route.method, "GET");
assert_eq!(route.path, "/users");
assert_eq!(route.handler, "list_users");
}
#[test]
fn test_rest_route_matches() {
let route = RestRoute::new("GET", "/users", "list_users");
assert!(route.matches("GET", "/users"));
assert!(!route.matches("POST", "/users"));
assert!(!route.matches("GET", "/posts"));
}
#[test]
fn test_rest_route_matches_path_with_params() {
let route = RestRoute::new("GET", "/users/:id/posts/:post_id", "handler");
assert!(route.matches_path("/users/42/posts/100"));
assert!(route.matches_path("/users/123/posts/456"));
assert!(!route.matches_path("/users/42"));
assert!(!route.matches_path("/users/42/posts"));
assert!(!route.matches_path("/users/42/posts/100/extra"));
}
}