use std::any::type_name;
use std::convert::Infallible;
use std::future::{Future, ready};
use ruststream::memory::prelude::*;
use ruststream::runtime::{ContainsMessage, Reads, SlotPos};
use ruststream::schemars::{JsonSchema, schema_for};
use ruststream::testing::TestApp;
use serde::{Deserialize, Serialize};
fn schema_of<T: JsonSchema>() -> String {
schema_for!(T).as_value().to_string()
}
#[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(Serialize, Deserialize, JsonSchema)]
struct ChunkDone {
output_key: String,
}
impl OutgoingDestination for ChunkDone {
type Form = FixedName;
const DESTINATION: &'static str = "chunks.done";
}
impl MessageHeaders for ChunkDone {
type Contract = WithHeaders<DoneMeta>;
}
impl MessageInfo for ChunkDone {
const NAME: &'static str = "ChunkDone";
}
impl ContainsMessage<Self, SlotPos<0>> for ChunkDone {}
impl<M: OutSlot> OutMessages<M> for ChunkDone {
fn outgoing() -> Vec<OutgoingMessageMetadata> {
vec![
OutgoingMessageMetadata::new(Self::DESTINATION, type_name::<Self>())
.with_message_name(Some(Self::NAME))
.with_payload_schema(Some(schema_of::<Self>()))
.with_headers_schema(Some(schema_of::<DoneMeta>())),
]
}
}
#[derive(Serialize, Deserialize, JsonSchema)]
struct Progress {
percent: u8,
}
impl OutgoingDestination for Progress {
type Form = FixedName;
const DESTINATION: &'static str = "chunks.progress";
}
impl MessageHeaders for Progress {
type Contract = NoHeaders;
}
impl MessageInfo for Progress {
const NAME: &'static str = "Progress";
}
impl ContainsMessage<Self, SlotPos<0>> for Progress {}
impl<M: OutSlot> OutMessages<M> for Progress {
fn outgoing() -> Vec<OutgoingMessageMetadata> {
vec![
OutgoingMessageMetadata::new(Self::DESTINATION, type_name::<Self>())
.with_message_name(Some(Self::NAME))
.with_payload_schema(Some(schema_of::<Self>())),
]
}
}
struct Events;
impl OutSlot for Events {
const NAME: &'static str = "Events";
type Destination = Reads;
fn outgoing() -> Vec<OutgoingMessageMetadata> {
let mut entries = <ChunkDone as OutMessages<Self>>::outgoing();
entries.extend(<Progress as OutMessages<Self>>::outgoing());
entries
}
}
impl PublishedThrough<Events> for ChunkDone {}
impl PublishedThrough<Events> for Progress {}
struct RawChunk(Vec<u8>);
impl Serialized for RawChunk {
type Error = Infallible;
fn wire_bytes(&self, _buf: &mut BytesMut) -> Result<WireBytes<'_>, Infallible> {
Ok(WireBytes::Own(&self.0))
}
}
impl MessageWire for RawChunk {
type Wire = SerializedWire;
}
impl OutgoingDestination for RawChunk {
type Form = CallerName;
}
impl MessageHeaders for RawChunk {
type Contract = WithHeaders<ChunkMeta>;
}
struct Chunk<'a>(&'a [u8]);
impl Deserialized for Chunk<'_> {
type Output<'a> = Chunk<'a>;
type Error = Infallible;
fn from_payload(payload: &[u8]) -> Result<Chunk<'_>, Self::Error> {
Ok(Chunk(payload))
}
}
impl Input for Chunk<'_> {
type Axis = SoloDeserialized<Chunk<'static>>;
}
#[derive(Clone, Copy)]
struct Convert;
impl<'p, E> Handle<Chunk<'p>, (), Outs<(E,)>> for Convert
where
E: OutEntry<Events, Wire: Publisher>,
{
async fn handle(
&self,
chunk: &Chunk<'p>,
outs: &Outs<(E,)>,
ctx: &mut Context<'_>,
) -> Result<(), HandlerOutcome> {
let policy = ctx.decode_policy();
let Headers(meta) = Headers::<ChunkMeta>::extract(&mut *ctx, policy)?;
let events = outs.get(Events);
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 Err(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 Err(HandlerOutcome::retry());
}
Ok(())
}
}
#[derive(Deserialize, JsonSchema)]
struct StatusRequest {
task_id: u64,
}
#[derive(Serialize, JsonSchema)]
struct StatusReply {
done: bool,
}
impl OutgoingDestination for StatusReply {
type Form = CallerName;
}
impl MessageHeaders for StatusReply {
type Contract = WithHeaders<DoneMeta>;
}
impl MessageInfo for StatusReply {
const NAME: &'static str = "StatusReply";
}
struct Status;
impl Handle<StatusRequest, StatusReply> for Status {
fn handle(
&self,
req: &StatusRequest,
_outs: &(),
_ctx: &mut Context<'_>,
) -> impl Future<Output = Result<StatusReply, HandlerOutcome>> {
ready(Ok(StatusReply {
done: req.task_id.is_multiple_of(2),
}))
}
}
struct Bulk;
impl Handle<[Message<ChunkMeta, Progress>]> for Bulk {
fn handle(
&self,
reports: &[Message<ChunkMeta, Progress>],
_outs: &(),
_ctx: &mut Context<'_>,
) -> impl Future<Output = Result<(), Vec<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,
);
}
ready(Ok(()))
}
}
#[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(subscriber("chunks.raw", Convert).build())
.out(Events, Publish)
.build();
b.include(
subscriber("jobs.status-requests", Status)
.reply()
.to("jobs.status")
.build(),
);
b.include(subscriber("chunks.bulk", Bulk).batch(nonzero!(2)).build());
},
);
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(())
}