Skip to main content

product_os_capabilities/
lib.rs

1//! # Product OS : Capabilities
2//!
3//! Product OS : Capabilities provides a modular system for implementing server features and services.
4//!
5//! ## Features
6//!
7//! - **Feature Registry**: Register and route HTTP features with path-based matching
8//! - **Service Registry**: Manage lifecycle of background services
9//! - **Trait-based Architecture**: Define features and services through traits
10//! - **Mutable & Immutable Patterns**: Support both shared and exclusive access patterns
11//! - **Error Handling**: Typed errors for robust error handling
12//!
13//! ## Example
14//!
15//! ```rust,no_run
16//! use product_os_capabilities::{Feature, Service, Features, Services};
17//! # use std::sync::Arc;
18//! # use product_os_router::ProductOSRouter;
19//!
20//! // Create registries
21//! let mut features = Features::new();
22//! let mut services = Services::new();
23//! let mut router = ProductOSRouter::new();
24//!
25//! // Register features and services (implementations not shown)
26//! // features.add(my_feature, "/api".to_string(), &mut router).await?;
27//! // services.add(my_service).await;
28//! ```
29
30#![no_std]
31#![warn(missing_docs)]
32#![warn(clippy::all)]
33#![allow(clippy::module_name_repetitions)]
34#![allow(clippy::missing_errors_doc)]
35#![allow(clippy::missing_panics_doc)]
36#![allow(clippy::struct_field_names)]
37#![allow(clippy::must_use_candidate)]
38#![allow(clippy::return_self_not_must_use)]
39#![allow(clippy::needless_pass_by_value)]
40#![allow(clippy::option_if_let_else)]
41#![allow(clippy::implicit_clone)]
42#![allow(clippy::manual_string_new)]
43#![allow(clippy::uninlined_format_args)]
44
45extern crate no_std_compat as std;
46
47use std::prelude::v1::*;
48
49pub use async_trait::async_trait;
50
51use std::fmt::{Debug, Formatter};
52use std::sync::Arc;
53use parking_lot::Mutex;
54
55use serde::{Deserialize, Serialize};
56
57#[cfg(feature = "std_base")]
58mod features;
59mod services;
60
61#[cfg(feature = "std_base")]
62pub use features::Features;
63#[cfg(feature = "std_base")]
64use product_os_router::{Body, ProductOSRouter, Response};
65#[cfg(feature = "std_base")]
66use serde_json::Value;
67pub use services::Services;
68
69/// Returns the current time as milliseconds since the Unix epoch.
70///
71/// When the `std_base` feature (which enables `no-std-compat/std`) is active,
72/// this uses `SystemTime` for a real clock value. In pure `no_std` mode, returns 0.
73#[cfg(feature = "std_base")]
74pub fn now_millis() -> i64 {
75    std::time::SystemTime::now()
76        .duration_since(std::time::SystemTime::UNIX_EPOCH)
77        .map(|d| d.as_millis() as i64)
78        .unwrap_or(0)
79}
80
81/// Returns 0 in pure no_std mode (no system clock available).
82#[cfg(not(feature = "std_base"))]
83pub fn now_millis() -> i64 {
84    0
85}
86
87/// A feature registered in the feature registry
88///
89/// Contains the feature identifier, registered paths, and either an immutable
90/// or mutable reference to the feature implementation.
91#[cfg(feature = "std_base")]
92#[derive(Deserialize, Serialize)]
93#[serde(rename_all = "camelCase")]
94pub struct RegistryFeature {
95    /// The unique identifier for this feature
96    pub identifier: String,
97    /// The paths this feature is registered to handle
98    pub paths: Vec<String>,
99    /// Immutable reference to the feature implementation
100    #[serde(skip, default = "default_feature")]
101    pub feature: Option<Arc<dyn Feature>>,
102    /// Mutable reference to the feature implementation
103    #[serde(skip, default = "default_feature_mut")]
104    pub feature_mut: Option<Arc<Mutex<dyn Feature>>>
105}
106
107#[cfg(feature = "std_base")]
108impl Debug for RegistryFeature {
109    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
110        write!(f, "Identifier: {}\nPaths: {:?}\n", self.identifier, self.paths)
111    }
112}
113
114#[cfg(feature = "std_base")]
115fn default_feature() -> Option<Arc<dyn Feature>> {
116    Some(Arc::new(DefaultFeature {}))
117}
118
119#[cfg(feature = "std_base")]
120fn default_feature_mut() -> Option<Arc<Mutex<dyn Feature>>> {
121    Some(Arc::new(Mutex::new(DefaultFeature {})))
122}
123
124/// Represents a characteristic or ID-based action selector for service calls
125pub enum What {
126    /// A named characteristic/action
127    Characteristic(String),
128    /// A numeric ID
129    Id(u8)
130}
131
132/// A service registered in the service registry
133///
134/// Contains service metadata, state information, and either an immutable
135/// or mutable reference to the service implementation.
136#[derive(Deserialize, Serialize)]
137#[serde(rename_all = "camelCase")]
138pub struct RegistryService {
139    /// The unique identifier for this service
140    pub identifier: String,
141    /// The lookup key for this service
142    pub key: String,
143    /// The type/kind of service
144    #[serde(rename = "type")]
145    pub kind: String,
146    /// Whether the service is currently active
147    pub active: bool,
148    /// Whether the service is enabled
149    pub enabled: bool,
150    /// When the service was created (milliseconds since Unix epoch)
151    pub created_at: i64,
152    /// When the service was last updated (milliseconds since Unix epoch)
153    pub updated_at: i64,
154    /// Immutable reference to the service implementation
155    #[serde(skip, default = "default_service")]
156    pub service: Option<Arc<dyn Service>>,
157    /// Mutable reference to the service implementation
158    #[serde(skip, default = "default_service_mut")]
159    pub service_mut: Option<Arc<Mutex<dyn Service>>>
160}
161
162impl Debug for RegistryService {
163    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
164        write!(f, "Identifier: {}\nKey: {}\nEnabled: {}\nActive:{:?}", self.identifier, self.key, self.enabled, self.active)
165    }
166}
167
168const LOCK_TIMEOUT: core::time::Duration = core::time::Duration::new(10, 0);
169
170macro_rules! delegate_lifecycle {
171    ($self:ident, $immutable_method:ident) => {{
172        if let Some(service) = &$self.service {
173            return service.$immutable_method().await;
174        }
175        if let Some(service_arc) = &$self.service_mut {
176            let service_clone = service_arc.clone();
177            let locked = service_clone.try_lock_for(LOCK_TIMEOUT);
178            if let Some(guard) = locked {
179                return guard.$immutable_method().await;
180            }
181        }
182        Err(())
183    }};
184    ($self:ident, $immutable_method:ident, $mutable_method:ident) => {{
185        if let Some(service) = &$self.service {
186            return service.$immutable_method().await;
187        }
188        if let Some(service_arc) = &$self.service_mut {
189            let service_clone = service_arc.clone();
190            let locked = service_clone.try_lock_for(LOCK_TIMEOUT);
191            if let Some(mut guard) = locked {
192                return guard.$mutable_method().await;
193            }
194        }
195        Err(())
196    }};
197}
198
199impl RegistryService {
200    /// Get the service identifier as a string slice
201    pub fn identifier(&self) -> &str {
202        &self.identifier
203    }
204
205    /// Get the service lookup key as a string slice
206    pub fn key(&self) -> &str {
207        &self.key
208    }
209
210    /// Get the service identifier as an owned String
211    #[deprecated(since = "0.0.28", note = "use identifier() which now returns &str; call .to_owned() if you need a String")]
212    pub fn identifier_owned(&self) -> String {
213        self.identifier.to_owned()
214    }
215
216    /// Get the service lookup key as an owned String
217    #[deprecated(since = "0.0.28", note = "use key() which now returns &str; call .to_owned() if you need a String")]
218    pub fn key_owned(&self) -> String {
219        self.key.to_owned()
220    }
221
222    /// Check if the service is enabled
223    pub fn is_enabled(&self) -> bool {
224        self.enabled
225    }
226
227    /// Check if the service is active
228    pub fn is_active(&self) -> bool {
229        self.active
230    }
231
232    /// Get the status of the service
233    ///
234    /// For mutable services, this temporarily locks the service with a 10-second timeout.
235    #[allow(clippy::await_holding_lock)]
236    pub async fn status(&self) -> Result<(), ()> {
237        delegate_lifecycle!(self, status)
238    }
239
240    /// Initialize the service
241    ///
242    /// For mutable services, this temporarily locks the service with a 10-second timeout.
243    #[allow(clippy::await_holding_lock)]
244    pub async fn init(&mut self) -> Result<(), ()> {
245        delegate_lifecycle!(self, init_service, init_service_mut)
246    }
247
248    /// Start the service
249    ///
250    /// For mutable services, this temporarily locks the service with a 10-second timeout.
251    #[allow(clippy::await_holding_lock)]
252    pub async fn start(&mut self) -> Result<(), ()> {
253        delegate_lifecycle!(self, start, start_mut)
254    }
255
256    /// Stop the service
257    ///
258    /// For mutable services, this temporarily locks the service with a 10-second timeout.
259    #[allow(clippy::await_holding_lock)]
260    pub async fn stop(&mut self) -> Result<(), ()> {
261        delegate_lifecycle!(self, stop, stop_mut)
262    }
263
264    /// Restart the service
265    ///
266    /// For mutable services, this temporarily locks the service with a 10-second timeout.
267    #[allow(clippy::await_holding_lock)]
268    pub async fn restart(&mut self) -> Result<(), ()> {
269        delegate_lifecycle!(self, restart, restart_mut)
270    }
271
272    /// Call a service action
273    ///
274    /// For mutable services, this temporarily locks the service with a 10-second timeout.
275    #[allow(clippy::await_holding_lock)]
276    pub async fn call(&mut self, action: &What, input: &Option<serde_json::Value>) -> Result<Option<serde_json::Value>, ServiceError> {
277        if let Some(service) = &self.service {
278            return service.call(action, input).await;
279        }
280        if let Some(service_arc) = &self.service_mut {
281            let service_clone = service_arc.clone();
282            let locked = service_clone.try_lock_for(LOCK_TIMEOUT);
283            if let Some(mut guard) = locked {
284                return guard.call_mut(action, input).await;
285            }
286            return Err(ServiceError::LockTimeout);
287        }
288        Err(ServiceError::GenericError("No service set".to_string()))
289    }
290}
291
292fn default_service() -> Option<Arc<dyn Service>> {
293    Some(Arc::new(DefaultService {}))
294}
295
296fn default_service_mut() -> Option<Arc<Mutex<dyn Service>>> {
297    Some(Arc::new(Mutex::new(DefaultService {})))
298}
299
300/// Trait for implementing server features
301///
302/// Features are modular components that handle specific HTTP routes and functionality.
303/// They can be registered with the feature registry and will be invoked when their
304/// registered paths are matched.
305///
306/// Only available when the `default` feature is enabled (requires `product-os-router/std`
307/// for `Body`, `ProductOSRouter`, and `Response` types).
308#[cfg(feature = "std_base")]
309#[async_trait]
310pub trait Feature: Send + Sync {
311    /// Register an immutable feature instance
312    async fn register(&self, feature: Arc<dyn Feature>, base_path: String, router: &mut product_os_router::ProductOSRouter) -> RegistryFeature;
313    /// Register a mutable feature instance
314    async fn register_mut(&self, feature: Arc<Mutex<dyn Feature>>, base_path: String, router: &mut product_os_router::ProductOSRouter) -> RegistryFeature;
315    /// Get the feature identifier
316    fn identifier(&self) -> String;
317    /// Handle a request (immutable)
318    async fn request(&self, action: &What, input: &Option<serde_json::Value>, semver: &str) -> Response<Body>;
319    /// Handle a request (mutable)
320    async fn request_mut(&mut self, action: &What, input: &Option<serde_json::Value>, semver: &str) -> Response<Body>;
321    /// Initialize the feature (immutable)
322    async fn init_feature(&self) -> Result<(), ()> { Ok(()) }
323    /// Initialize the feature (mutable)
324    async fn init_feature_mut(&mut self) -> Result<(), ()> { Ok(()) }
325}
326
327/// Default feature implementation
328///
329/// Used as a placeholder when no feature is provided.
330#[cfg(feature = "std_base")]
331pub struct DefaultFeature {}
332
333#[cfg(feature = "std_base")]
334#[async_trait]
335impl Feature for DefaultFeature {
336    async fn register(&self, feature: Arc<dyn Feature>, _: String, _: &mut ProductOSRouter) -> RegistryFeature {
337        RegistryFeature {
338            identifier: "".to_string(),
339            paths: vec![],
340            feature: Some(feature),
341            feature_mut: None
342        }
343    }
344
345    async fn register_mut(&self, feature: Arc<Mutex<dyn Feature>>, _: String, _: &mut ProductOSRouter) -> RegistryFeature {
346        RegistryFeature {
347            identifier: "".to_string(),
348            paths: vec![],
349            feature: None,
350            feature_mut: Some(feature)
351        }
352    }
353
354    fn identifier(&self) -> String {
355        "default".to_string()
356    }
357
358    async fn request(&self, _action: &What, _input: &Option<Value>, _semver: &str) -> Response<Body> {
359        Response::default()
360    }
361
362    async fn request_mut(&mut self, _action: &What, _input: &Option<Value>, _semver: &str) -> Response<Body> {
363        Response::default()
364    }
365}
366
367/// Errors that can occur when working with services
368#[derive(Debug, Clone)]
369pub enum ServiceError {
370    /// A generic error with a descriptive message
371    GenericError(String),
372    /// Failed to acquire lock on a mutable service within the timeout period
373    LockTimeout,
374    /// The requested service was not found
375    NotFound,
376    /// The service is not in the correct state for the requested operation
377    InvalidState(String),
378}
379
380impl std::fmt::Display for ServiceError {
381    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
382        match self {
383            ServiceError::GenericError(s) => write!(f, "{}", s),
384            ServiceError::LockTimeout => write!(f, "Failed to acquire service lock within timeout"),
385            ServiceError::NotFound => write!(f, "Service not found"),
386            ServiceError::InvalidState(s) => write!(f, "Invalid service state: {}", s),
387        }
388    }
389}
390
391impl std::error::Error for ServiceError {}
392
393/// Errors that can occur when working with features
394#[derive(Debug, Clone)]
395pub enum FeatureError {
396    /// A generic error with a descriptive message
397    GenericError(String),
398    /// Failed to acquire lock on a mutable feature within the timeout period
399    LockTimeout,
400    /// The requested feature was not found
401    NotFound,
402    /// Failed to register a path in the router
403    RouterInsertError {
404        /// Feature identifier
405        identifier: String,
406        /// Path that failed to insert
407        path: String,
408        /// Error details
409        details: String
410    },
411    /// Multiple fallback routes defined (only one is allowed)
412    MultipleFallbacks {
413        /// Feature identifier attempting to register fallback
414        identifier: String
415    },
416}
417
418impl std::fmt::Display for FeatureError {
419    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
420        match self {
421            FeatureError::GenericError(s) => write!(f, "{}", s),
422            FeatureError::LockTimeout => write!(f, "Failed to acquire feature lock within timeout"),
423            FeatureError::NotFound => write!(f, "Feature not found"),
424            FeatureError::RouterInsertError { identifier, path, details } => {
425                write!(f, "Failed to insert path '{}' for feature '{}': {}", path, identifier, details)
426            }
427            FeatureError::MultipleFallbacks { identifier } => {
428                write!(f, "Cannot set fallback route for '{}': fallback already defined", identifier)
429            }
430        }
431    }
432}
433
434impl std::error::Error for FeatureError {}
435
436/// Trait for implementing background services
437///
438/// Services are long-running components that provide functionality independent of
439/// HTTP requests. They have lifecycle methods (init, start, stop, restart) and can
440/// be called with actions.
441#[async_trait]
442pub trait Service: Send + Sync {
443    /// Register an immutable service instance
444    async fn register(&self, service: Arc<dyn Service>) -> RegistryService;
445    /// Register a mutable service instance
446    async fn register_mut(&self, service: Arc<Mutex<dyn Service>>) -> RegistryService;
447    /// Get the service identifier
448    fn identifier(&self) -> String;
449    /// Get the service lookup key
450    fn key(&self) -> String;
451    /// Check if the service is enabled
452    fn is_enabled(&self) -> bool;
453    /// Check if the service is active
454    fn is_active(&self) -> bool;
455    /// Check service status
456    async fn status(&self) -> Result<(), ()>;
457    /// Initialize the service (immutable)
458    async fn init_service(&self) -> Result<(), ()> { Ok(()) }
459    /// Start the service (immutable)
460    async fn start(&self) -> Result<(), ()>;
461    /// Stop the service (immutable)
462    async fn stop(&self) -> Result<(), ()>;
463    /// Call a service action (immutable)
464    async fn call(&self, _action: &What, _input: &Option<serde_json::Value>) -> Result<Option<serde_json::Value>, ServiceError> {
465        Ok(None)
466    }
467    /// Restart the service (immutable)
468    async fn restart(&self) -> Result<(), ()>;
469    /// Initialize the service (mutable)
470    async fn init_service_mut(&mut self) -> Result<(), ()> { Ok(()) }
471    /// Start the service (mutable)
472    async fn start_mut(&mut self) -> Result<(), ()>;
473    /// Stop the service (mutable)
474    async fn stop_mut(&mut self) -> Result<(), ()>;
475    /// Restart the service (mutable)
476    async fn restart_mut(&mut self) -> Result<(), ()>;
477    /// Call a service action (mutable)
478    async fn call_mut(&mut self, _action: &What, _input: &Option<serde_json::Value>) -> Result<Option<serde_json::Value>, ServiceError> {
479        Ok(None)
480    }
481}
482
483/// Default service implementation
484///
485/// Used as a placeholder when no service is provided.
486pub struct DefaultService {}
487
488#[async_trait]
489impl Service for DefaultService {
490    async fn register(&self, service: Arc<dyn Service>) -> RegistryService {
491        RegistryService {
492            identifier: "".to_string(),
493            key: "".to_string(),
494            kind: "".to_string(),
495            active: false,
496            enabled: false,
497            created_at: now_millis(),
498            updated_at: now_millis(),
499            service: Some(service),
500            service_mut: None
501        }
502    }
503
504    async fn register_mut(&self, service: Arc<Mutex<dyn Service>>) -> RegistryService {
505        RegistryService {
506            identifier: "".to_string(),
507            key: "".to_string(),
508            kind: "".to_string(),
509            active: false,
510            enabled: false,
511            created_at: now_millis(),
512            updated_at: now_millis(),
513            service: None,
514            service_mut: Some(service)
515        }
516    }
517
518    fn identifier(&self) -> String {
519        "default".to_string()
520    }
521
522    fn key(&self) -> String {
523        "key".to_string()
524    }
525
526    fn is_enabled(&self) -> bool {
527        false
528    }
529
530    fn is_active(&self) -> bool {
531        false
532    }
533
534    async fn status(&self) -> Result<(), ()> { Ok(()) }
535
536    async fn init_service(&self) -> Result<(), ()> { Ok(()) }
537
538    async fn start(&self) -> Result<(), ()> { Ok(()) }
539
540    async fn stop(&self) -> Result<(), ()> { Ok(()) }
541
542    async fn restart(&self) -> Result<(), ()> { Ok(()) }
543
544    async fn call(&self, _action: &What, _input: &Option<serde_json::Value>) -> Result<Option<serde_json::Value>, ServiceError> {
545        Ok(None)
546    }
547
548    async fn init_service_mut(&mut self) -> Result<(), ()> { Ok(()) }
549
550    async fn start_mut(&mut self) -> Result<(), ()> { Ok(()) }
551
552    async fn stop_mut(&mut self) -> Result<(), ()> { Ok(()) }
553
554    async fn restart_mut(&mut self) -> Result<(), ()> { Ok(()) }
555
556    async fn call_mut(&mut self, _action: &What, _input: &Option<serde_json::Value>) -> Result<Option<serde_json::Value>, ServiceError> {
557        Ok(None)
558    }
559}
560
561/// Combined trait for components that are both features and services
562///
563/// Only available when both the `default` and `feature_service` features are enabled.
564#[cfg(all(feature = "std_base", feature = "feature_service"))]
565pub trait FeatureService: Feature + Service {}