hyperlane 20.0.1

A lightweight, high-performance, and cross-platform Rust HTTP server library built on Tokio. It simplifies modern web service development by providing built-in support for middleware, WebSocket, Server-Sent Events (SSE), and raw TCP communication. With a unified and ergonomic API across Windows, Linux, and MacOS, it enables developers to build robust, scalable, and event-driven network applications with minimal overhead and maximum flexibility.
Documentation
use crate::*;

struct TestRoute {
    data: String,
}

impl ServerHook for TestRoute {
    async fn new(_ctx: &mut Context) -> Self {
        Self {
            data: String::new(),
        }
    }

    async fn handle(mut self, _ctx: &mut Context) {
        self.data = String::from("test");
    }
}

#[tokio::test]
#[should_panic(expected = "EmptyPattern")]
async fn empty_route() {
    let _server: &Server = Server::default().route::<TestRoute>(EMPTY_STR);
}

#[tokio::test]
#[should_panic(expected = "DuplicatePattern")]
async fn duplicate_route() {
    let _server: &Server = Server::default()
        .route::<TestRoute>(ROOT_PATH)
        .route::<TestRoute>(ROOT_PATH);
}

#[test]
fn get_route() {
    let mut server: Server = Server::default();
    server
        .route::<TestRoute>(ROOT_PATH)
        .route::<TestRoute>("/dynamic/{routing}")
        .route::<TestRoute>("/regex/{file:^.*$}");
    let route_matcher: RouteMatcher = server.get_route_matcher().clone();
    for key in route_matcher.get_static_route().keys() {
        println!("Static route: {key}");
    }
    for value in route_matcher.get_dynamic_route().values() {
        for (route_pattern, _) in value {
            println!("Dynamic route: {route_pattern}");
        }
    }
    for value in route_matcher.get_regex_route().values() {
        for (route_pattern, _) in value {
            println!("Regex route: {route_pattern}");
        }
    }
}

#[test]
fn segment_count_optimization() {
    let mut server: Server = Server::default();
    server.route::<TestRoute>("/users/{id}");
    server.route::<TestRoute>("/users/{id}/posts");
    server.route::<TestRoute>("/users/{id}/posts/{post_id}");
    server.route::<TestRoute>("/api/v1/users/{id}");
    let route_matcher: RouteMatcher = server.get_route_matcher().clone();
    assert!(
        route_matcher.get_dynamic_route().contains_key(&2),
        "Should have 2-segment routes"
    );
    assert!(
        route_matcher.get_dynamic_route().contains_key(&3),
        "Should have 3-segment routes"
    );
    assert!(
        route_matcher.get_dynamic_route().contains_key(&4),
        "Should have 4-segment routes"
    );
    assert_eq!(route_matcher.get_dynamic_route().get(&2).unwrap().len(), 1);
    assert_eq!(route_matcher.get_dynamic_route().get(&3).unwrap().len(), 1);
    assert_eq!(route_matcher.get_dynamic_route().get(&4).unwrap().len(), 2);
}

#[test]
fn regex_route_segment_count() {
    let mut server: Server = Server::default();
    server.route::<TestRoute>("/files/{path:.*}");
    server.route::<TestRoute>("/api/{version:\\d+}/users");
    server.route::<TestRoute>("/api/{version:\\d+}/posts/{id:\\d+}");
    let route_matcher: RouteMatcher = server.get_route_matcher().clone();
    assert!(
        route_matcher.get_regex_route().contains_key(&2),
        "Should have 2-segment regex routes"
    );
    assert!(
        route_matcher.get_regex_route().contains_key(&3),
        "Should have 3-segment regex routes"
    );
    assert!(
        route_matcher.get_regex_route().contains_key(&4),
        "Should have 4-segment regex routes"
    );
}

#[test]
fn mixed_route_types() {
    let mut server: Server = Server::default();
    server.route::<TestRoute>("/");
    server.route::<TestRoute>("/about");
    server.route::<TestRoute>("/users/{id}");
    server.route::<TestRoute>("/posts/{slug}");
    server.route::<TestRoute>("/files/{path:.*}");
    let route_matcher: RouteMatcher = server.get_route_matcher().clone();
    assert_eq!(route_matcher.get_static_route().len(), 2);
    assert!(route_matcher.get_dynamic_route().contains_key(&2));
    assert!(route_matcher.get_regex_route().contains_key(&2));
}

#[test]
fn large_dynamic_routes() {
    const ROUTE_COUNT: u32 = 1000;
    let mut server: Server = Server::default();
    let start_insert: Instant = Instant::now();
    for i in 0..ROUTE_COUNT {
        let path: String = format!("/api/resource{i}/{{id}}");
        server.route::<TestRoute>(&path);
    }
    let insert_duration: Duration = start_insert.elapsed();
    println!(
        "Inserted {} dynamic routes in: {:?}",
        ROUTE_COUNT, insert_duration
    );
    let route_matcher: RouteMatcher = server.get_route_matcher().clone();
    assert!(!route_matcher.get_dynamic_route().is_empty());
    let mut ctx: Context = Context::default();
    let start_match: Instant = Instant::now();
    for i in 0..ROUTE_COUNT {
        let path: String = format!("/api/resource{i}/123");
        let _ = route_matcher.try_resolve_route(&mut ctx, &path);
    }
    let match_duration: Duration = start_match.elapsed();
    println!(
        "Matched {} dynamic routes in: {:?}",
        ROUTE_COUNT, match_duration
    );
    println!(
        "Average per dynamic route match: {:?}",
        match_duration / ROUTE_COUNT
    );
}

#[test]
fn large_regex_routes() {
    const ROUTE_COUNT: u32 = 1000;
    let mut server: Server = Server::default();
    let start_insert: Instant = Instant::now();
    for i in 0..ROUTE_COUNT {
        let path: String = format!("/api/resource{i}/{{id:[0-9]+}}");
        server.route::<TestRoute>(&path);
    }
    let insert_duration: Duration = start_insert.elapsed();
    println!(
        "Inserted {} regex routes in: {:?}",
        ROUTE_COUNT, insert_duration
    );
    let route_matcher: RouteMatcher = server.get_route_matcher().clone();
    assert!(!route_matcher.get_regex_route().is_empty());
    let mut ctx: Context = Context::default();
    let start_match: Instant = Instant::now();
    for i in 0..ROUTE_COUNT {
        let path: String = format!("/api/resource{i}/123");
        let _ = route_matcher.try_resolve_route(&mut ctx, &path);
    }
    let match_duration: Duration = start_match.elapsed();
    println!(
        "Matched {} regex routes in: {:?}",
        ROUTE_COUNT, match_duration
    );
    println!(
        "Average per regex route match: {:?}",
        match_duration / ROUTE_COUNT
    );
}

#[test]
fn large_tail_regex_routes() {
    const ROUTE_COUNT: u32 = 1000;
    let mut server: Server = Server::default();
    let start_insert: Instant = Instant::now();
    for i in 0..ROUTE_COUNT {
        let path: String = format!("/api/resource{i}/{{path:.*}}");
        server.route::<TestRoute>(&path);
    }
    let insert_duration: Duration = start_insert.elapsed();
    println!(
        "Inserted {} tail regex routes in: {:?}",
        ROUTE_COUNT, insert_duration
    );
    let route_matcher: RouteMatcher = server.get_route_matcher().clone();
    assert!(!route_matcher.get_regex_route().is_empty());
    let mut ctx: Context = Context::default();
    let start_match: Instant = Instant::now();
    for i in 0..ROUTE_COUNT {
        let path: String = format!("/api/resource{i}/some/nested/path");
        let _ = route_matcher.try_resolve_route(&mut ctx, &path);
    }
    let match_duration: Duration = start_match.elapsed();
    println!(
        "Matched {} tail regex routes in: {:?}",
        ROUTE_COUNT, match_duration
    );
    println!(
        "Average per tail regex route match: {:?}",
        match_duration / ROUTE_COUNT
    );
}