Skip to content

Commit 6193d41

Browse files
author
g
committed
refactor: implement modular command handler architecture with async traits
1 parent 8c74c9d commit 6193d41

25 files changed

+1224
-673
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,6 @@
22

33
# Documentation files
44
TODO.md
5+
6+
# VS Code
7+
.vscode/

.vscode/settings.json

Lines changed: 0 additions & 3 deletions
This file was deleted.

AGENTS.md

Lines changed: 34 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,39 @@
33
## Technology Stack
44
- **Rust 2021 Edition**: Command-line tool for OBS Studio control
55
- **obws**: OBS WebSocket client library
6-
- **clap**: Command-line argument parsing
7-
- **tokio**: Async runtime
6+
- **clap**: Command-line argument parsing with derive macros
7+
- **tokio**: Async runtime with multi-threaded scheduler
8+
- **thiserror**: Error handling with derive macros
9+
- **async-trait**: Async trait support for command handlers
810
- The official obs-websocket spec https://raw.githubusercontent.com/obsproject/obs-websocket/master/docs/generated/protocol.md
911

10-
## Project Structure
11-
```
12-
src/
13-
├── main.rs # Application entry point and WebSocket connection
14-
├── cli.rs # CLI argument definitions and parsing
15-
└── handler.rs # Command handling and OBS API calls
16-
```
12+
## Architecture Patterns
13+
14+
### Command Handler Pattern
15+
- All OBS operations implement `CommandHandler` trait in `src/handlers/mod.rs`
16+
- Trait provides `execute()` and `description()` methods for consistent interface
17+
- Handlers are boxed and dispatched in `src/handler.rs`
18+
19+
### Connection Management
20+
- Retry logic with configurable timeouts in `src/connection.rs`
21+
- Connection health checks before command execution in `src/handler.rs`
22+
- Support for both CLI args and `OBS_WEBSOCKET_URL` environment variable
23+
24+
### Error Handling
25+
- Comprehensive error types in `src/error.rs` using thiserror
26+
- Result type alias for consistent error handling
27+
- Detailed error messages with actionable guidance
28+
29+
### CLI Design
30+
- Custom URL parsing for `obws://hostname:port/password` format in `src/cli.rs`
31+
- Subcommand structure for logical grouping (scenes, recording, streaming, etc.)
32+
- Duration parsing for media controls in `src/cli.rs`
1733

1834
## Development Commands
1935
```bash
2036
# Build and run
2137
cargo run # Build and run locally
22-
cargo build --release # Release build
38+
cargo build --release # Release build with optimizations
2339

2440
# Code quality
2541
cargo fmt # Format code
@@ -28,55 +44,19 @@ cargo test # Run tests
2844

2945
# Dependency management
3046
cargo update # Update dependencies
31-
cargo outdated # Check for outdated dependencies
3247
cargo audit # Security audit
3348
cargo deny check # License and dependency checks
3449

35-
3650
# Installation
3751
cargo install --path . # Install locally
3852
```
3953

40-
## Key Components
41-
42-
### CLI Structure (cli.rs)
43-
- Defines all OBS commands using clap subcommands
44-
- Handles WebSocket URL parsing and validation
45-
- Supports environment variable `OBS_WEBSOCKET_URL` for connection
46-
47-
### Command Handler (handler.rs)
48-
- Implements all OBS operations via obws client
49-
- Handles scenes, recording, streaming, audio, filters, and more
50-
- Provides error handling and user feedback
51-
52-
### Main Entry (main.rs)
53-
- Establishes WebSocket connection to OBS
54-
- Supports both CLI flag and environment variable configuration
55-
- Routes commands to handler
56-
57-
## Common Patterns
58-
- Async/await for all WebSocket operations
59-
- Result-based error handling throughout
60-
- Subcommand structure for logical command grouping
61-
- Environment variable fallback for configuration
62-
63-
## GitHub Workflows
64-
65-
### Development Workflow (rust.yml)
66-
- **Triggers**: Push/PR to main/master branch
67-
- **Platforms**: Ubuntu, macOS, Windows
68-
- **Jobs**: Testing, code coverage, performance benchmarks
69-
70-
### Security & Quality (security.yml)
71-
- **Triggers**: Push/PR to main/master + daily schedule
72-
- **Jobs**: Security audit, code quality, dependency analysis, secrets scan
73-
74-
### Release Automation (release.yml)
75-
- **Triggers**: Git tags starting with `v*`
76-
- **Features**: Multi-platform builds, automatic changelog, GitHub releases
54+
## Release Configuration
55+
- Optimized release profile in `Cargo.toml` with size optimizations
56+
- LTO enabled, single codegen unit, panic=abort for smaller binaries
57+
- Strip symbols for reduced binary size
7758

78-
### Branch Protection
79-
Configure `main`/`master` branch to require:
80-
- Rust workflow checks
81-
- Security workflow checks
82-
- Code quality validation
59+
## Security & Quality
60+
- License compliance via deny.toml
61+
- GitHub workflows for automated testing and security scanning
62+
- Branch protection requiring workflow checks

Cargo.lock

Lines changed: 14 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ clap = { version = "4.5", features = ["derive"] }
1717
url = "2.5"
1818
time = "0.3.37"
1919
thiserror = "2.0"
20+
async-trait = "0.1"
2021

2122
[profile.release]
2223
opt-level = "z"

src/cli.rs

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::str::FromStr;
44
use url::Url;
55

66
/// OBS WebSocket connection configuration.
7-
///
7+
///
88
/// This struct represents the connection parameters for connecting
99
/// to an OBS WebSocket server, typically parsed from a URL string.
1010
#[derive(Clone, Debug)]
@@ -114,42 +114,42 @@ pub enum SceneCollection {
114114
}
115115

116116
/// Command-line interface for obs-cmd.
117-
///
117+
///
118118
/// This struct defines the main CLI interface using clap for parsing.
119119
/// It supports connecting to OBS WebSocket and executing various commands.
120-
///
120+
///
121121
/// # Examples
122-
///
122+
///
123123
/// ```bash
124124
/// # Get OBS version info
125125
/// obs-cmd info
126-
///
126+
///
127127
/// # Start recording
128128
/// obs-cmd recording start
129-
///
129+
///
130130
/// # Switch to a scene
131131
/// obs-cmd scene switch "Main Scene"
132-
///
132+
///
133133
/// # Connect to custom OBS WebSocket
134134
/// obs-cmd --websocket obsws://192.168.1.100:4455/password info
135135
/// ```
136136
#[derive(Parser)]
137137
#[clap(author, version, about, long_about = None)]
138138
pub struct Cli {
139139
/// OBS WebSocket connection URL.
140-
///
140+
///
141141
/// If not provided, defaults to `obsws://localhost:4455/secret`.
142142
/// Can also be set via OBS_WEBSOCKET_URL environment variable.
143143
#[clap(short, long)]
144144
pub websocket: Option<ObsWebsocket>,
145-
145+
146146
/// The command to execute on OBS.
147147
#[clap(subcommand)]
148148
pub command: Commands,
149149
}
150150

151151
/// Available commands for controlling OBS.
152-
///
152+
///
153153
/// This enum represents all possible operations that can be performed
154154
/// on OBS Studio via the WebSocket interface.
155155
#[derive(Subcommand)]
@@ -257,23 +257,23 @@ pub enum MediaInput {
257257
}
258258

259259
/// Parses duration strings in [hh:]mm:ss format.
260-
///
260+
///
261261
/// This function converts human-readable time strings into Duration objects.
262262
/// Supports both minute:second and hour:minute:second formats.
263-
///
263+
///
264264
/// # Examples
265-
///
265+
///
266266
/// * "0:00" -> 0 seconds
267267
/// * "01:00" -> 1 minute
268268
/// * "1:00:00" -> 1 hour
269269
/// * "1:30:45" -> 1 hour, 30 minutes, 45 seconds
270-
///
270+
///
271271
/// # Arguments
272-
///
272+
///
273273
/// * `s` - The duration string to parse
274-
///
274+
///
275275
/// # Returns
276-
///
276+
///
277277
/// Returns a `time::Duration` on success, or an error string if format is invalid
278278
fn parse_duration(s: &str) -> Result<time::Duration, String> {
279279
let parts = s

src/connection.rs

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
#![allow(clippy::redundant_closure)]
2+
13
use crate::error::{ObsCmdError, Result};
24
use obws::Client;
35
use std::time::Duration;
46
use tokio::time::timeout;
57

68
/// Configuration for OBS WebSocket connection attempts.
7-
///
9+
///
810
/// This struct defines how connection attempts should be handled,
911
/// including timeouts, retry limits, and delays between attempts.
1012
pub struct ConnectionConfig {
@@ -30,25 +32,25 @@ impl Default for ConnectionConfig {
3032
}
3133

3234
/// Establishes a WebSocket connection to OBS with retry logic.
33-
///
35+
///
3436
/// This function attempts to connect to an OBS WebSocket server with the
3537
/// provided configuration. It will retry connection attempts according to
3638
/// the specified config and provide detailed error feedback.
37-
///
39+
///
3840
/// # Arguments
39-
///
41+
///
4042
/// * `hostname` - The OBS WebSocket server hostname or IP address
4143
/// * `port` - The port number where OBS WebSocket is listening
4244
/// * `password` - Optional password for OBS WebSocket authentication
4345
/// * `config` - Connection configuration including timeouts and retry settings
44-
///
46+
///
4547
/// # Returns
46-
///
48+
///
4749
/// Returns a connected `Client` instance on success, or an error if all
4850
/// connection attempts fail.
49-
///
51+
///
5052
/// # Examples
51-
///
53+
///
5254
/// ```rust
5355
/// let config = ConnectionConfig::default();
5456
/// let client = connect_with_retry(
@@ -100,31 +102,31 @@ pub async fn connect_with_retry(
100102
}
101103
}
102104

103-
Err(last_error.unwrap_or_else(|| {
104-
ObsCmdError::AllConnectionAttemptsFailed {
105+
Err(
106+
last_error.unwrap_or_else(|| ObsCmdError::AllConnectionAttemptsFailed {
105107
attempts: config.max_retries,
106-
}
107-
}))
108+
}),
109+
)
108110
}
109111

110112
/// Checks the health of an existing OBS WebSocket connection.
111-
///
113+
///
112114
/// This function verifies that the connection to OBS is still active
113115
/// and responsive by attempting to retrieve the OBS version.
114-
///
116+
///
115117
/// # Arguments
116-
///
118+
///
117119
/// * `client` - The OBS WebSocket client to check
118-
///
120+
///
119121
/// # Returns
120-
///
122+
///
121123
/// Returns `Ok(())` if the connection is healthy, or an error if the
122124
/// connection is unresponsive or broken.
123125
pub async fn check_connection_health(client: &Client) -> Result<()> {
124126
timeout(Duration::from_secs(5), client.general().version())
125127
.await
126128
.map_err(|_| ObsCmdError::ConnectionTimeout { timeout: 5 })?
127-
.map_err(ObsCmdError::ConnectionError)?;
129+
.map_err(|e| ObsCmdError::ConnectionError(e))?;
128130

129131
Ok(())
130132
}

0 commit comments

Comments
 (0)