///|
/// Several dependency types share one container through a sum type: the
/// exhaustive, type-safe stand-in for Python's dynamic `Any`.
enum Dep {
Cfg(String)
Conn(String)
Repo(String)
}
///|
/// The payload a dependency carries, for display.
fn Dep::show(self : Dep) -> String {
match self {
Cfg(s) => s
Conn(s) => s
Repo(s) => s
}
}
///|
/// The dependency-injection container — the explicit MoonBit equivalent of
/// FastAPI's `Depends`. `provide` registers a leaf; `provide_using` registers a
/// factory that resolves *sub-dependencies* through the request `Scope`; each is
/// built at most once per request (the per-request cache) and its `teardown`
/// runs LIFO when the scope closes. `Container::run` brackets a handler with a
/// fresh scope; `override_` / `clear_override` swap a dependency for a fake, the
/// way a test does.
///
/// moon run src/examples/09-dependency-injection
#coverage.skip
fn main raise {
let opens : Array[Int32] = [0]
let c : @moonapi.Container[Dep] = @moonapi.Container::new()
c.provide("config", () => Cfg("prod-dsn")) |> ignore
// A connection that resolves `config` through the scope, counts its opens, and
// registers a teardown — FastAPI's `yield` dependency.
c.provide_using(
"conn",
scope => {
opens[0] = opens[0] + 1
let dsn = match scope.get("config") {
Some(Cfg(s)) => s
_ => "?"
}
println(" [open] conn -> \{dsn}")
Conn(dsn)
},
teardown=(_v, _e) => println(" [close] conn"),
)
|> ignore
// A repository that depends on the connection.
c.provide_using("repo", scope => {
match scope.get("conn") {
Some(Conn(dsn)) => Repo("repo@" + dsn)
_ => Repo("repo@?")
}
})
|> ignore
// One request: resolve `repo` (pulls conn, pulls config), then resolve `conn`
// again — served from cache, so it opens exactly once; teardown fires on exit.
println("request A:")
let resp = c.run(scope => {
let repo = scope.get("repo").unwrap()
let _again = scope.get("conn").unwrap()
@moonapi.text(200, repo.show())
})
println(" -> \{resp.status} \{@utf8.decode_lossy(resp.body[:])}")
println(" conn opened \{opens[0]} time(s)")
// Override the config with a fake, as a test would; the fake flows through the
// sub-dependency chain into the repo.
println("request B (config overridden):")
c.override_("config", () => Cfg("test-dsn")) |> ignore
let resp2 = c.run(scope => {
@moonapi.text(200, scope.get("repo").unwrap().show())
})
println(" -> \{resp2.status} \{@utf8.decode_lossy(resp2.body[:])}")
c.clear_override("config")
// The explicit form `run` wraps: open a scope, resolve, close it by hand.
println("request C (manual scope):")
let scope = c.open_scope()
let cfg = scope.get("config").unwrap()
println(" resolved \{cfg.show()}")
scope.close()
// A key with no provider resolves to None.
let scope2 = c.open_scope()
println("unknown key resolved: \{scope2.get("missing") is Some(_)}")
scope2.close()
c.clear_overrides()
}