use std::cell::RefCell;
use std::collections::BTreeMap;
use std::future::Future;
use serde::de::DeserializeOwned;
use serde_json::Value;
use super::cache::loader_cache;
use super::load::LoadValue;
use super::load::LoaderError;
use super::registry::dispatch_load;
use super::request::FlowRequest;
use super::stream_load::is_stream_loader;
#[derive(Default)]
struct FlowRuntime {
request: Option<FlowRequest>,
loads: BTreeMap<String, Value>,
load_errors: BTreeMap<String, LoaderError>,
deferred_loaders: Vec<String>,
deferred_streaming: bool,
}
#[derive(Debug, Clone)]
pub struct DeferredStreamPlan {
pub deferred: Vec<String>,
pub request: FlowRequest,
}
tokio::task_local! {
static FLOW: RefCell<Option<FlowRuntime>>;
static DEFERRED_PLAN: RefCell<Option<DeferredStreamPlan>>;
}
thread_local! {
static FALLBACK_FLOW: RefCell<Option<FlowRuntime>> = const { RefCell::new(None) };
static FALLBACK_DEFERRED_PLAN: RefCell<Option<DeferredStreamPlan>> =
const { RefCell::new(None) };
}
fn with_flow<R>(f: impl FnOnce(&RefCell<Option<FlowRuntime>>) -> R) -> R {
let mut f = Some(f);
match FLOW.try_with(|cell| (f.take().expect("flow fn"))(cell)) {
Ok(out) => out,
Err(_) => FALLBACK_FLOW.with(|cell| (f.take().expect("flow fn"))(cell)),
}
}
fn with_deferred_plan<R>(f: impl FnOnce(&RefCell<Option<DeferredStreamPlan>>) -> R) -> R {
let mut f = Some(f);
match DEFERRED_PLAN.try_with(|cell| (f.take().expect("deferred fn"))(cell)) {
Ok(out) => out,
Err(_) => FALLBACK_DEFERRED_PLAN.with(|cell| (f.take().expect("deferred fn"))(cell)),
}
}
pub async fn scope_flow_runtime<F: Future>(fut: F) -> F::Output {
FLOW.scope(RefCell::new(None), async {
DEFERRED_PLAN.scope(RefCell::new(None), fut).await
})
.await
}
pub fn set_deferred_streaming(enabled: bool) {
with_flow(|cell| {
if let Some(rt) = cell.borrow_mut().as_mut() {
rt.deferred_streaming = enabled;
}
});
}
pub fn stage_deferred_stream_plan(deferred: Vec<String>, request: FlowRequest) {
if deferred.is_empty() {
return;
}
with_deferred_plan(|cell| {
*cell.borrow_mut() = Some(DeferredStreamPlan { deferred, request });
});
}
pub fn take_deferred_stream_plan() -> Option<DeferredStreamPlan> {
with_deferred_plan(|cell| cell.borrow_mut().take())
}
pub fn with_request<R>(req: FlowRequest, f: impl FnOnce() -> R) -> (R, FlowRequest) {
with_flow(|cell| {
let prev = cell.borrow_mut().replace(FlowRuntime {
request: Some(req.clone()),
loads: BTreeMap::new(),
load_errors: BTreeMap::new(),
deferred_loaders: Vec::new(),
deferred_streaming: false,
});
let out = f();
let final_req = cell
.borrow()
.as_ref()
.and_then(|rt| rt.request.clone())
.unwrap_or(req);
*cell.borrow_mut() = prev;
(out, final_req)
})
}
pub fn with_request_deferred<R>(
req: FlowRequest,
deferred_streaming: bool,
f: impl FnOnce() -> R,
) -> (R, FlowRequest, Vec<String>) {
with_flow(|cell| {
let prev = cell.borrow_mut().replace(FlowRuntime {
request: Some(req.clone()),
loads: BTreeMap::new(),
load_errors: BTreeMap::new(),
deferred_loaders: Vec::new(),
deferred_streaming,
});
let out = f();
let (final_req, deferred) = {
let borrow = cell.borrow();
let rt = borrow.as_ref().expect("FlowRuntime missing");
(
rt.request.clone().unwrap_or(req),
rt.deferred_loaders.clone(),
)
};
*cell.borrow_mut() = prev;
(out, final_req, deferred)
})
}
pub fn use_load<T: DeserializeOwned>(name: &str) -> LoadValue<T> {
match try_use_load(name) {
Ok(v) => LoadValue::Ok(v),
Err(e) => LoadValue::Err(e),
}
}
fn loader_no_runtime(name: &str) -> LoaderError {
LoaderError::new(
500,
format!("loader `{name}` requires FlowRequest — wrap render in with_request()"),
)
}
pub fn try_use_load<T: DeserializeOwned>(name: &str) -> Result<T, LoaderError> {
enum Prepared {
NoRuntime,
Err(LoaderError),
Cached(Value),
Fetch(FlowRequest),
}
let prepared = with_flow(|cell| {
let borrow = cell.borrow();
let Some(rt) = borrow.as_ref() else {
return Prepared::NoRuntime;
};
if let Some(err) = rt.load_errors.get(name) {
return Prepared::Err(err.clone());
}
if let Some(cached) = rt.loads.get(name) {
return Prepared::Cached(cached.clone());
}
let Some(req) = rt.request.clone() else {
return Prepared::NoRuntime;
};
Prepared::Fetch(req)
});
let req = match prepared {
Prepared::NoRuntime => return Err(loader_no_runtime(name)),
Prepared::Err(err) => return Err(err),
Prepared::Cached(cached) => {
return serde_json::from_value(cached)
.map_err(|e| LoaderError::new(500, format!("decode `{name}`: {e}")));
}
Prepared::Fetch(req) => req,
};
let value = tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(dispatch_load(name, req))
});
with_flow(|cell| {
let mut borrow = cell.borrow_mut();
let Some(rt) = borrow.as_mut() else {
return Err(loader_no_runtime(name));
};
match value {
Ok(v) => {
rt.loads.insert(name.to_string(), v.clone());
if let Some(cache) = loader_cache(name) {
if let Some(req) = rt.request.as_mut() {
req.cache_control.insert(name.to_string(), cache);
}
}
serde_json::from_value(v)
.map_err(|e| LoaderError::new(500, format!("decode `{name}`: {e}")))
}
Err(err) => {
let loader_err = LoaderError::new(500, err.to_string());
rt.load_errors.insert(name.to_string(), loader_err.clone());
Err(loader_err)
}
}
})
}
pub fn try_use_load_value<T: DeserializeOwned>(name: &str) -> LoadValue<T> {
let pending = with_flow(|cell| {
let mut borrow = cell.borrow_mut();
match borrow.as_mut() {
None => None,
Some(rt) => {
if rt.deferred_streaming && is_stream_loader(name) {
if !rt.deferred_loaders.iter().any(|n| n == name) {
rt.deferred_loaders.push(name.to_string());
}
Some(true)
} else {
Some(false)
}
}
}
});
let Some(pending) = pending else {
return LoadValue::Err(loader_no_runtime(name));
};
if pending {
return LoadValue::Pending;
}
match try_use_load(name) {
Ok(value) => LoadValue::Ok(value),
Err(err) => LoadValue::Err(err),
}
}
pub fn current_request() -> Option<FlowRequest> {
with_flow(|cell| cell.borrow().as_ref()?.request.clone())
}
pub fn first_load_error() -> Option<LoaderError> {
with_flow(|cell| cell.borrow().as_ref()?.load_errors.values().next().cloned())
}
pub fn set_load_cache(name: &str, value: impl Into<String>) {
with_flow(|cell| {
if let Some(rt) = cell.borrow_mut().as_mut() {
if let Some(req) = rt.request.as_mut() {
req.cache_control.insert(name.to_string(), value.into());
}
}
});
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn try_use_load_without_runtime_returns_err() {
let err = try_use_load::<serde_json::Value>("missing").unwrap_err();
assert!(err.message.contains("with_request"));
}
#[test]
fn try_use_load_value_without_runtime_returns_err() {
match try_use_load_value::<serde_json::Value>("missing") {
LoadValue::Err(e) => assert!(e.message.contains("with_request")),
other => panic!("expected Err, got {other:?}"),
}
}
}