a3s-boot 0.1.3

Adapter-first modular Rust web framework for A3S inspired by Nest.js
Documentation
use super::cache::ProviderCacheKey;
use super::ProviderToken;
use crate::{BootError, Result};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;

#[derive(Clone)]
pub(crate) struct ProviderResolutionFrame {
    cache_key: ProviderCacheKey,
    token: ProviderToken,
    active: Arc<AtomicBool>,
}

impl ProviderResolutionFrame {
    fn is_active(&self) -> bool {
        self.active.load(Ordering::Acquire)
    }
}

pub(crate) type ProviderResolutionStack = Arc<Vec<ProviderResolutionFrame>>;

pub(crate) struct ProviderResolutionGuard {
    active: Arc<AtomicBool>,
}

impl Drop for ProviderResolutionGuard {
    fn drop(&mut self) {
        self.active.store(false, Ordering::Release);
    }
}

pub(crate) fn new_resolution_stack() -> ProviderResolutionStack {
    Arc::new(Vec::new())
}

pub(crate) fn resolution_stack_is_empty(resolution_stack: &ProviderResolutionStack) -> bool {
    !resolution_stack
        .iter()
        .any(ProviderResolutionFrame::is_active)
}

pub(crate) fn ensure_not_resolving(
    resolution_stack: &ProviderResolutionStack,
    cache_key: ProviderCacheKey,
    token: &ProviderToken,
) -> Result<()> {
    let active_frames = resolution_stack
        .iter()
        .filter(|frame| frame.is_active())
        .collect::<Vec<_>>();
    if let Some(index) = active_frames
        .iter()
        .position(|active| active.cache_key == cache_key)
    {
        let mut chain = active_frames[index..]
            .iter()
            .map(|frame| frame.token.to_string())
            .collect::<Vec<_>>();
        chain.push(token.to_string());
        return Err(BootError::Internal(format!(
            "cyclic provider dependency detected: {}",
            chain.join(" -> ")
        )));
    }

    Ok(())
}

pub(crate) fn enter_resolution_stack(
    resolution_stack: &ProviderResolutionStack,
    cache_key: ProviderCacheKey,
    token: &ProviderToken,
) -> Result<(ProviderResolutionStack, ProviderResolutionGuard)> {
    ensure_not_resolving(resolution_stack, cache_key, token)?;

    let active = Arc::new(AtomicBool::new(true));
    let mut frames = resolution_stack
        .iter()
        .filter(|frame| frame.is_active())
        .cloned()
        .collect::<Vec<_>>();
    frames.push(ProviderResolutionFrame {
        cache_key,
        token: token.clone(),
        active: Arc::clone(&active),
    });
    Ok((Arc::new(frames), ProviderResolutionGuard { active }))
}

pub(crate) fn resolution_chain_with(
    resolution_stack: &ProviderResolutionStack,
    token: &ProviderToken,
) -> String {
    let mut chain = resolution_stack
        .iter()
        .filter(|frame| frame.is_active())
        .map(|frame| frame.token.clone())
        .collect::<Vec<_>>();
    chain.push(token.clone());
    chain
        .iter()
        .map(ToString::to_string)
        .collect::<Vec<_>>()
        .join(" -> ")
}