update coordinator and add end to end tests
This commit is contained in:
@@ -200,3 +200,213 @@ fn is_offsetdatetime_type(ty: &Type) -> bool {
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Derive macro for generating CRUD client methods for Osiris models
|
||||
///
|
||||
/// This macro generates async CRUD methods (create, get, update, delete, list) for a model,
|
||||
/// plus any custom methods defined on the model.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// #[derive(OsirisModel)]
|
||||
/// #[osiris(
|
||||
/// collection = "calendar_events",
|
||||
/// id_field = "event_id",
|
||||
/// methods = ["reschedule", "cancel"]
|
||||
/// )]
|
||||
/// pub struct CalendarEvent {
|
||||
/// pub event_id: String,
|
||||
/// pub title: String,
|
||||
/// pub start_time: i64,
|
||||
/// // ...
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This generates methods on OsirisClient:
|
||||
/// - `create_calendar_event(&self, event: CalendarEvent) -> Result<CalendarEvent>`
|
||||
/// - `get_calendar_event(&self, event_id: &str) -> Result<CalendarEvent>`
|
||||
/// - `update_calendar_event(&self, event_id: &str, event: CalendarEvent) -> Result<CalendarEvent>`
|
||||
/// - `delete_calendar_event(&self, event_id: &str) -> Result<()>`
|
||||
/// - `list_calendar_events(&self) -> Result<Vec<CalendarEvent>>`
|
||||
/// - `reschedule_calendar_event(&self, event_id: &str, new_time: i64) -> Result<CalendarEvent>`
|
||||
/// - `cancel_calendar_event(&self, event_id: &str) -> Result<CalendarEvent>`
|
||||
#[proc_macro_derive(OsirisModel, attributes(osiris))]
|
||||
pub fn derive_osiris_model(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
|
||||
let model_name = &input.ident;
|
||||
let model_name_snake = to_snake_case(&model_name.to_string());
|
||||
|
||||
// Parse attributes
|
||||
let mut collection = model_name_snake.clone();
|
||||
let mut id_field = "id".to_string();
|
||||
let mut custom_methods: Vec<String> = Vec::new();
|
||||
|
||||
for attr in &input.attrs {
|
||||
if attr.path().is_ident("osiris") {
|
||||
if let Ok(meta_list) = attr.parse_args::<syn::MetaList>() {
|
||||
// Parse nested attributes
|
||||
for nested in meta_list.tokens.clone() {
|
||||
let nested_str = nested.to_string();
|
||||
if nested_str.starts_with("collection") {
|
||||
if let Some(val) = extract_string_value(&nested_str) {
|
||||
collection = val;
|
||||
}
|
||||
} else if nested_str.starts_with("id_field") {
|
||||
if let Some(val) = extract_string_value(&nested_str) {
|
||||
id_field = val;
|
||||
}
|
||||
} else if nested_str.starts_with("methods") {
|
||||
custom_methods = extract_array_values(&nested_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate method names
|
||||
let create_method = syn::Ident::new(&format!("create_{}", model_name_snake), model_name.span());
|
||||
let get_method = syn::Ident::new(&format!("get_{}", model_name_snake), model_name.span());
|
||||
let update_method = syn::Ident::new(&format!("update_{}", model_name_snake), model_name.span());
|
||||
let delete_method = syn::Ident::new(&format!("delete_{}", model_name_snake), model_name.span());
|
||||
let list_method = syn::Ident::new(&format!("list_{}s", model_name_snake), model_name.span());
|
||||
|
||||
// Generate custom method implementations
|
||||
let custom_method_impls: Vec<_> = custom_methods.iter().map(|method_name| {
|
||||
let method_ident = syn::Ident::new(&format!("{}_{}", method_name, model_name_snake), model_name.span());
|
||||
let rhai_call = format!("{}_{}", model_name_snake, method_name);
|
||||
|
||||
quote! {
|
||||
pub async fn #method_ident(&self, id: &str, params: serde_json::Value) -> Result<#model_name, OsirisClientError> {
|
||||
let script = format!(
|
||||
r#"
|
||||
let obj = {}::get("{}");
|
||||
obj.{}(params);
|
||||
obj.save();
|
||||
obj
|
||||
"#,
|
||||
#collection, id, #method_name
|
||||
);
|
||||
|
||||
let response = self.execute_script(&script).await?;
|
||||
// Parse response and return model
|
||||
// This is a simplified version - actual implementation would parse the job result
|
||||
Err(OsirisClientError::CommandFailed("Not yet implemented".to_string()))
|
||||
}
|
||||
}
|
||||
}).collect();
|
||||
|
||||
let expanded = quote! {
|
||||
impl OsirisClient {
|
||||
/// Create a new instance of #model_name
|
||||
pub async fn #create_method(&self, model: &#model_name) -> Result<#model_name, OsirisClientError> {
|
||||
let json = serde_json::to_string(model)
|
||||
.map_err(|e| OsirisClientError::SerializationFailed(e.to_string()))?;
|
||||
|
||||
let script = format!(
|
||||
r#"
|
||||
let data = {};
|
||||
let obj = {}::new(data);
|
||||
obj.save();
|
||||
obj
|
||||
"#,
|
||||
json, #collection
|
||||
);
|
||||
|
||||
let response = self.execute_script(&script).await?;
|
||||
// Parse response - simplified for now
|
||||
Err(OsirisClientError::CommandFailed("Not yet implemented".to_string()))
|
||||
}
|
||||
|
||||
/// Get an instance of #model_name by ID
|
||||
pub async fn #get_method(&self, id: &str) -> Result<#model_name, OsirisClientError> {
|
||||
let query = format!(r#"{{ "{}": "{}" }}"#, #id_field, id);
|
||||
self.query::<#model_name>(#collection, &query).await
|
||||
}
|
||||
|
||||
/// Update an existing #model_name
|
||||
pub async fn #update_method(&self, id: &str, model: &#model_name) -> Result<#model_name, OsirisClientError> {
|
||||
let json = serde_json::to_string(model)
|
||||
.map_err(|e| OsirisClientError::SerializationFailed(e.to_string()))?;
|
||||
|
||||
let script = format!(
|
||||
r#"
|
||||
let obj = {}::get("{}");
|
||||
let data = {};
|
||||
obj.update(data);
|
||||
obj.save();
|
||||
obj
|
||||
"#,
|
||||
#collection, id, json
|
||||
);
|
||||
|
||||
let response = self.execute_script(&script).await?;
|
||||
Err(OsirisClientError::CommandFailed("Not yet implemented".to_string()))
|
||||
}
|
||||
|
||||
/// Delete an instance of #model_name
|
||||
pub async fn #delete_method(&self, id: &str) -> Result<(), OsirisClientError> {
|
||||
let script = format!(
|
||||
r#"
|
||||
let obj = {}::get("{}");
|
||||
obj.delete();
|
||||
"#,
|
||||
#collection, id
|
||||
);
|
||||
|
||||
self.execute_script(&script).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// List all instances of #model_name
|
||||
pub async fn #list_method(&self) -> Result<Vec<#model_name>, OsirisClientError> {
|
||||
self.query_all::<#model_name>(#collection).await
|
||||
}
|
||||
|
||||
#(#custom_method_impls)*
|
||||
}
|
||||
};
|
||||
|
||||
TokenStream::from(expanded)
|
||||
}
|
||||
|
||||
fn to_snake_case(s: &str) -> String {
|
||||
let mut result = String::new();
|
||||
for (i, ch) in s.chars().enumerate() {
|
||||
if ch.is_uppercase() {
|
||||
if i > 0 {
|
||||
result.push('_');
|
||||
}
|
||||
result.push(ch.to_lowercase().next().unwrap());
|
||||
} else {
|
||||
result.push(ch);
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn extract_string_value(s: &str) -> Option<String> {
|
||||
// Extract value from "key = \"value\"" format
|
||||
if let Some(eq_pos) = s.find('=') {
|
||||
let value_part = &s[eq_pos + 1..].trim();
|
||||
let cleaned = value_part.trim_matches(|c| c == '"' || c == ' ');
|
||||
return Some(cleaned.to_string());
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn extract_array_values(s: &str) -> Vec<String> {
|
||||
// Extract values from "methods = [\"method1\", \"method2\"]" format
|
||||
if let Some(start) = s.find('[') {
|
||||
if let Some(end) = s.find(']') {
|
||||
let array_content = &s[start + 1..end];
|
||||
return array_content
|
||||
.split(',')
|
||||
.map(|item| item.trim().trim_matches('"').to_string())
|
||||
.filter(|item| !item.is_empty())
|
||||
.collect();
|
||||
}
|
||||
}
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user