icarus 0.5.8

Build MCP (Model Context Protocol) servers that run as Internet Computer canisters
Documentation
# Icarus Macros Guide

Detailed guide to all macros provided by the Icarus SDK.

## Overview

Icarus macros eliminate boilerplate and generate standard ICP code while providing excellent developer experience. All macros are compile-time only - they generate standard Candid interfaces with no runtime overhead.

## Module-Level Macros

### `#[icarus_module]`

The most important macro that processes a module containing tool functions.

#### What it does:
1. Exports all public functions at crate level
2. Generates `list_tools()` query function
3. Preserves `#[update]` and `#[query]` attributes
4. Creates tool metadata from `#[icarus_tool]` attributes

#### Usage:
```rust
#[icarus_module]
mod tools {
    use super::*;
    
    #[update]
    #[icarus_tool("Store a value")]
    pub fn store(key: String, value: String) -> Result<(), String> {
        // Implementation
    }
    
    #[query]
    #[icarus_tool("Retrieve a value")]
    pub fn get(key: String) -> Option<String> {
        // Implementation
    }
}
```

#### Generated code:
- `store` and `get` functions exported at crate level
- `list_tools()` query function returning tool information
- Standard Candid methods with proper attributes

## Function-Level Macros

### `#[icarus_tool]`

Marks a function as an MCP tool and provides metadata.

#### Syntax variations:
```rust
// Simple form with description
#[icarus_tool("Tool description")]

// With custom name
#[icarus_tool(name = "custom_name", description = "Tool description")]

// Query operations (read-only)
#[icarus_tool(description = "Read data", is_query = true)]
```

#### Requirements:
- Must be inside an `#[icarus_module]` module
- Function must be `pub`
- Should have `#[update]` or `#[query]` attribute

#### Examples:

```rust
#[icarus_module]
mod tools {
    // Basic tool
    #[update]
    #[icarus_tool("Add two numbers")]
    pub fn add(a: i32, b: i32) -> i32 {
        a + b
    }
    
    // Tool with optional parameters
    #[update]
    #[icarus_tool("Greet a user")]
    pub fn greet(name: String, title: Option<String>) -> String {
        match title {
            Some(t) => format!("Hello, {} {}!", t, name),
            None => format!("Hello, {}!", name),
        }
    }
    
    // Tool with error handling
    #[update]
    #[icarus_tool("Divide two numbers")]
    pub fn divide(a: f64, b: f64) -> Result<f64, String> {
        if b == 0.0 {
            Err("Division by zero".to_string())
        } else {
            Ok(a / b)
        }
    }
}
```

## Derive Macros

### `#[derive(IcarusStorable)]`

Enables a type to be stored in IC stable memory.

#### Basic usage:
```rust
#[derive(Debug, Clone, Serialize, Deserialize, CandidType, IcarusStorable)]
pub struct User {
    pub id: String,
    pub name: String,
    pub created_at: u64,
}
```

#### Size constraints:

**Default (1MB limit):**
```rust
#[derive(IcarusStorable)]
pub struct Config {
    settings: HashMap<String, String>,
}
```

**Unbounded (no limit):**
```rust
#[derive(IcarusStorable)]
#[icarus_storable(unbounded)]
pub struct Document {
    content: String,
    attachments: Vec<Attachment>,
}
```

**Custom limit:**
```rust
#[derive(IcarusStorable)]
#[icarus_storable(max_size = "64KB")]
pub struct Message {
    text: String,
    metadata: MessageMeta,
}
```

#### Requirements:
- Type must implement `Serialize` and `Deserialize`
- Type must implement `CandidType`
- All nested types must also be storable

## Storage Macros

### `stable_storage!`

Declares stable storage variables that persist across canister upgrades.

#### Syntax:
```rust
stable_storage! {
    VARIABLE_NAME: StorageType = initialization;
}
```

#### Supported storage types:

**BTreeMap:**
```rust
stable_storage! {
    USERS: StableBTreeMap<String, User, Memory> = memory_id!(0);
    SESSIONS: StableBTreeMap<Principal, Session, Memory> = memory_id!(1);
}
```

**Vector:**
```rust
stable_storage! {
    EVENTS: StableVec<Event, Memory> = memory_id!(2);
}
```

**Cell (single value):**
```rust
stable_storage! {
    CONFIG: StableCell<AppConfig, Memory> = memory_id!(3);
}
```

**Primitives:**
```rust
stable_storage! {
    COUNTER: u64 = 0;
    LAST_UPDATE: i64 = 0;
}
```

#### Complete example:
```rust
use ic_stable_structures::{StableBTreeMap, StableVec, StableCell};
use ic_stable_structures::memory_manager::VirtualMemory;
use ic_stable_structures::DefaultMemoryImpl;

type Memory = VirtualMemory<DefaultMemoryImpl>;

stable_storage! {
    // User storage with string keys
    USERS: StableBTreeMap<String, User, Memory> = memory_id!(0);
    
    // Event log
    EVENT_LOG: StableVec<Event, Memory> = memory_id!(1);
    
    // Global configuration
    CONFIG: StableCell<Config, Memory> = memory_id!(2);
    
    // Simple counters
    USER_COUNT: u64 = 0;
    LAST_EVENT_ID: u64 = 0;
}
```

### `memory_id!`

Creates a memory ID for stable storage allocation.

#### Usage:
```rust
memory_id!(0)  // First memory region
memory_id!(1)  // Second memory region
memory_id!(2)  // Third memory region
```

#### Rules:
1. IDs must be unique per storage variable
2. Start from 0 and increment
3. Cannot skip numbers
4. Cannot reuse IDs

## Macro Patterns

### Pattern 1: CRUD Operations

```rust
#[icarus_module]
mod tools {
    #[update]
    #[icarus_tool("Create a new item")]
    pub fn create(data: ItemData) -> Result<String, String> {
        let id = generate_id();
        let item = Item { id: id.clone(), data };
        ITEMS.with(|items| items.borrow_mut().insert(id.clone(), item));
        Ok(id)
    }
    
    #[query]
    #[icarus_tool("Read an item by ID")]
    pub fn read(id: String) -> Option<Item> {
        ITEMS.with(|items| items.borrow().get(&id))
    }
    
    #[update]
    #[icarus_tool("Update an existing item")]
    pub fn update(id: String, data: ItemData) -> Result<(), String> {
        ITEMS.with(|items| {
            let mut items = items.borrow_mut();
            match items.get(&id) {
                Some(mut item) => {
                    item.data = data;
                    items.insert(id, item);
                    Ok(())
                }
                None => Err(format!("Item {} not found", id))
            }
        })
    }
    
    #[update]
    #[icarus_tool("Delete an item")]
    pub fn delete(id: String) -> Result<(), String> {
        ITEMS.with(|items| {
            if items.borrow_mut().remove(&id).is_some() {
                Ok(())
            } else {
                Err(format!("Item {} not found", id))
            }
        })
    }
}
```

### Pattern 2: Batch Operations

```rust
#[icarus_module]
mod tools {
    #[update]
    #[icarus_tool("Import multiple items")]
    pub fn batch_import(items: Vec<ImportItem>) -> Result<BatchResult, String> {
        let mut success = 0;
        let mut failed = 0;
        let mut errors = vec![];
        
        ITEMS.with(|storage| {
            let mut storage = storage.borrow_mut();
            for item in items {
                match validate_item(&item) {
                    Ok(valid_item) => {
                        storage.insert(valid_item.id.clone(), valid_item);
                        success += 1;
                    }
                    Err(e) => {
                        failed += 1;
                        errors.push(format!("{}: {}", item.id, e));
                    }
                }
            }
        });
        
        Ok(BatchResult { success, failed, errors })
    }
}
```

### Pattern 3: Complex Queries

```rust
#[icarus_module]
mod tools {
    #[query]
    #[icarus_tool("Search items with filters")]
    pub fn search(filters: SearchFilters) -> SearchResult {
        ITEMS.with(|items| {
            let items = items.borrow();
            let mut results = vec![];
            
            for (_, item) in items.iter() {
                if matches_filters(&item, &filters) {
                    results.push(item.clone());
                }
            }
            
            // Sort and paginate
            results.sort_by(|a, b| b.created_at.cmp(&a.created_at));
            let total = results.len();
            let page_results = results
                .into_iter()
                .skip(filters.offset.unwrap_or(0))
                .take(filters.limit.unwrap_or(10))
                .collect();
                
            SearchResult {
                items: page_results,
                total,
                offset: filters.offset.unwrap_or(0),
                limit: filters.limit.unwrap_or(10),
            }
        })
    }
}
```

## Best Practices

1. **Module Organization**: Group related tools in the same module
2. **Error Messages**: Provide clear, actionable error messages
3. **Parameter Validation**: Validate inputs before processing
4. **Query vs Update**: Use `#[query]` for read-only operations
5. **Memory Efficiency**: Choose appropriate storage types and limits
6. **Documentation**: Use descriptive tool descriptions

## Common Pitfalls

1. **Forgetting `#[update]` or `#[query]`**: Always specify the method type
2. **Private functions**: Tool functions must be `pub`
3. **Memory ID conflicts**: Each storage variable needs a unique ID
4. **Size limits**: Consider data growth when setting limits
5. **Missing derives**: Ensure all required traits are derived

## Debugging

To see generated code:
```bash
cargo expand
```

To check metadata generation:
```rust
#[test]
fn test_metadata() {
    let metadata = list_tools();
    println!("{}", metadata);
}
```