chassis 0.2.0

Compile-time dependency injection framework
Documentation
use std::any::{Any, TypeId};
use std::marker::PhantomData;

use crate::injector::{DependenciesAccess, DependenciesVisitor, Injector, IntrospectVisitor};
use crate::{AssembleError, TypeInfo};

pub fn check_factory<T: Any, F: Injector>() -> Vec<AssembleError> {
    let mut checker = IntegrityChecker::<F>::new();
    let mut visitor = CheckImplementations::new(&mut checker);
    F::introspect::<T>(&mut visitor);
    visitor.finish::<T>();
    checker.into_errors()
}

struct IntegrityChecker<F: Injector> {
    _introspect: PhantomData<F>,
    stack: Vec<TypeId>,
    errors: Vec<AssembleError>,
}

impl<F: Injector> IntegrityChecker<F> {
    fn new() -> Self {
        Self {
            _introspect: PhantomData,
            stack: vec![],
            errors: vec![],
        }
    }

    fn add_error(&mut self, err: AssembleError) {
        self.errors.push(err);
    }

    fn into_errors(self) -> Vec<AssembleError> {
        self.errors
    }
}

struct CheckDeps<'a, F: Injector> {
    checker: &'a mut IntegrityChecker<F>,
}

impl<'a, F: Injector> DependenciesVisitor for CheckDeps<'a, F> {
    fn dependency<T: Any>(&mut self) {
        let type_id = TypeId::of::<T>();

        if self.checker.stack.contains(&type_id) {
            self.checker.add_error(AssembleError::CyclicDependency {
                ty: TypeInfo::of::<T>(),
            });
            return;
        }

        self.checker.stack.push(type_id);

        let mut visitor = CheckImplementations::new(self.checker);
        F::introspect::<T>(&mut visitor);
        visitor.finish::<T>();

        self.checker.stack.pop();
    }
}

enum Implementations {
    Single,
    Multiple,
}

struct CheckImplementations<'a, F: Injector> {
    implementations: Option<Implementations>,
    checker: &'a mut IntegrityChecker<F>,
}

impl<'a, F: Injector> CheckImplementations<'a, F> {
    fn new(checker: &'a mut IntegrityChecker<F>) -> Self {
        Self {
            implementations: None,
            checker,
        }
    }

    fn finish<T: Any>(self) {
        match self.implementations {
            None => self
                .checker
                .add_error(AssembleError::MissingImplementation {
                    ty: TypeInfo::of::<T>(),
                }),
            Some(Implementations::Multiple) => {
                self.checker
                    .add_error(AssembleError::DuplicateImplementation {
                        ty: TypeInfo::of::<T>(),
                    })
            }
            Some(Implementations::Single) => (),
        }
    }
}

impl<'a, F: Injector> IntrospectVisitor for CheckImplementations<'a, F> {
    fn item<M: Any, T: Any, A: DependenciesAccess>(&mut self, deps: A) {
        match &mut self.implementations {
            None => self.implementations = Some(Implementations::Single),
            Some(Implementations::Single) => self.implementations = Some(Implementations::Multiple),
            Some(Implementations::Multiple) => {
                self.implementations = Some(Implementations::Multiple)
            }
        }
        deps.next(&mut CheckDeps {
            checker: self.checker,
        });
    }
}