use ruststream::memory::prelude::*;
use ruststream::schemars::JsonSchema;
use ruststream::testing::TestApp;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, JsonSchema)]
struct ChunkMeta {
task_id: u64,
chunk_no: u32,
chunks_total: u32,
}
#[derive(Serialize, Deserialize, JsonSchema)]
struct DoneMeta {
task_id: u64,
duration_ms: u64,
}
#[derive(Outgoing, Serialize, Deserialize, JsonSchema)]
#[outgoing(name = "chunks.done", headers = DoneMeta)]
struct ChunkDone {
output_key: String,
}
#[derive(Outgoing, Serialize, Deserialize, JsonSchema)]
#[outgoing(name = "chunks.progress")]
struct Progress {
percent: u8,
}
#[derive(OutSlot)]
#[publishes(ChunkDone, Progress)]
struct Events;
#[derive(Outgoing, Serialized)]
#[outgoing(headers = ChunkMeta)]
struct RawChunk(Vec<u8>);
#[derive(Deserialized)]
struct Chunk<'a>(&'a [u8]);
#[subscriber("chunks.raw")]
async fn convert(
chunk: &Chunk<'_>,
Headers(meta): Headers<ChunkMeta>,
Out(events): Out<impl Publisher, Events, (ChunkDone, Progress)>,
) -> HandlerOutcome {
let percent = u8::try_from(meta.chunk_no * 100 / meta.chunks_total.max(1)).unwrap_or(100);
if events
.message(&Progress { percent })
.publish()
.await
.is_err()
{
return HandlerOutcome::retry();
}
let done = ChunkDone {
output_key: format!("chunks/{}/{}.part", meta.task_id, meta.chunk_no),
};
let done_meta = DoneMeta {
task_id: meta.task_id,
duration_ms: chunk.0.len() as u64,
};
if events
.message(&done)
.with_headers(&done_meta)
.publish()
.await
.is_err()
{
return HandlerOutcome::retry();
}
HandlerOutcome::ack()
}
#[derive(Deserialize, JsonSchema)]
struct StatusRequest {
task_id: u64,
}
#[derive(Outgoing, Serialize, JsonSchema)]
#[outgoing(headers = DoneMeta)]
struct StatusReply {
done: bool,
}
#[subscriber("jobs.status-requests", publish("jobs.status"))]
async fn status(req: &StatusRequest) -> StatusReply {
StatusReply {
done: req.task_id.is_multiple_of(2),
}
}
#[subscriber("chunks.bulk")]
async fn bulk(reports: &[Message<ChunkMeta, Progress>]) -> HandlerOutcome {
for report in reports {
let meta = &report.headers;
println!(
"task {}: chunk {} of {} at {}%",
meta.task_id, meta.chunk_no, meta.chunks_total, report.body.percent,
);
}
HandlerOutcome::ack()
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let app = RustStream::new(AppInfo::new("transcoder", "0.1.0")).with_broker(
MemoryBroker::new(),
|b| {
b.include(convert).out(Events, Publish).build();
b.include(status);
b.include(bulk.batch(nonzero!(2)));
},
);
let tb = TestApp::start(app).await?;
let meta = ChunkMeta {
task_id: 7,
chunk_no: 3,
chunks_total: 12,
};
tb.broker::<MemoryBroker>()
.message(&RawChunk(vec![0; 16]))
.with_headers(&meta)
.to("chunks.raw")
.publish()
.await?;
let done = tb
.broker::<MemoryBroker>()
.published::<ChunkDone>("chunks.done")
.assert_called_once();
println!("published: {:?}", done.decoded()[0].output_key);
println!(
"task_id header: {:?}",
done.messages()[0].headers().get_str("task_id")
);
Ok(())
}