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