1use async_trait::async_trait;
4use http::StatusCode;
5use saluki_api::{
6 extract::State,
7 response::IntoResponse,
8 routing::{get, Router},
9 APIHandler, DynamicRoute, EndpointType,
10};
11use saluki_common::sync::shutdown::ShutdownHandle;
12use saluki_config::GenericConfiguration;
13use saluki_core::{
14 diagnostic::DiagnosticsEmitter,
15 runtime::{state::DataspaceRegistry, InitializationError, Supervisable, SupervisorFuture},
16 support::SubsystemIdentifier,
17};
18use saluki_error::generic_error;
19use serde_json::Value;
20
21#[derive(Clone)]
23pub struct ConfigState {
24 config: GenericConfiguration,
25}
26
27pub struct ConfigAPIHandler {
33 state: ConfigState,
34}
35
36impl ConfigAPIHandler {
37 fn new(config: GenericConfiguration) -> Self {
38 Self {
39 state: ConfigState { config },
40 }
41 }
42
43 async fn config_handler(State(state): State<ConfigState>) -> impl IntoResponse {
44 match state.config.as_typed::<Value>() {
45 Ok(config) => (StatusCode::OK, serde_json::to_string(&config).unwrap()).into_response(),
46 Err(e) => (
47 StatusCode::INTERNAL_SERVER_ERROR,
48 format!("Failed to get configuration: {}", e),
49 )
50 .into_response(),
51 }
52 }
53}
54
55impl APIHandler for ConfigAPIHandler {
56 type State = ConfigState;
57
58 fn generate_initial_state(&self) -> Self::State {
59 self.state.clone()
60 }
61
62 fn generate_routes(&self) -> Router<Self::State> {
63 Router::new().route("/config", get(Self::config_handler))
64 }
65}
66
67pub struct ConfigWorker {
73 handler: ConfigAPIHandler,
74}
75
76impl ConfigWorker {
77 pub fn new(config: GenericConfiguration) -> Self {
79 Self {
80 handler: ConfigAPIHandler::new(config),
81 }
82 }
83}
84
85#[async_trait]
86impl Supervisable for ConfigWorker {
87 fn name(&self) -> &str {
88 "config-api"
89 }
90
91 async fn initialize(&self, process_shutdown: ShutdownHandle) -> Result<SupervisorFuture, InitializationError> {
92 let config_route = DynamicRoute::http(EndpointType::Privileged, &self.handler);
93
94 let config = self.handler.state.config.clone();
95
96 Ok(Box::pin(async move {
97 let dataspace =
98 DataspaceRegistry::try_current().ok_or_else(|| generic_error!("Dataspace not available."))?;
99
100 dataspace.assert(config_route, "config-api");
101
102 let diagnostics =
103 DiagnosticsEmitter::from_dataspace(SubsystemIdentifier::from_segments(["config-api"]), dataspace);
104 diagnostics.register_collector("runtime_config_dump.yaml", move || {
105 config
106 .as_typed::<serde_json::Value>()
107 .map(|v| serde_json::to_vec_pretty(&v).unwrap_or_default())
108 .unwrap_or_default()
109 });
110
111 process_shutdown.await;
112 Ok(())
113 }))
114 }
115}