// ASGI 2.0 legacy double-callable applications (main spec, "Legacy Applications").
// The current spec is single-callable — application(scope, receive, send) — but a 3.0
// server is encouraged to keep running legacy 2.0 apps for backward compatibility.
// A legacy app's first callable is synchronous and takes the scope, returning the
// asynchronous instance that then drives the connection. MoonBit has no signature or
// attribute reflection to auto-detect the convention, so the two conventions are
// distinct types and the choice is an explicit constructor (AsgiApplication).
///|
/// The asynchronous instance a legacy app's first callable returns: already bound to
/// the scope, it drives the connection with `receive` / `send`.
pub type AsgiAppInstance = async (Receive, Send) -> Unit
///|
/// A legacy ASGI 2.0 double-callable application: a synchronous `application(scope)`
/// that returns the `AsgiAppInstance`.
pub type LegacyAsgiApp = (Scope) -> AsgiAppInstance
///|
/// An application in either calling convention — the current 3.0 single-callable form
/// or the legacy 2.0 double-callable form. A server holds one of these and normalizes
/// it with `guarantee_single_callable`; the enum is the typed, reflection-free way
/// to name which convention an application is written in.
pub(all) enum AsgiApplication {
Single(AsgiApp)
Legacy(LegacyAsgiApp)
}
///|
/// Wrap a legacy 2.0 double-callable app as a 3.0 single-callable `AsgiApp`: call the
/// first callable with the scope to get the instance, then drive it with `receive` /
/// `send`.
pub fn double_to_single_callable(app : LegacyAsgiApp) -> AsgiApp {
(scope, receive, send) => {
let instance = app(scope)
instance(receive, send)
}
}
///|
/// Normalize any application to a single-callable `AsgiApp`: a 3.0 app passes
/// straight through, and a legacy 2.0 app is wrapped. A server calls this once and
/// then only ever drives single-callables.
pub fn guarantee_single_callable(app : AsgiApplication) -> AsgiApp {
match app {
Single(a) => a
Legacy(l) => double_to_single_callable(l)
}
}
///|
/// The synchronous core of the legacy two-call convention, testable without an async
/// runtime the way `run_http` is for `to_asgi`: the first callable takes the scope and
/// returns an instance that folds the inbound event stream into the outbound one.
/// `double_to_single_callable` is the async lift of exactly this two-step shape, so a
/// green `run_legacy` covers the convention's semantics that the async wrapper relies
/// on.
pub fn run_legacy(
app : (Scope) -> (Array[Event]) -> Array[Event],
scope : Scope,
inbound : Array[Event],
) -> Array[Event] {
let instance = app(scope)
instance(inbound)
}