pharos_runtime_core/
builtin.rs1use crate::config::AppRuntime;
2use crate::stack::RuntimeStack;
3
4#[derive(Debug, Clone, Eq, PartialEq)]
6pub struct FrameworkBuiltinResponse {
7 pub status_code: u16,
8 pub status_text: &'static str,
9 pub body: String,
10}
11
12#[allow(clippy::too_many_arguments)]
14pub fn framework_builtin_response(
15 method: &str,
16 target: &str,
17 app: Option<&AppRuntime>,
18 stack: Option<&RuntimeStack>,
19 version: &str,
20 target_id: &str,
21 app_ready: impl Fn(&AppRuntime) -> bool,
22 stack_ready: impl Fn(&RuntimeStack) -> bool,
23) -> Option<FrameworkBuiltinResponse> {
24 let path_only = target.split('?').next().unwrap_or_default();
25 if !matches!(path_only, "/health" | "/live" | "/ready" | "/version") {
26 return None;
27 }
28 if !matches!(method, "GET" | "HEAD") {
29 return Some(FrameworkBuiltinResponse {
30 status_code: 405,
31 status_text: "Method Not Allowed",
32 body: "method not allowed\n".to_owned(),
33 });
34 }
35 match path_only {
36 "/version" => Some(FrameworkBuiltinResponse {
37 status_code: 200,
38 status_text: "OK",
39 body: format!("pharos {version} {target_id}\n"),
40 }),
41 "/health" => {
42 let ready = stack
43 .map(&stack_ready)
44 .or_else(|| app.map(&app_ready))
45 .unwrap_or(false);
46 let failed_app_count = stack.map(|value| value.failed_apps.len()).unwrap_or(0);
47 Some(FrameworkBuiltinResponse {
48 status_code: 200,
49 status_text: "OK",
50 body: format!(
51 "server_status=green\nprobe=liveness\nreadiness_status={}\nfailed_app_count={}\n",
52 if ready { "ready" } else { "not_ready" },
53 failed_app_count
54 ),
55 })
56 }
57 "/live" => Some(FrameworkBuiltinResponse {
58 status_code: 200,
59 status_text: "OK",
60 body: "server_status=live\n".to_owned(),
61 }),
62 "/ready" => {
63 let ready = stack
64 .map(stack_ready)
65 .or_else(|| app.map(app_ready))
66 .unwrap_or(false);
67 Some(FrameworkBuiltinResponse {
68 status_code: if ready { 200 } else { 503 },
69 status_text: if ready { "OK" } else { "Service Unavailable" },
70 body: if ready {
71 "server_status=ready\n".to_owned()
72 } else {
73 "server_status=not_ready\n".to_owned()
74 },
75 })
76 }
77 _ => None,
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84
85 #[test]
86 fn health_distinguishes_live_from_ready() {
87 let stack = RuntimeStack::default();
88 let health = framework_builtin_response(
89 "GET",
90 "/health",
91 None,
92 Some(&stack),
93 "0.0.0",
94 "test-target",
95 |_| false,
96 |_| false,
97 )
98 .expect("health response");
99 assert_eq!(health.status_code, 200);
100 assert!(health.body.contains("readiness_status=not_ready"));
101
102 let ready = framework_builtin_response(
103 "GET",
104 "/ready",
105 None,
106 Some(&stack),
107 "0.0.0",
108 "test-target",
109 |_| false,
110 |_| false,
111 )
112 .expect("ready response");
113 assert_eq!(ready.status_code, 503);
114 }
115}