-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
Signed-off-by: Pavel Kirilin <[email protected]>
- Loading branch information
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
use std::time::Duration; | ||
|
||
use axum::http::HeaderMap; | ||
use lapin::{ | ||
options::{ExchangeDeclareOptions, QueueBindOptions, QueueDeclareOptions, BasicPublishOptions}, | ||
types::{AMQPValue, FieldTable, LongString}, | ||
ConnectionProperties, ExchangeKind, BasicProperties, | ||
}; | ||
use mobc::Pool; | ||
use strum::IntoEnumIterator; | ||
|
||
use crate::{ | ||
config::AMQPHooksOptions, | ||
errors::RustusResult, | ||
notifiers::{base::Notifier, hooks::Hook}, | ||
utils::lapin_pool::{ChannelPool, ConnnectionPool}, | ||
}; | ||
|
||
#[allow(clippy::struct_excessive_bools)] | ||
#[derive(Clone)] | ||
pub struct DeclareOptions { | ||
pub declare_exchange: bool, | ||
pub durable_exchange: bool, | ||
pub declare_queues: bool, | ||
pub durable_queues: bool, | ||
} | ||
|
||
#[derive(Clone)] | ||
pub struct AMQPNotifier { | ||
exchange_name: String, | ||
channel_pool: Pool<ChannelPool>, | ||
queues_prefix: String, | ||
exchange_kind: String, | ||
routing_key: Option<String>, | ||
declare_options: DeclareOptions, | ||
celery: bool, | ||
} | ||
|
||
/// ManagerConnection for ChannelPool. | ||
Check failure on line 39 in src/notifiers/impls/amqp_notifier.rs GitHub Actions / clippyitem in documentation is missing backticks
Check failure on line 39 in src/notifiers/impls/amqp_notifier.rs GitHub Actions / clippyitem in documentation is missing backticks
|
||
/// | ||
/// This manager helps you maintain opened channels. | ||
impl AMQPNotifier { | ||
#[allow(clippy::fn_params_excessive_bools)] | ||
pub async fn new(options: AMQPHooksOptions) -> RustusResult<Self> { | ||
Check failure on line 44 in src/notifiers/impls/amqp_notifier.rs GitHub Actions / clippydocs for function returning `Result` missing `# Errors` section
Check failure on line 44 in src/notifiers/impls/amqp_notifier.rs GitHub Actions / clippydocs for function which may panic missing `# Panics` section
|
||
let manager = ConnnectionPool::new( | ||
options.hooks_amqp_url.unwrap().clone(), | ||
ConnectionProperties::default(), | ||
); | ||
let connection_pool = mobc::Pool::builder() | ||
.max_idle_lifetime( | ||
options | ||
.hooks_amqp_idle_connection_timeout | ||
.map(Duration::from_secs), | ||
) | ||
.max_open(options.hooks_amqp_connection_pool_size) | ||
.build(manager); | ||
let channel_pool = mobc::Pool::builder() | ||
.max_idle_lifetime( | ||
options | ||
.hooks_amqp_idle_channels_timeout | ||
.map(Duration::from_secs), | ||
) | ||
.max_open(options.hooks_amqp_channel_pool_size) | ||
.build(ChannelPool::new(connection_pool)); | ||
|
||
Ok(Self { | ||
channel_pool, | ||
celery: options.hooks_amqp_celery, | ||
routing_key: options.hooks_amqp_routing_key, | ||
declare_options: DeclareOptions { | ||
declare_exchange: options.hooks_amqp_declare_exchange, | ||
durable_exchange: options.hooks_amqp_durable_exchange, | ||
declare_queues: options.hooks_amqp_declare_queues, | ||
durable_queues: options.hooks_amqp_durable_queues, | ||
}, | ||
exchange_kind: options.hooks_amqp_exchange_kind, | ||
exchange_name: options.hooks_amqp_exchange, | ||
queues_prefix: options.hooks_amqp_queues_prefix, | ||
}) | ||
} | ||
Check failure on line 80 in src/notifiers/impls/amqp_notifier.rs GitHub Actions / clippyunused `async` for function with no await statements
|
||
|
||
/// Generate queue name based on hook type. | ||
/// | ||
/// If specific routing key is not empty, it returns it. | ||
/// Otherwise it will generate queue name based on hook name. | ||
pub fn get_queue_name(&self, hook: &Hook) -> String { | ||
Check failure on line 86 in src/notifiers/impls/amqp_notifier.rs GitHub Actions / clippythis method could have a `#[must_use]` attribute
|
||
if let Some(routing_key) = self.routing_key.as_ref() { | ||
routing_key.into() | ||
} else { | ||
format!("{}.{hook}", self.queues_prefix.as_str()) | ||
} | ||
} | ||
} | ||
|
||
impl Notifier for AMQPNotifier { | ||
async fn prepare(&mut self) -> RustusResult<()> { | ||
let chan = self.channel_pool.get().await?; | ||
if self.declare_options.declare_exchange { | ||
chan.exchange_declare( | ||
self.exchange_name.as_str(), | ||
ExchangeKind::Custom(self.exchange_kind.clone()), | ||
ExchangeDeclareOptions { | ||
durable: self.declare_options.durable_exchange, | ||
..ExchangeDeclareOptions::default() | ||
}, | ||
FieldTable::default(), | ||
) | ||
.await?; | ||
} | ||
if self.declare_options.declare_queues { | ||
for hook in Hook::iter() { | ||
let queue_name = self.get_queue_name(&hook); | ||
chan.queue_declare( | ||
queue_name.as_str(), | ||
QueueDeclareOptions { | ||
durable: self.declare_options.durable_queues, | ||
..QueueDeclareOptions::default() | ||
}, | ||
FieldTable::default(), | ||
) | ||
.await?; | ||
chan.queue_bind( | ||
queue_name.as_str(), | ||
self.exchange_name.as_str(), | ||
queue_name.as_str(), | ||
QueueBindOptions::default(), | ||
FieldTable::default(), | ||
) | ||
.await?; | ||
} | ||
} | ||
Ok(()) | ||
} | ||
|
||
async fn send_message( | ||
&self, | ||
message: String, | ||
hook: Hook, | ||
_header_map: &HeaderMap, | ||
) -> RustusResult<()> { | ||
let chan = self.channel_pool.get().await?; | ||
let queue = self.get_queue_name(&hook); | ||
let routing_key = self.routing_key.as_ref().unwrap_or(&queue); | ||
let payload = if self.celery { | ||
format!("[[{message}], {{}}, {{}}]").as_bytes().to_vec() | ||
} else { | ||
message.as_bytes().to_vec() | ||
}; | ||
let mut headers = FieldTable::default(); | ||
if self.celery { | ||
headers.insert( | ||
"id".into(), | ||
AMQPValue::LongString(LongString::from(uuid::Uuid::new_v4().to_string())), | ||
); | ||
headers.insert( | ||
"task".into(), | ||
AMQPValue::LongString(LongString::from(format!("rustus.{hook}"))), | ||
); | ||
} | ||
chan.basic_publish( | ||
self.exchange_name.as_str(), | ||
routing_key.as_str(), | ||
BasicPublishOptions::default(), | ||
payload.as_slice(), | ||
BasicProperties::default() | ||
.with_headers(headers) | ||
.with_content_type("application/json".into()) | ||
.with_content_encoding("utf-8".into()), | ||
) | ||
.await?; | ||
Ok(()) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
use crate::{ | ||
errors::RustusError, | ||
notifiers::{base::Notifier, hooks::Hook}, | ||
RustusResult, | ||
}; | ||
use axum::http::HeaderMap; | ||
use log::debug; | ||
use std::path::PathBuf; | ||
use tokio::process::Command; | ||
|
||
#[derive(Clone)] | ||
pub struct DirNotifier { | ||
pub dir: PathBuf, | ||
} | ||
|
||
impl DirNotifier { | ||
pub fn new(dir: PathBuf) -> Self { | ||
Check failure on line 17 in src/notifiers/impls/dir_notifier.rs GitHub Actions / clippythis method could have a `#[must_use]` attribute
|
||
Self { dir } | ||
} | ||
} | ||
|
||
impl Notifier for DirNotifier { | ||
#[cfg_attr(coverage, no_coverage)] | ||
async fn prepare(&mut self) -> RustusResult<()> { | ||
Ok(()) | ||
} | ||
|
||
async fn send_message( | ||
&self, | ||
message: String, | ||
hook: Hook, | ||
_headers_map: &HeaderMap, | ||
) -> RustusResult<()> { | ||
let hook_path = self.dir.join(hook.to_string()); | ||
if !hook_path.exists() { | ||
debug!("Hook {} not found.", hook.to_string()); | ||
return Err(RustusError::HookError(format!( | ||
"Hook file {hook} not found." | ||
))); | ||
} | ||
debug!("Running hook: {}", hook_path.as_path().display()); | ||
let mut command = Command::new(hook_path).arg(message).spawn()?; | ||
let stat = command.wait().await?; | ||
if !stat.success() { | ||
return Err(RustusError::HookError("Returned wrong status code".into())); | ||
} | ||
Ok(()) | ||
} | ||
} |