Skip to content

Commit 1c4e45c

Browse files
committed
feat: cyper support
1 parent 4735df7 commit 1c4e45c

File tree

9 files changed

+158
-54
lines changed

9 files changed

+158
-54
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@
44
*.env
55
output.json
66
/.direnv
7-
/.vscode
7+
/.vscode
8+
/.zed

Cargo.toml

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,18 @@ all-features = true
1717
rustdoc-args = ["--cfg", "docsrs"]
1818

1919
[features]
20-
default = ["builder", "native-tls"]
21-
rustls-tls = ["reqwest/rustls-tls"]
22-
native-tls = ["reqwest/default-tls"]
20+
default = ["builder", "native-tls", "reqwest"]
21+
reqwest = ["dep:reqwest"]
22+
cyper = ["dep:cyper"]
23+
rustls-tls = ["reqwest?/rustls", "cyper?/rustls"]
24+
native-tls = ["reqwest?/default-tls", "cyper?/native-tls"]
2325

2426
# enables typed-builder on args types
2527
builder = ["dep:typed-builder"]
2628

2729
[dependencies]
2830
typed-builder = { version = "0.18.2", optional = true }
2931
serde = { version = "1.0.202", features = ["derive"] }
30-
reqwest = { version = "0.12.4", default-features = false, features = ["charset", "http2", "macos-system-configuration", "json", "multipart"] }
3132
url = { version = "2.5.0", features = ["serde"] }
3233

3334
mod_use = "0.2.1"
@@ -39,8 +40,28 @@ thiserror = "1.0.61"
3940
tracing = "0.1.40"
4041
serde_json = "1.0.117"
4142
bytes = "1.6.0"
43+
cfg-if = "1.0.4"
44+
http = "1.1.0"
45+
mime = "0.3.17"
46+
47+
# HTTP Client
48+
# compio / cyper
49+
[dependencies.cyper]
50+
version = "0.8"
51+
optional = true
52+
default-features = false
53+
features = ["json", "multipart"]
54+
55+
# tokio / reqwest
56+
[dependencies.reqwest]
57+
version = "0.13"
58+
optional = true
59+
default-features = false
60+
features = ["json", "form", "multipart", "query"]
61+
4262
[dev-dependencies]
4363
tokio = { version = "1.27.0", features = ["full"] }
64+
compio = { version = "0.18.0", features = ["all"] }
4465

4566
dotenv = "0.15.0"
4667
tracing-subscriber = "0.3.16"

README.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ or manually add to `Cargo.toml`:
2222

2323
```toml
2424
[dependencies]
25-
qbit-rs = "0.4"
25+
qbit-rs = "0.5"
2626
```
2727

2828
Then use it in your code:
@@ -48,10 +48,20 @@ let api = Qbit::builder()
4848
let torrents = api.get_version().await;
4949
```
5050

51-
For more methods, see [`Qbit`](https://docs.rs/qbit-rs/latest/qbit_rs/struct.Qbit.html).
51+
## HTTP Client
52+
53+
The crate by default uses `reqwest` as the HTTP client. It also supports [`cyper`](https://github.com/compio-rs/cyper) (HTTP client for [`compio`](https://github.com/compio-rs/compio) based on hyper) under the `cyper` feature flag. To uses it, disable default features and enable `cyper`:
54+
55+
```bash
56+
cargo add qbit-rs --no-default-features --features cyper
57+
```
58+
59+
Notice that this will also disable tls and `typed-builder` support for args. Make sure to enable `builder`, `native-tls` or `rustls-tls` if you need them.
5260

5361
## API Coverage
5462

63+
For all methods, see [`Qbit`](https://docs.rs/qbit-rs/latest/qbit_rs/struct.Qbit.html).
64+
5565
Most of the API is covered, except `RSS` and `Search`. PR is welcomed if you need that part of the API. The following is a list of the implementation status:
5666

5767
1. [x] Authentication
@@ -154,4 +164,4 @@ Most of the API is covered, except `RSS` and `Search`. PR is welcomed if you nee
154164
1. Undocumented
155165
1. [x] [Export torrent](https://docs.rs/qbit-rs/latest/qbit_rs/struct.Qbit.html#method.export_torrent)[^1]
156166

157-
[^1]: The endpoint is added in [this PR](https://github.com/qbittorrent/qBittorrent/pull/16968)
167+
[^1]: The endpoint is added in [this PR](https://github.com/qbittorrent/qBittorrent/pull/16968)

flake.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
llvm.libstdcxxClang
4444
openssl
4545
pkg-config
46+
cargo-nextest
4647
]
4748
++ (
4849
if

src/builder.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22

33
use std::{fmt::Debug, sync::Mutex};
44

5-
use reqwest::Client;
65
use tap::Pipe;
76
use url::Url;
87

9-
use crate::{LoginState, Qbit, ext::Cookie, model::Credential};
8+
use crate::{Client, LoginState, Qbit, ext::Cookie, model::Credential};
109

1110
pub struct QbitBuilder<C = (), R = (), E = ()> {
1211
credential: C,
@@ -84,7 +83,7 @@ impl<C, R, E> QbitBuilder<C, R, E> {
8483
}
8584
}
8685

87-
impl<C, U> QbitBuilder<C, reqwest::Client, U>
86+
impl<C, U> QbitBuilder<C, Client, U>
8887
where
8988
C: IntoLoginState,
9089
U: TryInto<Url>,
@@ -109,14 +108,14 @@ where
109108
U::Error: Debug,
110109
{
111110
pub fn build(self) -> Qbit {
112-
self.client(reqwest::Client::new()).build()
111+
self.client(Client::new()).build()
113112
}
114113
}
115114

116115
#[test]
117116
fn test_builder() {
118117
QbitBuilder::new()
119-
.client(reqwest::Client::new())
118+
.client(Client::new())
120119
.endpoint("http://localhost:8080")
121120
.credential(Credential::new("admin", "adminadmin"))
122121
.build();
@@ -127,7 +126,7 @@ fn test_builder() {
127126
.build();
128127

129128
QbitBuilder::new()
130-
.client(reqwest::Client::new())
129+
.client(Client::new())
131130
.endpoint("http://localhost:8080")
132131
.cookie("SID=1234567890")
133132
.build();

src/client.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#![allow(unused_imports)]
2+
3+
cfg_if::cfg_if! {
4+
if #[cfg(all(feature = "reqwest", feature = "cyper"))] {
5+
compile_error!("The 'reqwest' and 'cyper' features cannot be enabled at the same time. To use `cyper`, disable default feature first.");
6+
} else if #[cfg(feature = "reqwest")] {
7+
pub(crate) use reqwest::{Client, Error, Method, Response, StatusCode, Url, get, header, multipart};
8+
} else if #[cfg(feature = "cyper")] {
9+
pub(crate) use cyper::{Client, Response, Error, multipart};
10+
pub(crate) use url::Url;
11+
pub(crate) use http::{Method, StatusCode, header};
12+
pub(crate) use cyper_ext::*;
13+
} else {
14+
compile_error!("Either the 'reqwest' or 'compio' feature must be enabled");
15+
}
16+
}
17+
18+
pub(crate) trait CheckError: Sized {
19+
type Ok;
20+
type Error;
21+
fn check(self) -> Result<Self::Ok, Self::Error>;
22+
}
23+
24+
#[cfg(feature = "reqwest")]
25+
impl CheckError for reqwest::RequestBuilder {
26+
type Error = reqwest::Error;
27+
type Ok = reqwest::RequestBuilder;
28+
29+
#[inline(always)]
30+
fn check(self) -> Result<Self, Self::Error> {
31+
Ok(self)
32+
}
33+
}
34+
35+
#[cfg(feature = "cyper")]
36+
mod cyper_ext {
37+
use cyper::multipart::Part;
38+
use mime::FromStrError;
39+
40+
use super::*;
41+
42+
pub(crate) trait PartExt: Sized {
43+
fn mime_str(self, mime: &str) -> Result<Part, FromStrError>;
44+
}
45+
46+
impl PartExt for multipart::Part {
47+
fn mime_str(self, mime: &str) -> Result<Part, FromStrError> {
48+
let mime = mime.parse()?;
49+
Ok(self.mime(mime))
50+
}
51+
}
52+
53+
#[cfg(test)]
54+
pub(crate) async fn get<T: cyper::IntoUrl>(url: T) -> Result<Response, cyper::Error> {
55+
Client::new().get(url)?.send().await
56+
}
57+
58+
impl<T> CheckError for cyper::Result<T> {
59+
type Error = cyper::Error;
60+
type Ok = T;
61+
62+
#[inline(always)]
63+
fn check(self) -> Result<T, Self::Error> {
64+
self
65+
}
66+
}
67+
}

src/ext.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
use reqwest::{Response, StatusCode, header::SET_COOKIE};
21
use tap::Pipe;
32

4-
use crate::{ApiError, Error, Result};
3+
use crate::{ApiError, Error, Response, Result, StatusCode, header::SET_COOKIE};
54

65
pub trait FromResponse {
76
fn from_response(response: &Response) -> Result<Self>

0 commit comments

Comments
 (0)