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]