# Mecha10 Macros

Procedural macros to reduce boilerplate in Mecha10 configuration structs.

## Overview

Writing configuration structs in Mecha10 typically involves a lot of repetitive code:
- Individual `default_*()` functions for each field
- `#[serde(default = "default_*")]` attributes on every field
- Manual `Default` trait implementation

The `ConfigDefaults` derive macro eliminates most of this boilerplate.

## Installation

Add to your `Cargo.toml`:

```toml
[dependencies]
mecha10-macros = { path = "../mecha10-macros" }
serde = { version = "1.0", features = ["derive"] }
```

## Usage

### Before (Manual Approach)

```rust
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CameraConfig {
    /// Frame rate in Hz
    #[serde(default = "default_fps")]
    pub fps: f32,

    /// Image width
    #[serde(default = "default_width")]
    pub width: u32,

    /// Image height
    #[serde(default = "default_height")]
    pub height: u32,

    /// Optional device path
    pub device: Option<String>,
}

fn default_fps() -> f32 { 30.0 }
fn default_width() -> u32 { 640 }
fn default_height() -> u32 { 480 }

impl Default for CameraConfig {
    fn default() -> Self {
        Self {
            fps: default_fps(),
            width: default_width(),
            height: default_height(),
            device: None,
        }
    }
}
```

**Problems with this approach:**
- 20+ lines of boilerplate for a simple config
- Easy to forget to update `Default` impl when adding fields
- Repetitive function definitions
- Error-prone: typos in function names or serde attributes

### After (Using ConfigDefaults)

```rust
use mecha10_macros::ConfigDefaults;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize, ConfigDefaults)]
pub struct CameraConfig {
    /// Frame rate in Hz
    #[serde(default = "CameraConfig::default_fps")]
    #[config(default = 30.0)]
    pub fps: f32,

    /// Image width
    #[serde(default = "CameraConfig::default_width")]
    #[config(default = 640)]
    pub width: u32,

    /// Image height
    #[serde(default = "CameraConfig::default_height")]
    #[config(default = 480)]
    pub height: u32,

    /// Optional device path
    pub device: Option<String>,
}
```

**Benefits:**
- ~10 lines of code instead of 30+
- No manual `Default` impl needed
- No separate `default_*()` function definitions
- Single source of truth for default values
- `Option` fields automatically default to `None`

## How It Works

The `ConfigDefaults` macro generates:

1. **Associated functions** for each field with `#[config(default = value)]`:
   ```rust
   impl CameraConfig {
       pub fn default_fps() -> f32 { 30.0 }
       pub fn default_width() -> u32 { 640 }
       pub fn default_height() -> u32 { 480 }
   }
   ```

2. **Default trait implementation**:
   ```rust
   impl Default for CameraConfig {
       fn default() -> Self {
           Self {
               fps: Self::default_fps(),
               width: Self::default_width(),
               height: Self::default_height(),
               device: None,
           }
       }
   }
   ```

## Examples

### String Defaults

```rust
#[derive(ConfigDefaults, Serialize, Deserialize)]
pub struct ImuConfig {
    #[serde(default = "ImuConfig::default_i2c_bus")]
    #[config(default = "/dev/i2c-1".to_string())]
    pub i2c_bus: String,

    #[serde(default = "ImuConfig::default_mode")]
    #[config(default = "NDOF".to_string())]
    pub mode: String,
}
```

### Numeric Defaults

```rust
#[derive(ConfigDefaults, Serialize, Deserialize)]
pub struct MotorConfig {
    #[serde(default = "MotorConfig::default_max_velocity")]
    #[config(default = 1.0)]
    pub max_velocity: f32,

    #[serde(default = "MotorConfig::default_max_acceleration")]
    #[config(default = 0.5)]
    pub max_acceleration: f32,

    #[serde(default = "MotorConfig::default_encoder_tpr")]
    #[config(default = 1024)]
    pub encoder_tpr: u32,
}
```

### Optional Fields

Fields with `Option<T>` type automatically default to `None` (no `#[config(default)]` needed):

```rust
#[derive(ConfigDefaults, Serialize, Deserialize)]
pub struct SensorConfig {
    #[serde(default = "SensorConfig::default_rate")]
    #[config(default = 100.0)]
    pub update_rate_hz: f32,

    // Automatically defaults to None
    pub calibration_file: Option<String>,
    pub serial_number: Option<String>,
}
```

### Complex Default Values

You can use any expression that compiles:

```rust
use std::path::PathBuf;

#[derive(ConfigDefaults, Serialize, Deserialize)]
pub struct AppConfig {
    #[serde(default = "AppConfig::default_data_dir")]
    #[config(default = PathBuf::from("/var/lib/mecha10"))]
    pub data_dir: PathBuf,

    #[serde(default = "AppConfig::default_workers")]
    #[config(default = std::thread::available_parallelism().map(|n| n.get()).unwrap_or(4))]
    pub worker_threads: usize,
}
```

## Migration Guide

To migrate existing configs to use `ConfigDefaults`:

1. Add `ConfigDefaults` to the derive list:
   ```rust
   #[derive(Debug, Clone, Serialize, Deserialize, ConfigDefaults)]
   //                                              ^^^^^^^^^^^^^^
   ```

2. For each field with a default, add `#[config(default = value)]`:
   ```rust
   #[serde(default = "TypeName::default_field")]
   #[config(default = value)]  // Add this
   pub field: Type,
   ```

3. Update serde default path from `"default_field"` to `"TypeName::default_field"`:
   ```rust
   // Before:
   #[serde(default = "default_fps")]

   // After:
   #[serde(default = "CameraConfig::default_fps")]
   ```

4. Remove the separate `default_*()` function definitions

5. Remove the manual `impl Default` block

## Best Practices

### Document Default Values

Include default values in field documentation:

```rust
/// Frame rate in Hz (default: 30.0)
#[serde(default = "CameraConfig::default_fps")]
#[config(default = 30.0)]
pub fps: f32,
```

### Use Constants for Magic Numbers

For commonly used values, define constants:

```rust
const DEFAULT_I2C_ADDRESS: u8 = 0x28;
const DEFAULT_UPDATE_RATE: f32 = 100.0;

#[derive(ConfigDefaults, Serialize, Deserialize)]
pub struct ImuConfig {
    #[serde(default = "ImuConfig::default_i2c_address")]
    #[config(default = DEFAULT_I2C_ADDRESS)]
    pub i2c_address: u8,

    #[serde(default = "ImuConfig::default_update_rate")]
    #[config(default = DEFAULT_UPDATE_RATE)]
    pub update_rate_hz: f32,
}
```

### Group Related Defaults

Organize config structs by functionality:

```rust
#[derive(ConfigDefaults, Serialize, Deserialize)]
pub struct CameraConfig {
    // Resolution settings
    #[config(default = 640)]
    pub width: u32,

    #[config(default = 480)]
    pub height: u32,

    // Performance settings
    #[config(default = 30.0)]
    pub fps: f32,

    // Optional features
    pub enable_depth: Option<bool>,
}
```

## Limitations

1. **Serde attributes still required**: You still need to add `#[serde(default = "TypeName::default_field")]` to each field. The macro can't automatically inject these attributes (Rust limitation).

2. **Simple expressions only**: The `#[config(default = expr)]` value must be a valid Rust expression that can be evaluated at the definition site.

3. **Named fields only**: The macro only works with structs that have named fields (not tuple structs or unit structs).

## Future Enhancements

Planned features for future versions:

- Validation attributes: `#[config(min = 0.0, max = 10.0)]`
- Range checking: `#[config(range = "0.1..=1.0")]`
- Custom validators: `#[config(validate_with = "my_validator")]`
- Auto-documentation: Generate config schema files

## See Also

- [Serde Documentation](https://serde.rs/)
- [Mecha10 Core Documentation](../core/)
