Skip to main content

lib_cpi/
lib.rs

1// File: lib_cpi/src/lib.rs
2use std::collections::HashMap;
3use serde::{Serialize, Deserialize};
4use serde_json::Value;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct ActionParameter {
8    pub name: String,
9    pub description: String,
10    pub required: bool,
11    pub param_type: ParamType,
12    pub default_value: Option<Value>,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub enum ParamType {
17    String,
18    Number,
19    Boolean,
20    Object,
21    Array,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct ActionDefinition {
26    pub name: String,
27    pub description: String,
28    pub parameters: Vec<ActionParameter>,
29}
30
31pub type ActionResult = Result<Value, String>;
32
33/// Main trait that must be implemented by CPI extensions
34pub trait CpiExtension: Send + Sync {
35    /// Returns the name of the extension
36    fn name(&self) -> &str;
37    
38    /// Returns the provider type
39    fn provider_type(&self) -> &str;
40    
41    /// Returns all available actions
42    fn list_actions(&self) -> Vec<String>;
43    
44    /// Returns the definition of a specific action
45    fn get_action_definition(&self, action: &str) -> Option<ActionDefinition>;
46    
47    /// Executes an action with the given parameters
48    fn execute_action(&self, action: &str, params: &HashMap<String, Value>) -> ActionResult;
49    
50    /// Optional method that returns default parameter values for the provider
51    fn default_settings(&self) -> HashMap<String, Value> {
52        HashMap::new()
53    }
54    
55    /// Test if the extension is properly installed
56    fn test_install(&self) -> ActionResult {
57        // Default implementation returns success
58        Ok(serde_json::json!({"status": "ok"}))
59    }
60
61    fn version(&self) -> String {
62        // Default implementation returns a placeholder version
63        "NONE".to_string()
64    }
65}
66
67// Required function signature for dynamic registration
68pub type GetExtensionFn = unsafe extern "C" fn() -> *mut dyn CpiExtension;
69
70// Explicitly re-export the action macro from lib_cpi_macros
71// This ensures the macro is available when users import lib_cpi
72pub use lib_cpi_macros::action;
73
74// Entry point macro that every extension DLL must implement
75#[macro_export]
76macro_rules! register_extension {
77    ($ext_type:ty) => {
78        #[no_mangle]
79        pub unsafe extern "C" fn get_extension() -> *mut dyn $crate::CpiExtension {
80            // Create a Box containing the extension implementation
81            let extension = Box::new(<$ext_type>::new());
82            // Convert the Box into a raw pointer and return it
83            Box::into_raw(extension)
84        }
85    };
86}
87
88// Helper functions for parameter validation
89pub mod validation {
90    use super::*;
91    
92    pub fn extract_string(params: &HashMap<String, Value>, name: &str) -> Result<String, String> {
93        match params.get(name) {
94            Some(Value::String(s)) => Ok(s.clone()),
95            Some(_) => Err(format!("Parameter '{}' must be a string", name)),
96            None => Err(format!("Required parameter '{}' not provided", name)),
97        }
98    }
99    
100    pub fn extract_string_opt(params: &HashMap<String, Value>, name: &str) -> Result<Option<String>, String> {
101        match params.get(name) {
102            Some(Value::String(s)) => Ok(Some(s.clone())),
103            Some(_) => Err(format!("Parameter '{}' must be a string", name)),
104            None => Ok(None),
105        }
106    }
107    
108    pub fn extract_int(params: &HashMap<String, Value>, name: &str) -> Result<i64, String> {
109        match params.get(name) {
110            Some(Value::Number(n)) if n.is_i64() => Ok(n.as_i64().unwrap()),
111            Some(_) => Err(format!("Parameter '{}' must be an integer", name)),
112            None => Err(format!("Required parameter '{}' not provided", name)),
113        }
114    }
115    
116    pub fn extract_int_opt(params: &HashMap<String, Value>, name: &str) -> Result<Option<i64>, String> {
117        match params.get(name) {
118            Some(Value::Number(n)) if n.is_i64() => Ok(Some(n.as_i64().unwrap())),
119            Some(_) => Err(format!("Parameter '{}' must be an integer", name)),
120            None => Ok(None),
121        }
122    }
123    
124    pub fn extract_float(params: &HashMap<String, Value>, name: &str) -> Result<f64, String> {
125        match params.get(name) {
126            Some(Value::Number(n)) => Ok(n.as_f64().unwrap()),
127            Some(_) => Err(format!("Parameter '{}' must be a number", name)),
128            None => Err(format!("Required parameter '{}' not provided", name)),
129        }
130    }
131    
132    pub fn extract_bool(params: &HashMap<String, Value>, name: &str) -> Result<bool, String> {
133        match params.get(name) {
134            Some(Value::Bool(b)) => Ok(*b),
135            Some(_) => Err(format!("Parameter '{}' must be a boolean", name)),
136            None => Err(format!("Required parameter '{}' not provided", name)),
137        }
138    }
139    
140    pub fn extract_json(params: &HashMap<String, Value>, name: &str) -> Result<Value, String> {
141        match params.get(name) {
142            Some(v) => Ok(v.clone()),
143            None => Err(format!("Required parameter '{}' not provided", name)),
144        }
145    }
146    
147    pub fn validate_params(
148        params: &HashMap<String, Value>, 
149        required: &[&str]
150    ) -> Result<(), String> {
151        for &param in required {
152            if !params.contains_key(param) {
153                return Err(format!("Required parameter '{}' not provided", param));
154            }
155        }
156        Ok(())
157    }
158}
159
160// Helper macro to simplify creating parameter definitions
161#[macro_export]
162macro_rules! param {
163    ($name:expr, $desc:expr, $type:expr, required) => {
164        $crate::ActionParameter {
165            name: $name.to_string(),
166            description: $desc.to_string(),
167            required: true,
168            param_type: $type,
169            default_value: None,
170        }
171    };
172    ($name:expr, $desc:expr, $type:expr, optional, $default:expr) => {
173        $crate::ActionParameter {
174            name: $name.to_string(),
175            description: $desc.to_string(),
176            required: false,
177            param_type: $type,
178            default_value: Some($default),
179        }
180    };
181    ($name:expr, $desc:expr, $type:expr, optional) => {
182        $crate::ActionParameter {
183            name: $name.to_string(),
184            description: $desc.to_string(),
185            required: false,
186            param_type: $type,
187            default_value: None,
188        }
189    };
190}
191
192// Helper module to create standard action responses
193pub mod response {
194    use serde_json::{json, Value};
195    
196    pub fn success(data: Option<Value>) -> Value {
197        match data {
198            Some(value) => json!({
199                "success": true,
200                "data": value,
201            }),
202            None => json!({
203                "success": true,
204            }),
205        }
206    }
207    
208    pub fn bool_result(result: bool) -> Value {
209        json!({
210            "success": true,
211            "result": result
212        })
213    }
214    
215    pub fn error(message: impl AsRef<str>) -> Value {
216        json!({
217            "success": false,
218            "error": message.as_ref(),
219        })
220    }
221}