Skip to main content

rohas_codegen/
rust.rs

1use crate::error::Result;
2use crate::templates;
3use rohas_parser::{Api, Event, FieldType, Model, Schema, WebSocket};
4use std::fs;
5use std::path::Path;
6
7pub fn generate_models(schema: &Schema, output_dir: &Path) -> Result<()> {
8    let models_dir = output_dir.join("generated/models");
9
10    for model in &schema.models {
11        let content = generate_model_content(model);
12        let file_name = format!("{}.rs", templates::to_snake_case(&model.name));
13        fs::write(models_dir.join(file_name), content)?;
14    }
15
16    let mut mod_content = String::new();
17    mod_content.push_str("// Auto-generated module declarations\n");
18    for model in &schema.models {
19        let mod_name = templates::to_snake_case(&model.name);
20        mod_content.push_str(&format!("pub mod {};\n", mod_name));
21        mod_content.push_str(&format!("pub use {}::{};\n", mod_name, model.name));
22    }
23    fs::write(models_dir.join("mod.rs"), mod_content)?;
24
25    Ok(())
26}
27
28fn generate_model_content(model: &Model) -> String {
29    let mut content = String::new();
30
31    content.push_str("use serde::{Deserialize, Serialize};\n\n");
32    content.push_str(&format!("#[derive(Debug, Clone, Serialize, Deserialize)]\n"));
33    content.push_str(&format!("pub struct {}\n", model.name));
34    content.push_str("{\n");
35
36    for field in &model.fields {
37        let rust_type = field.field_type.to_rust();
38        let type_hint = if field.optional {
39            format!("Option<{}>", rust_type)
40        } else {
41            rust_type
42        };
43
44        let field_name = &field.name;
45        content.push_str(&format!("    pub {}: {},\n", field_name, type_hint));
46    }
47
48    if model.fields.is_empty() {
49        content.push_str("    // No fields\n");
50    }
51
52    content.push_str("}\n");
53
54    content
55}
56
57pub fn generate_dtos(schema: &Schema, output_dir: &Path) -> Result<()> {
58    let dto_dir = output_dir.join("generated/dto");
59
60    for input in &schema.inputs {
61        let content = generate_model_content(&rohas_parser::Model {
62            name: input.name.clone(),
63            fields: input.fields.clone(),
64            attributes: vec![],
65        });
66        let file_name = format!("{}.rs", templates::to_snake_case(&input.name));
67        fs::write(dto_dir.join(file_name), content)?;
68    }
69
70    let mut mod_content = String::new();
71    mod_content.push_str("// Auto-generated module declarations\n");
72    for input in &schema.inputs {
73        let mod_name = templates::to_snake_case(&input.name);
74        mod_content.push_str(&format!("pub mod {};\n", mod_name));
75        mod_content.push_str(&format!("pub use {}::{};\n", mod_name, input.name));
76    }
77    fs::write(dto_dir.join("mod.rs"), mod_content)?;
78
79    Ok(())
80}
81
82pub fn generate_apis(schema: &Schema, output_dir: &Path) -> Result<()> {
83    let api_dir = output_dir.join("generated/api");
84
85    for api in &schema.apis {
86        let content = generate_api_content(api);
87        let file_name = format!("{}.rs", templates::to_snake_case(&api.name));
88        fs::write(api_dir.join(file_name), content)?;
89    }
90
91    let mut mod_content = String::new();
92    mod_content.push_str("// Auto-generated module declarations\n");
93    for api in &schema.apis {
94        let mod_name = templates::to_snake_case(&api.name);
95        mod_content.push_str(&format!("pub mod {};\n", mod_name));
96        mod_content.push_str(&format!("pub use {}::{{ {}Request, {}Response }};\n", mod_name, api.name, api.name));
97    }
98    fs::write(api_dir.join("mod.rs"), mod_content)?;
99
100    let handlers_dir = output_dir.join("handlers/api");
101    for api in &schema.apis {
102        let file_name = format!("{}.rs", templates::to_snake_case(&api.name));
103        let handler_path = handlers_dir.join(&file_name);
104
105        if !handler_path.exists() {
106            let content = generate_api_handler_stub(api);
107            fs::write(handler_path, content)?;
108        }
109    }
110
111    Ok(())
112}
113
114fn generate_api_content(api: &Api) -> String {
115    let mut content = String::new();
116
117    content.push_str("use serde::{Deserialize, Serialize};\n");
118
119    if let Some(body_type) = &api.body {
120        let body_type_snake = templates::to_snake_case(body_type);
121        if body_type.ends_with("Input") {
122            content.push_str(&format!("use super::super::dto::{}::{};\n", body_type_snake, body_type));
123        } else {
124            content.push_str(&format!("use super::super::models::{}::{};\n", body_type_snake, body_type));
125        }
126    }
127
128    let response_field_type = rohas_parser::FieldType::from_str(&api.response);
129    let is_custom_response = matches!(response_field_type, rohas_parser::FieldType::Custom(_));
130    if is_custom_response {
131        let response_type_snake = templates::to_snake_case(&api.response);
132        content.push_str(&format!("use super::super::models::{}::{};\n", response_type_snake, api.response));
133    }
134    content.push_str("\n");
135
136    if let Some(body_type) = &api.body {
137        content.push_str(&format!(
138            "pub type {}Request = {};\n\n",
139            api.name, body_type
140        ));
141    } else {
142        content.push_str(&format!(
143            "#[derive(Debug, Clone, Serialize, Deserialize)]\n"
144        ));
145        content.push_str(&format!("pub struct {}Request\n", api.name));
146        content.push_str("{\n");
147        content.push_str("    // No body fields\n");
148        content.push_str("}\n\n");
149    }
150
151    let response_rust_type = response_field_type.to_rust();
152    content.push_str(&format!(
153        "pub type {}Response = {};\n",
154        api.name, response_rust_type
155    ));
156
157    content
158}
159
160fn generate_api_handler_stub(api: &Api) -> String {
161    let mut content = String::new();
162
163    let request_type = format!("{}Request", api.name);
164    let response_type = format!("{}Response", api.name);
165    let handler_name = format!("handle_{}", templates::to_snake_case(&api.name));
166    let module_name = templates::to_snake_case(&api.name);
167
168    content.push_str(&format!(
169        "use crate::generated::api::{}::{{ {}, {} }};\n",
170        module_name, request_type, response_type
171    ));
172    content.push_str("use crate::generated::state::State;\n");
173    content.push_str("use rohas_runtime::{HandlerContext, HandlerResult, Result};\n\n");
174
175    content.push_str(&format!(
176        "/// Rust handler for {} API.\n",
177        api.name
178    ));
179    content.push_str(&format!(
180        "pub async fn {}(\n",
181        handler_name
182    ));
183    content.push_str(&format!("    req: {},\n", request_type));
184    content.push_str("    state: &mut State,\n");
185    content.push_str(&format!(") -> Result<{}> {{\n", response_type));
186    content.push_str("    // TODO: Implement handler logic\n");
187    content.push_str("    // For auto-triggers (defined in schema triggers): use state.set_payload(\"EventName\", value)\n");
188    content.push_str("    // For manual triggers: use state.trigger_event(\"EventName\", value)\n");
189    content.push_str("    // Use state.logger for structured logging\n");
190    content.push_str(&format!(
191        "    Err(rohas_runtime::RuntimeError::ExecutionFailed(\"Handler not implemented\".into()))\n"
192    ));
193    content.push_str("}\n");
194
195    content
196}
197
198pub fn generate_events(schema: &Schema, output_dir: &Path) -> Result<()> {
199    let events_dir = output_dir.join("generated/events");
200
201    for event in &schema.events {
202        let content = generate_event_content(event);
203        let file_name = format!("{}.rs", templates::to_snake_case(&event.name));
204        fs::write(events_dir.join(file_name), content)?;
205    }
206
207    let mut mod_content = String::new();
208    mod_content.push_str("// Auto-generated module declarations\n");
209    for event in &schema.events {
210        let mod_name = templates::to_snake_case(&event.name);
211        mod_content.push_str(&format!("pub mod {};\n", mod_name));
212        mod_content.push_str(&format!("pub use {}::{};\n", mod_name, event.name));
213    }
214    fs::write(events_dir.join("mod.rs"), mod_content)?;
215
216    let handlers_dir = output_dir.join("handlers/events");
217    for event in &schema.events {
218        for handler in &event.handlers {
219            let file_name = format!("{}.rs", handler);
220            let handler_path = handlers_dir.join(&file_name);
221
222            if !handler_path.exists() {
223                let content = generate_event_handler_stub(event, handler);
224                fs::write(handler_path, content)?;
225            }
226        }
227    }
228
229    Ok(())
230}
231
232fn generate_event_content(event: &Event) -> String {
233    let mut content = String::new();
234
235    content.push_str("use serde::{Deserialize, Serialize};\n");
236    content.push_str("use chrono::{DateTime, Utc};\n\n");
237
238    let payload_field_type = FieldType::from_str(&event.payload);
239    let payload_rust_type = payload_field_type.to_rust();
240
241    let is_custom_type = matches!(payload_field_type, FieldType::Custom(_));
242    if is_custom_type {
243        let model_module = templates::to_snake_case(&event.payload);
244        content.push_str(&format!(
245            "use crate::generated::models::{}::{};\n",
246            model_module, event.payload
247        ));
248    }
249
250    content.push_str(&format!("#[derive(Debug, Clone, Serialize, Deserialize)]\n"));
251    content.push_str(&format!("pub struct {}\n", event.name));
252    content.push_str("{\n");
253    content.push_str(&format!("    pub payload: {},\n", payload_rust_type));
254    content.push_str("    pub timestamp: DateTime<Utc>,\n");
255    content.push_str("}\n");
256
257    content
258}
259
260fn generate_event_handler_stub(event: &Event, handler_name: &str) -> String {
261    let mut content = String::new();
262
263    let event_module = templates::to_snake_case(&event.name);
264
265    content.push_str(&format!(
266        "use crate::generated::events::{}::{};\n",
267        event_module, event.name
268    ));
269    content.push_str("use rohas_runtime::{HandlerContext, HandlerResult, Result};\n\n");
270
271    content.push_str(&format!(
272        "/// High-performance Rust event handler.\n"
273    ));
274    content.push_str(&format!(
275        "pub async fn {}(\n",
276        handler_name
277    ));
278    content.push_str(&format!("    event: {},\n", event.name));
279    content.push_str(") -> Result<HandlerResult> {\n");
280    content.push_str("    // TODO: Implement event handler\n");
281    content.push_str(&format!(
282        "    tracing::info!(\"Handling event: {{:?}}\", event);\n"
283    ));
284    content.push_str("    Ok(HandlerResult::success(serde_json::json!({}), 0))\n");
285    content.push_str("}\n");
286
287    content
288}
289
290pub fn generate_crons(schema: &Schema, output_dir: &Path) -> Result<()> {
291    let handlers_dir = output_dir.join("handlers/cron");
292
293    for cron in &schema.crons {
294        let file_name = format!("{}.rs", templates::to_snake_case(&cron.name));
295        let handler_path = handlers_dir.join(&file_name);
296
297        if !handler_path.exists() {
298            let content = generate_cron_handler_stub(cron);
299            fs::write(handler_path, content)?;
300        }
301    }
302
303    Ok(())
304}
305
306fn generate_cron_handler_stub(cron: &rohas_parser::Cron) -> String {
307    let mut content = String::new();
308
309    let handler_name = format!("handle_{}", templates::to_snake_case(&cron.name));
310
311    content.push_str("use rohas_runtime::{HandlerContext, HandlerResult, Result};\n");
312    content.push_str("use crate::generated::state::State;\n\n");
313
314    content.push_str(&format!(
315        "/// High-performance Rust cron handler.\n"
316    ));
317    content.push_str(&format!(
318        "pub async fn {}(\n",
319        handler_name
320    ));
321    content.push_str("    state: &mut State,\n");
322    content.push_str(") -> Result<HandlerResult> {\n");
323    content.push_str("    // TODO: Implement cron handler\n");
324    content.push_str(&format!(
325        "    tracing::info!(\"Executing cron: {}\");\n",
326        cron.name
327    ));
328    content.push_str("    Ok(HandlerResult::success(serde_json::json!({}), 0))\n");
329    content.push_str("}\n");
330
331    content
332}
333
334pub fn generate_websockets(schema: &Schema, output_dir: &Path) -> Result<()> {
335    let ws_dir = output_dir.join("generated/websockets");
336
337    for ws in &schema.websockets {
338        let content = generate_websocket_content(ws);
339        let file_name = format!("{}.rs", templates::to_snake_case(&ws.name));
340        fs::write(ws_dir.join(file_name), content)?;
341    }
342
343    let mut mod_content = String::new();
344    mod_content.push_str("// Auto-generated module declarations\n");
345    for ws in &schema.websockets {
346        let mod_name = templates::to_snake_case(&ws.name);
347        mod_content.push_str(&format!("pub mod {};\n", mod_name));
348        mod_content.push_str(&format!("pub use {}::{{ {}Connection", mod_name, ws.name));
349        if ws.message.is_some() {
350            mod_content.push_str(&format!(", {}Message", ws.name));
351        }
352        mod_content.push_str(" };\n");
353    }
354    fs::write(ws_dir.join("mod.rs"), mod_content)?;
355
356    let handlers_dir = output_dir.join("handlers/websockets");
357    for ws in &schema.websockets {
358        for handler in &ws.on_connect {
359            let file_name = format!("{}.rs", handler);
360            let handler_path = handlers_dir.join(&file_name);
361            if !handler_path.exists() {
362                let content = generate_websocket_handler_stub(ws, handler, "connect");
363                fs::write(handler_path, content)?;
364            }
365        }
366        for handler in &ws.on_message {
367            let file_name = format!("{}.rs", handler);
368            let handler_path = handlers_dir.join(&file_name);
369            if !handler_path.exists() {
370                let content = generate_websocket_handler_stub(ws, handler, "message");
371                fs::write(handler_path, content)?;
372            }
373        }
374        for handler in &ws.on_disconnect {
375            let file_name = format!("{}.rs", handler);
376            let handler_path = handlers_dir.join(&file_name);
377            if !handler_path.exists() {
378                let content = generate_websocket_handler_stub(ws, handler, "disconnect");
379                fs::write(handler_path, content)?;
380            }
381        }
382    }
383
384    Ok(())
385}
386
387fn generate_websocket_content(ws: &WebSocket) -> String {
388    let mut content = String::new();
389
390    content.push_str("use serde::{Deserialize, Serialize};\n\n");
391
392    if let Some(message_type) = &ws.message {
393        let rust_type = FieldType::from_str(message_type).to_rust();
394
395        content.push_str(&format!(
396            "pub type {}Message = {};\n\n",
397            ws.name, rust_type
398        ));
399    }
400
401    content.push_str(&format!(
402        "#[derive(Debug, Clone, Serialize, Deserialize)]\n"
403    ));
404    content.push_str(&format!("pub struct {}Connection\n", ws.name));
405    content.push_str("{\n");
406    content.push_str("    // Connection metadata\n");
407    content.push_str("}\n");
408
409    content
410}
411
412fn generate_websocket_handler_stub(ws: &WebSocket, handler_name: &str, event_type: &str) -> String {
413    let mut content = String::new();
414
415    let ws_module = templates::to_snake_case(&ws.name);
416
417    content.push_str(&format!(
418        "use crate::generated::websockets::{}::{}Connection;\n",
419        ws_module, ws.name
420    ));
421
422    if ws.message.is_some() {
423        content.push_str(&format!(
424            "use crate::generated::websockets::{}::{}Message;\n",
425            ws_module, ws.name
426        ));
427    }
428
429    content.push_str("use rohas_runtime::{HandlerContext, HandlerResult, Result};\n");
430    content.push_str("use crate::generated::state::State;\n\n");
431
432    content.push_str(&format!(
433        "/// Rust WebSocket {} handler.\n",
434        event_type
435    ));
436    content.push_str(&format!("pub async fn {}(\n", handler_name));
437
438    if event_type == "message" {
439        if let Some(_) = &ws.message {
440            content.push_str(&format!("    message: {}Message,\n", ws.name));
441        }
442        content.push_str(&format!("    connection: {}Connection,\n", ws.name));
443        content.push_str("    state: &mut State,\n");
444    } else {
445        content.push_str(&format!("    connection: {}Connection,\n", ws.name));
446        if event_type == "connect" {
447            content.push_str("    state: &mut State,\n");
448        }
449    }
450
451    content.push_str(") -> Result<HandlerResult> {\n");
452    content.push_str(&format!(
453        "    tracing::info!(\"WebSocket {} handler: {{:?}}\", connection);\n",
454        event_type
455    ));
456    content.push_str("    Ok(HandlerResult::success(serde_json::json!({}), 0))\n");
457    content.push_str("}\n");
458
459    content
460}
461
462pub fn generate_middlewares(schema: &Schema, output_dir: &Path) -> Result<()> {
463    let mut middleware_names = std::collections::HashSet::new();
464
465    for api in &schema.apis {
466        for mw in &api.middlewares {
467            middleware_names.insert(mw.clone());
468        }
469    }
470
471    for ws in &schema.websockets {
472        for mw in &ws.middlewares {
473            middleware_names.insert(mw.clone());
474        }
475    }
476
477    let handlers_dir = output_dir.join("handlers/middlewares");
478    for mw_name in middleware_names {
479        let file_name = format!("{}.rs", templates::to_snake_case(&mw_name));
480        let handler_path = handlers_dir.join(&file_name);
481
482        if !handler_path.exists() {
483            let content = generate_middleware_stub(&mw_name);
484            fs::write(handler_path, content)?;
485        }
486    }
487
488    Ok(())
489}
490
491fn generate_middleware_stub(mw_name: &str) -> String {
492    let mut content = String::new();
493
494    let handler_name = format!("{}_middleware", templates::to_snake_case(mw_name));
495
496    content.push_str("use rohas_runtime::{HandlerContext, HandlerResult, Result};\n");
497    content.push_str("use crate::generated::state::State;\n\n");
498
499    content.push_str(&format!(
500        "/// High-performance Rust middleware.\n"
501    ));
502    content.push_str(&format!("pub async fn {}(\n", handler_name));
503    content.push_str("    ctx: HandlerContext,\n");
504    content.push_str("    state: &mut State,\n");
505    content.push_str(") -> Result<HandlerResult> {\n");
506    content.push_str("    // TODO: Implement middleware logic\n");
507    content.push_str("    // Return Ok to continue, Err to abort\n");
508    content.push_str(&format!(
509        "    tracing::info!(\"Middleware {} executed\");\n",
510        mw_name
511    ));
512    content.push_str("    Ok(HandlerResult::success(serde_json::json!({}), 0))\n");
513    content.push_str("}\n");
514
515    content
516}
517
518pub fn generate_state(output_dir: &Path) -> Result<()> {
519    let generated_dir = output_dir.join("generated");
520    let content = r#"use serde_json::Value;
521use std::collections::HashMap;
522use tracing::{error, warn, info, debug, trace};
523
524/// State struct for Rust handlers.
525#[derive(Debug, Clone)]
526pub struct State {
527    handler_name: String,
528    triggers: Vec<TriggeredEvent>,
529    auto_trigger_payloads: HashMap<String, Value>,
530}
531
532#[derive(Debug, Clone)]
533pub struct TriggeredEvent {
534    pub event_name: String,
535    pub payload: Value,
536}
537
538impl State {
539    /// Create a new State instance.
540    pub fn new(handler_name: impl Into<String>) -> Self {
541        Self {
542            handler_name: handler_name.into(),
543            triggers: Vec::new(),
544            auto_trigger_payloads: HashMap::new(),
545        }
546    }
547
548    /// Manually trigger an event (for events NOT in schema triggers).
549    pub fn trigger_event(&mut self, event_name: impl Into<String>, payload: Value) {
550        self.triggers.push(TriggeredEvent {
551            event_name: event_name.into(),
552            payload,
553        });
554    }
555
556    /// Set payload for an auto-triggered event (for events IN schema triggers).
557    pub fn set_payload(&mut self, event_name: impl Into<String>, payload: Value) {
558        self.auto_trigger_payloads.insert(event_name.into(), payload);
559    }
560
561    /// Get all manually triggered events (internal use).
562    pub fn get_triggers(&self) -> &[TriggeredEvent] {
563        &self.triggers
564    }
565
566    /// Get all auto-trigger payloads (internal use).
567    pub fn get_all_auto_trigger_payloads(&self) -> &HashMap<String, Value> {
568        &self.auto_trigger_payloads
569    }
570
571    /// Get a logger instance for this handler.
572    pub fn logger(&self) -> Logger {
573        Logger::new(&self.handler_name)
574    }
575}
576
577/// Structured logger for handlers.
578pub struct Logger {
579    handler_name: String,
580}
581
582impl Logger {
583    pub fn new(handler_name: impl Into<String>) -> Self {
584        Self {
585            handler_name: handler_name.into(),
586        }
587    }
588
589    pub fn info(&self, message: &str) {
590        info!(handler = %self.handler_name, %message);
591    }
592
593    pub fn error(&self, message: &str) {
594        error!(handler = %self.handler_name, %message);
595    }
596
597    pub fn warn(&self, message: &str) {
598        warn!(handler = %self.handler_name, %message);
599    }
600
601    pub fn debug(&self, message: &str) {
602        debug!(handler = %self.handler_name, %message);
603    }
604
605    pub fn trace(&self, message: &str) {
606        trace!(handler = %self.handler_name, %message);
607    }
608}
609"#;
610
611    fs::write(generated_dir.join("state.rs"), content)?;
612    Ok(())
613}
614
615/// Generate lib.rs for the generated crate.
616pub fn generate_lib_rs(schema: &Schema, output_dir: &Path) -> Result<()> {
617    let generated_dir = output_dir.join("generated");
618
619    let mut content = String::new();
620    content.push_str("// Auto-generated Rust code from Rohas schema\n");
621    content.push_str("// DO NOT EDIT MANUALLY\n\n");
622
623    // Generate module declarations
624    content.push_str("pub mod state;\n");
625    content.push_str("pub mod models;\n");
626    content.push_str("pub mod dto;\n");
627    content.push_str("pub mod api;\n");
628    content.push_str("pub mod events;\n");
629    content.push_str("pub mod websockets;\n");
630    content.push_str("pub mod handlers;\n\n");
631
632    // Re-export commonly used types
633    content.push_str("pub use state::State;\n");
634    content.push_str("pub use handlers::register_all_handlers;\n");
635    content.push_str("pub use handlers::set_runtime;\n\n");
636
637    fs::write(generated_dir.join("lib.rs"), content)?;
638
639    // Generate handlers registration module
640    generate_handlers_registration(schema, output_dir)?;
641
642
643    // Also generate the main src/lib.rs that sets up the module structure
644    let mut main_lib_content = String::new();
645    main_lib_content.push_str("// Main library entry point for Rohas Rust application\n");
646    main_lib_content.push_str("// This file sets up the module structure\n\n");
647    main_lib_content.push_str("#[path = \"generated/lib.rs\"]\n");
648    main_lib_content.push_str("pub mod generated;\n\n");
649    main_lib_content.push_str("// Re-export generated types for convenience\n");
650    main_lib_content.push_str("pub use generated::*;\n\n");
651
652    // Generate handlers module declarations
653    let handlers_dir = output_dir.join("handlers");
654    if handlers_dir.join("api").exists() || handlers_dir.join("events").exists() {
655        main_lib_content.push_str("pub mod handlers;\n\n");
656    }
657
658    // Add initialization function that can be called to register handlers
659    main_lib_content.push_str("/// Initialize and register all handlers with the Rust runtime.\n");
660    main_lib_content.push_str("/// This function should be called during engine startup.\n");
661    main_lib_content.push_str("/// It will automatically register all handlers using the global registry.\n");
662    main_lib_content.push_str("pub async fn init_handlers(runtime: std::sync::Arc<rohas_runtime::RustRuntime>) -> rohas_runtime::Result<()> {\n");
663    main_lib_content.push_str("    generated::register_all_handlers(runtime).await\n");
664    main_lib_content.push_str("}\n\n");
665
666    // Add a C-compatible FFI function that can be called from the engine
667    // This allows the engine to automatically register handlers
668    main_lib_content.push_str("/// C-compatible FFI function for automatic handler registration.\n");
669    main_lib_content.push_str("/// This is called automatically by the engine.\n");
670    main_lib_content.push_str("/// Returns 0 on success, non-zero on error.\n");
671    main_lib_content.push_str("#[no_mangle]\n");
672    main_lib_content.push_str("pub extern \"C\" fn rohas_set_runtime(runtime_ptr: *mut std::ffi::c_void) -> i32 {\n");
673    main_lib_content.push_str("    use std::sync::Arc;\n");
674    main_lib_content.push_str("    \n");
675    main_lib_content.push_str("    if runtime_ptr.is_null() {\n");
676    main_lib_content.push_str("        return 1; // Error: null pointer\n");
677    main_lib_content.push_str("    }\n");
678    main_lib_content.push_str("    \n");
679    main_lib_content.push_str("    // Safety: The engine passes a valid Arc<RustRuntime> pointer that was created with Arc::into_raw.\n");
680    main_lib_content.push_str("    // We reconstruct the Arc temporarily to clone it, then forget it so the engine retains ownership.\n");
681    main_lib_content.push_str("    unsafe {\n");
682    main_lib_content.push_str("        // Convert the raw pointer back to Arc<RustRuntime>\n");
683    main_lib_content.push_str("        // The engine created this with Arc::into_raw, so we reconstruct it temporarily\n");
684    main_lib_content.push_str("        let runtime: Arc<rohas_runtime::RustRuntime> = Arc::from_raw(runtime_ptr as *const rohas_runtime::RustRuntime);\n");
685    main_lib_content.push_str("        \n");
686    main_lib_content.push_str("        // Clone the Arc - this increments the reference count\n");
687    main_lib_content.push_str("        let runtime_clone = runtime.clone();\n");
688    main_lib_content.push_str("        \n");
689    main_lib_content.push_str("        // Forget the reconstructed Arc - we don't want to drop it here since the engine still owns it\n");
690    main_lib_content.push_str("        // The engine will manage the original Arc's lifetime\n");
691    main_lib_content.push_str("        std::mem::forget(runtime);\n");
692    main_lib_content.push_str("        \n");
693    main_lib_content.push_str("        // Call the generated set_runtime function which will register all handlers\n");
694    main_lib_content.push_str("        // This will store the cloned Arc in a OnceLock and register handlers synchronously\n");
695    main_lib_content.push_str("        // Note: If registration fails, set_runtime will panic (via .expect())\n");
696    main_lib_content.push_str("        generated::set_runtime(runtime_clone);\n");
697    main_lib_content.push_str("        \n");
698    main_lib_content.push_str("        0 // Success\n");
699    main_lib_content.push_str("    }\n");
700    main_lib_content.push_str("}\n");
701
702    fs::write(output_dir.join("lib.rs"), main_lib_content)?;
703
704    // Generate handlers/mod.rs if handlers exist
705    if handlers_dir.join("api").exists() || handlers_dir.join("events").exists() {
706        generate_handlers_mod(schema, output_dir)?;
707    }
708
709    Ok(())
710}
711
712fn generate_handlers_mod(schema: &Schema, output_dir: &Path) -> Result<()> {
713    let handlers_dir = output_dir.join("handlers");
714    let mut content = String::new();
715
716    content.push_str("// Handler module declarations\n\n");
717
718    if handlers_dir.join("api").exists() {
719        content.push_str("pub mod api;\n");
720    }
721
722    if handlers_dir.join("events").exists() {
723        content.push_str("pub mod events;\n");
724    }
725
726    fs::write(handlers_dir.join("mod.rs"), content)?;
727
728    if handlers_dir.join("api").exists() {
729        let mut api_mod = String::new();
730        api_mod.push_str("// API handler modules\n\n");
731
732        for api in &schema.apis {
733            let handler_name = templates::to_snake_case(&api.name);
734            let handler_file = handlers_dir.join("api").join(format!("{}.rs", handler_name));
735            if handler_file.exists() {
736                api_mod.push_str(&format!("pub mod {};\n", handler_name));
737            }
738        }
739
740        fs::write(handlers_dir.join("api").join("mod.rs"), api_mod)?;
741    }
742
743    if handlers_dir.join("events").exists() {
744        let mut events_mod = String::new();
745        events_mod.push_str("// Event handler modules\n\n");
746
747        for event in &schema.events {
748            for handler in &event.handlers {
749                let handler_file = handlers_dir.join("events").join(format!("{}.rs", handler));
750                if handler_file.exists() {
751                    events_mod.push_str(&format!("pub mod {};\n", handler));
752                }
753            }
754        }
755
756        fs::write(handlers_dir.join("events").join("mod.rs"), events_mod)?;
757    }
758
759    Ok(())
760}
761
762fn generate_handlers_registration(schema: &Schema, output_dir: &Path) -> Result<()> {
763    let generated_dir = output_dir.join("generated");
764    let handlers_dir = output_dir.join("handlers");
765
766    let mut content = String::new();
767    content.push_str("// Auto-generated handler registration\n");
768    content.push_str("// DO NOT EDIT MANUALLY\n\n");
769
770    content.push_str("use rohas_runtime::{RustRuntime, HandlerContext, HandlerResult, Result};\n");
771    content.push_str("use std::sync::Arc;\n");
772    content.push_str("use std::sync::OnceLock;\n\n");
773
774    content.push_str("// Global registry for automatic handler registration\n");
775    content.push_str("static RUNTIME_REGISTRY: OnceLock<Arc<RustRuntime>> = OnceLock::new();\n\n");
776    content.push_str("/// Set the runtime for automatic handler registration.\n");
777    content.push_str("/// This is called automatically by the engine.\n");
778    content.push_str("/// This function is public so it can be called from the engine.\n");
779    content.push_str("/// Note: Each dylib has its own OnceLock, so this can be called fresh on each reload.\n");
780    content.push_str("pub fn set_runtime(runtime: Arc<RustRuntime>) {\n");
781    content.push_str("    // Set the runtime (this will only succeed once per dylib load, which is what we want)\n");
782    content.push_str("    let _ = RUNTIME_REGISTRY.set(runtime);\n");
783    content.push_str("    // Always trigger registration (important for hot reload)\n");
784    content.push_str("    register_all_handlers_internal().expect(\"Failed to register handlers\");\n");
785    content.push_str("}\n\n");
786
787    let mut has_handlers = false;
788
789    for api in &schema.apis {
790        let handler_name = templates::to_snake_case(&api.name);
791        let handler_file = handlers_dir.join("api").join(format!("{}.rs", handler_name));
792        if handler_file.exists() {
793            has_handlers = true;
794            break;
795        }
796    }
797
798    if !has_handlers {
799        for event in &schema.events {
800            for handler in &event.handlers {
801                let handler_file = handlers_dir.join("events").join(format!("{}.rs", handler));
802                if handler_file.exists() {
803                    has_handlers = true;
804                    break;
805                }
806            }
807            if has_handlers {
808                break;
809            }
810        }
811    }
812
813    if !has_handlers {
814        content.push_str("/// Register all handlers with the Rust runtime.\n");
815        content.push_str("/// No handlers found - implement handlers in src/handlers/ to register them.\n");
816        content.push_str("pub async fn register_all_handlers(_runtime: Arc<RustRuntime>) -> Result<()> {\n");
817        content.push_str("    Ok(())\n");
818        content.push_str("}\n\n");
819        content.push_str("fn register_all_handlers_internal() -> Result<()> {\n");
820        content.push_str("    Ok(())\n");
821        content.push_str("}\n");
822        fs::write(generated_dir.join("handlers.rs"), content)?;
823        return Ok(());
824    }
825
826    content.push_str("// Import handler functions\n");
827
828    for api in &schema.apis {
829        let handler_name = templates::to_snake_case(&api.name);
830        let handler_file = handlers_dir.join("api").join(format!("{}.rs", handler_name));
831
832        if handler_file.exists() {
833            content.push_str(&format!(
834                "use crate::handlers::api::{}::handle_{};\n",
835                handler_name, handler_name
836            ));
837        }
838    }
839
840    for event in &schema.events {
841        for handler in &event.handlers {
842            let handler_file = handlers_dir.join("events").join(format!("{}.rs", handler));
843
844            if handler_file.exists() {
845                content.push_str(&format!(
846                    "use crate::handlers::events::{}::{};\n",
847                    handler, handler
848                ));
849            }
850        }
851    }
852
853    content.push_str("\n");
854    content.push_str("/// Register all handlers with the Rust runtime.\n");
855    content.push_str("/// This function should be called during engine initialization.\n");
856    content.push_str("pub async fn register_all_handlers(runtime: Arc<RustRuntime>) -> Result<()> {\n");
857    content.push_str("    set_runtime(runtime);\n");
858    content.push_str("    Ok(())\n");
859    content.push_str("}\n\n");
860
861    content.push_str("/// Internal registration function (synchronous, for static initialization).\n");
862    content.push_str("fn register_all_handlers_internal() -> Result<()> {\n");
863    content.push_str("    use tracing::info;\n");
864    content.push_str("    info!(\"Registering Rust handlers from dylib...\");\n");
865    content.push_str("    let runtime = RUNTIME_REGISTRY.get().ok_or_else(|| rohas_runtime::RuntimeError::ExecutionFailed(\"Runtime not set\".into()))?;\n");
866    content.push_str("    let rt = tokio::runtime::Runtime::new().map_err(|e| rohas_runtime::RuntimeError::ExecutionFailed(e.to_string()))?;\n");
867    content.push_str("    rt.block_on(async {\n");
868
869    for api in &schema.apis {
870        let handler_name = templates::to_snake_case(&api.name);
871        let handler_file = handlers_dir.join("api").join(format!("{}.rs", handler_name));
872
873        if handler_file.exists() {
874            content.push_str(&format!(
875                "        // Register API handler: {}\n",
876                api.name
877            ));
878            content.push_str(&format!(
879                "        runtime.register_handler(\n"
880            ));
881            content.push_str(&format!(
882                "            \"{}\".to_string(),\n",
883                handler_name
884            ));
885            content.push_str(&format!(
886                "            |ctx: HandlerContext| async move {{\n"
887            ));
888            content.push_str(&format!(
889                "                // Parse request from context\n"
890            ));
891            content.push_str(&format!(
892                "                let req: crate::generated::api::{}::{}Request = serde_json::from_value(ctx.payload.clone())?;\n",
893                handler_name, api.name
894            ));
895            content.push_str(&format!(
896                "                let mut state = crate::generated::state::State::new(&ctx.handler_name);\n"
897            ));
898            content.push_str(&format!(
899                "                let response = handle_{}(req, &mut state).await?;\n",
900                handler_name
901            ));
902            content.push_str(&format!(
903                "                Ok(HandlerResult::success(serde_json::to_value(response)?, 0))\n"
904            ));
905            content.push_str(&format!(
906                "            }}\n"
907            ));
908            content.push_str(&format!(
909                "        ).await;\n"
910            ));
911            content.push_str(&format!(
912                "        info!(\"Registered handler: {}\");\n",
913                handler_name
914            ));
915        }
916    }
917
918    content.push_str("        Ok::<(), rohas_runtime::RuntimeError>(())\n");
919    content.push_str("    })?;\n");
920    content.push_str("    Ok(())\n");
921    content.push_str("}\n");
922
923    fs::write(generated_dir.join("handlers.rs"), content)?;
924    Ok(())
925}
926
927pub fn is_in_rohas_workspace(output_dir: &Path) -> bool {
928    let project_root = if output_dir.file_name().and_then(|s| s.to_str()) == Some("src") {
929        output_dir.parent().unwrap_or(output_dir)
930    } else {
931        output_dir
932    };
933
934    let path_str = project_root.to_string_lossy();
935    if path_str.contains("/examples/") || path_str.contains("\\examples\\") {
936        return true;
937    }
938
939    let mut current = project_root;
940    for _ in 0..5 {
941        let crates_dir = current.join("crates").join("rohas-cli");
942        if crates_dir.exists() {
943            return true;
944        }
945        if let Some(parent) = current.parent() {
946            current = parent;
947        } else {
948            break;
949        }
950    }
951
952    false
953}
954
955pub fn generate_dev_scripts(output_dir: &Path) -> Result<()> {
956    let project_root = if output_dir.file_name().and_then(|s| s.to_str()) == Some("src") {
957        output_dir.parent().unwrap_or(output_dir).to_path_buf()
958    } else {
959        output_dir.to_path_buf()
960    };
961
962    let dev_script = r#"#!/bin/bash
963# Development helper script for Rohas developers
964# For end users: install rohas CLI and run "rohas dev --workbench" directly
965
966set -e
967
968# Find the workspace root (look for Cargo.toml with [workspace])
969SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
970WORKSPACE_ROOT="$SCRIPT_DIR"
971
972# Look for workspace root (go up to 10 levels to handle nested examples)
973for i in {1..10}; do
974    if [ -f "$WORKSPACE_ROOT/Cargo.toml" ]; then
975        # Check if it's a workspace (has [workspace] and contains crates/rohas-cli)
976        if grep -q "^\[workspace\]" "$WORKSPACE_ROOT/Cargo.toml" 2>/dev/null && \
977            [ -d "$WORKSPACE_ROOT/crates/rohas-cli" ]; then
978            break
979        fi
980    fi
981    WORKSPACE_ROOT="$(dirname "$WORKSPACE_ROOT")"
982    # Stop if we've reached the filesystem root
983    if [ "$WORKSPACE_ROOT" = "/" ] || [ "$WORKSPACE_ROOT" = "$SCRIPT_DIR" ]; then
984        break
985    fi
986done
987
988if [ -f "$WORKSPACE_ROOT/Cargo.toml" ] && grep -q "^\[workspace\]" "$WORKSPACE_ROOT/Cargo.toml" 2>/dev/null && \
989   [ -d "$WORKSPACE_ROOT/crates/rohas-cli" ]; then
990    cd "$WORKSPACE_ROOT"
991    REL_SCHEMA_PATH=$(python3 -c "import os; print(os.path.relpath('$SCRIPT_DIR/schema', '$WORKSPACE_ROOT'))" 2>/dev/null || \
992                      perl -MFile::Spec -e "print File::Spec->abs2rel('$SCRIPT_DIR/schema', '$WORKSPACE_ROOT')" 2>/dev/null || \
993                      echo "schema")
994    # Check if --schema argument is already provided
995    HAS_SCHEMA_ARG=false
996    for arg in "$@"; do
997        if [[ "$arg" == "--schema" ]] || [[ "$arg" == "-s" ]]; then
998            HAS_SCHEMA_ARG=true
999            break
1000        fi
1001    done
1002    # If no schema arg provided, add it
1003    if [ "$HAS_SCHEMA_ARG" = false ]; then
1004        exec cargo run -p rohas-cli -- dev --schema "$REL_SCHEMA_PATH" "$@"
1005    else
1006        exec cargo run -p rohas-cli -- dev "$@"
1007    fi
1008else
1009    # Not in workspace - try installed binary or show helpful error
1010    if command -v rohas >/dev/null 2>&1; then
1011        cd "$SCRIPT_DIR"
1012        exec rohas dev "$@"
1013    else
1014        echo "Error: Could not find Rohas workspace root and rohas CLI is not installed"
1015        echo ""
1016        echo "For Rohas developers: Run this script from within the rohas workspace"
1017        echo "For end users: Install rohas CLI first:"
1018        echo "  cargo install --path <path-to-rohas>/crates/rohas-cli"
1019        echo "  Then run: rohas dev --workbench"
1020        exit 1
1021    fi
1022fi
1023"#;
1024
1025    let dev_script_path = project_root.join("dev.sh");
1026    fs::write(&dev_script_path, dev_script)?;
1027
1028    // Make it executable (Unix-like systems)
1029    #[cfg(unix)]
1030    {
1031        use std::os::unix::fs::PermissionsExt;
1032        let mut perms = fs::metadata(&dev_script_path)?.permissions();
1033        perms.set_mode(0o755);
1034        fs::set_permissions(&dev_script_path, perms)?;
1035    }
1036
1037    let makefile_content = r#"# Makefile for Rohas developers working in examples
1038# End users: Install rohas CLI and use "rohas dev --workbench" directly
1039
1040.PHONY: dev dev-watch codegen check build validate
1041
1042# Run development server (for Rohas developers - finds workspace automatically)
1043# Usage: make dev ARGS="--workbench"
1044dev:
1045	@./dev.sh $(ARGS)
1046
1047# Run development server with workbench
1048dev-watch:
1049	@./dev.sh --workbench
1050
1051# Generate code from schema (for Rohas developers)
1052codegen:
1053	@SCRIPT_DIR=$$(pwd); \
1054	WORKSPACE_ROOT=$$SCRIPT_DIR; \
1055	for i in {1..10}; do \
1056		if [ -f "$$WORKSPACE_ROOT/Cargo.toml" ] && grep -q "^\[workspace\]" "$$WORKSPACE_ROOT/Cargo.toml" 2>/dev/null && [ -d "$$WORKSPACE_ROOT/crates/rohas-cli" ]; then \
1057			break; \
1058		fi; \
1059		WORKSPACE_ROOT=$$(dirname "$$WORKSPACE_ROOT"); \
1060	done; \
1061	cd "$$WORKSPACE_ROOT" && cargo run -p rohas-cli -- codegen --schema "$$SCRIPT_DIR/schema" --output "$$SCRIPT_DIR/src" --lang rust
1062
1063# Check Rust code
1064check:
1065	@CARGO_TARGET_DIR=../../target cargo check
1066
1067# Build Rust project
1068build:
1069	@CARGO_TARGET_DIR=../../target cargo build --release
1070
1071# Validate schema (for Rohas developers)
1072validate:
1073	@SCRIPT_DIR=$$(pwd); \
1074	WORKSPACE_ROOT=$$SCRIPT_DIR; \
1075	for i in {1..10}; do \
1076		if [ -f "$$WORKSPACE_ROOT/Cargo.toml" ] && grep -q "^\[workspace\]" "$$WORKSPACE_ROOT/Cargo.toml" 2>/dev/null && [ -d "$$WORKSPACE_ROOT/crates/rohas-cli" ]; then \
1077			break; \
1078		fi; \
1079		WORKSPACE_ROOT=$$(dirname "$$WORKSPACE_ROOT"); \
1080	done; \
1081	cd "$$WORKSPACE_ROOT" && cargo run -p rohas-cli -- validate --schema "$$SCRIPT_DIR/schema"
1082"#;
1083
1084    fs::write(project_root.join("Makefile"), makefile_content)?;
1085
1086    Ok(())
1087}
1088