docs: document revolt-coalesced
This commit is contained in:
@@ -11,7 +11,7 @@ tokio = ["dep:tokio"]
|
|||||||
queue = ["dep:indexmap"]
|
queue = ["dep:indexmap"]
|
||||||
cache = ["dep:lru"]
|
cache = ["dep:lru"]
|
||||||
|
|
||||||
default = ["tokio", "queue", "cache"]
|
default = ["tokio"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tokio = { version = "1.47.0", features = ["sync"], optional = true }
|
tokio = { version = "1.47.0", features = ["sync"], optional = true }
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
/// Config values for [`CoalescionService`].
|
||||||
pub struct CoalescionServiceConfig {
|
pub struct CoalescionServiceConfig {
|
||||||
/// How many tasks are running at once
|
/// How many tasks are running at once
|
||||||
pub max_concurrent: Option<usize>,
|
pub max_concurrent: Option<usize>,
|
||||||
|
|||||||
@@ -1,18 +1,25 @@
|
|||||||
use std::fmt::Display;
|
use std::fmt;
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||||
|
/// Coalescion service error.
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
/// Failed to receive the actions return from the channel for unknown reason
|
||||||
RecvError,
|
RecvError,
|
||||||
|
/// Reached the `max_concurrent` amount of actions running at once and could not queue the action
|
||||||
MaxConcurrent,
|
MaxConcurrent,
|
||||||
|
/// Reached the `max_queue` amount of actions in the queue
|
||||||
MaxQueue,
|
MaxQueue,
|
||||||
|
/// Failed to downcast the type to the current type being returned, this will be most likely an ID collision
|
||||||
|
DowncastError,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Error {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Error::RecvError => write!(f, "Unable to receive data from the channel"),
|
Error::RecvError => write!(f, "Unable to receive data from the channel"),
|
||||||
Error::MaxConcurrent => write!(f, "Max number of tasks running at once"),
|
Error::MaxConcurrent => write!(f, "Max number of tasks running at once"),
|
||||||
Error::MaxQueue => write!(f, "Max number of tasks in queue"),
|
Error::MaxQueue => write!(f, "Max number of tasks in queue"),
|
||||||
|
Error::DowncastError => write!(f, "Failed to downcast type, possible key collision with different types")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,39 @@
|
|||||||
|
//! # Coalesced
|
||||||
|
//!
|
||||||
|
//! Coalescion service to group, caching and queue duplicate actions.
|
||||||
|
//! useful for deduplicating web requests, database lookups and other similar resource
|
||||||
|
//! intensive or rate-limited actions.
|
||||||
|
//!
|
||||||
|
//! ## Features
|
||||||
|
//! - `tokio`: Uses tokio for the async backend, this is currently the only backend.
|
||||||
|
//! - `queue`: Whether to support queueing requests to only allow X amount of actions running at once.
|
||||||
|
//! - `cache`: Whether to cache the actions results for future actions with the same id, uses an LRU cache internally.
|
||||||
|
//!
|
||||||
|
//! [`CoalescionService`] uses both [`Arc`] and [`RwLock`] internally and can be cheaply cloned to
|
||||||
|
//! use in your codebase.
|
||||||
|
//!
|
||||||
|
//! It is common practice to wrap the service and in your own which delegates the executions to ensure all ids are tracked in one location across your codebase.
|
||||||
|
//!
|
||||||
|
//! All values are stored using [`Any`] and must be [`'static`] + [`Send`] + [`Sync`], if there is an id mismatch
|
||||||
|
//! and a type is wrong the library will return an error, values returned from the service are also
|
||||||
|
//! wrapped in an [`Arc`] as they are shared to each duplicate action.
|
||||||
|
//!
|
||||||
|
//! ## Example:
|
||||||
|
//! ```rs
|
||||||
|
//! use revolt_coalesced::CoalescionService;
|
||||||
|
//!
|
||||||
|
//! let service = CoalescionService::new();
|
||||||
|
//!
|
||||||
|
//! let user_id = "my_user_id";
|
||||||
|
//! let user = service.execute(user_id, || async move {
|
||||||
|
//! database.fetch_user(user_id).await.unwrap()
|
||||||
|
//! }).await;
|
||||||
|
//! ```
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
mod error;
|
mod error;
|
||||||
mod service;
|
mod service;
|
||||||
|
|
||||||
pub use config::CoalescionServiceConfig;
|
pub use config::CoalescionServiceConfig;
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
pub use service::CoalescionService;
|
pub use service::CoalescionService;
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
use std::{collections::{HashMap}, fmt::Debug, hash::Hash, sync::Arc, future::Future};
|
use std::{any::Any, collections::HashMap, fmt::Debug, future::Future, hash::Hash, sync::Arc};
|
||||||
|
|
||||||
use tokio::{sync::{watch::{channel as watch_channel, Receiver}, RwLock, Mutex}};
|
use tokio::sync::{
|
||||||
|
watch::{channel as watch_channel, Receiver},
|
||||||
|
RwLock,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(feature = "cache")]
|
#[cfg(feature = "cache")]
|
||||||
use lru::LruCache;
|
use lru::LruCache;
|
||||||
@@ -10,20 +13,23 @@ use indexmap::IndexMap;
|
|||||||
|
|
||||||
use crate::{CoalescionServiceConfig, Error};
|
use crate::{CoalescionServiceConfig, Error};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Debug, Clone)]
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
pub struct CoalescionService<Id: Hash + Eq, Value> {
|
/// # Coalescion service
|
||||||
|
///
|
||||||
|
/// See module description for example usage.
|
||||||
|
pub struct CoalescionService<Id: Hash + Clone + Eq> {
|
||||||
config: Arc<CoalescionServiceConfig>,
|
config: Arc<CoalescionServiceConfig>,
|
||||||
watchers: Arc<RwLock<HashMap<Id, Receiver<Option<Result<Arc<Value>, Error>>>>>>,
|
watchers: Arc<RwLock<HashMap<Id, Receiver<Option<Result<Arc<dyn Any + Send + Sync>, Error>>>>>>,
|
||||||
#[cfg(feature = "queue")]
|
#[cfg(feature = "queue")]
|
||||||
queue: Arc<RwLock<IndexMap<Id, Receiver<Option<Result<Arc<Value>, Error>>>>>>,
|
queue: Arc<RwLock<IndexMap<Id, Receiver<Option<Result<Arc<dyn Any + Send + Sync>, Error>>>>>>,
|
||||||
#[cfg(feature = "cache")]
|
#[cfg(feature = "cache")]
|
||||||
cache: Option<Arc<Mutex<LruCache<Id, Arc<Value>>>>>,
|
cache: Option<Arc<tokio::sync::Mutex<LruCache<Id, Arc<dyn Any + Send + Sync>>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Id: Hash + PartialEq + Eq + Clone + Ord, Value> CoalescionService<Id, Value> {
|
impl<Id: Hash + Clone + Eq> CoalescionService<Id> {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Default::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_config(config: CoalescionServiceConfig) -> Self {
|
pub fn from_config(config: CoalescionServiceConfig) -> Self {
|
||||||
@@ -38,22 +44,37 @@ impl<Id: Hash + PartialEq + Eq + Clone + Ord, Value> CoalescionService<Id, Value
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "cache")]
|
#[cfg(feature = "cache")]
|
||||||
pub fn from_cache(config: CoalescionServiceConfig, cache: LruCache<Id, Arc<Value>>) -> Self {
|
pub fn from_cache(
|
||||||
|
config: CoalescionServiceConfig,
|
||||||
|
cache: LruCache<Id, Arc<dyn Any + Send + Sync>>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
cache: Some(Arc::new(Mutex::new(cache))),
|
cache: Some(Arc::new(Mutex::new(cache))),
|
||||||
..Self::from_config(config)
|
..Self::from_config(config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn wait_for(&self, mut receiver: Receiver<Option<Result<Arc<Value>, Error>>>) -> Result<Arc<Value>, Error> {
|
async fn wait_for<Value: Any + Send + Sync>(
|
||||||
|
&self,
|
||||||
|
mut receiver: Receiver<Option<Result<Arc<dyn Any + Send + Sync>, Error>>>,
|
||||||
|
) -> Result<Arc<Value>, Error> {
|
||||||
receiver
|
receiver
|
||||||
.wait_for(|v| v.is_some())
|
.wait_for(|v| v.is_some())
|
||||||
.await
|
.await
|
||||||
.map_err(|_| Error::RecvError)
|
.map_err(|_| Error::RecvError)
|
||||||
.and_then(|r| r.clone().unwrap())
|
.and_then(|r| r.clone().unwrap())
|
||||||
|
.and_then(|arc| Arc::downcast(arc).map_err(|_| Error::DowncastError))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn insert_and_execute<F: FnOnce() -> Fut, Fut: Future<Output = Value>>(&self, id: Id, func: F) -> Result<Arc<Value>, Error> {
|
async fn insert_and_execute<
|
||||||
|
Value: Send + Sync + 'static,
|
||||||
|
F: FnOnce() -> Fut,
|
||||||
|
Fut: Future<Output = Value>,
|
||||||
|
>(
|
||||||
|
&self,
|
||||||
|
id: Id,
|
||||||
|
func: F,
|
||||||
|
) -> Result<Arc<Value>, Error> {
|
||||||
let (send, recv) = watch_channel(None);
|
let (send, recv) = watch_channel(None);
|
||||||
|
|
||||||
self.watchers.write().await.insert(id.clone(), recv);
|
self.watchers.write().await.insert(id.clone(), recv);
|
||||||
@@ -61,7 +82,7 @@ impl<Id: Hash + PartialEq + Eq + Clone + Ord, Value> CoalescionService<Id, Value
|
|||||||
let value = Ok(Arc::new(func().await));
|
let value = Ok(Arc::new(func().await));
|
||||||
|
|
||||||
send.send_modify(|opt| {
|
send.send_modify(|opt| {
|
||||||
opt.replace(value.clone());
|
opt.replace(value.clone().map(|v| v as Arc<dyn Any + Send + Sync>));
|
||||||
});
|
});
|
||||||
|
|
||||||
#[cfg(feature = "cache")]
|
#[cfg(feature = "cache")]
|
||||||
@@ -76,11 +97,21 @@ impl<Id: Hash + PartialEq + Eq + Clone + Ord, Value> CoalescionService<Id, Value
|
|||||||
value
|
value
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn execute<F: FnOnce() -> Fut, Fut: Future<Output = Value>>(&self, id: Id, func: F) -> Result<Arc<Value>, Error> {
|
/// Coalesces an function, the actual function may not run if one with the same id is already running,
|
||||||
|
/// queued to be ran, or cached, the id should be globally unique for this specific action.
|
||||||
|
pub async fn execute<
|
||||||
|
Value: Send + Sync + 'static,
|
||||||
|
F: FnOnce() -> Fut,
|
||||||
|
Fut: Future<Output = Value>,
|
||||||
|
>(
|
||||||
|
&self,
|
||||||
|
id: Id,
|
||||||
|
func: F,
|
||||||
|
) -> Result<Arc<Value>, Error> {
|
||||||
#[cfg(feature = "cache")]
|
#[cfg(feature = "cache")]
|
||||||
if let Some(cache) = self.cache.as_ref() {
|
if let Some(cache) = self.cache.as_ref() {
|
||||||
if let Some(value) = cache.lock().await.get(&id) {
|
if let Some(value) = cache.lock().await.get(&id) {
|
||||||
return Ok(value.clone())
|
return Arc::downcast::<Value>(value.clone()).map_err(|_| Error::DowncastError);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -105,10 +136,14 @@ impl<Id: Hash + PartialEq + Eq + Clone + Ord, Value> CoalescionService<Id, Value
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(receiver) = receiver {
|
if let Some(receiver) = receiver {
|
||||||
return self.wait_for(receiver).await
|
return self.wait_for(receiver).await;
|
||||||
} else {
|
} else {
|
||||||
if self.config.max_queue.is_some_and(|max_queue| max_queue >= length) {
|
if self
|
||||||
return Err(Error::MaxQueue)
|
.config
|
||||||
|
.max_queue
|
||||||
|
.is_some_and(|max_queue| max_queue >= length)
|
||||||
|
{
|
||||||
|
return Err(Error::MaxQueue);
|
||||||
};
|
};
|
||||||
|
|
||||||
let (send, recv) = watch_channel(None);
|
let (send, recv) = watch_channel(None);
|
||||||
@@ -130,10 +165,14 @@ impl<Id: Hash + PartialEq + Eq + Clone + Ord, Value> CoalescionService<Id, Value
|
|||||||
let response = self.insert_and_execute(id, func).await;
|
let response = self.insert_and_execute(id, func).await;
|
||||||
|
|
||||||
send.send_modify(|opt| {
|
send.send_modify(|opt| {
|
||||||
opt.replace(response.clone());
|
opt.replace(
|
||||||
|
response
|
||||||
|
.clone()
|
||||||
|
.map(|v| v as Arc<dyn Any + Send + Sync>),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return response
|
return response;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,23 +184,24 @@ impl<Id: Hash + PartialEq + Eq + Clone + Ord, Value> CoalescionService<Id, Value
|
|||||||
#[cfg(not(feature = "queue"))]
|
#[cfg(not(feature = "queue"))]
|
||||||
Err(Error::MaxConcurrent)
|
Err(Error::MaxConcurrent)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => self.insert_and_execute(id, func).await,
|
||||||
self.insert_and_execute(id, func).await
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetches the amount of currently running tasks
|
||||||
pub async fn current_task_count(&self) -> usize {
|
pub async fn current_task_count(&self) -> usize {
|
||||||
self.watchers.read().await.len()
|
self.watchers.read().await.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "queue")]
|
||||||
|
/// Fetches the current length of the queue
|
||||||
pub async fn current_queue_len(&self) -> usize {
|
pub async fn current_queue_len(&self) -> usize {
|
||||||
self.queue.read().await.len()
|
self.queue.read().await.len()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Id: Hash + PartialEq + Eq + Clone + Ord, Value> Default for CoalescionService<Id, Value> {
|
impl<Id: Hash + Clone + Eq> Default for CoalescionService<Id> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::from_config(CoalescionServiceConfig::default())
|
Self::from_config(CoalescionServiceConfig::default())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,13 @@ revolt-result = { version = "0.8.8", path = "../../core/result", features = [
|
|||||||
"utoipa",
|
"utoipa",
|
||||||
"axum",
|
"axum",
|
||||||
] }
|
] }
|
||||||
revolt-coalesced = { version = "0.8.8", path = "../../core/coalesced" }
|
revolt-coalesced = { version = "0.8.8", path = "../../core/coalesced", features = [
|
||||||
revolt-database = { version = "0.8.8", path = "../../core/database", features = ["axum-impl"] }
|
"queue",
|
||||||
|
] }
|
||||||
|
revolt-database = { version = "0.8.8", path = "../../core/database", features = [
|
||||||
|
"axum-impl",
|
||||||
|
] }
|
||||||
|
|
||||||
# Axum / web server
|
# Axum / web server
|
||||||
axum = { version = "0.7.5" }
|
axum = { version = "0.7.5" }
|
||||||
axum-extra = { version = "0.9", features = ["typed-header"] }
|
axum-extra = { version = "0.9", features = ["typed-header"] }
|
||||||
@@ -37,4 +42,4 @@ tracing = "0.1"
|
|||||||
|
|
||||||
# Utils
|
# Utils
|
||||||
lru_time_cache = "0.11.11"
|
lru_time_cache = "0.11.11"
|
||||||
urlencoding = "2.1.3"
|
urlencoding = "2.1.3"
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use crate::{tenor, types};
|
|||||||
|
|
||||||
#[derive(Deserialize, IntoParams)]
|
#[derive(Deserialize, IntoParams)]
|
||||||
pub struct CategoriesQueryParams {
|
pub struct CategoriesQueryParams {
|
||||||
|
/// Users locale
|
||||||
#[param(example = "en_US")]
|
#[param(example = "en_US")]
|
||||||
pub locale: String,
|
pub locale: String,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,4 +19,4 @@ pub async fn root() -> Json<types::RootResponse<'static>> {
|
|||||||
message: "Gifbox lives on!",
|
message: "Gifbox lives on!",
|
||||||
version: CRATE_VERSION,
|
version: CRATE_VERSION,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,12 +11,17 @@ use crate::{tenor, types};
|
|||||||
|
|
||||||
#[derive(Deserialize, IntoParams)]
|
#[derive(Deserialize, IntoParams)]
|
||||||
pub struct SearchQueryParams {
|
pub struct SearchQueryParams {
|
||||||
|
/// Search query
|
||||||
#[param(example = "Wave")]
|
#[param(example = "Wave")]
|
||||||
pub query: String,
|
pub query: String,
|
||||||
|
/// Users locale
|
||||||
#[param(example = "en_US")]
|
#[param(example = "en_US")]
|
||||||
pub locale: String,
|
pub locale: String,
|
||||||
|
/// Amount of results to respond with
|
||||||
pub limit: Option<u32>,
|
pub limit: Option<u32>,
|
||||||
|
/// Flag for if searching in a gif category
|
||||||
pub is_category: Option<bool>,
|
pub is_category: Option<bool>,
|
||||||
|
/// Value of `next` for getting the next page of results with the current search query
|
||||||
pub position: Option<String>,
|
pub position: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,20 +4,20 @@ use axum::{
|
|||||||
};
|
};
|
||||||
use revolt_database::User;
|
use revolt_database::User;
|
||||||
use revolt_result::{create_error, Result};
|
use revolt_result::{create_error, Result};
|
||||||
use serde::{Deserialize};
|
use serde::Deserialize;
|
||||||
use utoipa::{IntoParams};
|
use utoipa::IntoParams;
|
||||||
|
|
||||||
use crate::{
|
use crate::{tenor, types};
|
||||||
tenor,
|
|
||||||
types,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Deserialize, IntoParams)]
|
#[derive(Deserialize, IntoParams)]
|
||||||
pub struct TrendingQueryParams {
|
pub struct TrendingQueryParams {
|
||||||
#[param(example = "en_US")]
|
#[param(example = "en_US")]
|
||||||
|
/// Users locale
|
||||||
pub locale: String,
|
pub locale: String,
|
||||||
|
/// Amount of results to respond with
|
||||||
pub limit: Option<u32>,
|
pub limit: Option<u32>,
|
||||||
pub position: Option<String>
|
/// Value of `next` for getting the next page of results of featured gifs
|
||||||
|
pub position: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trending GIFs
|
/// Trending GIFs
|
||||||
@@ -37,7 +37,11 @@ pub async fn trending(
|
|||||||
State(tenor): State<tenor::Tenor>,
|
State(tenor): State<tenor::Tenor>,
|
||||||
) -> Result<Json<types::PaginatedMediaResponse>> {
|
) -> Result<Json<types::PaginatedMediaResponse>> {
|
||||||
tenor
|
tenor
|
||||||
.featured(¶ms.locale, params.limit.unwrap_or(50), params.position.as_deref().unwrap_or_default())
|
.featured(
|
||||||
|
¶ms.locale,
|
||||||
|
params.limit.unwrap_or(50),
|
||||||
|
params.position.as_deref().unwrap_or_default(),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| create_error!(InternalError))
|
.map_err(|_| create_error!(InternalError))
|
||||||
.map(|results| results.as_ref().clone().into())
|
.map(|results| results.as_ref().clone().into())
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//! Interal Tenor API wrapper
|
||||||
|
|
||||||
use std::{sync::Arc, time::Duration};
|
use std::{sync::Arc, time::Duration};
|
||||||
|
|
||||||
use lru_time_cache::LruCache;
|
use lru_time_cache::LruCache;
|
||||||
@@ -20,18 +22,11 @@ pub enum TenorError {
|
|||||||
pub struct Tenor {
|
pub struct Tenor {
|
||||||
pub key: Arc<str>,
|
pub key: Arc<str>,
|
||||||
pub client: Client,
|
pub client: Client,
|
||||||
|
pub coalescion: CoalescionService<String>,
|
||||||
pub cache: Arc<RwLock<LruCache<String, Arc<types::PaginatedMediaResponse>>>>,
|
pub cache: Arc<RwLock<LruCache<String, Arc<types::PaginatedMediaResponse>>>>,
|
||||||
pub cache_coalescion:
|
|
||||||
CoalescionService<String, Result<Arc<types::PaginatedMediaResponse>, TenorError>>,
|
|
||||||
|
|
||||||
pub categories: Arc<RwLock<LruCache<String, Arc<types::CategoriesResponse>>>>,
|
pub categories: Arc<RwLock<LruCache<String, Arc<types::CategoriesResponse>>>>,
|
||||||
pub categories_coalescion:
|
|
||||||
CoalescionService<String, Result<Arc<types::CategoriesResponse>, TenorError>>,
|
|
||||||
|
|
||||||
pub featured: Arc<RwLock<LruCache<String, Arc<types::PaginatedMediaResponse>>>>,
|
pub featured: Arc<RwLock<LruCache<String, Arc<types::PaginatedMediaResponse>>>>,
|
||||||
pub featured_coalescion:
|
|
||||||
CoalescionService<String, Result<Arc<types::PaginatedMediaResponse>, TenorError>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tenor {
|
impl Tenor {
|
||||||
@@ -39,39 +34,29 @@ impl Tenor {
|
|||||||
Self {
|
Self {
|
||||||
key: Arc::from(key),
|
key: Arc::from(key),
|
||||||
client: Client::new(),
|
client: Client::new(),
|
||||||
|
coalescion: CoalescionService::from_config(CoalescionServiceConfig {
|
||||||
|
max_concurrent: Some(100),
|
||||||
|
queue_requests: true,
|
||||||
|
max_queue: None,
|
||||||
|
}),
|
||||||
|
|
||||||
// 1 hour, 1k requests
|
// 1 hour, 1k requests
|
||||||
cache: Arc::new(RwLock::new(LruCache::with_expiry_duration_and_capacity(
|
cache: Arc::new(RwLock::new(LruCache::with_expiry_duration_and_capacity(
|
||||||
Duration::from_secs(60 * 60),
|
Duration::from_secs(60 * 60),
|
||||||
1000,
|
1000,
|
||||||
))),
|
))),
|
||||||
cache_coalescion: CoalescionService::from_config(CoalescionServiceConfig {
|
|
||||||
max_concurrent: Some(100),
|
|
||||||
queue_requests: true,
|
|
||||||
max_queue: None,
|
|
||||||
}),
|
|
||||||
|
|
||||||
// 1 day, 1k requests
|
// 1 day, 1k requests
|
||||||
categories: Arc::new(RwLock::new(LruCache::with_expiry_duration_and_capacity(
|
categories: Arc::new(RwLock::new(LruCache::with_expiry_duration_and_capacity(
|
||||||
Duration::from_secs(60 * 60 * 24),
|
Duration::from_secs(60 * 60 * 24),
|
||||||
1000,
|
1000,
|
||||||
))),
|
))),
|
||||||
categories_coalescion: CoalescionService::from_config(CoalescionServiceConfig {
|
|
||||||
max_concurrent: Some(100),
|
|
||||||
queue_requests: true,
|
|
||||||
max_queue: None,
|
|
||||||
}),
|
|
||||||
|
|
||||||
// 1 day, 1k requests
|
// 1 day, 1k requests
|
||||||
featured: Arc::new(RwLock::new(LruCache::with_expiry_duration_and_capacity(
|
featured: Arc::new(RwLock::new(LruCache::with_expiry_duration_and_capacity(
|
||||||
Duration::from_secs(60 * 60 * 24),
|
Duration::from_secs(60 * 60 * 24),
|
||||||
1000,
|
1000,
|
||||||
))),
|
))),
|
||||||
featured_coalescion: CoalescionService::from_config(CoalescionServiceConfig {
|
|
||||||
max_concurrent: Some(100),
|
|
||||||
queue_requests: true,
|
|
||||||
max_queue: None,
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +95,7 @@ impl Tenor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = self.cache_coalescion.execute(unique_key.clone(), || {
|
let res = self.coalescion.execute(unique_key.clone(), || {
|
||||||
let mut path = format!(
|
let mut path = format!(
|
||||||
"/search?key={}&q={}&client_key=Gifbox&media_filter=webm,tinywebm&locale={}&contentfilter=high&limit={limit}",
|
"/search?key={}&q={}&client_key=Gifbox&media_filter=webm,tinywebm&locale={}&contentfilter=high&limit={limit}",
|
||||||
&self.key,
|
&self.key,
|
||||||
@@ -127,7 +112,7 @@ impl Tenor {
|
|||||||
path.push_str("&component=categories");
|
path.push_str("&component=categories");
|
||||||
}
|
}
|
||||||
|
|
||||||
self.request(path)
|
self.request::<types::PaginatedMediaResponse>(path)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -152,7 +137,7 @@ impl Tenor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let res = self
|
let res = self
|
||||||
.categories_coalescion
|
.coalescion
|
||||||
.execute(unique_key.clone(), || {
|
.execute(unique_key.clone(), || {
|
||||||
let path = format!(
|
let path = format!(
|
||||||
"/categories?key={}&client_key=Gifbox&locale={}&contentfilter=high",
|
"/categories?key={}&client_key=Gifbox&locale={}&contentfilter=high",
|
||||||
@@ -160,7 +145,7 @@ impl Tenor {
|
|||||||
url_encode(locale),
|
url_encode(locale),
|
||||||
);
|
);
|
||||||
|
|
||||||
self.request(path)
|
self.request::<types::CategoriesResponse>(path)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -189,7 +174,7 @@ impl Tenor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = self.featured_coalescion.execute(unique_key.clone(), || {
|
let res = self.coalescion.execute(unique_key.clone(), || {
|
||||||
let mut path = format!(
|
let mut path = format!(
|
||||||
"/featured?key={}&client_key=Gifbox&media_filter=webm,tinywebm&locale={}&contentfilter=high&limit={limit}",
|
"/featured?key={}&client_key=Gifbox&media_filter=webm,tinywebm&locale={}&contentfilter=high&limit={limit}",
|
||||||
&self.key,
|
&self.key,
|
||||||
@@ -201,16 +186,13 @@ impl Tenor {
|
|||||||
path.push_str(position);
|
path.push_str(position);
|
||||||
};
|
};
|
||||||
|
|
||||||
self.request(path)
|
self.request::<types::PaginatedMediaResponse>(path)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
if let Ok(resp) = &*res {
|
if let Ok(resp) = &*res {
|
||||||
self.featured
|
self.featured.write().await.insert(unique_key, resp.clone());
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.insert(unique_key, resp.clone());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
(*res).clone()
|
(*res).clone()
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//! Tenor API models
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|||||||
Reference in New Issue
Block a user