#[cfg(feature = "generator")]
use genawaiter::GeneratorState;
#[cfg(feature = "generator")]
use genawaiter::sync::{Co, Gen};
#[cfg(feature = "generator")]
use std::future::Future;
#[cfg(feature = "generator")]
use std::marker::PhantomData;
#[cfg(feature = "generator")]
use std::pin::Pin;
#[cfg(feature = "generator")]
pub struct DependencyGenerator<T, R> {
generator: Gen<T, (), Pin<Box<dyn Future<Output = R> + Send + 'static>>>,
_phantom: PhantomData<(T, R)>,
}
#[cfg(feature = "generator")]
impl<T, R> DependencyGenerator<T, R>
where
T: 'static,
R: 'static,
{
pub fn new<F>(producer: F) -> Self
where
F: FnOnce(Co<T>) -> Pin<Box<dyn Future<Output = R> + Send + 'static>> + Send + 'static,
{
Self {
generator: Gen::new(producer),
_phantom: PhantomData,
}
}
pub async fn next(&mut self) -> Option<T> {
match self.generator.async_resume().await {
GeneratorState::Yielded(value) => Some(value),
GeneratorState::Complete(_) => None,
}
}
pub async fn collect(mut self) -> Vec<T> {
let mut deps = Vec::new();
while let Some(dep) = self.next().await {
deps.push(dep);
}
deps
}
}
#[cfg(feature = "generator")]
pub struct DependencyStream<T> {
generator: Gen<T, (), Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,
peeked: Option<T>,
_phantom: PhantomData<T>,
}
#[cfg(feature = "generator")]
impl<T> DependencyStream<T>
where
T: 'static,
{
pub fn new<F>(producer: F) -> Self
where
F: FnOnce(Co<T>) -> Pin<Box<dyn Future<Output = ()> + Send + 'static>> + Send + 'static,
{
Self {
generator: Gen::new(producer),
peeked: None,
_phantom: PhantomData,
}
}
pub async fn next(&mut self) -> Option<T> {
if let Some(value) = self.peeked.take() {
return Some(value);
}
match self.generator.async_resume().await {
GeneratorState::Yielded(value) => Some(value),
GeneratorState::Complete(_) => None,
}
}
pub async fn is_empty(&mut self) -> bool {
if self.peeked.is_some() {
return false;
}
match self.generator.async_resume().await {
GeneratorState::Yielded(value) => {
self.peeked = Some(value);
false
}
GeneratorState::Complete(_) => true,
}
}
}
#[cfg(feature = "generator")]
pub struct RequestScopedGenerator<T> {
request_id: String,
stream: DependencyStream<T>,
}
#[cfg(feature = "generator")]
impl<T> RequestScopedGenerator<T>
where
T: 'static,
{
pub fn new(request_id: String, stream: DependencyStream<T>) -> Self {
Self { request_id, stream }
}
pub fn request_id(&self) -> &str {
&self.request_id
}
pub async fn resolve_next(&mut self) -> Option<T> {
self.stream.next().await
}
}
#[cfg(feature = "generator")]
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
#[rstest]
#[tokio::test]
async fn test_dependency_generator_basic() {
let mut generator = DependencyGenerator::new(|co| {
Box::pin(async move {
co.yield_(1).await;
co.yield_(2).await;
co.yield_(3).await;
})
});
assert_eq!(generator.next().await, Some(1));
assert_eq!(generator.next().await, Some(2));
assert_eq!(generator.next().await, Some(3));
assert_eq!(generator.next().await, None);
}
#[rstest]
#[tokio::test]
async fn test_dependency_generator_collect() {
let generator = DependencyGenerator::new(|co| {
Box::pin(async move {
co.yield_(1).await;
co.yield_(2).await;
co.yield_(3).await;
})
});
let deps = generator.collect().await;
assert_eq!(deps, vec![1, 2, 3]);
}
#[rstest]
#[tokio::test]
async fn test_dependency_stream() {
let mut stream = DependencyStream::new(|co| {
Box::pin(async move {
co.yield_("db".to_string()).await;
co.yield_("cache".to_string()).await;
})
});
assert_eq!(stream.next().await, Some("db".to_string()));
assert_eq!(stream.next().await, Some("cache".to_string()));
assert_eq!(stream.next().await, None);
}
#[rstest]
#[tokio::test]
async fn test_request_scoped_generator() {
let stream = DependencyStream::new(|co| {
Box::pin(async move {
co.yield_("dependency1".to_string()).await;
co.yield_("dependency2".to_string()).await;
})
});
let mut generator = RequestScopedGenerator::new("request-123".to_string(), stream);
assert_eq!(generator.request_id(), "request-123");
assert_eq!(
generator.resolve_next().await,
Some("dependency1".to_string())
);
assert_eq!(
generator.resolve_next().await,
Some("dependency2".to_string())
);
assert_eq!(generator.resolve_next().await, None);
}
#[rstest]
#[tokio::test]
async fn test_dependency_stream_is_empty_does_not_consume() {
let mut stream = DependencyStream::new(|co| {
Box::pin(async move {
co.yield_("first".to_string()).await;
co.yield_("second".to_string()).await;
})
});
let empty = stream.is_empty().await;
assert!(!empty);
assert_eq!(stream.next().await, Some("first".to_string()));
assert_eq!(stream.next().await, Some("second".to_string()));
assert_eq!(stream.next().await, None);
}
#[rstest]
#[tokio::test]
async fn test_dependency_stream_is_empty_on_exhausted_stream() {
let mut stream: DependencyStream<i32> =
DependencyStream::new(|_co| Box::pin(async move {}));
assert!(stream.is_empty().await);
}
#[rstest]
#[tokio::test]
async fn test_dependency_stream_is_empty_after_partial_consumption() {
let mut stream = DependencyStream::new(|co| {
Box::pin(async move {
co.yield_(1).await;
co.yield_(2).await;
})
});
assert_eq!(stream.next().await, Some(1));
assert!(!stream.is_empty().await);
assert_eq!(stream.next().await, Some(2));
assert!(stream.is_empty().await);
}
#[rstest]
#[tokio::test]
async fn test_dependency_stream_multiple_is_empty_calls() {
let mut stream = DependencyStream::new(|co| {
Box::pin(async move {
co.yield_(42).await;
})
});
assert!(!stream.is_empty().await);
assert!(!stream.is_empty().await);
assert!(!stream.is_empty().await);
assert_eq!(stream.next().await, Some(42));
assert_eq!(stream.next().await, None);
}
#[rstest]
#[tokio::test]
async fn test_async_operations_in_generator() {
let generator = DependencyGenerator::new(|co| {
Box::pin(async move {
co.yield_("database".to_string()).await;
co.yield_("cache".to_string()).await;
})
});
let deps = generator.collect().await;
assert_eq!(deps, vec!["database".to_string(), "cache".to_string()]);
}
#[rstest]
#[tokio::test]
async fn test_is_empty_does_not_drop_first_element_regression() {
let mut stream = DependencyStream::new(|co| {
Box::pin(async move {
co.yield_("a".to_string()).await;
co.yield_("b".to_string()).await;
co.yield_("c".to_string()).await;
})
});
let result = stream.is_empty().await;
assert!(
!result,
"Regression #453: stream with 3 elements must not be empty"
);
assert_eq!(
stream.next().await,
Some("a".to_string()),
"Regression #453: first element must not be dropped after is_empty()"
);
assert_eq!(stream.next().await, Some("b".to_string()));
assert_eq!(stream.next().await, Some("c".to_string()));
assert_eq!(stream.next().await, None);
}
#[rstest]
#[tokio::test]
async fn test_repeated_is_empty_buffers_single_value_regression() {
let mut stream = DependencyStream::new(|co| {
Box::pin(async move {
co.yield_(10_u32).await;
co.yield_(20_u32).await;
})
});
assert!(
!stream.is_empty().await,
"Regression #453: first is_empty() call should return false"
);
assert!(
!stream.is_empty().await,
"Regression #453: second is_empty() call should return false (peeked slot not overwritten)"
);
assert!(
!stream.is_empty().await,
"Regression #453: third is_empty() call should return false"
);
assert_eq!(
stream.next().await,
Some(10_u32),
"Regression #453: element 10 must survive three is_empty() calls"
);
assert_eq!(stream.next().await, Some(20_u32));
assert_eq!(stream.next().await, None);
}
}