tasmor_lib 0.2.1

Rust library to control Tasmota devices via MQTT and HTTP
Documentation
# Contributing to TasmoR Lib

Thank you for your interest in contributing to TasmoR Lib! This document provides guidelines and instructions for contributing.

## Table of Contents

- [Code of Conduct]#code-of-conduct
- [Getting Started]#getting-started
- [Development Workflow]#development-workflow
- [Design Principles]#design-principles
- [Code Style]#code-style
- [Testing]#testing
- [Documentation]#documentation
- [Commit Messages]#commit-messages
- [Pull Requests]#pull-requests
- [License]#license

## Code of Conduct

Please be respectful and constructive in all interactions. We welcome contributors of all experience levels.

## Getting Started

### Prerequisites

- Rust 1.92.0 or later (enforced by `rust-toolchain.toml`)
- A Tasmota device for integration testing (optional but recommended)

### Setup

1. Clone the repository:
   ```bash
   git clone https://codeberg.org/Bawycle/tasmor_lib.git
   cd tasmor_lib
   ```

2. The correct Rust version will be installed automatically via `rust-toolchain.toml`.

3. Build the project:
   ```bash
   cargo build
   ```

4. Run tests:
   ```bash
   cargo test
   ```

## Development Workflow

We follow Test-Driven Development (TDD):

1. **Write tests first** - Define expected behavior through tests
2. **Implement code** - Write the minimum code to make tests pass
3. **Verify** - Run the full verification pipeline before committing

### Verification Pipeline

Always run the complete verification before submitting changes:

```bash
cargo check && cargo build && cargo test && cargo fmt --check && cargo clippy -- -D warnings -W clippy::pedantic
```

Or individually:

```bash
cargo check                                       # Type checking
cargo build                                       # Compile
cargo test                                        # Run all tests
cargo test --doc                                  # Run documentation tests
cargo fmt --check                                 # Check formatting
cargo clippy -- -D warnings -W clippy::pedantic  # Lint with pedantic warnings
```

## Design Principles

This project follows specific architectural principles. Please adhere to these when contributing.

### Core Principles

| Principle | Description |
|-----------|-------------|
| **Single Source of Truth** | Each piece of data has one authoritative source |
| **DRY** | Don't Repeat Yourself - factor out common logic |
| **KISS** | Keep It Simple - prefer simplicity over complexity |
| **YAGNI** | You Aren't Gonna Need It - don't implement speculative features |

### Newtype Pattern

Use newtypes for domain values with constraints. This provides compile-time guarantees and self-documenting code.

```rust
/// Dimmer level (0-100).
pub struct Dimmer(u8);

impl Dimmer {
    pub fn new(value: u8) -> Result<Self, ValueError> {
        if value > 100 {
            return Err(ValueError::out_of_range("Dimmer", 0, 100, value));
        }
        Ok(Self(value))
    }

    pub const fn value(self) -> u8 {
        self.0
    }
}
```

**Benefits:**
- Invalid states are unrepresentable
- Type system prevents mixing different value types
- Validation happens once at construction

### Parse, Don't Validate

Convert data to valid types at system boundaries. Once parsed, the type guarantees validity.

```rust
// Good: Parse once, use safely everywhere
let dimmer = Dimmer::new(user_input)?;  // Validates here
device.set_dimmer(dimmer).await?;        // No validation needed

// Bad: Validate repeatedly
fn set_dimmer(value: u8) {
    assert!(value <= 100);  // Must validate every time
}
```

### Error Handling

- Use `Result<T, E>` for operations that can fail
- Use `Option<T>` for optional values
- Never use `.unwrap()` or `.expect()` on user-provided data
- Propagate errors with `?`, add context when helpful

```rust
// Good
pub fn parse_color(hex: &str) -> Result<RgbColor, ValueError> {
    let r = u8::from_str_radix(&hex[1..3], 16)
        .map_err(|_| ValueError::invalid_format("hex color", hex))?;
    // ...
}

// Bad
pub fn parse_color(hex: &str) -> RgbColor {
    let r = u8::from_str_radix(&hex[1..3], 16).unwrap();  // Panics on invalid input
    // ...
}
```

### Immutability

Prefer immutable data structures. Use mutation only when necessary for performance.

```rust
// Good: Methods return new values
impl Dimmer {
    #[must_use]
    pub fn with_value(self, value: u8) -> Result<Self, ValueError> {
        Self::new(value)
    }
}

// Acceptable: Mutation for state tracking
impl DeviceState {
    pub fn set_dimmer(&mut self, dimmer: Dimmer) {
        self.dimmer = Some(dimmer);
    }
}
```

### Avoid Over-Engineering

- Only make changes that are directly requested or clearly necessary
- Don't add features, refactor code, or make "improvements" beyond what was asked
- Don't add error handling for scenarios that can't happen
- Don't create abstractions for one-time operations

## Code Style

### Formatting

- Use `cargo fmt` to format code
- Run `cargo fmt --check` to verify formatting without modifying files

### Linting

We use Clippy with pedantic warnings as errors:

```bash
cargo clippy -- -D warnings -W clippy::pedantic
```

If you need to suppress a warning, use an `#[allow(...)]` attribute with a comment explaining why:

```rust
#[allow(clippy::cast_possible_truncation)]
// Truncation is acceptable here because value is validated to be < 256
fn to_u8(value: u32) -> u8 {
    value as u8
}
```

### General Guidelines

- **No unsafe code** - The crate forbids unsafe code
- **Error handling** - Use `thiserror` for error types, propagate with `?`
- **No `.unwrap()` on user data** - Always handle errors gracefully
- **Prefer `&str` over `String`** - When ownership isn't needed
- **Use newtypes** - For domain values with constraints (e.g., `Dimmer`, `PowerIndex`)

## Testing

### Test Coverage

We aim for high test coverage. Check coverage with:

```bash
cargo tarpaulin
```

### Recommended Tools

These tools are not installed by `rust-toolchain.toml` but are recommended for contributors:

```bash
# Code coverage
cargo install cargo-tarpaulin
cargo tarpaulin

# Security audit
cargo install cargo-audit
cargo audit
```

### Test Organization

- **Unit tests** - In the same file as the code, in a `tests` module
- **Integration tests** - In `tests/` directory
- **Documentation tests** - Examples in doc comments

### Writing Tests

```rust
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn descriptive_test_name() {
        // Arrange
        let input = ...;

        // Act
        let result = function_under_test(input);

        // Assert
        assert_eq!(result, expected);
    }
}
```

## Documentation

### Requirements

All public items must have documentation:

- **Summary line** - First line, concise description
- **Detailed description** - If non-trivial
- **`# Examples`** - Runnable code examples
- **`# Errors`** - If the function returns `Result`
- **`# Panics`** - If the function can panic

### Example

```rust
/// Connects to a Tasmota device.
///
/// Establishes an HTTP connection to the device at the specified address
/// and retrieves its current state.
///
/// # Examples
///
/// ```
/// use tasmor_lib::Device;
///
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
/// let (device, state) = Device::http("192.168.1.100")
///     .build_with_probe()
///     .await?;
/// # Ok(())
/// # }
/// ```
///
/// # Errors
///
/// Returns `DeviceError::Connection` if the device is unreachable.
pub async fn build_with_probe(self) -> Result<(Device, DeviceState), DeviceError> {
    // ...
}
```

### Building Documentation

```bash
cargo doc --no-deps --open                    # Generate and open docs
cargo doc --no-deps --document-private-items  # Include private items
```

## Commit Messages

We use [Conventional Commits](https://www.conventionalcommits.org/):

```
<type>(<scope>): <description>

[optional body]

[optional footer]
```

### Types

- `feat` - New feature
- `fix` - Bug fix
- `docs` - Documentation changes
- `refactor` - Code refactoring (no feature/fix)
- `test` - Adding or updating tests
- `chore` - Maintenance tasks

### Examples

```
feat(mqtt): add subscription support for power state changes

fix(http): handle timeout when device is unreachable

docs(readme): add MQTT connection examples

refactor(types): extract color validation to separate module
```

## Pull Requests

### Before Submitting

1. Ensure all tests pass: `cargo test`
2. Ensure no linting errors: `cargo clippy -- -D warnings -W clippy::pedantic`
3. Ensure code is formatted: `cargo fmt --check`
4. Update documentation if needed
5. Add tests for new functionality

### PR Process

1. Fork the repository
2. Create a feature branch from `dev`: `git checkout -b feat/my-feature dev`
3. Make your changes following the guidelines above
4. Push to your fork
5. Open a Pull Request against the `dev` branch
6. Respond to review feedback

### Branch Naming

- `feat/description` - New features
- `fix/description` - Bug fixes
- `docs/description` - Documentation
- `refactor/description` - Refactoring

## License

By contributing, you agree that your contributions will be licensed under the MPL-2.0 license.