///|
/// State-machine wrapper around `MultipartWriter` that binds one emitted
/// message to a previously validated content-length plan.
pub struct PlannedEmitter {
priv plan : MultipartPlan
priv writer : MultipartWriter
priv mut next_part : Int
priv mut active_part : Int?
priv mut active_bytes : Int64
priv mut done : Bool
priv mut failed : Bool
}
///|
fn[T] PlannedEmitter::fail(
self : PlannedEmitter,
error : @model.MultipartError,
) -> T raise @model.MultipartError {
self.failed = true
raise error
}
///|
fn PlannedEmitter::invalid_state(
self : PlannedEmitter,
operation : String,
) -> @model.MultipartError {
let phase = if self.failed {
@model.ParserPhase::Failed
} else if self.done {
Done
} else {
Body
}
InvalidState(operation~, phase~)
}
///|
pub fn PlannedEmitter::new(
plan : MultipartPlan,
) -> PlannedEmitter raise @model.MultipartError {
let writer = MultipartWriter::new(plan.boundary, plan.limits)
{
plan,
writer,
next_part: 0,
active_part: None,
active_bytes: 0L,
done: false,
failed: false,
}
}
///|
pub fn PlannedEmitter::begin_next(
self : PlannedEmitter,
) -> Bytes raise @model.MultipartError {
if self.failed ||
self.done ||
self.active_part is Some(_) ||
self.next_part >= self.plan.parts.length() {
self.fail(self.invalid_state("begin_next"))
}
let part = self.plan.parts[self.next_part]
let opening = self.writer.begin_part(part.meta) catch {
error => self.fail(error)
}
self.active_part = Some(self.next_part)
self.active_bytes = 0L
opening
}
///|
pub fn PlannedEmitter::write_chunk(
self : PlannedEmitter,
chunk : Bytes,
) -> Bytes raise @model.MultipartError {
if self.failed || self.done {
self.fail(self.invalid_state("write_chunk"))
}
let index = match self.active_part {
Some(index) => index
None => self.fail(self.invalid_state("write_chunk"))
}
let planned = self.plan.parts[index]
let observed = self.active_bytes + chunk.length().to_int64()
if observed > planned.body_length {
self.fail(
BodyLengthMismatch(
part_index=planned.meta.index,
expected=planned.body_length,
observed~,
),
)
}
let output = self.writer.write(chunk) catch { error => self.fail(error) }
self.active_bytes = observed
output
}
///|
pub fn PlannedEmitter::end_next(
self : PlannedEmitter,
) -> Bytes raise @model.MultipartError {
if self.failed || self.done {
self.fail(self.invalid_state("end_next"))
}
let index = match self.active_part {
Some(index) => index
None => self.fail(self.invalid_state("end_next"))
}
let planned = self.plan.parts[index]
if self.active_bytes != planned.body_length {
self.fail(
BodyLengthMismatch(
part_index=planned.meta.index,
expected=planned.body_length,
observed=self.active_bytes,
),
)
}
let ending = self.writer.end_part() catch { error => self.fail(error) }
self.active_part = None
self.active_bytes = 0L
self.next_part += 1
ending
}
///|
pub fn PlannedEmitter::finish(
self : PlannedEmitter,
) -> Bytes raise @model.MultipartError {
if self.failed ||
self.done ||
self.active_part is Some(_) ||
self.next_part != self.plan.parts.length() {
self.fail(self.invalid_state("finish"))
}
let closing = self.writer.finish() catch { error => self.fail(error) }
self.done = true
closing
}