Skip to main content

hawdb_core/
capability.rs

1// Copyright 2026 Nowledge
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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}