Skip to main content

icap_rs/server/
router.rs

1use std::borrow::Cow;
2use std::collections::HashMap;
3use std::future::Future;
4
5use crate::{IncomingRequest, Method, Response};
6
7use super::handler::HandlerResult;
8use super::options::ServiceOptions;
9use super::preview::PreviewDecision;
10
11/// A per-service ICAP handler.
12///
13/// One handler can serve multiple ICAP methods declared for a service via
14/// [`crate::ServerBuilder::route`].
15pub(super) type RequestHandler = Box<
16    dyn Fn(
17            IncomingRequest,
18        )
19            -> std::pin::Pin<Box<dyn Future<Output = HandlerResult<PreviewDecision>> + Send>>
20        + Send
21        + Sync,
22>;
23
24/// Return type adapter for route handlers.
25///
26/// Handlers returning `HandlerResult<Response>` keep the full-body behavior:
27/// the server reads the whole request body before invoking them. Handlers
28/// returning `HandlerResult<PreviewDecision>` are preview-aware and may be
29/// invoked before `100 Continue`. If a preview-aware handler returns
30/// [`PreviewDecision::Continue`], the server sends `100 Continue`, reads the
31/// remainder, and invokes the same route again with `Body::Full`.
32pub trait RouteOutput: Send + 'static {
33    const PREVIEW_AWARE: bool;
34
35    fn into_preview_decision(self) -> HandlerResult<PreviewDecision>;
36}
37
38impl RouteOutput for HandlerResult<Response> {
39    const PREVIEW_AWARE: bool = false;
40
41    fn into_preview_decision(self) -> HandlerResult<PreviewDecision> {
42        self.map(PreviewDecision::Respond)
43    }
44}
45
46impl RouteOutput for HandlerResult<PreviewDecision> {
47    const PREVIEW_AWARE: bool = true;
48
49    fn into_preview_decision(self) -> HandlerResult<PreviewDecision> {
50        self
51    }
52}
53
54pub(super) struct HandlerEntry {
55    pub(super) handler: RequestHandler,
56    pub(super) preview_aware: bool,
57}
58
59/// Route entry for a service: per-method handlers plus optional OPTIONS config.
60pub(super) struct RouteEntry {
61    pub(super) handlers: HashMap<Method, HandlerEntry>,
62    pub(super) options: Option<ServiceOptions>,
63}
64
65/// Resolve default service and bounded alias rewrites.
66///
67/// Rules:
68/// - If `raw` is empty or exactly "/", use `default_service` (when set).
69/// - Apply up to 4 alias rewrites (`from` -> `to`) to avoid cycles.
70pub(super) fn resolve_service<'a>(
71    raw: &'a str,
72    aliases: &'a HashMap<String, String>,
73    default_service: Option<&'a str>,
74) -> Cow<'a, str> {
75    let mut cur: Cow<'a, str> = if raw.is_empty() || raw == "/" {
76        default_service.map_or(Cow::Borrowed(raw), Cow::Borrowed)
77    } else {
78        Cow::Borrowed(raw)
79    };
80
81    for _ in 0..4 {
82        if let Some(next) = aliases.get(cur.as_ref()) {
83            cur = Cow::Borrowed(next.as_str());
84        } else {
85            break;
86        }
87    }
88
89    cur
90}