Skip to main content

pharos_runtime_core/
request.rs

1use crate::builtin::{FrameworkBuiltinResponse, framework_builtin_response};
2use crate::config::AppRuntime;
3use crate::stack::RuntimeStack;
4
5/// Root-supplied hooks for app-neutral runtime request dispatch.
6pub trait RuntimeRequestService {
7    /// Looks up a cached route-to-app match for the absolute request target.
8    fn route_match_lookup(&self, target: &str) -> Option<String>;
9
10    /// Stores a route-to-app match after app ownership has been resolved.
11    fn route_match_store(&self, target: &str, app_id: &str);
12
13    /// Returns a framework webhook endpoint id for request targets that use a framework-owned path.
14    fn framework_webhook_endpoint_id(&self, target: &str) -> Option<String>;
15
16    /// Returns whether one app declares ownership of a framework webhook endpoint.
17    fn app_declares_framework_webhook_endpoint(&self, app: &AppRuntime, endpoint_id: &str) -> bool;
18}
19
20/// Finds the mounted app that owns a request target.
21pub fn runtime_match_app<'a>(
22    stack: &'a RuntimeStack,
23    target: &str,
24    service: &impl RuntimeRequestService,
25) -> Option<&'a AppRuntime> {
26    if let Some(app_id) = service.route_match_lookup(target)
27        && let Some(app) = stack
28            .apps
29            .iter()
30            .find(|app| app.app_id.as_deref() == Some(app_id.as_str()))
31    {
32        return Some(app);
33    }
34
35    if let Some(endpoint_id) = service.framework_webhook_endpoint_id(target) {
36        for app in &stack.apps {
37            if service.app_declares_framework_webhook_endpoint(app, &endpoint_id) {
38                if let Some(app_id) = app.app_id.as_deref() {
39                    service.route_match_store(target, app_id);
40                }
41                return Some(app);
42            }
43        }
44    }
45
46    crate::best_mount_index(
47        target,
48        stack
49            .apps
50            .iter()
51            .enumerate()
52            .map(|(index, app)| (index, app.base_path.as_deref().unwrap_or_default())),
53    )
54    .and_then(|index| stack.apps.get(index))
55    .inspect(|app| {
56        if let Some(app_id) = app.app_id.as_deref() {
57            service.route_match_store(target, app_id);
58        }
59    })
60}
61
62/// Handles framework-owned runtime routes after resolving the owning app.
63#[allow(clippy::too_many_arguments)]
64pub fn dynamic_runtime_handle_request(
65    method: &str,
66    target: &str,
67    stack: &RuntimeStack,
68    version: &str,
69    target_id: &str,
70    app_ready: impl Fn(&AppRuntime) -> bool,
71    stack_ready: impl Fn(&RuntimeStack) -> bool,
72    service: &impl RuntimeRequestService,
73) -> Option<FrameworkBuiltinResponse> {
74    let app = runtime_match_app(stack, target, service);
75    framework_builtin_response(
76        method,
77        target,
78        app,
79        Some(stack),
80        version,
81        target_id,
82        app_ready,
83        stack_ready,
84    )
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[derive(Default)]
92    struct TestService;
93
94    impl RuntimeRequestService for TestService {
95        fn route_match_lookup(&self, _target: &str) -> Option<String> {
96            None
97        }
98
99        fn route_match_store(&self, _target: &str, _app_id: &str) {}
100
101        fn framework_webhook_endpoint_id(&self, _target: &str) -> Option<String> {
102            None
103        }
104
105        fn app_declares_framework_webhook_endpoint(
106            &self,
107            _app: &AppRuntime,
108            _endpoint_id: &str,
109        ) -> bool {
110            false
111        }
112    }
113
114    #[test]
115    fn dynamic_request_resolves_builtins_through_core_dispatch() {
116        let stack = RuntimeStack {
117            apps: vec![AppRuntime {
118                app_id: Some("docs".to_owned()),
119                base_path: Some("/docs".to_owned()),
120                artifact_dir: Some("dist/apps/docs".to_owned()),
121                ..AppRuntime::default()
122            }],
123            ..RuntimeStack::default()
124        };
125        let response = dynamic_runtime_handle_request(
126            "GET",
127            "/health",
128            &stack,
129            "0.0.0",
130            "test-target",
131            |_| true,
132            |_| true,
133            &TestService,
134        )
135        .expect("builtin response");
136        assert_eq!(response.status_code, 200);
137    }
138}