1use crate::{HawDBError, Result};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
18pub enum RuntimeCapability {
19 AccessControl,
20 FullTextSearch,
21 SearchProjectionPublication,
22 VectorSearch,
23 GraphAnalytics,
24 BackgroundMaintenance,
25}
26
27impl RuntimeCapability {
28 pub const fn as_str(self) -> &'static str {
29 match self {
30 Self::AccessControl => "access_control",
31 Self::FullTextSearch => "full_text_search",
32 Self::SearchProjectionPublication => "search_projection_publication",
33 Self::VectorSearch => "vector_search",
34 Self::GraphAnalytics => "graph_analytics",
35 Self::BackgroundMaintenance => "background_maintenance",
36 }
37 }
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub struct RuntimeCapabilities {
42 pub access_control: bool,
43 pub full_text_search: bool,
44 pub search_projection_publication: bool,
45 pub vector_search: bool,
46 pub graph_analytics: bool,
47 pub background_maintenance: bool,
48}
49
50impl RuntimeCapabilities {
51 pub const fn shared_host() -> Self {
52 Self {
53 access_control: false,
54 full_text_search: true,
55 search_projection_publication: true,
56 vector_search: true,
57 graph_analytics: true,
58 background_maintenance: true,
59 }
60 }
61
62 pub const fn mobile_embedded() -> Self {
63 Self {
64 access_control: false,
65 full_text_search: true,
66 search_projection_publication: true,
67 vector_search: true,
68 graph_analytics: false,
69 background_maintenance: false,
70 }
71 }
72
73 pub const fn is_enabled(self, capability: RuntimeCapability) -> bool {
74 match capability {
75 RuntimeCapability::AccessControl => self.access_control,
76 RuntimeCapability::FullTextSearch => self.full_text_search,
77 RuntimeCapability::SearchProjectionPublication => self.search_projection_publication,
78 RuntimeCapability::VectorSearch => self.vector_search,
79 RuntimeCapability::GraphAnalytics => self.graph_analytics,
80 RuntimeCapability::BackgroundMaintenance => self.background_maintenance,
81 }
82 }
83
84 pub fn require(self, capability: RuntimeCapability) -> Result<()> {
85 if self.is_enabled(capability) {
86 return Ok(());
87 }
88 Err(HawDBError::CapabilityUnavailable { capability })
89 }
90
91 pub const fn with(mut self, capability: RuntimeCapability, enabled: bool) -> Self {
92 match capability {
93 RuntimeCapability::AccessControl => self.access_control = enabled,
94 RuntimeCapability::FullTextSearch => self.full_text_search = enabled,
95 RuntimeCapability::SearchProjectionPublication => {
96 self.search_projection_publication = enabled
97 }
98 RuntimeCapability::VectorSearch => self.vector_search = enabled,
99 RuntimeCapability::GraphAnalytics => self.graph_analytics = enabled,
100 RuntimeCapability::BackgroundMaintenance => self.background_maintenance = enabled,
101 }
102 self
103 }
104
105 pub const fn intersection(self, available: Self) -> Self {
106 Self {
107 access_control: self.access_control && available.access_control,
108 full_text_search: self.full_text_search && available.full_text_search,
109 search_projection_publication: self.search_projection_publication
110 && available.search_projection_publication,
111 vector_search: self.vector_search && available.vector_search,
112 graph_analytics: self.graph_analytics && available.graph_analytics,
113 background_maintenance: self.background_maintenance && available.background_maintenance,
114 }
115 }
116}
117
118impl Default for RuntimeCapabilities {
119 fn default() -> Self {
120 Self::shared_host()
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn mobile_disables_only_optional_heavy_runtime_capabilities() {
130 let capabilities = RuntimeCapabilities::mobile_embedded();
131
132 assert!(!capabilities.is_enabled(RuntimeCapability::AccessControl));
133 assert!(capabilities.is_enabled(RuntimeCapability::FullTextSearch));
134 assert!(capabilities.is_enabled(RuntimeCapability::VectorSearch));
135 assert!(!capabilities.is_enabled(RuntimeCapability::GraphAnalytics));
136 assert!(!capabilities.is_enabled(RuntimeCapability::BackgroundMaintenance));
137 }
138
139 #[test]
140 fn disabled_capability_returns_typed_error() {
141 let error = RuntimeCapabilities::mobile_embedded()
142 .require(RuntimeCapability::GraphAnalytics)
143 .unwrap_err();
144
145 assert_eq!(
146 error,
147 HawDBError::CapabilityUnavailable {
148 capability: RuntimeCapability::GraphAnalytics
149 }
150 );
151 assert_eq!(error.to_string(), "capability unavailable: graph_analytics");
152 }
153
154 #[test]
155 fn requested_capabilities_are_bounded_by_compiled_availability() {
156 let available = RuntimeCapabilities::shared_host()
157 .with(RuntimeCapability::AccessControl, true)
158 .with(RuntimeCapability::SearchProjectionPublication, false)
159 .with(RuntimeCapability::GraphAnalytics, false)
160 .with(RuntimeCapability::BackgroundMaintenance, false);
161 let effective = RuntimeCapabilities::shared_host().intersection(available);
162
163 assert!(!effective.access_control);
164 assert!(effective.full_text_search);
165 assert!(!effective.search_projection_publication);
166 assert!(effective.vector_search);
167 assert!(!effective.graph_analytics);
168 assert!(!effective.background_maintenance);
169 }
170}