1pub(crate) use crate::application::json_input::{
4 json_bool_field, json_f32_field, json_string_field, json_usize_field,
5};
6pub(crate) use crate::application::{
7 AdminUseCases, CreateDocumentInput, CreateEdgeInput, CreateEntityOutput, CreateKvInput,
8 CreateNodeEmbeddingInput, CreateNodeGraphLinkInput, CreateNodeInput, CreateNodeTableLinkInput,
9 CreateRowInput, CreateVectorInput, DeleteEntityInput, EntityUseCases, ExecuteQueryInput,
10 ExplainQueryInput, GraphCentralityInput, GraphClusteringInput, GraphCommunitiesInput,
11 GraphComponentsInput, GraphCyclesInput, GraphHitsInput, GraphNeighborhoodInput,
12 GraphPersonalizedPageRankInput, GraphShortestPathInput, GraphTopologicalSortInput,
13 GraphTraversalInput, GraphUseCases, InspectNativeArtifactInput, NativeUseCases,
14 PatchEntityInput, PatchEntityOperation, PatchEntityOperationType, SearchHybridInput,
15 SearchIvfInput, SearchMultimodalInput, SearchSimilarInput, SearchTextInput, TreeUseCases,
16};
17use std::collections::{BTreeMap, HashMap};
18use std::io::{self, Read, Write};
19use std::net::{TcpListener, TcpStream};
20use std::sync::atomic::{AtomicU64, Ordering};
21use std::thread;
22use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
23
24use std::sync::Arc;
25
26use crate::api::{RedDBError, RedDBOptions, RedDBResult};
27use crate::auth::store::AuthStore;
28use crate::catalog::{CatalogModelSnapshot, CollectionDescriptor, CollectionModel, SchemaMode};
29use crate::health::{HealthProvider, HealthReport, HealthState};
30use crate::json::{parse_json, to_vec as json_to_vec, Map, Value as JsonValue};
31use crate::runtime::{
32 RedDBRuntime, RuntimeFilter, RuntimeFilterValue, RuntimeGraphCentralityAlgorithm,
33 RuntimeGraphCentralityResult, RuntimeGraphClusteringResult, RuntimeGraphCommunityAlgorithm,
34 RuntimeGraphCommunityResult, RuntimeGraphComponentsMode, RuntimeGraphComponentsResult,
35 RuntimeGraphCyclesResult, RuntimeGraphDirection, RuntimeGraphHitsResult,
36 RuntimeGraphNeighborhoodResult, RuntimeGraphPathAlgorithm, RuntimeGraphPathResult,
37 RuntimeGraphPattern, RuntimeGraphProjection, RuntimeGraphTopologicalSortResult,
38 RuntimeGraphTraversalResult, RuntimeGraphTraversalStrategy, RuntimeIvfSearchResult,
39 RuntimeQueryWeights, RuntimeStats, ScanCursor, ScanPage,
40};
41use crate::storage::unified::devx::refs::{NodeRef, TableRef, VectorRef};
42use crate::storage::unified::dsl::{MatchComponents, QueryResult as DslQueryResult};
43use crate::storage::unified::{MetadataValue, RefTarget, SparseVector};
44use crate::storage::{CrossRef, EntityData, EntityId, EntityKind, SimilarResult, UnifiedEntity};
45use crate::transport::execution_admission::ExecutionAdmission;
46use reddb_types::Value;
47
48fn analytics_job_json(job: &crate::PhysicalAnalyticsJob) -> JsonValue {
49 crate::presentation::admin_json::analytics_job_json(job)
50}
51
52#[cfg(test)]
54pub(crate) static RED_ADMIN_TOKEN_ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59 use crate::api::RedDBOptions;
60 use crate::health::HealthReport;
61 use crate::transport::{TransportListenerFailure, TransportListenerState, TransportReadiness};
62
63 #[test]
64 fn server_options_default_http_body_limit_is_32_mib() {
65 assert_eq!(ServerOptions::default().max_body_bytes, 32 * 1024 * 1024);
66 }
67
68 #[test]
69 fn health_json_reports_transport_listeners() {
70 let runtime = RedDBRuntime::with_options(RedDBOptions::in_memory()).expect("runtime");
71 let mut options = ServerOptions::default();
72 options.transport_readiness = TransportReadiness {
73 active: vec![TransportListenerState {
74 transport: "grpc".to_string(),
75 bind_addr: "127.0.0.1:5000".to_string(),
76 explicit: true,
77 }],
78 failed: vec![TransportListenerFailure {
79 transport: "http".to_string(),
80 bind_addr: "127.0.0.1:5000".to_string(),
81 explicit: false,
82 reason: "http listener bind 127.0.0.1:5000: address in use".to_string(),
83 }],
84 };
85 let server = RedDBServer::with_options(runtime, options);
86
87 let payload = server.health_json_with_transport(&HealthReport::healthy());
88 let JsonValue::Object(root) = payload else {
89 panic!("health payload should be an object");
90 };
91 let Some(JsonValue::Object(listeners)) = root.get("transport_listeners") else {
92 panic!("health payload should include transport_listeners");
93 };
94 let Some(JsonValue::Array(active)) = listeners.get("active") else {
95 panic!("transport_listeners.active should be an array");
96 };
97 let Some(JsonValue::Array(failed)) = listeners.get("failed") else {
98 panic!("transport_listeners.failed should be an array");
99 };
100
101 assert_eq!(active.len(), 1);
102 assert_eq!(failed.len(), 1);
103 }
104}
105
106fn graph_projection_json(projection: &crate::PhysicalGraphProjection) -> JsonValue {
107 crate::presentation::admin_json::graph_projection_json(projection)
108}
109
110mod axum_edge;
111pub mod handlers_admin;
112mod handlers_admin_metrics;
113mod handlers_admin_status;
114mod handlers_ai;
115mod handlers_ai_model_cache;
116pub(crate) mod handlers_auth;
117mod handlers_backup;
118mod handlers_browser_auth;
119mod handlers_cache;
120mod handlers_capabilities;
121mod handlers_collection_policy;
122mod handlers_ec;
123pub(crate) mod handlers_entity;
124mod handlers_failover;
125mod handlers_geo;
126mod handlers_graph;
127mod handlers_iam_policy;
128mod handlers_keyed;
129mod handlers_log;
130mod handlers_metrics;
131mod handlers_ops;
132mod handlers_ops_policy;
133pub mod ui_bridge;
137mod ws_edge;
138pub mod ui_bundle_resolver;
142pub mod ui_auth;
145mod ui_static;
149pub mod ui_deeplink;
154mod execution_watchdog;
159pub(crate) mod handlers_query;
160mod handlers_replication;
161mod handlers_topology;
162mod handlers_vector;
163pub mod header_escape_guard;
164pub mod http_connection_limiter;
165pub mod http_handler_metrics;
166pub mod http_limits;
167pub mod http_principal_limiter;
168pub mod http_request_metrics;
169pub mod ingest_pipeline;
170pub mod output_stream;
171mod patch_support;
172mod request_body;
173mod request_context;
174mod request_identity;
175pub(crate) mod route_catalog;
176mod routes;
177pub(crate) use routes::discovered_route_catalog;
178mod routing;
179mod serverless_support;
180pub mod tls;
181mod transport;
182mod transport_surface;
183
184#[doc(hidden)]
188pub fn command_coverage_matrix() -> String {
189 route_catalog::render_command_coverage_matrix(routes::discovered_route_catalog())
190}
191
192use self::execution_watchdog::ExecutionWatchdog;
193use self::handlers_ai::*;
194use self::handlers_entity::*;
195use self::handlers_graph::*;
196use self::handlers_keyed::*;
197use self::handlers_metrics::*;
198use self::handlers_ops::*;
199use self::handlers_query::*;
200use self::http_connection_limiter::{
201 HandlerDeadline, HttpConnectionLimiter, MonotonicClock, SystemMonotonicClock,
202};
203use self::http_handler_metrics::{HttpHandlerMetrics, HttpRejectReason, HttpTransport};
204pub use self::http_limits::{
205 HttpLimitsCliInput, HttpLimitsResolved, DEFAULT_HANDLER_TIMEOUT_MS, DEFAULT_RETRY_AFTER_SECS,
206};
207use self::http_principal_limiter::PrincipalConnectionLimiter;
208use self::http_request_metrics::HttpRequestMetrics;
209use self::patch_support::*;
210pub(crate) use self::request_body::is_trivial_scalar_sql;
211use self::request_body::*;
212use self::routing::*;
213use self::serverless_support::*;
214use self::transport::*;
215
216pub(crate) fn command_catalog() -> &'static route_catalog::CommandCatalog {
217 routes::discovered_route_catalog()
218}
219
220#[derive(Debug, Clone, Copy, PartialEq, Eq)]
226pub enum ServerSurface {
227 Public,
229 AdminOnly,
233 MetricsOnly,
237}
238
239#[derive(Debug, Clone)]
240pub struct ServerOptions {
241 pub bind_addr: String,
242 pub max_body_bytes: usize,
243 pub read_timeout_ms: u64,
244 pub write_timeout_ms: u64,
245 pub max_scan_limit: usize,
246 pub surface: ServerSurface,
250 pub transport_readiness: crate::transport::TransportReadiness,
251 pub websocket_allowed_origins: Vec<String>,
259 pub ui_dir: Option<std::path::PathBuf>,
268}
269
270pub const DEFAULT_HTTP_MAX_BODY_BYTES: usize = 32 * 1024 * 1024;
271
272impl Default for ServerOptions {
273 fn default() -> Self {
274 Self {
275 bind_addr: "127.0.0.1:5000".to_string(),
276 max_body_bytes: DEFAULT_HTTP_MAX_BODY_BYTES,
277 read_timeout_ms: 5_000,
278 write_timeout_ms: 5_000,
279 max_scan_limit: 1_000,
280 surface: ServerSurface::Public,
281 transport_readiness: crate::transport::TransportReadiness::default(),
282 websocket_allowed_origins: Vec::new(),
283 ui_dir: None,
284 }
285 }
286}
287
288pub struct ServerReplicationState {
290 pub config: crate::replication::ReplicationConfig,
291 pub primary: Option<crate::replication::primary::PrimaryReplication>,
292}
293
294#[derive(Clone)]
295pub struct RedDBServer {
296 runtime: RedDBRuntime,
297 options: ServerOptions,
298 auth_store: Option<Arc<AuthStore>>,
299 replication: Option<Arc<ServerReplicationState>>,
300 http_limiter: HttpConnectionLimiter,
305 execution_admission: ExecutionAdmission,
309 execution_watchdog: ExecutionWatchdog,
313 health_limiter: HttpConnectionLimiter,
319 handler_timeout: Duration,
325 handler_clock: Arc<dyn MonotonicClock>,
332 slow_inject_ms: Arc<AtomicU64>,
341 http_metrics: HttpHandlerMetrics,
347 http_request_metrics: HttpRequestMetrics,
353 retry_after_secs: u64,
357 principal_limiter: PrincipalConnectionLimiter,
365 pub(crate) stream_capacity: Arc<output_stream::StreamCapacityRegistry>,
373 pub(crate) lease_registry: Arc<output_stream::LeaseRegistry>,
378 pub(crate) cursor_registry: Arc<output_stream::CursorRegistry>,
383}
384
385const DEFAULT_HANDLER_TIMEOUT: Duration = Duration::from_millis(30_000);
387
388const HEALTH_HANDLER_CAP: usize = 4;
391
392#[derive(Debug, Clone, Copy, PartialEq, Eq)]
393enum ServerlessWarmupScope {
394 Indexes,
395 GraphProjections,
396 AnalyticsJobs,
397 NativeArtifacts,
398}
399
400#[derive(Debug, Clone, Copy, PartialEq, Eq)]
401enum DeploymentProfile {
402 Embedded,
403 Server,
404 Serverless,
405}
406
407fn percent_decode_path_segment(input: &str) -> Result<String, String> {
408 let bytes = input.as_bytes();
409 let mut out = Vec::with_capacity(bytes.len());
410 let mut index = 0;
411 while index < bytes.len() {
412 match bytes[index] {
413 b'%' => {
414 if index + 2 >= bytes.len() {
415 return Err("truncated percent escape".to_string());
416 }
417 let high = hex_value(bytes[index + 1])
418 .ok_or_else(|| "invalid percent escape".to_string())?;
419 let low = hex_value(bytes[index + 2])
420 .ok_or_else(|| "invalid percent escape".to_string())?;
421 out.push((high << 4) | low);
422 index += 3;
423 }
424 byte => {
425 out.push(byte);
426 index += 1;
427 }
428 }
429 }
430 String::from_utf8(out).map_err(|_| "path segment is not valid UTF-8".to_string())
431}
432
433fn hex_value(byte: u8) -> Option<u8> {
434 match byte {
435 b'0'..=b'9' => Some(byte - b'0'),
436 b'a'..=b'f' => Some(byte - b'a' + 10),
437 b'A'..=b'F' => Some(byte - b'A' + 10),
438 _ => None,
439 }
440}
441
442#[derive(Debug, Clone)]
443struct ParsedQueryRequest {
444 query: String,
445 entity_types: Option<Vec<String>>,
446 capabilities: Option<Vec<String>>,
447 commit_policy: Option<crate::replication::CommitPolicy>,
448 params: Option<Vec<crate::runtime::query_request::ParamValue>>,
451}
452
453#[derive(Debug, Clone, Copy)]
454enum PatchOperationType {
455 Set,
456 Replace,
457 Unset,
458}
459
460#[derive(Debug, Clone)]
461struct PatchOperation {
462 op: PatchOperationType,
463 path: Vec<String>,
464 value: Option<JsonValue>,
465}
466
467impl RedDBServer {
468 pub fn new(runtime: RedDBRuntime) -> Self {
469 Self::with_options(runtime, ServerOptions::default())
470 }
471
472 pub fn from_database_options(
473 db_options: RedDBOptions,
474 server_options: ServerOptions,
475 ) -> RedDBResult<Self> {
476 let runtime = RedDBRuntime::with_options(db_options)?;
477 Ok(Self::with_options(runtime, server_options))
478 }
479
480 pub fn with_options(runtime: RedDBRuntime, options: ServerOptions) -> Self {
481 let http_limits = HttpLimitsResolved::builtin_defaults();
482 let execution_admission = runtime.configure_execution_admission(
483 http_limits.execution_workers,
484 http_limits
485 .max_handlers
486 .saturating_sub(http_limits.execution_workers),
487 http_limits.handler_timeout_ms,
488 );
489 Self {
490 runtime,
491 options,
492 auth_store: None,
493 replication: None,
494 http_limiter: HttpConnectionLimiter::new(http_limits.max_handlers),
495 execution_admission,
496 execution_watchdog: ExecutionWatchdog::default(),
497 health_limiter: HttpConnectionLimiter::new(HEALTH_HANDLER_CAP),
498 handler_timeout: DEFAULT_HANDLER_TIMEOUT,
499 handler_clock: Arc::new(SystemMonotonicClock::new()),
500 slow_inject_ms: Arc::new(AtomicU64::new(0)),
501 http_metrics: HttpHandlerMetrics::new(),
502 http_request_metrics: HttpRequestMetrics::new(),
503 retry_after_secs: DEFAULT_RETRY_AFTER_SECS,
504 principal_limiter: PrincipalConnectionLimiter::new(
505 http_limits.max_inflight_per_principal,
506 ),
507 stream_capacity: output_stream::StreamCapacityRegistry::new(),
508 lease_registry: output_stream::LeaseRegistry::new(),
509 cursor_registry: output_stream::CursorRegistry::new(),
510 }
511 }
512
513 #[doc(hidden)]
514 pub fn stream_capacity(&self) -> &Arc<output_stream::StreamCapacityRegistry> {
515 &self.stream_capacity
516 }
517
518 #[doc(hidden)]
519 pub fn lease_registry(&self) -> &Arc<output_stream::LeaseRegistry> {
520 &self.lease_registry
521 }
522
523 #[doc(hidden)]
524 pub fn cursor_registry(&self) -> &Arc<output_stream::CursorRegistry> {
525 &self.cursor_registry
526 }
527
528 #[doc(hidden)]
529 pub fn http_metrics(&self) -> &HttpHandlerMetrics {
530 &self.http_metrics
531 }
532
533 #[doc(hidden)]
536 pub fn http_request_metrics(&self) -> &HttpRequestMetrics {
537 &self.http_request_metrics
538 }
539
540 #[doc(hidden)]
545 pub fn with_http_limiter_cap(mut self, cap: usize) -> Self {
546 self.http_limiter = HttpConnectionLimiter::new(cap);
547 let execution_workers = self.execution_admission.active_max().min(cap);
548 self.execution_admission = self.runtime.configure_execution_admission(
549 execution_workers,
550 cap.saturating_sub(execution_workers),
551 self.handler_timeout.as_millis().min(u64::MAX as u128) as u64,
552 );
553 self
554 }
555
556 pub fn with_http_limits(mut self, limits: HttpLimitsResolved) -> Self {
561 self.http_limiter = HttpConnectionLimiter::new(limits.max_handlers);
562 self.execution_admission = self.runtime.configure_execution_admission(
563 limits.execution_workers,
564 limits.max_handlers.saturating_sub(limits.execution_workers),
565 limits.handler_timeout_ms,
566 );
567 self.handler_timeout = Duration::from_millis(limits.handler_timeout_ms);
568 self.retry_after_secs = limits.retry_after_secs;
569 self.principal_limiter = PrincipalConnectionLimiter::new(limits.max_inflight_per_principal);
570 self
571 }
572
573 #[doc(hidden)]
578 pub fn with_principal_inflight_cap(mut self, cap: usize) -> Self {
579 self.principal_limiter = PrincipalConnectionLimiter::new(cap);
580 self
581 }
582
583 #[doc(hidden)]
584 pub fn principal_limiter(&self) -> &PrincipalConnectionLimiter {
585 &self.principal_limiter
586 }
587
588 #[doc(hidden)]
589 pub fn retry_after_secs(&self) -> u64 {
590 self.retry_after_secs
591 }
592
593 #[doc(hidden)]
594 pub fn http_limiter(&self) -> &HttpConnectionLimiter {
595 &self.http_limiter
596 }
597
598 pub(crate) fn execution_admission(&self) -> &ExecutionAdmission {
599 &self.execution_admission
600 }
601
602 #[doc(hidden)]
603 pub fn http_execution_active(&self) -> usize {
604 self.execution_admission.active()
605 }
606
607 #[doc(hidden)]
608 pub fn http_execution_queued(&self) -> usize {
609 self.execution_admission.queued()
610 }
611
612 #[doc(hidden)]
613 pub fn http_execution_workers(&self) -> usize {
614 self.execution_admission.active_max()
615 }
616
617 #[doc(hidden)]
619 pub fn with_http_execution_workers(mut self, workers: usize) -> Self {
620 assert!(workers > 0, "HTTP execution workers must be positive");
621 let workers = workers.min(self.http_limiter.cap());
622 self.execution_admission = self.runtime.configure_execution_admission(
623 workers,
624 self.http_limiter.cap().saturating_sub(workers),
625 self.handler_timeout.as_millis().min(u64::MAX as u128) as u64,
626 );
627 self
628 }
629
630 #[doc(hidden)]
632 pub fn health_limiter(&self) -> &HttpConnectionLimiter {
633 &self.health_limiter
634 }
635
636 #[doc(hidden)]
638 pub fn with_health_limiter_cap(mut self, cap: usize) -> Self {
639 self.health_limiter = HttpConnectionLimiter::new(cap);
640 self
641 }
642
643 #[doc(hidden)]
646 pub fn with_handler_timeout(mut self, timeout: Duration) -> Self {
647 self.handler_timeout = timeout;
648 self
649 }
650
651 #[doc(hidden)]
652 pub fn handler_timeout(&self) -> Duration {
653 self.handler_timeout
654 }
655
656 #[doc(hidden)]
661 pub fn with_handler_clock(mut self, clock: Arc<dyn MonotonicClock>) -> Self {
662 self.handler_clock = clock;
663 self
664 }
665
666 #[doc(hidden)]
671 pub fn set_test_slow_inject_ms(&self, ms: u64) {
672 self.slow_inject_ms.store(ms, Ordering::Release);
673 }
674
675 fn apply_test_slow_inject(&self) {
676 let inject_ms = self.slow_inject_ms.load(Ordering::Acquire);
677 if inject_ms == 0 {
678 return;
679 }
680 if inject_ms != u64::MAX {
681 thread::sleep(Duration::from_millis(inject_ms));
682 return;
683 }
684 while self.slow_inject_ms.load(Ordering::Acquire) == u64::MAX {
685 thread::park_timeout(Duration::from_millis(1));
686 }
687 }
688
689 fn test_slow_inject_is_blocking_gate(&self) -> bool {
690 self.slow_inject_ms.load(Ordering::Acquire) == u64::MAX
691 }
692
693 pub fn with_auth(mut self, auth_store: Arc<AuthStore>) -> Self {
697 self.runtime.set_auth_store(Arc::clone(&auth_store));
698 self.auth_store = Some(auth_store);
699 self
700 }
701
702 pub fn with_replication(mut self, state: Arc<ServerReplicationState>) -> Self {
704 self.replication = Some(state);
705 self
706 }
707
708 pub fn with_websocket_allowed_origins(mut self, origins: Vec<String>) -> Self {
713 self.options.websocket_allowed_origins = origins;
714 self
715 }
716
717 pub(crate) fn websocket_allowed_origins(&self) -> &[String] {
719 &self.options.websocket_allowed_origins
720 }
721
722 pub fn with_ui_dir(mut self, dir: std::path::PathBuf) -> Self {
728 self.options.ui_dir = Some(dir);
729 self
730 }
731
732 pub(crate) fn ui_dir(&self) -> Option<&std::path::Path> {
735 self.options.ui_dir.as_deref()
736 }
737
738 pub fn with_browser_token_authority(
745 self,
746 authority: Arc<crate::auth::browser_token::BrowserTokenAuthority>,
747 ) -> Self {
748 self.runtime.set_browser_token_authority(Some(authority));
749 self
750 }
751
752 pub fn runtime(&self) -> &RedDBRuntime {
753 &self.runtime
754 }
755
756 pub fn options(&self) -> &ServerOptions {
757 &self.options
758 }
759
760 fn admin_use_cases(&self) -> AdminUseCases<'_, RedDBRuntime> {
761 AdminUseCases::new(&self.runtime)
762 }
763
764 fn entity_use_cases(&self) -> EntityUseCases<'_, RedDBRuntime> {
765 EntityUseCases::new(&self.runtime)
766 }
767
768 fn graph_use_cases(&self) -> GraphUseCases<'_, RedDBRuntime> {
769 GraphUseCases::new(&self.runtime)
770 }
771
772 fn native_use_cases(&self) -> NativeUseCases<'_, RedDBRuntime> {
773 NativeUseCases::new(&self.runtime)
774 }
775
776 fn tree_use_cases(&self) -> TreeUseCases<'_, RedDBRuntime> {
777 TreeUseCases::new(&self.runtime)
778 }
779
780 fn transport_readiness_json(&self) -> JsonValue {
781 let active = self
782 .options
783 .transport_readiness
784 .active
785 .iter()
786 .map(|listener| {
787 let mut object = Map::new();
788 object.insert(
789 "transport".to_string(),
790 JsonValue::String(listener.transport.clone()),
791 );
792 object.insert(
793 "bind_addr".to_string(),
794 JsonValue::String(listener.bind_addr.clone()),
795 );
796 object.insert("explicit".to_string(), JsonValue::Bool(listener.explicit));
797 JsonValue::Object(object)
798 })
799 .collect();
800 let failed = self
801 .options
802 .transport_readiness
803 .failed
804 .iter()
805 .map(|listener| {
806 let mut object = Map::new();
807 object.insert(
808 "transport".to_string(),
809 JsonValue::String(listener.transport.clone()),
810 );
811 object.insert(
812 "bind_addr".to_string(),
813 JsonValue::String(listener.bind_addr.clone()),
814 );
815 object.insert("explicit".to_string(), JsonValue::Bool(listener.explicit));
816 object.insert(
817 "reason".to_string(),
818 JsonValue::String(listener.reason.clone()),
819 );
820 JsonValue::Object(object)
821 })
822 .collect();
823
824 let mut object = Map::new();
825 object.insert("active".to_string(), JsonValue::Array(active));
826 object.insert("failed".to_string(), JsonValue::Array(failed));
827 JsonValue::Object(object)
828 }
829
830 fn handle_grpc_discovery(&self) -> HttpResponse {
831 let mut methods = Map::new();
832 methods.insert(
833 "query".to_string(),
834 JsonValue::String("reddb.v1.RedDB/Query".to_string()),
835 );
836 methods.insert(
837 "batch_query".to_string(),
838 JsonValue::String("reddb.v1.RedDB/BatchQuery".to_string()),
839 );
840 methods.insert(
841 "health".to_string(),
842 JsonValue::String("reddb.v1.RedDB/Health".to_string()),
843 );
844 methods.insert(
845 "prepare".to_string(),
846 JsonValue::String("reddb.v1.RedDB/Prepare".to_string()),
847 );
848 methods.insert(
849 "execute_prepared".to_string(),
850 JsonValue::String("reddb.v1.RedDB/ExecutePrepared".to_string()),
851 );
852
853 let mut examples = Map::new();
854 examples.insert(
855 "query".to_string(),
856 JsonValue::String(
857 "grpcurl -plaintext -d '{\"query\":\"SELECT 1\"}' 127.0.0.1:5000 reddb.v1.RedDB/Query"
858 .to_string(),
859 ),
860 );
861 examples.insert(
862 "query_with_params".to_string(),
863 JsonValue::String(
864 "grpcurl -plaintext -d '{\"query\":\"SELECT $1 AS value\",\"params\":[{\"intValue\":42}]}' 127.0.0.1:5000 reddb.v1.RedDB/Query"
865 .to_string(),
866 ),
867 );
868 examples.insert(
869 "health".to_string(),
870 JsonValue::String(
871 "grpcurl -plaintext -d '{}' 127.0.0.1:5000 reddb.v1.RedDB/Health".to_string(),
872 ),
873 );
874
875 let mut object = Map::new();
876 object.insert("ok".to_string(), JsonValue::Bool(true));
877 object.insert(
878 "service".to_string(),
879 JsonValue::String("reddb.v1.RedDB".to_string()),
880 );
881 object.insert(
882 "package".to_string(),
883 JsonValue::String("reddb.v1".to_string()),
884 );
885 object.insert(
886 "proto".to_string(),
887 JsonValue::String("crates/reddb-grpc-proto/proto/reddb.proto".to_string()),
888 );
889 object.insert("methods".to_string(), JsonValue::Object(methods));
890 object.insert("examples".to_string(), JsonValue::Object(examples));
891 object.insert(
892 "transport_listeners".to_string(),
893 self.transport_readiness_json(),
894 );
895 object.insert(
896 "hint".to_string(),
897 JsonValue::String(
898 "If grpcurl cannot list services, pass the proto file with -import-path crates/reddb-grpc-proto/proto -proto reddb.proto."
899 .to_string(),
900 ),
901 );
902 json_response(200, JsonValue::Object(object))
903 }
904
905 fn handle_query_contract(&self) -> HttpResponse {
906 let mut examples = Map::new();
907 examples.insert(
908 "raw_sql".to_string(),
909 JsonValue::String("curl -sS http://127.0.0.1:5000/query -d 'SELECT 1'".to_string()),
910 );
911 examples.insert(
912 "json_query".to_string(),
913 JsonValue::String(
914 "curl -sS http://127.0.0.1:5000/query -H 'content-type: application/json' -d '{\"query\":\"SELECT 1\"}'"
915 .to_string(),
916 ),
917 );
918 examples.insert(
919 "json_query_with_params".to_string(),
920 JsonValue::String(
921 "curl -sS http://127.0.0.1:5000/query -H 'content-type: application/json' -d '{\"query\":\"SELECT $1 AS value\",\"params\":[42]}'"
922 .to_string(),
923 ),
924 );
925
926 let mut request_body = Map::new();
927 request_body.insert(
928 "query".to_string(),
929 JsonValue::String("required string".to_string()),
930 );
931 request_body.insert(
932 "params".to_string(),
933 JsonValue::String("optional array".to_string()),
934 );
935
936 let mut response_shape = Map::new();
937 response_shape.insert(
938 "columns".to_string(),
939 JsonValue::String("projected column names".to_string()),
940 );
941 response_shape.insert(
942 "records[].values".to_string(),
943 JsonValue::String("only projected values".to_string()),
944 );
945 response_shape.insert(
946 "records[].meta".to_string(),
947 JsonValue::String("internal metadata when present".to_string()),
948 );
949
950 let mut object = Map::new();
951 object.insert("ok".to_string(), JsonValue::Bool(false));
952 object.insert(
953 "code".to_string(),
954 JsonValue::String("method_not_allowed".to_string()),
955 );
956 object.insert(
957 "message".to_string(),
958 JsonValue::String("/query accepts POST requests".to_string()),
959 );
960 object.insert(
961 "hint".to_string(),
962 JsonValue::String(
963 "Send raw SQL in the body, or JSON with a string 'query' field.".to_string(),
964 ),
965 );
966 object.insert("method".to_string(), JsonValue::String("POST".to_string()));
967 object.insert("path".to_string(), JsonValue::String("/query".to_string()));
968 object.insert("request_body".to_string(), JsonValue::Object(request_body));
969 object.insert(
970 "response_shape".to_string(),
971 JsonValue::Object(response_shape),
972 );
973 object.insert("examples".to_string(), JsonValue::Object(examples));
974 object.insert(
975 "docs".to_string(),
976 JsonValue::String("https://reddb.io/docs/query".to_string()),
977 );
978
979 json_response(405, JsonValue::Object(object))
980 .with_header("Allow", http::HeaderValue::from_static("POST"))
981 }
982
983 fn handle_root_discovery(&self) -> HttpResponse {
984 let mut endpoints = Map::new();
985 endpoints.insert(
986 "health".to_string(),
987 JsonValue::String("GET /health".to_string()),
988 );
989 endpoints.insert(
990 "ready".to_string(),
991 JsonValue::String("GET /ready".to_string()),
992 );
993 endpoints.insert(
994 "query".to_string(),
995 JsonValue::String("POST /query".to_string()),
996 );
997 endpoints.insert(
998 "query_readiness".to_string(),
999 JsonValue::String("GET /ready/query".to_string()),
1000 );
1001 endpoints.insert(
1002 "catalog".to_string(),
1003 JsonValue::String("GET /catalog".to_string()),
1004 );
1005 endpoints.insert(
1006 "deployment_profiles".to_string(),
1007 JsonValue::String("GET /deployment/profiles".to_string()),
1008 );
1009
1010 let mut examples = Map::new();
1011 examples.insert(
1012 "http_raw_sql".to_string(),
1013 JsonValue::String("curl -sS http://127.0.0.1:5000/query -d 'SELECT 1'".to_string()),
1014 );
1015 examples.insert(
1016 "http_json_query".to_string(),
1017 JsonValue::String(
1018 "curl -sS http://127.0.0.1:5000/query -H 'content-type: application/json' -d '{\"query\":\"SELECT 1\"}'"
1019 .to_string(),
1020 ),
1021 );
1022 examples.insert(
1023 "http_json_query_with_params".to_string(),
1024 JsonValue::String(
1025 "curl -sS http://127.0.0.1:5000/query -H 'content-type: application/json' -d '{\"query\":\"SELECT $1 AS value\",\"params\":[42]}'"
1026 .to_string(),
1027 ),
1028 );
1029
1030 let mut object = Map::new();
1031 object.insert("ok".to_string(), JsonValue::Bool(true));
1032 object.insert(
1033 "service".to_string(),
1034 JsonValue::String("reddb".to_string()),
1035 );
1036 object.insert(
1037 "version".to_string(),
1038 JsonValue::String(env!("CARGO_PKG_VERSION").to_string()),
1039 );
1040 object.insert("endpoints".to_string(), JsonValue::Object(endpoints));
1041 object.insert("examples".to_string(), JsonValue::Object(examples));
1042 object.insert(
1043 "docs".to_string(),
1044 JsonValue::String("https://reddb.io/docs".to_string()),
1045 );
1046 object.insert(
1047 "transport_listeners".to_string(),
1048 self.transport_readiness_json(),
1049 );
1050 json_response(200, JsonValue::Object(object))
1051 }
1052
1053 fn health_json_with_transport(&self, report: &HealthReport) -> JsonValue {
1054 let mut value = crate::presentation::ops_json::health_json(report);
1055 if let JsonValue::Object(ref mut object) = value {
1056 object.insert(
1057 "transport_listeners".to_string(),
1058 self.transport_readiness_json(),
1059 );
1060 }
1061 value
1062 }
1063
1064 pub fn serve(&self) -> io::Result<()> {
1065 let listener = TcpListener::bind(&self.options.bind_addr)?;
1066 self.serve_on(listener)
1067 }
1068
1069 pub fn serve_on(&self, listener: TcpListener) -> io::Result<()> {
1076 let runtime = axum_edge::build_edge_runtime()?;
1077 runtime.block_on(
1078 self.clone()
1079 .serve_edge_on_std(listener, HttpTransport::Http),
1080 )
1081 }
1082
1083 pub fn serve_one_on(&self, listener: TcpListener) -> io::Result<()> {
1087 let runtime = axum_edge::build_background_edge_runtime()?;
1088 let server = self.clone();
1089 runtime.block_on(async move {
1090 listener.set_nonblocking(true)?;
1091 let listener = tokio::net::TcpListener::from_std(listener)?;
1092 let (stream, _peer) = listener.accept().await?;
1093 server.serve_edge_one(stream).await;
1094 Ok(())
1095 })
1096 }
1097
1098 pub fn serve_in_background(&self) -> thread::JoinHandle<io::Result<()>> {
1099 let server = self.clone();
1100 thread::spawn(move || server.serve())
1101 }
1102
1103 pub fn serve_in_background_on(
1104 &self,
1105 listener: TcpListener,
1106 ) -> thread::JoinHandle<io::Result<()>> {
1107 let server = self.clone();
1108 thread::spawn(move || {
1109 let runtime = axum_edge::build_background_edge_runtime()?;
1110 runtime.block_on(server.serve_edge_on_std(listener, HttpTransport::Http))
1111 })
1112 }
1113
1114 pub fn serve_tls(&self, tls_config: std::sync::Arc<rustls::ServerConfig>) -> io::Result<()> {
1118 let listener = TcpListener::bind(&self.options.bind_addr)?;
1119 self.serve_tls_on(listener, tls_config)
1120 }
1121
1122 pub fn serve_tls_on(
1123 &self,
1124 listener: TcpListener,
1125 tls_config: std::sync::Arc<rustls::ServerConfig>,
1126 ) -> io::Result<()> {
1127 let runtime = axum_edge::build_edge_runtime()?;
1128 let acceptor = axum_edge::tls_acceptor(tls_config);
1129 runtime.block_on(self.clone().serve_edge_tls_on_std(
1130 listener,
1131 acceptor,
1132 HttpTransport::Https,
1133 ))
1134 }
1135
1136 pub fn serve_tls_in_background(
1137 &self,
1138 tls_config: std::sync::Arc<rustls::ServerConfig>,
1139 ) -> thread::JoinHandle<io::Result<()>> {
1140 let server = self.clone();
1141 thread::spawn(move || server.serve_tls(tls_config))
1142 }
1143
1144 pub fn serve_tls_in_background_on(
1145 &self,
1146 listener: TcpListener,
1147 tls_config: std::sync::Arc<rustls::ServerConfig>,
1148 ) -> thread::JoinHandle<io::Result<()>> {
1149 let server = self.clone();
1150 thread::spawn(move || {
1151 let runtime = axum_edge::build_background_edge_runtime()?;
1152 let acceptor = axum_edge::tls_acceptor(tls_config);
1153 runtime.block_on(server.serve_edge_tls_on_std(listener, acceptor, HttpTransport::Https))
1154 })
1155 }
1156
1157 fn handle_connection(&self, stream: TcpStream) -> io::Result<()> {
1158 let started = Instant::now();
1159 let result = self.handle_connection_inner(stream);
1160 let elapsed = started.elapsed().as_secs_f64();
1161 self.http_metrics
1162 .record_duration(HttpTransport::Http, elapsed);
1163 result
1164 }
1165
1166 fn handle_connection_inner(&self, mut stream: TcpStream) -> io::Result<()> {
1167 stream.set_read_timeout(Some(Duration::from_millis(self.options.read_timeout_ms)))?;
1168 stream.set_write_timeout(Some(Duration::from_millis(self.options.write_timeout_ms)))?;
1169
1170 let deadline = HandlerDeadline::arm(Arc::clone(&self.handler_clock), self.handler_timeout);
1178
1179 let request = HttpRequest::read_from(&mut stream, self.options.max_body_bytes)?;
1180
1181 if deadline.expired() {
1183 self.http_metrics
1184 .record_reject(HttpTransport::Http, HttpRejectReason::HandlerTimeout);
1185 Self::write_handler_timeout_503(&mut stream);
1186 return Ok(());
1187 }
1188
1189 if self.try_route_streaming(&request, &mut stream)? {
1190 return Ok(());
1191 }
1192 let response = self.route(request);
1193
1194 self.apply_test_slow_inject();
1198
1199 if deadline.expired() {
1201 self.http_metrics
1202 .record_reject(HttpTransport::Http, HttpRejectReason::HandlerTimeout);
1203 Self::write_handler_timeout_503(&mut stream);
1204 return Ok(());
1205 }
1206
1207 response.write_to(&mut stream)?;
1208 stream.flush()?;
1209 Ok(())
1210 }
1211
1212 fn write_handler_timeout_503<S: Write>(stream: &mut S) {
1218 const RESPONSE: &[u8] = b"HTTP/1.1 503 Service Unavailable\r\n\
1219 Connection: close\r\n\
1220 Content-Length: 0\r\n\
1221 \r\n";
1222 let _ = stream.write_all(RESPONSE);
1223 let _ = stream.flush();
1224 }
1225}