rapace_registry/
introspection.rs

1//! Service introspection support.
2//!
3//! Provides the `ServiceIntrospection` trait and default implementation for
4//! querying available services at runtime.
5
6use facet::Facet;
7
8use crate::ServiceRegistry;
9
10/// Information about a registered service.
11#[derive(Clone, Debug, Facet)]
12pub struct ServiceInfo {
13    /// Service name (e.g., "Calculator").
14    pub name: String,
15    /// Service documentation.
16    pub doc: String,
17    /// Methods provided by this service.
18    pub methods: Vec<MethodInfo>,
19}
20
21/// Information about a method.
22#[derive(Clone, Debug, Facet)]
23pub struct MethodInfo {
24    /// Method ID (for debugging/logging).
25    pub id: u32,
26    /// Method name (e.g., "add").
27    pub name: String,
28    /// Full qualified name (e.g., "Calculator.add").
29    pub full_name: String,
30    /// Method documentation.
31    pub doc: String,
32    /// Argument names and types.
33    pub args: Vec<ArgInfo>,
34    /// Whether this is a streaming method.
35    pub is_streaming: bool,
36}
37
38/// Argument metadata.
39#[derive(Clone, Debug, Facet)]
40pub struct ArgInfo {
41    /// Argument name (e.g., "a", "name").
42    pub name: String,
43    /// Argument type name (e.g., "i32", "String").
44    pub type_name: String,
45}
46
47// Note: We can't use #[rapace::service] here because it would create a circular dependency
48// (rapace-macros depends on rapace-registry). Instead, we define the trait and let
49// the user generate the client/server in a crate that depends on rapace.
50//
51// Users will do:
52// ```
53// use rapace_registry::introspection::ServiceIntrospection;
54//
55// #[rapace::service]
56// pub trait ServiceIntrospection {
57//     async fn list_services(&self) -> Vec<ServiceInfo>;
58//     async fn describe_service(&self, name: String) -> Option<ServiceInfo>;
59//     async fn has_method(&self, method_id: u32) -> bool;
60// }
61// ```
62
63/// Default implementation that reads from the global registry.
64///
65/// This implementation provides runtime introspection by reading
66/// from the global service registry.
67///
68/// # Example
69///
70/// ```ignore
71/// use rapace_registry::introspection::DefaultServiceIntrospection;
72///
73/// let introspection = DefaultServiceIntrospection;
74/// let services = introspection.list_services();
75/// for service in services {
76///     println!("Service: {}", service.name);
77/// }
78/// ```
79#[derive(Clone, Debug, Default)]
80pub struct DefaultServiceIntrospection;
81
82impl DefaultServiceIntrospection {
83    /// Create a new default introspection implementation.
84    pub fn new() -> Self {
85        Self
86    }
87
88    /// List all services registered in the global registry.
89    ///
90    /// Returns a snapshot of all currently registered services and their methods.
91    pub fn list_services(&self) -> Vec<ServiceInfo> {
92        ServiceRegistry::with_global(|registry| {
93            registry
94                .iter_services()
95                .map(|service| ServiceInfo {
96                    name: service.name.to_string(),
97                    doc: service.doc.clone(),
98                    methods: service
99                        .iter_methods()
100                        .map(|method| MethodInfo {
101                            id: method.id.0,
102                            name: method.name.to_string(),
103                            full_name: method.full_name.clone(),
104                            doc: method.doc.clone(),
105                            args: method
106                                .args
107                                .iter()
108                                .map(|arg| ArgInfo {
109                                    name: arg.name.to_string(),
110                                    type_name: arg.type_name.to_string(),
111                                })
112                                .collect(),
113                            is_streaming: method.is_streaming,
114                        })
115                        .collect(),
116                })
117                .collect()
118        })
119    }
120
121    /// Describe a specific service by name.
122    ///
123    /// Returns `Some(ServiceInfo)` if the service exists, `None` otherwise.
124    pub fn describe_service(&self, name: &str) -> Option<ServiceInfo> {
125        self.list_services().into_iter().find(|s| s.name == name)
126    }
127
128    /// Check if a method ID is supported.
129    ///
130    /// Returns `true` if any registered service has a method with this ID.
131    pub fn has_method(&self, method_id: u32) -> bool {
132        ServiceRegistry::with_global(|registry| {
133            registry.method_by_id(crate::MethodId(method_id)).is_some()
134        })
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[test]
143    fn test_introspection_empty() {
144        // Note: Global registry might have services from other tests,
145        // so we just verify the API works
146        let introspection = DefaultServiceIntrospection::new();
147        let services = introspection.list_services();
148        // Should return a list (possibly empty or with services from other tests)
149        let _ = services;
150    }
151
152    #[test]
153    fn test_describe_service() {
154        let introspection = DefaultServiceIntrospection::new();
155        // Non-existent service should return None
156        let result = introspection.describe_service("NonExistentService12345");
157        assert!(result.is_none() || result.is_some()); // May or may not exist depending on test order
158    }
159
160    #[test]
161    fn test_has_method() {
162        let introspection = DefaultServiceIntrospection::new();
163        // Method 0 is reserved for control
164        let has_control = introspection.has_method(0);
165        // Should be false (control is not registered as a service method)
166        let _ = has_control;
167    }
168}