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;
154pub(crate) mod handlers_query;
159mod handlers_replication;
160mod handlers_topology;
161mod handlers_vector;
162pub mod header_escape_guard;
163pub mod http_connection_limiter;
164pub mod http_handler_metrics;
165pub mod http_limits;
166pub mod http_principal_limiter;
167pub mod http_request_metrics;
168pub mod ingest_pipeline;
169pub mod output_stream;
170mod patch_support;
171mod request_body;
172mod request_context;
173mod request_identity;
174pub(crate) mod route_catalog;
175mod routes;
176pub(crate) use routes::discovered_route_catalog;
177mod routing;
178mod serverless_support;
179pub mod tls;
180mod transport;
181mod transport_surface;
182
183#[doc(hidden)]
187pub fn command_coverage_matrix() -> String {
188 route_catalog::render_command_coverage_matrix(routes::discovered_route_catalog())
189}
190
191use self::handlers_ai::*;
192use self::handlers_entity::*;
193use self::handlers_graph::*;
194use self::handlers_keyed::*;
195use self::handlers_metrics::*;
196use self::handlers_ops::*;
197use self::handlers_query::*;
198use self::http_connection_limiter::{
199 HandlerDeadline, HttpConnectionLimiter, MonotonicClock, SystemMonotonicClock,
200};
201use self::http_handler_metrics::{HttpHandlerMetrics, HttpRejectReason, HttpTransport};
202pub use self::http_limits::{
203 HttpLimitsCliInput, HttpLimitsResolved, DEFAULT_HANDLER_TIMEOUT_MS, DEFAULT_RETRY_AFTER_SECS,
204};
205use self::http_principal_limiter::PrincipalConnectionLimiter;
206use self::http_request_metrics::HttpRequestMetrics;
207use self::patch_support::*;
208use self::request_body::*;
209use self::routing::*;
210use self::serverless_support::*;
211use self::transport::*;
212
213pub(crate) fn command_catalog() -> &'static route_catalog::CommandCatalog {
214 routes::discovered_route_catalog()
215}
216
217#[derive(Debug, Clone, Copy, PartialEq, Eq)]
223pub enum ServerSurface {
224 Public,
226 AdminOnly,
230 MetricsOnly,
234}
235
236#[derive(Debug, Clone)]
237pub struct ServerOptions {
238 pub bind_addr: String,
239 pub max_body_bytes: usize,
240 pub read_timeout_ms: u64,
241 pub write_timeout_ms: u64,
242 pub max_scan_limit: usize,
243 pub surface: ServerSurface,
247 pub transport_readiness: crate::transport::TransportReadiness,
248 pub websocket_allowed_origins: Vec<String>,
256 pub ui_dir: Option<std::path::PathBuf>,
265}
266
267pub const DEFAULT_HTTP_MAX_BODY_BYTES: usize = 32 * 1024 * 1024;
268
269impl Default for ServerOptions {
270 fn default() -> Self {
271 Self {
272 bind_addr: "127.0.0.1:5000".to_string(),
273 max_body_bytes: DEFAULT_HTTP_MAX_BODY_BYTES,
274 read_timeout_ms: 5_000,
275 write_timeout_ms: 5_000,
276 max_scan_limit: 1_000,
277 surface: ServerSurface::Public,
278 transport_readiness: crate::transport::TransportReadiness::default(),
279 websocket_allowed_origins: Vec::new(),
280 ui_dir: None,
281 }
282 }
283}
284
285pub struct ServerReplicationState {
287 pub config: crate::replication::ReplicationConfig,
288 pub primary: Option<crate::replication::primary::PrimaryReplication>,
289}
290
291#[derive(Clone)]
292pub struct RedDBServer {
293 runtime: RedDBRuntime,
294 options: ServerOptions,
295 auth_store: Option<Arc<AuthStore>>,
296 replication: Option<Arc<ServerReplicationState>>,
297 http_limiter: HttpConnectionLimiter,
302 execution_admission: ExecutionAdmission,
306 health_limiter: HttpConnectionLimiter,
312 handler_timeout: Duration,
318 handler_clock: Arc<dyn MonotonicClock>,
325 slow_inject_ms: Arc<AtomicU64>,
332 http_metrics: HttpHandlerMetrics,
338 http_request_metrics: HttpRequestMetrics,
344 retry_after_secs: u64,
348 principal_limiter: PrincipalConnectionLimiter,
356 pub(crate) stream_capacity: Arc<output_stream::StreamCapacityRegistry>,
364 pub(crate) lease_registry: Arc<output_stream::LeaseRegistry>,
369 pub(crate) cursor_registry: Arc<output_stream::CursorRegistry>,
374}
375
376const DEFAULT_HANDLER_TIMEOUT: Duration = Duration::from_millis(30_000);
378
379const HEALTH_HANDLER_CAP: usize = 4;
382
383#[derive(Debug, Clone, Copy, PartialEq, Eq)]
384enum ServerlessWarmupScope {
385 Indexes,
386 GraphProjections,
387 AnalyticsJobs,
388 NativeArtifacts,
389}
390
391#[derive(Debug, Clone, Copy, PartialEq, Eq)]
392enum DeploymentProfile {
393 Embedded,
394 Server,
395 Serverless,
396}
397
398fn percent_decode_path_segment(input: &str) -> Result<String, String> {
399 let bytes = input.as_bytes();
400 let mut out = Vec::with_capacity(bytes.len());
401 let mut index = 0;
402 while index < bytes.len() {
403 match bytes[index] {
404 b'%' => {
405 if index + 2 >= bytes.len() {
406 return Err("truncated percent escape".to_string());
407 }
408 let high = hex_value(bytes[index + 1])
409 .ok_or_else(|| "invalid percent escape".to_string())?;
410 let low = hex_value(bytes[index + 2])
411 .ok_or_else(|| "invalid percent escape".to_string())?;
412 out.push((high << 4) | low);
413 index += 3;
414 }
415 byte => {
416 out.push(byte);
417 index += 1;
418 }
419 }
420 }
421 String::from_utf8(out).map_err(|_| "path segment is not valid UTF-8".to_string())
422}
423
424fn hex_value(byte: u8) -> Option<u8> {
425 match byte {
426 b'0'..=b'9' => Some(byte - b'0'),
427 b'a'..=b'f' => Some(byte - b'a' + 10),
428 b'A'..=b'F' => Some(byte - b'A' + 10),
429 _ => None,
430 }
431}
432
433#[derive(Debug, Clone)]
434struct ParsedQueryRequest {
435 query: String,
436 entity_types: Option<Vec<String>>,
437 capabilities: Option<Vec<String>>,
438 commit_policy: Option<crate::replication::CommitPolicy>,
439 params: Option<Vec<crate::runtime::query_request::ParamValue>>,
442}
443
444#[derive(Debug, Clone, Copy)]
445enum PatchOperationType {
446 Set,
447 Replace,
448 Unset,
449}
450
451#[derive(Debug, Clone)]
452struct PatchOperation {
453 op: PatchOperationType,
454 path: Vec<String>,
455 value: Option<JsonValue>,
456}
457
458impl RedDBServer {
459 pub fn new(runtime: RedDBRuntime) -> Self {
460 Self::with_options(runtime, ServerOptions::default())
461 }
462
463 pub fn from_database_options(
464 db_options: RedDBOptions,
465 server_options: ServerOptions,
466 ) -> RedDBResult<Self> {
467 let runtime = RedDBRuntime::with_options(db_options)?;
468 Ok(Self::with_options(runtime, server_options))
469 }
470
471 pub fn with_options(runtime: RedDBRuntime, options: ServerOptions) -> Self {
472 let http_limits = HttpLimitsResolved::builtin_defaults();
473 let execution_admission = runtime.configure_execution_admission(
474 http_limits.execution_workers,
475 http_limits
476 .max_handlers
477 .saturating_sub(http_limits.execution_workers),
478 http_limits.handler_timeout_ms,
479 );
480 Self {
481 runtime,
482 options,
483 auth_store: None,
484 replication: None,
485 http_limiter: HttpConnectionLimiter::new(http_limits.max_handlers),
486 execution_admission,
487 health_limiter: HttpConnectionLimiter::new(HEALTH_HANDLER_CAP),
488 handler_timeout: DEFAULT_HANDLER_TIMEOUT,
489 handler_clock: Arc::new(SystemMonotonicClock::new()),
490 slow_inject_ms: Arc::new(AtomicU64::new(0)),
491 http_metrics: HttpHandlerMetrics::new(),
492 http_request_metrics: HttpRequestMetrics::new(),
493 retry_after_secs: DEFAULT_RETRY_AFTER_SECS,
494 principal_limiter: PrincipalConnectionLimiter::new(
495 http_limits.max_inflight_per_principal,
496 ),
497 stream_capacity: output_stream::StreamCapacityRegistry::new(),
498 lease_registry: output_stream::LeaseRegistry::new(),
499 cursor_registry: output_stream::CursorRegistry::new(),
500 }
501 }
502
503 #[doc(hidden)]
504 pub fn stream_capacity(&self) -> &Arc<output_stream::StreamCapacityRegistry> {
505 &self.stream_capacity
506 }
507
508 #[doc(hidden)]
509 pub fn lease_registry(&self) -> &Arc<output_stream::LeaseRegistry> {
510 &self.lease_registry
511 }
512
513 #[doc(hidden)]
514 pub fn cursor_registry(&self) -> &Arc<output_stream::CursorRegistry> {
515 &self.cursor_registry
516 }
517
518 #[doc(hidden)]
519 pub fn http_metrics(&self) -> &HttpHandlerMetrics {
520 &self.http_metrics
521 }
522
523 #[doc(hidden)]
526 pub fn http_request_metrics(&self) -> &HttpRequestMetrics {
527 &self.http_request_metrics
528 }
529
530 #[doc(hidden)]
535 pub fn with_http_limiter_cap(mut self, cap: usize) -> Self {
536 self.http_limiter = HttpConnectionLimiter::new(cap);
537 let execution_workers = self.execution_admission.active_max().min(cap);
538 self.execution_admission = self.runtime.configure_execution_admission(
539 execution_workers,
540 cap.saturating_sub(execution_workers),
541 self.handler_timeout.as_millis().min(u64::MAX as u128) as u64,
542 );
543 self
544 }
545
546 pub fn with_http_limits(mut self, limits: HttpLimitsResolved) -> Self {
551 self.http_limiter = HttpConnectionLimiter::new(limits.max_handlers);
552 self.execution_admission = self.runtime.configure_execution_admission(
553 limits.execution_workers,
554 limits.max_handlers.saturating_sub(limits.execution_workers),
555 limits.handler_timeout_ms,
556 );
557 self.handler_timeout = Duration::from_millis(limits.handler_timeout_ms);
558 self.retry_after_secs = limits.retry_after_secs;
559 self.principal_limiter = PrincipalConnectionLimiter::new(limits.max_inflight_per_principal);
560 self
561 }
562
563 #[doc(hidden)]
568 pub fn with_principal_inflight_cap(mut self, cap: usize) -> Self {
569 self.principal_limiter = PrincipalConnectionLimiter::new(cap);
570 self
571 }
572
573 #[doc(hidden)]
574 pub fn principal_limiter(&self) -> &PrincipalConnectionLimiter {
575 &self.principal_limiter
576 }
577
578 #[doc(hidden)]
579 pub fn retry_after_secs(&self) -> u64 {
580 self.retry_after_secs
581 }
582
583 #[doc(hidden)]
584 pub fn http_limiter(&self) -> &HttpConnectionLimiter {
585 &self.http_limiter
586 }
587
588 pub(crate) fn execution_admission(&self) -> &ExecutionAdmission {
589 &self.execution_admission
590 }
591
592 #[doc(hidden)]
593 pub fn http_execution_active(&self) -> usize {
594 self.execution_admission.active()
595 }
596
597 #[doc(hidden)]
598 pub fn http_execution_queued(&self) -> usize {
599 self.execution_admission.queued()
600 }
601
602 #[doc(hidden)]
603 pub fn http_execution_workers(&self) -> usize {
604 self.execution_admission.active_max()
605 }
606
607 #[doc(hidden)]
609 pub fn with_http_execution_workers(mut self, workers: usize) -> Self {
610 assert!(workers > 0, "HTTP execution workers must be positive");
611 let workers = workers.min(self.http_limiter.cap());
612 self.execution_admission = self.runtime.configure_execution_admission(
613 workers,
614 self.http_limiter.cap().saturating_sub(workers),
615 self.handler_timeout.as_millis().min(u64::MAX as u128) as u64,
616 );
617 self
618 }
619
620 #[doc(hidden)]
622 pub fn health_limiter(&self) -> &HttpConnectionLimiter {
623 &self.health_limiter
624 }
625
626 #[doc(hidden)]
628 pub fn with_health_limiter_cap(mut self, cap: usize) -> Self {
629 self.health_limiter = HttpConnectionLimiter::new(cap);
630 self
631 }
632
633 #[doc(hidden)]
636 pub fn with_handler_timeout(mut self, timeout: Duration) -> Self {
637 self.handler_timeout = timeout;
638 self
639 }
640
641 #[doc(hidden)]
642 pub fn handler_timeout(&self) -> Duration {
643 self.handler_timeout
644 }
645
646 #[doc(hidden)]
651 pub fn with_handler_clock(mut self, clock: Arc<dyn MonotonicClock>) -> Self {
652 self.handler_clock = clock;
653 self
654 }
655
656 #[doc(hidden)]
662 pub fn set_test_slow_inject_ms(&self, ms: u64) {
663 self.slow_inject_ms.store(ms, Ordering::Relaxed);
664 }
665
666 pub fn with_auth(mut self, auth_store: Arc<AuthStore>) -> Self {
670 self.runtime.set_auth_store(Arc::clone(&auth_store));
671 self.auth_store = Some(auth_store);
672 self
673 }
674
675 pub fn with_replication(mut self, state: Arc<ServerReplicationState>) -> Self {
677 self.replication = Some(state);
678 self
679 }
680
681 pub fn with_websocket_allowed_origins(mut self, origins: Vec<String>) -> Self {
686 self.options.websocket_allowed_origins = origins;
687 self
688 }
689
690 pub(crate) fn websocket_allowed_origins(&self) -> &[String] {
692 &self.options.websocket_allowed_origins
693 }
694
695 pub fn with_ui_dir(mut self, dir: std::path::PathBuf) -> Self {
701 self.options.ui_dir = Some(dir);
702 self
703 }
704
705 pub(crate) fn ui_dir(&self) -> Option<&std::path::Path> {
708 self.options.ui_dir.as_deref()
709 }
710
711 pub fn with_browser_token_authority(
718 self,
719 authority: Arc<crate::auth::browser_token::BrowserTokenAuthority>,
720 ) -> Self {
721 self.runtime.set_browser_token_authority(Some(authority));
722 self
723 }
724
725 pub fn runtime(&self) -> &RedDBRuntime {
726 &self.runtime
727 }
728
729 pub fn options(&self) -> &ServerOptions {
730 &self.options
731 }
732
733 fn admin_use_cases(&self) -> AdminUseCases<'_, RedDBRuntime> {
734 AdminUseCases::new(&self.runtime)
735 }
736
737 fn entity_use_cases(&self) -> EntityUseCases<'_, RedDBRuntime> {
738 EntityUseCases::new(&self.runtime)
739 }
740
741 fn graph_use_cases(&self) -> GraphUseCases<'_, RedDBRuntime> {
742 GraphUseCases::new(&self.runtime)
743 }
744
745 fn native_use_cases(&self) -> NativeUseCases<'_, RedDBRuntime> {
746 NativeUseCases::new(&self.runtime)
747 }
748
749 fn tree_use_cases(&self) -> TreeUseCases<'_, RedDBRuntime> {
750 TreeUseCases::new(&self.runtime)
751 }
752
753 fn transport_readiness_json(&self) -> JsonValue {
754 let active = self
755 .options
756 .transport_readiness
757 .active
758 .iter()
759 .map(|listener| {
760 let mut object = Map::new();
761 object.insert(
762 "transport".to_string(),
763 JsonValue::String(listener.transport.clone()),
764 );
765 object.insert(
766 "bind_addr".to_string(),
767 JsonValue::String(listener.bind_addr.clone()),
768 );
769 object.insert("explicit".to_string(), JsonValue::Bool(listener.explicit));
770 JsonValue::Object(object)
771 })
772 .collect();
773 let failed = self
774 .options
775 .transport_readiness
776 .failed
777 .iter()
778 .map(|listener| {
779 let mut object = Map::new();
780 object.insert(
781 "transport".to_string(),
782 JsonValue::String(listener.transport.clone()),
783 );
784 object.insert(
785 "bind_addr".to_string(),
786 JsonValue::String(listener.bind_addr.clone()),
787 );
788 object.insert("explicit".to_string(), JsonValue::Bool(listener.explicit));
789 object.insert(
790 "reason".to_string(),
791 JsonValue::String(listener.reason.clone()),
792 );
793 JsonValue::Object(object)
794 })
795 .collect();
796
797 let mut object = Map::new();
798 object.insert("active".to_string(), JsonValue::Array(active));
799 object.insert("failed".to_string(), JsonValue::Array(failed));
800 JsonValue::Object(object)
801 }
802
803 fn handle_grpc_discovery(&self) -> HttpResponse {
804 let mut methods = Map::new();
805 methods.insert(
806 "query".to_string(),
807 JsonValue::String("reddb.v1.RedDB/Query".to_string()),
808 );
809 methods.insert(
810 "batch_query".to_string(),
811 JsonValue::String("reddb.v1.RedDB/BatchQuery".to_string()),
812 );
813 methods.insert(
814 "health".to_string(),
815 JsonValue::String("reddb.v1.RedDB/Health".to_string()),
816 );
817 methods.insert(
818 "prepare".to_string(),
819 JsonValue::String("reddb.v1.RedDB/Prepare".to_string()),
820 );
821 methods.insert(
822 "execute_prepared".to_string(),
823 JsonValue::String("reddb.v1.RedDB/ExecutePrepared".to_string()),
824 );
825
826 let mut examples = Map::new();
827 examples.insert(
828 "query".to_string(),
829 JsonValue::String(
830 "grpcurl -plaintext -d '{\"query\":\"SELECT 1\"}' 127.0.0.1:5000 reddb.v1.RedDB/Query"
831 .to_string(),
832 ),
833 );
834 examples.insert(
835 "query_with_params".to_string(),
836 JsonValue::String(
837 "grpcurl -plaintext -d '{\"query\":\"SELECT $1 AS value\",\"params\":[{\"intValue\":42}]}' 127.0.0.1:5000 reddb.v1.RedDB/Query"
838 .to_string(),
839 ),
840 );
841 examples.insert(
842 "health".to_string(),
843 JsonValue::String(
844 "grpcurl -plaintext -d '{}' 127.0.0.1:5000 reddb.v1.RedDB/Health".to_string(),
845 ),
846 );
847
848 let mut object = Map::new();
849 object.insert("ok".to_string(), JsonValue::Bool(true));
850 object.insert(
851 "service".to_string(),
852 JsonValue::String("reddb.v1.RedDB".to_string()),
853 );
854 object.insert(
855 "package".to_string(),
856 JsonValue::String("reddb.v1".to_string()),
857 );
858 object.insert(
859 "proto".to_string(),
860 JsonValue::String("crates/reddb-grpc-proto/proto/reddb.proto".to_string()),
861 );
862 object.insert("methods".to_string(), JsonValue::Object(methods));
863 object.insert("examples".to_string(), JsonValue::Object(examples));
864 object.insert(
865 "transport_listeners".to_string(),
866 self.transport_readiness_json(),
867 );
868 object.insert(
869 "hint".to_string(),
870 JsonValue::String(
871 "If grpcurl cannot list services, pass the proto file with -import-path crates/reddb-grpc-proto/proto -proto reddb.proto."
872 .to_string(),
873 ),
874 );
875 json_response(200, JsonValue::Object(object))
876 }
877
878 fn handle_query_contract(&self) -> HttpResponse {
879 let mut examples = Map::new();
880 examples.insert(
881 "raw_sql".to_string(),
882 JsonValue::String("curl -sS http://127.0.0.1:5000/query -d 'SELECT 1'".to_string()),
883 );
884 examples.insert(
885 "json_query".to_string(),
886 JsonValue::String(
887 "curl -sS http://127.0.0.1:5000/query -H 'content-type: application/json' -d '{\"query\":\"SELECT 1\"}'"
888 .to_string(),
889 ),
890 );
891 examples.insert(
892 "json_query_with_params".to_string(),
893 JsonValue::String(
894 "curl -sS http://127.0.0.1:5000/query -H 'content-type: application/json' -d '{\"query\":\"SELECT $1 AS value\",\"params\":[42]}'"
895 .to_string(),
896 ),
897 );
898
899 let mut request_body = Map::new();
900 request_body.insert(
901 "query".to_string(),
902 JsonValue::String("required string".to_string()),
903 );
904 request_body.insert(
905 "params".to_string(),
906 JsonValue::String("optional array".to_string()),
907 );
908
909 let mut response_shape = Map::new();
910 response_shape.insert(
911 "columns".to_string(),
912 JsonValue::String("projected column names".to_string()),
913 );
914 response_shape.insert(
915 "records[].values".to_string(),
916 JsonValue::String("only projected values".to_string()),
917 );
918 response_shape.insert(
919 "records[].meta".to_string(),
920 JsonValue::String("internal metadata when present".to_string()),
921 );
922
923 let mut object = Map::new();
924 object.insert("ok".to_string(), JsonValue::Bool(false));
925 object.insert(
926 "code".to_string(),
927 JsonValue::String("method_not_allowed".to_string()),
928 );
929 object.insert(
930 "message".to_string(),
931 JsonValue::String("/query accepts POST requests".to_string()),
932 );
933 object.insert(
934 "hint".to_string(),
935 JsonValue::String(
936 "Send raw SQL in the body, or JSON with a string 'query' field.".to_string(),
937 ),
938 );
939 object.insert("method".to_string(), JsonValue::String("POST".to_string()));
940 object.insert("path".to_string(), JsonValue::String("/query".to_string()));
941 object.insert("request_body".to_string(), JsonValue::Object(request_body));
942 object.insert(
943 "response_shape".to_string(),
944 JsonValue::Object(response_shape),
945 );
946 object.insert("examples".to_string(), JsonValue::Object(examples));
947 object.insert(
948 "docs".to_string(),
949 JsonValue::String("https://reddb.io/docs/query".to_string()),
950 );
951
952 json_response(405, JsonValue::Object(object))
953 .with_header("Allow", http::HeaderValue::from_static("POST"))
954 }
955
956 fn handle_root_discovery(&self) -> HttpResponse {
957 let mut endpoints = Map::new();
958 endpoints.insert(
959 "health".to_string(),
960 JsonValue::String("GET /health".to_string()),
961 );
962 endpoints.insert(
963 "ready".to_string(),
964 JsonValue::String("GET /ready".to_string()),
965 );
966 endpoints.insert(
967 "query".to_string(),
968 JsonValue::String("POST /query".to_string()),
969 );
970 endpoints.insert(
971 "query_readiness".to_string(),
972 JsonValue::String("GET /ready/query".to_string()),
973 );
974 endpoints.insert(
975 "catalog".to_string(),
976 JsonValue::String("GET /catalog".to_string()),
977 );
978 endpoints.insert(
979 "deployment_profiles".to_string(),
980 JsonValue::String("GET /deployment/profiles".to_string()),
981 );
982
983 let mut examples = Map::new();
984 examples.insert(
985 "http_raw_sql".to_string(),
986 JsonValue::String("curl -sS http://127.0.0.1:5000/query -d 'SELECT 1'".to_string()),
987 );
988 examples.insert(
989 "http_json_query".to_string(),
990 JsonValue::String(
991 "curl -sS http://127.0.0.1:5000/query -H 'content-type: application/json' -d '{\"query\":\"SELECT 1\"}'"
992 .to_string(),
993 ),
994 );
995 examples.insert(
996 "http_json_query_with_params".to_string(),
997 JsonValue::String(
998 "curl -sS http://127.0.0.1:5000/query -H 'content-type: application/json' -d '{\"query\":\"SELECT $1 AS value\",\"params\":[42]}'"
999 .to_string(),
1000 ),
1001 );
1002
1003 let mut object = Map::new();
1004 object.insert("ok".to_string(), JsonValue::Bool(true));
1005 object.insert(
1006 "service".to_string(),
1007 JsonValue::String("reddb".to_string()),
1008 );
1009 object.insert(
1010 "version".to_string(),
1011 JsonValue::String(env!("CARGO_PKG_VERSION").to_string()),
1012 );
1013 object.insert("endpoints".to_string(), JsonValue::Object(endpoints));
1014 object.insert("examples".to_string(), JsonValue::Object(examples));
1015 object.insert(
1016 "docs".to_string(),
1017 JsonValue::String("https://reddb.io/docs".to_string()),
1018 );
1019 object.insert(
1020 "transport_listeners".to_string(),
1021 self.transport_readiness_json(),
1022 );
1023 json_response(200, JsonValue::Object(object))
1024 }
1025
1026 fn health_json_with_transport(&self, report: &HealthReport) -> JsonValue {
1027 let mut value = crate::presentation::ops_json::health_json(report);
1028 if let JsonValue::Object(ref mut object) = value {
1029 object.insert(
1030 "transport_listeners".to_string(),
1031 self.transport_readiness_json(),
1032 );
1033 }
1034 value
1035 }
1036
1037 pub fn serve(&self) -> io::Result<()> {
1038 let listener = TcpListener::bind(&self.options.bind_addr)?;
1039 self.serve_on(listener)
1040 }
1041
1042 pub fn serve_on(&self, listener: TcpListener) -> io::Result<()> {
1049 let runtime = axum_edge::build_edge_runtime()?;
1050 runtime.block_on(
1051 self.clone()
1052 .serve_edge_on_std(listener, HttpTransport::Http),
1053 )
1054 }
1055
1056 pub fn serve_one_on(&self, listener: TcpListener) -> io::Result<()> {
1060 let runtime = axum_edge::build_background_edge_runtime()?;
1061 let server = self.clone();
1062 runtime.block_on(async move {
1063 listener.set_nonblocking(true)?;
1064 let listener = tokio::net::TcpListener::from_std(listener)?;
1065 let (stream, _peer) = listener.accept().await?;
1066 server.serve_edge_one(stream).await;
1067 Ok(())
1068 })
1069 }
1070
1071 pub fn serve_in_background(&self) -> thread::JoinHandle<io::Result<()>> {
1072 let server = self.clone();
1073 thread::spawn(move || server.serve())
1074 }
1075
1076 pub fn serve_in_background_on(
1077 &self,
1078 listener: TcpListener,
1079 ) -> thread::JoinHandle<io::Result<()>> {
1080 let server = self.clone();
1081 thread::spawn(move || {
1082 let runtime = axum_edge::build_background_edge_runtime()?;
1083 runtime.block_on(server.serve_edge_on_std(listener, HttpTransport::Http))
1084 })
1085 }
1086
1087 pub fn serve_tls(&self, tls_config: std::sync::Arc<rustls::ServerConfig>) -> io::Result<()> {
1091 let listener = TcpListener::bind(&self.options.bind_addr)?;
1092 self.serve_tls_on(listener, tls_config)
1093 }
1094
1095 pub fn serve_tls_on(
1096 &self,
1097 listener: TcpListener,
1098 tls_config: std::sync::Arc<rustls::ServerConfig>,
1099 ) -> io::Result<()> {
1100 let runtime = axum_edge::build_edge_runtime()?;
1101 let acceptor = axum_edge::tls_acceptor(tls_config);
1102 runtime.block_on(self.clone().serve_edge_tls_on_std(
1103 listener,
1104 acceptor,
1105 HttpTransport::Https,
1106 ))
1107 }
1108
1109 pub fn serve_tls_in_background(
1110 &self,
1111 tls_config: std::sync::Arc<rustls::ServerConfig>,
1112 ) -> thread::JoinHandle<io::Result<()>> {
1113 let server = self.clone();
1114 thread::spawn(move || server.serve_tls(tls_config))
1115 }
1116
1117 pub fn serve_tls_in_background_on(
1118 &self,
1119 listener: TcpListener,
1120 tls_config: std::sync::Arc<rustls::ServerConfig>,
1121 ) -> thread::JoinHandle<io::Result<()>> {
1122 let server = self.clone();
1123 thread::spawn(move || {
1124 let runtime = axum_edge::build_background_edge_runtime()?;
1125 let acceptor = axum_edge::tls_acceptor(tls_config);
1126 runtime.block_on(server.serve_edge_tls_on_std(listener, acceptor, HttpTransport::Https))
1127 })
1128 }
1129
1130 fn handle_connection(&self, stream: TcpStream) -> io::Result<()> {
1131 let started = Instant::now();
1132 let result = self.handle_connection_inner(stream);
1133 let elapsed = started.elapsed().as_secs_f64();
1134 self.http_metrics
1135 .record_duration(HttpTransport::Http, elapsed);
1136 result
1137 }
1138
1139 fn handle_connection_inner(&self, mut stream: TcpStream) -> io::Result<()> {
1140 stream.set_read_timeout(Some(Duration::from_millis(self.options.read_timeout_ms)))?;
1141 stream.set_write_timeout(Some(Duration::from_millis(self.options.write_timeout_ms)))?;
1142
1143 let deadline = HandlerDeadline::arm(Arc::clone(&self.handler_clock), self.handler_timeout);
1151
1152 let request = HttpRequest::read_from(&mut stream, self.options.max_body_bytes)?;
1153
1154 if deadline.expired() {
1156 self.http_metrics
1157 .record_reject(HttpTransport::Http, HttpRejectReason::HandlerTimeout);
1158 Self::write_handler_timeout_503(&mut stream);
1159 return Ok(());
1160 }
1161
1162 if self.try_route_streaming(&request, &mut stream)? {
1163 return Ok(());
1164 }
1165 let response = self.route(request);
1166
1167 let inject_ms = self.slow_inject_ms.load(Ordering::Relaxed);
1171 if inject_ms > 0 {
1172 thread::sleep(Duration::from_millis(inject_ms));
1173 }
1174
1175 if deadline.expired() {
1177 self.http_metrics
1178 .record_reject(HttpTransport::Http, HttpRejectReason::HandlerTimeout);
1179 Self::write_handler_timeout_503(&mut stream);
1180 return Ok(());
1181 }
1182
1183 response.write_to(&mut stream)?;
1184 stream.flush()?;
1185 Ok(())
1186 }
1187
1188 fn write_handler_timeout_503<S: Write>(stream: &mut S) {
1194 const RESPONSE: &[u8] = b"HTTP/1.1 503 Service Unavailable\r\n\
1195 Connection: close\r\n\
1196 Content-Length: 0\r\n\
1197 \r\n";
1198 let _ = stream.write_all(RESPONSE);
1199 let _ = stream.flush();
1200 }
1201}