api
This commit is contained in:
231
doc/API_DESIGN.md
Normal file
231
doc/API_DESIGN.md
Normal file
@@ -0,0 +1,231 @@
|
||||
# Route-Switcher API Design
|
||||
|
||||
## Overview
|
||||
|
||||
HTTP REST API with Basic Authentication for Home Assistant integration, exposing state machine state and ping statistics.
|
||||
|
||||
## Design Principles
|
||||
|
||||
- **Minimal surface area**: Only expose necessary information
|
||||
- **Simple authentication**: HTTP Basic Auth (no JWT complexity)
|
||||
- **State-focused**: Centered on state machine state and ping history
|
||||
- **Home Assistant friendly**: Structured for HA REST integration
|
||||
- **Opt-in**: API disabled by default
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### GET /api/state
|
||||
|
||||
Returns current state machine state with ping statistics.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"state": "Primary",
|
||||
"primary_stats": {
|
||||
"success_rate": 95.5,
|
||||
"failures": 2,
|
||||
"total_pings": 44,
|
||||
"last_ping": "Ok"
|
||||
},
|
||||
"secondary_stats": {
|
||||
"success_rate": 98.2,
|
||||
"failures": 1,
|
||||
"total_pings": 56,
|
||||
"last_ping": "Ok"
|
||||
},
|
||||
"last_failover": "2024-02-15T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Fields:**
|
||||
- `state`: Current state machine state (Boot/Primary/Fallback)
|
||||
- `primary_stats`: Ping statistics for primary interface
|
||||
- `secondary_stats`: Ping statistics for secondary interface
|
||||
- `last_failover`: ISO 8601 timestamp of last failover (null if never)
|
||||
|
||||
### POST /api/state
|
||||
|
||||
Manually set state machine state.
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"state": "fallback"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"state": "Fallback",
|
||||
"previous_state": "Primary",
|
||||
"primary_stats": { ... },
|
||||
"secondary_stats": { ... },
|
||||
"last_failover": "2024-02-15T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Valid states:** `primary`, `fallback`
|
||||
|
||||
## Authentication
|
||||
|
||||
HTTP Basic Authentication with username/password configured via environment variables.
|
||||
|
||||
**Security considerations:**
|
||||
- Passwords stored as bcrypt hash
|
||||
- HTTPS recommended for production
|
||||
- Local network access only
|
||||
- No token management (stateless)
|
||||
|
||||
## Data Structures
|
||||
|
||||
### PingStats
|
||||
|
||||
Calculated from state machine ping history (60 entries per interface):
|
||||
|
||||
```rust
|
||||
struct PingStats {
|
||||
success_rate: f64, // Percentage of successful pings
|
||||
failures: usize, // Number of failed pings in history
|
||||
total_pings: usize, // Total pings in history
|
||||
last_ping: String, // "Ok" or "Failed"
|
||||
}
|
||||
```
|
||||
|
||||
### StateResponse
|
||||
|
||||
```rust
|
||||
struct StateResponse {
|
||||
state: String,
|
||||
primary_stats: PingStats,
|
||||
secondary_stats: PingStats,
|
||||
last_failover: Option<String>,
|
||||
}
|
||||
```
|
||||
|
||||
## Home Assistant Integration
|
||||
|
||||
### REST Sensor Configuration
|
||||
|
||||
```yaml
|
||||
sensor:
|
||||
- platform: rest
|
||||
name: Route Switcher State
|
||||
resource: http://route-switcher.local:8080/api/state
|
||||
username: !secret route_switcher_user
|
||||
password: !secret route_switcher_pass
|
||||
value_template: "{{ value_json.state }}"
|
||||
json_attributes:
|
||||
- primary_stats
|
||||
- secondary_stats
|
||||
- last_failover
|
||||
|
||||
- platform: template
|
||||
sensors:
|
||||
route_switcher_primary_success_rate:
|
||||
value_template: "{{ state_attr('sensor.route_switcher_state', 'primary_stats').success_rate | default(0) }}"
|
||||
unit_of_measurement: "%"
|
||||
route_switcher_secondary_success_rate:
|
||||
value_template: "{{ state_attr('sensor.route_switcher_state', 'secondary_stats').success_rate | default(0) }}"
|
||||
unit_of_measurement: "%"
|
||||
route_switcher_primary_failures:
|
||||
value_template: "{{ state_attr('sensor.route_switcher_state', 'primary_stats').failures | default(0) }}"
|
||||
route_switcher_secondary_failures:
|
||||
value_template: "{{ state_attr('sensor.route_switcher_state', 'secondary_stats').failures | default(0) }}"
|
||||
|
||||
switch:
|
||||
- platform: rest
|
||||
name: Route Switcher Control
|
||||
resource: http://route-switcher.local:8080/api/state
|
||||
username: !secret route_switcher_user
|
||||
password: !secret route_switcher_pass
|
||||
body_on: '{"state": "fallback"}'
|
||||
body_off: '{"state": "primary"}'
|
||||
is_on_template: "{{ value_json.state == 'fallback' }}"
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
```bash
|
||||
# API Configuration
|
||||
API_ENABLED=true
|
||||
API_BIND_ADDRESS=0.0.0.0
|
||||
API_PORT=8080
|
||||
API_USERNAME=admin
|
||||
API_PASSWORD_HASH=<bcrypt-hash>
|
||||
|
||||
# CORS Configuration
|
||||
API_CORS_ORIGINS=http://homeassistant.local:8123
|
||||
```
|
||||
|
||||
### Password Hash Generation
|
||||
|
||||
```bash
|
||||
# Generate bcrypt hash
|
||||
echo -n "your-password" | bcrypt
|
||||
```
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Dependencies
|
||||
|
||||
```toml
|
||||
axum = "0.7"
|
||||
tokio = { version = "1.42", features = ["full"] }
|
||||
tower = "0.4"
|
||||
tower-http = { version = "0.5", features = ["cors", "auth"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
bcrypt = "0.15"
|
||||
base64 = "0.22"
|
||||
```
|
||||
|
||||
### Architecture
|
||||
|
||||
- **API Module**: `src/api.rs` - HTTP server and endpoints
|
||||
- **State Sharing**: Thread-safe access to state machine and ping history
|
||||
- **Authentication**: Basic Auth middleware with bcrypt validation
|
||||
- **Error Handling**: Standardized JSON error responses
|
||||
- **Integration**: Minimal changes to existing state machine
|
||||
|
||||
### Thread Safety
|
||||
|
||||
- `Arc<Mutex<StateMachine>>` for shared state access
|
||||
- Non-blocking async operations
|
||||
- Minimal locking duration
|
||||
|
||||
## Error Handling
|
||||
|
||||
Standardized error responses:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "Invalid state",
|
||||
"message": "State must be 'primary' or 'fallback'"
|
||||
}
|
||||
```
|
||||
|
||||
HTTP Status Codes:
|
||||
- 200: Success
|
||||
- 400: Bad Request (invalid state)
|
||||
- 401: Unauthorized (invalid credentials)
|
||||
- 500: Internal Server Error
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- Network access restrictions (local only recommended)
|
||||
- HTTPS for credential protection
|
||||
- Rate limiting considerations
|
||||
- Audit logging for manual state changes
|
||||
- No configuration exposure (state only)
|
||||
|
||||
## Backward Compatibility
|
||||
|
||||
- API disabled by default
|
||||
- No changes to existing CLI functionality
|
||||
- Service continues without API if disabled
|
||||
- Graceful degradation on API errors
|
||||
Reference in New Issue
Block a user