UNPKG

@isthatuzii/create-nano-app

Version:

Desktop application scaffolding tool for the Nano Framework

383 lines (297 loc) 10.4 kB
# Registering External Functions with Nano Framework This guide explains how to register functions that exist outside of `nano_impl.rs` with the nano registry system, allowing them to be called from the frontend via `invoke()`. ## Overview The nano framework allows you to register any async function from anywhere in your codebase, as long as it follows the correct signature pattern: ```rust pub async fn your_function(args: YourArgsType) -> Result<YourReturnType, String> ``` ## Method 1: External Module Integration ### Step 1: Create Your External Module Create your external system in a separate file or module: ```rust // src-nano/external/my_system.rs use serde::{Deserialize, Serialize}; #[derive(Debug, Deserialize)] pub struct DatabaseQuery { pub table: String, pub id: u32, } #[derive(Debug, Serialize)] pub struct DatabaseRecord { pub id: u32, pub name: String, pub data: String, } /// Example external function from your existing system pub async fn query_database(args: DatabaseQuery) -> Result<DatabaseRecord, String> { // Your existing database logic here if args.table.is_empty() { return Err("Table name cannot be empty".to_string()); } // Simulate database query Ok(DatabaseRecord { id: args.id, name: format!("Record_{}", args.id), data: format!("Data from table: {}", args.table), }) } /// Another external function pub async fn process_data(args: serde_json::Value) -> Result<String, String> { // Your complex business logic here Ok(format!("Processed: {}", args)) } ``` ### Step 2: Add Module Declaration Add your module to the main module structure: ```rust // src-nano/main.rs mod server; mod modules; mod config; mod ipc; mod nano_functions; mod external; // Add this line // Or if you have a more complex structure: // mod external { // pub mod my_system; // pub mod another_system; // } ``` ### Step 3: Import and Register Functions Update your nano registry to include the external functions: ```rust // src-nano/nano_functions/nano_registry.rs use super::nano_impl::*; use crate::external::my_system::*; // Import external functions pub fn register_all_nano_functions() { use crate::register_nano_command; tracing::info!("Registering nano functions..."); // Built-in nano functions register_nano_command!("greet", greet); register_nano_command!("multiply", multiply); register_nano_command!("add", add); // ... other built-in functions // External system functions register_nano_command!("query_database", query_database); register_nano_command!("process_data", process_data); tracing::info!("Registered {} nano functions", 13); // Update count } ``` ## Method 2: Separate Folder Structure ### Step 1: Create External Folder Structure ``` src-nano/ ├── nano_functions/ │ ├── mod.rs │ ├── nano_types.rs │ ├── nano_impl.rs │ └── nano_registry.rs ├── external_systems/ │ ├── mod.rs │ ├── database.rs │ ├── auth.rs │ └── payment.rs └── main.rs ``` ### Step 2: Set Up External Systems Module ```rust // src-nano/external_systems/mod.rs pub mod database; pub mod auth; pub mod payment; ``` ```rust // src-nano/external_systems/database.rs use serde::{Deserialize, Serialize}; #[derive(Debug, Deserialize)] pub struct CreateUserRequest { pub username: String, pub email: String, pub role: String, } #[derive(Debug, Serialize)] pub struct CreateUserResponse { pub user_id: u32, pub username: String, pub created_at: String, } pub async fn create_user(args: CreateUserRequest) -> Result<CreateUserResponse, String> { // Validate input if args.username.trim().is_empty() { return Err("Username is required".to_string()); } if !args.email.contains('@') { return Err("Invalid email format".to_string()); } // Simulate user creation Ok(CreateUserResponse { user_id: 12345, username: args.username, created_at: chrono::Utc::now().to_rfc3339(), }) } pub async fn delete_user(args: serde_json::Value) -> Result<String, String> { let user_id: u32 = args["user_id"].as_u64().unwrap_or(0) as u32; if user_id == 0 { return Err("Invalid user ID".to_string()); } // Simulate user deletion Ok(format!("User {} deleted successfully", user_id)) } ``` ### Step 3: Register External Systems ```rust // src-nano/main.rs mod nano_functions; mod external_systems; // Add this // In your main function or initialization: use external_systems::database; use external_systems::auth; use external_systems::payment; ``` ```rust // src-nano/nano_functions/nano_registry.rs use super::nano_impl::*; use crate::external_systems::database; use crate::external_systems::auth; use crate::external_systems::payment; pub fn register_all_nano_functions() { use crate::register_nano_command; tracing::info!("Registering nano functions..."); // Built-in nano functions register_nano_command!("greet", greet); register_nano_command!("multiply", multiply); // ... other built-in functions // External database functions register_nano_command!("create_user", database::create_user); register_nano_command!("delete_user", database::delete_user); // External auth functions register_nano_command!("login", auth::login); register_nano_command!("logout", auth::logout); // External payment functions register_nano_command!("process_payment", payment::process_payment); register_nano_command!("refund_payment", payment::refund_payment); tracing::info!("Registered {} nano functions", 18); // Update count } ``` ## Method 3: Direct Registration from External Crates If you have an external crate/library you want to integrate: ### Step 1: Add Dependency ```toml # Cargo.toml [dependencies] my_external_crate = "1.0" # ... other dependencies ``` ### Step 2: Create Wrapper Functions ```rust // src-nano/wrappers/external_crate_wrapper.rs use my_external_crate::{ExternalSystem, ProcessRequest}; use serde::{Deserialize, Serialize}; #[derive(Debug, Deserialize)] pub struct ExternalProcessArgs { pub input_data: String, pub process_type: String, } /// Wrapper function for external crate functionality pub async fn process_with_external_crate(args: ExternalProcessArgs) -> Result<String, String> { let external_system = ExternalSystem::new(); let request = ProcessRequest { data: args.input_data, process_type: args.process_type, }; // Call external crate function match external_system.process(request).await { Ok(result) => Ok(result.output), Err(e) => Err(format!("External processing failed: {}", e)), } } ``` ### Step 3: Register Wrapper Function ```rust // src-nano/nano_functions/nano_registry.rs use crate::wrappers::external_crate_wrapper::*; pub fn register_all_nano_functions() { // ... existing registrations // External crate wrapper register_nano_command!("process_external", process_with_external_crate); // ... rest of function } ``` ## Frontend Usage Once registered, all external functions can be called from the frontend just like built-in nano functions: ```typescript // Frontend usage examples import { invoke } from "./api/api"; // Database operations const user = await invoke("create_user", { username: "john_doe", email: "john@example.com", role: "admin" }); // External crate processing const result = await invoke("process_external", { input_data: "some data to process", process_type: "advanced" }); // Custom system query const records = await invoke("query_database", { table: "users", id: 42 }); ``` ## Best Practices ### 1. **Function Signature Consistency** Always use the same signature pattern: ```rust pub async fn your_function(args: YourArgsType) -> Result<YourReturnType, String> ``` ### 2. **Error Handling** - Return descriptive error messages as `String` - Validate input parameters before processing - Handle external system failures gracefully ### 3. **Type Definitions** - Define clear `Deserialize` types for arguments - Define clear `Serialize` types for return values - Keep types in separate files or modules for organization ### 4. **Documentation** - Document each external function with `///` comments - Explain what the function does and any special requirements - Include examples of expected input/output ### 5. **Testing** ```rust #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn test_external_function() { let args = CreateUserRequest { username: "test_user".to_string(), email: "test@example.com".to_string(), role: "user".to_string(), }; let result = create_user(args).await; assert!(result.is_ok()); } } ``` ## Security Considerations 1. **Input Validation**: Always validate external inputs 2. **Authentication**: Consider adding auth checks for sensitive functions 3. **Rate Limiting**: Implement rate limiting for resource-intensive operations 4. **Logging**: Add appropriate logging for external function calls ```rust pub async fn sensitive_operation(args: SensitiveArgs) -> Result<String, String> { tracing::info!("Sensitive operation called with user: {}", args.user_id); // Validate permissions if !has_permission(args.user_id, "admin").await { tracing::warn!("Unauthorized access attempt by user: {}", args.user_id); return Err("Insufficient permissions".to_string()); } // Perform operation let result = perform_sensitive_operation().await?; tracing::info!("Sensitive operation completed successfully"); Ok(result) } ``` This system provides maximum flexibility while maintaining the simplicity of the nano framework's `invoke()` pattern!