@isthatuzii/create-nano-app
Version:
Desktop application scaffolding tool for the Nano Framework
383 lines (297 loc) • 10.4 kB
Markdown
# 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};
pub struct DatabaseQuery {
pub table: String,
pub id: u32,
}
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};
pub struct CreateUserRequest {
pub username: String,
pub email: String,
pub role: String,
}
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};
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
mod tests {
use super::*;
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!