Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Module resolution

Lazy dependency resolution via SeedPipeline. A grow function resolves dependency references (seeds) into modules (nodes), which may themselves have dependencies. Error handling uses Either<Error, Valid> — error nodes are leaves with no children.

See Seed-based lazy discovery for the SeedPipeline API and its internal mechanics.

#![allow(unused)]
fn main() {
//! Minified module resolution — the pattern that motivated hylic.
//! Demonstrates: SeedPipeline for lazy dependency discovery,
//! error handling via Either, and seeds_for_fallible.

#[cfg(test)]
mod tests {
    use std::collections::HashMap;
    use either::Either;

    use hylic_pipeline::prelude::*;
    use insta::assert_snapshot;


    /// A module has a name and declares dependencies on other modules.
    #[derive(Clone, Debug)]
    struct Module {
        name: String,
        deps: Vec<String>,
    }

    /// A module registry: maps names to module definitions.
    struct Registry(HashMap<String, Module>);

    impl Registry {
        fn new(modules: &[(&str, &[&str])]) -> Self {
            Registry(modules.iter().map(|(name, deps)| {
                (name.to_string(), Module {
                    name: name.to_string(),
                    deps: deps.iter().map(|s| s.to_string()).collect(),
                })
            }).collect())
        }
    }

    /// Error when a module can't be found.
    #[derive(Clone, Debug)]
    struct ResolveError(String);

    /// Resolution result: either an error or a list of resolved module names.
    #[derive(Clone, Debug)]
    struct Resolved {
        modules: Vec<String>,
        errors: Vec<String>,
    }

    #[test]
    fn resolve_modules() {
        let registry = Registry::new(&[
            ("app",    &["logging", "config", "ghost"]),
            ("logging", &["utils"]),
            ("config", &["utils"]),
            ("utils",  &[]),
            // "ghost" is not in the registry — will produce an error
        ]);

        // Node = Either<ResolveError, Module>. `seeds_for_fallible` adapts a
        // valid-side edge function so errors produce no seeds.
        let seeds_from_node: Edgy<Either<ResolveError, Module>, String> =
            seeds_for_fallible(edgy(move |module: &Module| module.deps.clone()));

        // grow: dependency name → Either<Error, Module>.
        let grow = {
            let reg = registry;
            move |dep_name: &String| -> Either<ResolveError, Module> {
                match reg.0.get(dep_name) {
                    Some(m) => Either::Right(m.clone()),
                    None    => Either::Left(ResolveError(format!("not found: {}", dep_name))),
                }
            }
        };

        let collect: Fold<Either<ResolveError, Module>, Resolved, Resolved> = fold(
            |node: &Either<ResolveError, Module>| match node {
                Either::Right(m) => Resolved { modules: vec![m.name.clone()], errors: vec![] },
                Either::Left(e)  => Resolved { modules: vec![], errors: vec![e.0.clone()] },
            },
            |heap: &mut Resolved, child: &Resolved| {
                heap.modules.extend(child.modules.iter().cloned());
                heap.errors.extend(child.errors.iter().cloned());
            },
            |h: &Resolved| h.clone(),
        );

        let pipeline: SeedPipeline<Shared, Either<ResolveError, Module>, String, Resolved, Resolved> =
            SeedPipeline::new(grow, seeds_from_node, &collect);

        let result: Resolved = pipeline.run_from_slice(
            &FUSED,
            &["app".to_string()],
            Resolved { modules: vec![], errors: vec![] },
        );

        assert!(result.modules.contains(&"utils".to_string()));
        assert!(result.modules.contains(&"app".to_string()));
        assert!(result.errors.contains(&"not found: ghost".to_string()));

        assert_snapshot!("resolution", format!(
            "resolved: [{}], errors: [{}]",
            result.modules.join(", "),
            result.errors.join(", "),
        ));
    }
}
}

Output:

resolved: [app, logging, utils, config, utils], errors: [not found: ghost]