use core::convert::Infallible;
use crate::context::{Cons, Context, Nil};
use crate::kernel::Effect;
use crate::schema::equal::EffectHash;
pub use crate::context::Tagged;
pub type Service<K, V> = Tagged<K, V>;
#[inline]
pub fn service<K: EffectHash, V>(value: V) -> Service<K, V> {
Service::<K, V>::new(value)
}
pub type ServiceEnv<K, V> = Context<Cons<Service<K, V>, Nil>>;
#[inline]
pub fn service_env<K: EffectHash, V>(v: V) -> ServiceEnv<K, V> {
Context::new(Cons(service::<K, V>(v), Nil))
}
#[inline]
pub fn layer_service_env<K: EffectHash, V: Clone>(
v: V,
) -> crate::layer::LayerFn<impl Fn() -> Result<ServiceEnv<K, V>, Infallible>> {
crate::layer::LayerFn(move || Ok(service_env::<K, V>(v.clone())))
}
#[inline]
pub fn layer_service<K: EffectHash, V: Clone>(
value: V,
) -> crate::layer::LayerFn<impl Fn() -> Result<Service<K, V>, Infallible>> {
crate::layer::LayerFn(move || Ok(service::<K, V>(value.clone())))
}
#[inline]
#[allow(clippy::type_complexity)]
pub fn provide_service<K: EffectHash, V, A, E, Tail>(
effect: Effect<A, E, Context<Cons<Service<K, V>, Tail>>>,
value: V,
) -> Effect<A, E, Context<Tail>>
where
A: 'static,
E: 'static,
V: Clone + 'static,
Tail: Clone + 'static,
{
effect.provide_head(value)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::context::{Cons, Context, Nil};
use crate::layer::Layer;
use crate::schema::data::EffectData;
use crate::schema::equal::{EffectHash, equals};
use rstest::rstest;
use std::collections::HashSet;
crate::service_key!(struct PortKey);
crate::service_def!(struct HttpKey as HttpService => u16);
mod constructors {
use super::*;
#[rstest]
#[case::default_port(8080u16)]
#[case::alternate_port(3000u16)]
fn layer_service_with_clonable_value_builds_tagged_cell(#[case] port: u16) {
let layer = layer_service::<PortKey, _>(port);
let cell = layer.build().expect("layer service should build");
assert_eq!(cell.value, port);
}
#[test]
fn service_def_macro_defines_key_and_service_alias() {
let svc: HttpService = service::<HttpKey, _>(8080u16);
assert_eq!(svc.value, 8080u16);
}
}
mod environment_access {
use super::*;
#[test]
fn context_get_with_service_cell_resolves_service_value() {
let ctx = Context::new(Cons(Service::<PortKey, _>::new(9u8), Nil));
assert_eq!(*ctx.get::<PortKey>(), 9);
}
#[test]
fn service_env_with_value_matches_manual_context_layout() {
let a = service_env::<PortKey, _>(7u8);
let b = Context::new(Cons(Service::<PortKey, _>::new(7u8), Nil));
assert_eq!(*a.get::<PortKey>(), *b.get::<PortKey>());
}
#[test]
fn layer_service_env_with_value_builds_context_with_single_service_cell() {
let layer = layer_service_env::<PortKey, _>(77u8);
let env = layer.build().expect("service env layer should build");
assert_eq!(*env.get::<PortKey>(), 77u8);
}
}
mod providing {
use super::*;
#[test]
fn provide_service_helper_with_effect_matches_method_semantics() {
let effect = Effect::new(|ctx: &mut Context<Cons<Service<PortKey, u8>, Nil>>| {
Ok::<u8, ()>(*ctx.get::<PortKey>())
});
let provided = provide_service(effect, 42u8);
let out = crate::runtime::run_blocking(provided, Context::new(Nil));
assert_eq!(out, Ok(42));
}
}
mod brand_equal_and_hashset {
use super::*;
fn assert_key_bounds<K: EffectHash + Eq + Copy>() {}
#[test]
fn service_key_struct_eq_by_value() {
assert_key_bounds::<PortKey>();
let a = PortKey;
let b = PortKey;
assert!(equals(&a, &b));
assert_eq!(a, b);
assert_eq!(EffectHash::effect_hash(&a), EffectHash::effect_hash(&b));
}
#[test]
fn two_distinct_service_keys_not_equal() {
let x = service::<PortKey, _>(1u8);
let y = service::<PortKey, _>(2u8);
assert_ne!(x, y);
assert!(!equals(&x, &y));
}
#[test]
fn hashset_of_service_keys_deduplicates() {
let mut set = HashSet::new();
set.insert(service::<PortKey, _>(9u8));
set.insert(service::<PortKey, _>(9u8));
assert_eq!(set.len(), 1);
assert!(set.contains(&service::<PortKey, _>(9u8)));
}
#[test]
fn service_cell_is_effect_data_when_payload_is_effect_data() {
fn assert_effect_data<T: EffectData>() {}
assert_effect_data::<Service<PortKey, u16>>();
}
}
}