1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
use crate::prelude::*;
use caesura_macros::Options;
use serde::{Deserialize, Serialize};
/// qBittorrent connection options shared by any command that talks to qBittorrent.
#[derive(Options, Clone, Debug, Deserialize, Serialize)]
pub struct QbitOptions {
/// qBittorrent API base URL
///
/// The base URL for your qBittorrent instance
///
/// Examples: `http://localhost:8080`, `http://qbit`, `https://qbit.example.com`
///
/// Or, the proxy URL with key if using [qui reverse proxy](https://getqui.com/docs/features/reverse-proxy)
///
/// Examples:
/// - `http://localhost:7476/proxy/YOUR_CLIENT_PROXY_KEY`
/// - `https://qui.example.com/proxy/YOUR_CLIENT_PROXY_KEY`
#[arg(long)]
pub qbit_url: Option<String>,
/// qBittorrent username.
///
/// Not required when using qui reverse proxy.
#[arg(long)]
pub qbit_username: Option<String>,
/// qBittorrent password.
///
/// Not required when using qui reverse proxy.
#[arg(long)]
pub qbit_password: Option<String>,
}
impl QbitOptions {
/// Push [`OptionRule`] violations for missing connection fields.
pub(crate) fn validate_connection(&self, errors: &mut Vec<OptionRule>) {
if self.qbit_url.is_none() {
errors.push(NotSet("qBittorrent URL".to_owned()));
}
if self.requires_credentials() {
if self.qbit_username.is_none() {
errors.push(NotSet("qBittorrent username".to_owned()));
}
if self.qbit_password.is_none() {
errors.push(NotSet("qBittorrent password".to_owned()));
}
}
}
/// Whether [`qbit_username`](Self::qbit_username) and
/// [`qbit_password`](Self::qbit_password) must be set.
///
/// - Returns `false` if using [qui reverse proxy](https://getqui.com/docs/features/reverse-proxy)
/// - Returns `true` otherwise
fn requires_credentials(&self) -> bool {
!self
.qbit_url
.as_ref()
.is_some_and(|url| url.contains("/proxy/"))
}
/// Create a [`QbitOptions`] with mock values for testing.
#[cfg(test)]
#[must_use]
pub fn mock() -> Self {
Self {
qbit_url: Some("http://127.0.0.1:8080".to_owned()),
qbit_username: Some("user".to_owned()),
qbit_password: Some("hunter2".to_owned()),
}
}
}
impl OptionsContract for QbitOptions {
type Partial = QbitOptionsPartial;
fn validate(&self, errors: &mut Vec<OptionRule>) {
if let Some(url) = &self.qbit_url
&& !url.starts_with("https://")
&& !url.starts_with("http://")
{
errors.push(UrlNotHttp("qBittorrent URL".to_owned(), url.clone()));
}
if let Some(url) = &self.qbit_url
&& url.ends_with('/')
{
errors.push(UrlInvalidSuffix("qBittorrent URL".to_owned(), url.clone()));
}
}
}