Skip to content

Commit

Permalink
Add async feature and rework Dispatcher
Browse files Browse the repository at this point in the history
* Rename `NtfyError` to `Error`

Ref #7

Signed-off-by: Yuki Kishimoto <[email protected]>
  • Loading branch information
yukibtc committed Jan 6, 2025
1 parent d821c02 commit b926e20
Show file tree
Hide file tree
Showing 13 changed files with 258 additions and 235 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ jobs:
matrix:
build-args:
[
--no-default-features,
--no-default-features --features async,
--no-default-features --features blocking,
]
steps:
- name: Checkout
Expand Down
65 changes: 0 additions & 65 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ readme = "README.md"
keywords = ["ntfy", "notifications", "sdk"]

[features]
default = ["dep:reqwest"]
default = ["async"]
async = ["dep:reqwest"]
blocking = ["dep:ureq"]

[dependencies]
Expand All @@ -23,14 +24,15 @@ ureq = { version = "2.12", features = ["json", "socks-proxy"], optional = true }
url = { version = "2", features = ["serde"] }

[dev-dependencies]
tokio = { version = "1", features = ["full"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

[[example]]
name = "blocking"
required-features = ["blocking"]

[[example]]
name = "client"
required-features = ["async"]

[profile.release]
lto = true
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
use ntfy::prelude::*;
#[tokio::main]
async fn main() -> Result<(), NtfyError> {
let dispatcher = Dispatcher::builder("https://ntfy.sh")
async fn main() -> Result<(), Error> {
let dispatcher = DispatcherBuilder::new("https://ntfy.sh")
.credentials(Auth::credentials("username", "password")) // Add optional credentials
.proxy("socks5h://127.0.0.1:9050") // Add optional proxy
.build()?; // Build dispatcher
.build_async()?; // Build dispatcher
let action = Action::new(
ActionType::Http,
Expand Down
6 changes: 3 additions & 3 deletions examples/blocking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@

use ntfy::prelude::*;

fn main() -> Result<(), NtfyError> {
let dispatcher = Dispatcher::builder("https://ntfy.sh")
fn main() -> Result<(), Error> {
let dispatcher = DispatcherBuilder::new("https://ntfy.sh")
.credentials(Auth::credentials("username", "password")) // Add optional credentials
.proxy("socks5://127.0.0.1:9050") // Add optional proxy
.build()?; // Build dispatcher
.build_blocking()?; // Build dispatcher

let action = Action::new(
ActionType::Http,
Expand Down
8 changes: 4 additions & 4 deletions examples/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
use ntfy::prelude::*;

#[tokio::main]
async fn main() -> Result<(), NtfyError> {
let dispatcher = Dispatcher::builder("https://ntfy.sh")
async fn main() -> Result<(), Error> {
let dispatcher = DispatcherBuilder::new("https://ntfy.sh")
.credentials(Auth::credentials("username", "password")) // Add optional credentials
.proxy("socks5://127.0.0.1:9050") // Add optional proxy
.build()?; // Build dispatcher
.proxy("socks5h://127.0.0.1:9050") // Add optional proxy
.build_async()?; // Build dispatcher

let action = Action::new(
ActionType::Http,
Expand Down
9 changes: 6 additions & 3 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

check:
cargo fmt --all -- --config format_code_in_doc_comments=true
cargo check
cargo test
cargo clippy -- -D warnings
cargo check --no-default-features --features async
cargo check --no-default-features --features blocking
cargo test --no-default-features --features async
cargo test --no-default-features --features blocking
cargo clippy --no-default-features --features async -- -D warnings
cargo clippy --no-default-features --features blocking -- -D warnings
68 changes: 68 additions & 0 deletions src/dispatcher/async.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright (c) 2022 Yuki Kishimoto
// Distributed under the MIT software license

use reqwest::header::{HeaderMap, HeaderValue};
use reqwest::{Client, ClientBuilder, Proxy, Response};
use url::Url;

use super::builder::DispatcherBuilder;
use crate::error::Error;
use crate::payload::Payload;

/// Async dispatcher
#[derive(Debug, Clone)]
pub struct Async {
client: Client,
}

impl Async {
#[inline]
pub(crate) fn new(builder: DispatcherBuilder) -> Result<Self, Error> {
let mut client = ClientBuilder::new();

if let Some(auth) = builder.auth {
let mut headers = HeaderMap::new();
let mut auth_value = HeaderValue::from_str(&auth.to_header_value())?;
auth_value.set_sensitive(true);
headers.insert("Authorization", auth_value);
client = client.default_headers(headers);
}

if let Some(proxy) = builder.proxy {
client = client.proxy(Proxy::all(proxy)?);
}

Ok(Self {
client: client.build()?,
})
}

/// Send payload to ntfy server
pub(crate) async fn send(&self, url: &Url, payload: &Payload) -> Result<(), Error> {
// Build request
let mut builder = self.client.post(url.as_str());

// If markdown, set headers
if payload.markdown {
builder = builder.header("Markdown", "yes");
}

// Add payload
builder = builder.json(payload);

// Send request
let res: Response = builder.send().await?;
let res: Response = res.error_for_status()?;

// Get full response text
let text: String = res.text().await?;

if text.is_empty() {
return Err(Error::EmptyResponse);
}

// TODO: check the text?

Ok(())
}
}
66 changes: 66 additions & 0 deletions src/dispatcher/blocking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) 2022 Yuki Kishimoto
// Distributed under the MIT software license

use ureq::{Agent, MiddlewareNext, Request, Response};
use url::Url;

use super::builder::DispatcherBuilder;
use crate::error::Error;
use crate::payload::Payload;

/// Blocking dispatcher
#[derive(Debug, Clone)]
pub struct Blocking {
client: Agent,
}

impl Blocking {
#[inline]
pub(crate) fn new(builder: DispatcherBuilder) -> Result<Self, Error> {
let mut client = ureq::builder();

if let Some(auth) = builder.auth {
let heaver_value = auth.to_header_value();

// Set the authorization headers of every request using a middleware function
client = client.middleware(
move |req: Request, next: MiddlewareNext| -> Result<Response, ureq::Error> {
next.handle(req.set("Authorization", &heaver_value))
},
);
}

if let Some(proxy) = builder.proxy {
let proxy = ureq::Proxy::new(proxy)?;
client = client.proxy(proxy);
}

Ok(Self {
client: client.build(),
})
}

pub(crate) fn send(&self, url: &Url, payload: &Payload) -> Result<(), Error> {
// Build request
let mut builder = self.client.post(url.as_str());

// If markdown, set headers
if payload.markdown {
builder = builder.set("Markdown", "yes");
}

// Send request
let res: Response = builder.send_json(payload)?;

// Get full response text
let text: String = res.into_string()?;

if text.is_empty() {
return Err(Error::EmptyResponse);
}

// TODO: check the text?

Ok(())
}
}
Loading

0 comments on commit b926e20

Please sign in to comment.