use super::concurrency_limit::ConcurrencyLimitLayer;
use super::load_shed::LoadShedLayer;
use super::rate_limit::RateLimitLayer;
use super::retry::RetryLayer;
use super::timeout::TimeoutLayer;
use super::{Identity, Layer, Stack};
use std::sync::Arc;
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct ServiceBuilder<L> {
layer: L,
}
impl ServiceBuilder<Identity> {
#[must_use]
pub fn new() -> Self {
Self { layer: Identity }
}
}
impl Default for ServiceBuilder<Identity> {
fn default() -> Self {
Self::new()
}
}
impl<L> ServiceBuilder<L> {
#[must_use]
pub fn layer<T>(self, layer: T) -> ServiceBuilder<Stack<L, T>> {
ServiceBuilder {
layer: Stack::new(self.layer, layer),
}
}
#[must_use]
pub fn service<S>(self, service: S) -> L::Service
where
L: Layer<S>,
{
self.layer.layer(service)
}
#[must_use]
pub fn layer_ref(&self) -> &L {
&self.layer
}
#[must_use]
pub fn timeout(self, timeout: Duration) -> ServiceBuilder<Stack<L, TimeoutLayer>> {
self.layer(TimeoutLayer::new(timeout))
}
#[must_use]
pub fn load_shed(self) -> ServiceBuilder<Stack<L, LoadShedLayer>> {
self.layer(LoadShedLayer::new())
}
#[must_use]
pub fn concurrency_limit(self, max: usize) -> ServiceBuilder<Stack<L, ConcurrencyLimitLayer>> {
self.layer(ConcurrencyLimitLayer::new(max))
}
#[must_use]
pub fn concurrency_limit_with_semaphore(
self,
semaphore: Arc<crate::sync::Semaphore>,
) -> ServiceBuilder<Stack<L, ConcurrencyLimitLayer>> {
self.layer(ConcurrencyLimitLayer::with_semaphore(semaphore))
}
#[must_use]
pub fn rate_limit(
self,
rate: u64,
period: Duration,
) -> ServiceBuilder<Stack<L, RateLimitLayer>> {
self.layer(RateLimitLayer::new(rate, period))
}
#[must_use]
pub fn retry<P>(self, policy: P) -> ServiceBuilder<Stack<L, RetryLayer<P>>>
where
P: Clone,
{
self.layer(RetryLayer::new(policy))
}
}
#[cfg(test)]
mod tests {
#![allow(
clippy::pedantic,
clippy::nursery,
clippy::expect_fun_call,
clippy::map_unwrap_or,
clippy::cast_possible_wrap,
clippy::future_not_send
)]
use super::*;
use crate::service::{Identity, Service, Stack};
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
#[derive(Debug, Clone)]
struct Echo;
impl Service<String> for Echo {
type Response = String;
type Error = std::convert::Infallible;
type Future = Pin<Box<dyn Future<Output = Result<String, Self::Error>> + Send>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: String) -> Self::Future {
Box::pin(async move { Ok(req) })
}
}
#[test]
fn test_new_creates_identity_builder() {
let builder = ServiceBuilder::new();
let _: &Identity = builder.layer_ref();
}
#[test]
fn test_default_same_as_new() {
let _builder: ServiceBuilder<Identity> = ServiceBuilder::default();
}
#[test]
fn test_service_with_identity_returns_inner() {
let mut svc = ServiceBuilder::new().service(Echo);
let fut = svc.call("hello".to_string());
drop(fut);
}
#[test]
fn test_layer_adds_stack() {
let builder = ServiceBuilder::new().layer(Identity);
let _: &Stack<Identity, Identity> = builder.layer_ref();
}
#[test]
fn test_timeout_convenience() {
let builder = ServiceBuilder::new().timeout(Duration::from_secs(5));
let _ = builder.layer_ref();
}
#[test]
fn test_load_shed_convenience() {
let builder = ServiceBuilder::new().load_shed();
let _ = builder.layer_ref();
}
#[test]
fn test_concurrency_limit_convenience() {
let builder = ServiceBuilder::new().concurrency_limit(10);
let _ = builder.layer_ref();
}
#[test]
fn test_concurrency_limit_with_semaphore() {
let sem = Arc::new(crate::sync::Semaphore::new(5));
let builder = ServiceBuilder::new().concurrency_limit_with_semaphore(sem);
let _ = builder.layer_ref();
}
#[test]
fn test_rate_limit_convenience() {
let builder = ServiceBuilder::new().rate_limit(100, Duration::from_secs(1));
let _ = builder.layer_ref();
}
#[test]
fn test_retry_convenience() {
use crate::service::retry::LimitedRetry;
let builder = ServiceBuilder::new().retry(LimitedRetry::<String>::new(3));
let _ = builder.layer_ref();
}
#[test]
fn test_chaining_multiple_layers() {
let builder = ServiceBuilder::new()
.timeout(Duration::from_secs(30))
.concurrency_limit(50)
.load_shed()
.rate_limit(1000, Duration::from_secs(1));
let _ = builder.layer_ref();
}
#[test]
fn test_builder_is_clone() {
fn assert_clone<T: Clone>(_value: &T) {}
let builder = ServiceBuilder::new().timeout(Duration::from_secs(1));
assert_clone(&builder);
let clone = builder.clone();
let _ = builder.layer_ref();
let _ = clone.layer_ref();
}
#[test]
fn test_builder_is_debug() {
let builder = ServiceBuilder::new();
let debug = format!("{builder:?}");
assert!(debug.contains("ServiceBuilder"));
}
#[test]
fn test_concurrency_limit_zero() {
let builder = ServiceBuilder::new().concurrency_limit(0);
let _ = builder.layer_ref();
}
#[test]
fn test_rate_limit_zero_rate() {
let builder = ServiceBuilder::new().rate_limit(0, Duration::from_secs(1));
let _ = builder.layer_ref();
}
#[test]
fn test_timeout_zero_duration() {
let builder = ServiceBuilder::new().timeout(Duration::ZERO);
let _ = builder.layer_ref();
}
#[test]
fn test_retry_with_no_retry_policy() {
use crate::service::retry::NoRetry;
let builder = ServiceBuilder::new().retry(NoRetry);
let _ = builder.layer_ref();
}
}