# Architecture Documentation
## Overview
The MyTotalConnectComfort Rust client library provides a high-performance, type-safe async interface to the MyTotalConnectComfort API for the **International Honeywell Evohome** heating system. This system is provided by **Resideo**, who licensed the Honeywell brand from Honeywell International. The library is designed with performance, safety, and developer experience in mind.
> **Important**: This library targets the international version of the Evohome system (`international.mytotalconnectcomfort.com`). North American Honeywell systems may use different APIs.
## Design Principles
1. **Async First** - Non-blocking I/O with tokio for high performance
2. **Type Safety** - Compile-time guarantees with Rust's type system
3. **Zero Unsafe** - 100% safe Rust code
4. **Error Handling** - Explicit error types with `Result<T, Error>`
5. **Minimal Dependencies** - Only essential, well-maintained crates
## Architecture Diagram
```
┌─────────────────────────────────────────────────────────────┐
│ User Application (async) │
└───────────────────────────┬─────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Client (client.rs) │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ • Async Authentication (login) │ │
│ │ • Session Management (cookies, CSRF tokens) │ │
│ │ • Async API Methods (get_locations, set_temp, etc.) │ │
│ │ • Error Handling & Type Conversion │ │
│ └────────────────────────────────────────────────────────┘ │
└───────────────────────────┬─────────────────────────────────┘
│
┌───────────┴───────────┐
▼ ▼
┌───────────────────────┐ ┌──────────────────────┐
│ Models (models.rs) │ │ Error (error.rs) │
│ • Zone │ │ • Error enum │
│ • Location │ │ • Result<T> alias │
│ • UserInfo │ │ • thiserror derive │
│ • Gateway │ └──────────────────────┘
│ (serde derive) │ │
└───────────────────────┘ │
┌───────────┴──────────┐
▼ ▼
┌──────────────────┐ ┌─────────────────┐
│ Types (types.rs) │ │ Async Runtime │
│ • Enums │ │ (tokio) │
│ • Constants │ └─────────────────┘
└──────────────────┘ │
│ ▼
▼ ┌─────────────────────────┐
┌─────────────────────────────────────────────────────────────┐
│ reqwest::Client │
│ (Async HTTP Client with Cookie Store) │
└───────────────────────────┬─────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ MyTotalConnectComfort API (HTTPS) │
│ https://international.mytotalconnectcomfort.com │
└─────────────────────────────────────────────────────────────┘
```
## Module Structure
### `lib.rs` - Library Entry Point
**Responsibilities:**
- Re-export public API
- Module organization
- Crate-level documentation
- Usage examples in docs
**Design Patterns:**
- **Facade Pattern** - Clean public API
- **Module System** - Organized code structure
### `client.rs` - Async API Client
**Responsibilities:**
- Async HTTP communication via `reqwest`
- Authentication and session lifecycle
- CSRF token management
- Request/response handling
- Error translation
**Key Components:**
```rust
pub struct Client {
http_client: HttpClient, // reqwest::Client
cookie_jar: Arc<Jar>, // Cookie storage
authenticated: bool, // Auth state
}
```
**Design Patterns:**
- **Builder Pattern** - Client configuration
- **Async/Await** - Non-blocking I/O
- **Error Conversion** - From reqwest/serde to domain errors
### `models.rs` - Data Models
**Responsibilities:**
- Type-safe data structures
- JSON serialization/deserialization
- Data validation via types
- Helper methods
**Key Models:**
```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Zone {
// Temperature data
// Status flags
// Device information
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Location {
// Address information
// Zones collection
// Gateway devices
}
```
**Design Patterns:**
- **Data Transfer Object (DTO)** - Immutable data
- **Serde Integration** - Automatic JSON handling
- **Composite Pattern** - Location contains Zones
### `error.rs` - Error Handling
**Error Hierarchy:**
```rust
#[derive(Error, Debug)]
pub enum Error {
#[error("Authentication failed: {0}")]
Authentication(String),
#[error("API error: {0}")]
Api(String),
#[error("Zone not found: {0}")]
ZoneNotFound(String),
#[error("HTTP request failed: {0}")]
Http(#[from] reqwest::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
// ... more variants
}
```
**Design Patterns:**
- **Error Enum** - Type-safe error handling
- **thiserror** - Automatic Display/Error impl
- **From Trait** - Automatic error conversion
### `types.rs` - Common Types
**Responsibilities:**
- Shared type definitions
- Enums for status codes
- Constants
```rust
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum SetPointStatus {
FollowingSchedule = 0,
ManualOverride = 2,
}
```
## Async Architecture
### Tokio Runtime
The library uses tokio as the async runtime:
```rust
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut client = Client::new();
client.login("email", "password").await?;
// ... async operations
Ok(())
}
```
**Benefits:**
- Non-blocking I/O
- Concurrent operations with `try_join!`
- Efficient resource usage
- Scalable to many connections
### Async Request Flow
```
1. User calls async method (e.g., get_locations())
│
▼
2. ensure_authenticated() checks login state
│
▼
3. Build HTTP request with reqwest
│
├─ Add CSRF token for POST
├─ Set headers
└─ Cookies included automatically
│
▼
4. await http_client.request().send()
│ (yields to tokio runtime)
│
▼
5. Response validation
│
├─ Check HTTP status
├─ Parse JSON with serde
└─ Check API errors
│
▼
6. Return Result<T, Error>
│
▼
7. Convert to model objects
```
## Authentication Flow
```
1. User calls client.login(email, password).await
│
▼
2. GET /Account/Login (get CSRF tokens)
│
▼
3. Extract __RequestVerificationToken from cookies
│
▼
4. POST /api/accountApi/login with credentials
│
▼
5. Receive SessionCookie and RefreshCookie
│
▼
6. Store in Arc<Jar> (shared with reqwest)
│
▼
7. All subsequent requests include cookies
```
## Session Management
**Cookie Lifecycle:**
- `SessionCookie` - Valid for 1 hour
- `RefreshCookie` - Valid for 6 months
- Stored in `Arc<Jar>` for thread-safe sharing
- Automatic inclusion via reqwest cookie store
**CSRF Protection:**
- `__RequestVerificationToken` cookie
- `antiForgeryToken` header for POST
- Extracted from login page
- Included in state-changing operations
## Type Safety
### Compile-Time Guarantees
```rust
// Won't compile - wrong type
client.set_zone_temperature("zone_id", "21.0", true, 0, 0).await?;
^^^^^^ expected f64
// Won't compile - missing await
let locations = client.get_locations();
^^^^^^^^^^^^^^^^^^^^^^^ expected `Result<...>`, found opaque type
```
### Serde Type Safety
```rust
#[derive(Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Zone {
pub temperature: f64, // Compile error if API returns string
pub is_alive: bool, // Compile error if API returns int
}
```
## Error Handling Strategy
### Three-Layer Error Handling
1. **HTTP Layer** - `reqwest::Error`
- Connection errors
- Timeout errors
- HTTP status codes
2. **Serialization Layer** - `serde_json::Error`
- JSON parsing errors
- Type mismatches
3. **Domain Layer** - Custom `Error` enum
- Authentication failures
- Zone/Location not found
- API errors
### Error Conversion
```rust
HTTP 401 → Error::Authentication
HTTP 404 → Error::ZoneNotFound / LocationNotFound
reqwest::Error → Error::Http (automatic via #[from])
serde_json::Error → Error::Json (automatic via #[from])
```
## Concurrency
### Concurrent Operations
```rust
use tokio::try_join;
// Execute multiple async operations concurrently
let (system, account) = try_join!(
client.get_location_system(&location_id),
client.get_account_info()
)?;
```
### Thread Safety
- `Client` is `Send + Sync`
- Can be shared across threads
- `Arc<Jar>` for shared cookie storage
- No interior mutability except for cookies
## Performance Considerations
### Connection Pooling
- `reqwest::Client` maintains connection pool
- Reuses TCP connections
- Reduces latency for subsequent requests
### Zero-Copy Deserialization
- Serde deserializes directly to structs
- No intermediate representations
- Minimal allocations
### Async Efficiency
- Non-blocking I/O
- Minimal thread overhead
- Efficient task scheduling via tokio
## Security Considerations
### Credential Handling
- No credential storage in library
- User responsible for secure storage
- Credentials only in memory during login
### Session Security
- HTTPS only (enforced by base URL)
- Secure cookie handling via reqwest
- CSRF protection on all POST requests
### Memory Safety
- No unsafe code
- Rust's ownership prevents data races
- Compile-time memory safety guarantees
## Testing Strategy
### Unit Tests
- Test error type conversions
- Test enum implementations
- Test helper methods
### Integration Tests
- Mock-free authentication tests
- Real API workflow tests (with `#[ignore]`)
- Concurrent operation tests
### Test Coverage
```bash
cargo test # Run all tests
cargo test --ignored # Run integration tests
cargo tarpaulin # Generate coverage report
```
## Extensibility Points
### Adding New Endpoints
1. Add async method to `Client`
2. Use existing HTTP client
3. Parse response with serde
4. Return `Result<T, Error>`
### Adding New Models
1. Create struct with `#[derive(Deserialize)]`
2. Add `#[serde(rename_all = "PascalCase")]`
3. Use `Option<T>` for nullable fields
4. Add helper methods as needed
### Custom Error Types
1. Add variant to `Error` enum
2. Add `#[error("...")]` attribute
3. Use in appropriate contexts
## Future Enhancements
**Potential Additions:**
- Custom base URL support for testing
- Retry logic with exponential backoff
- Rate limiting protection
- Metrics and tracing support
- WebSocket support for real-time updates
- Caching layer for frequently accessed data
- Configuration file support
## Comparison with Python Client
| **Execution** | Synchronous | Asynchronous |
| **Type Safety** | Runtime | Compile-time |
| **Memory Safety** | GC | Ownership |
| **Performance** | Good | Excellent |
| **Concurrency** | Threading/asyncio | Tokio tasks |
| **Error Handling** | Exceptions | Result types |
| **Dependencies** | requests | reqwest, serde, tokio |
## Dependency Graph
```
evohome_rs
├── reqwest (HTTP client)
│ ├── tokio (async runtime)
│ ├── serde (serialization)
│ └── cookie_store (cookies)
├── serde (serialization framework)
│ └── serde_json (JSON support)
├── thiserror (error handling)
└── async-trait (async traits)
```
## Build and Distribution
### Cargo Configuration
```toml
[package]
name = "evohome_rs"
version = "0.1.0"
edition = "2021"
rust-version = "1.70"
[dependencies]
reqwest = { version = "0.11", features = ["json", "cookies"] }
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.0", features = ["full"] }
thiserror = "1.0"
```
### Publishing to crates.io
1. Ensure all tests pass
2. Update version in `Cargo.toml`
3. Run `cargo publish --dry-run`
4. Run `cargo publish`
## Documentation
### Rustdoc
All public items have documentation:
```rust
/// Client for the MyTotalConnectComfort API.
///
/// # Example
///
/// ```no_run
/// use evohome_rs::Client;
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut client = Client::new();
/// client.login("user@example.com", "password").await?;
/// Ok(())
/// }
/// ```
pub struct Client { /* ... */ }
```
Generate docs with:
```bash
cargo doc --open
```