///|
/// Render a stable line-oriented report suitable for terminals and snapshots.
pub fn render_scenario_text(report : ScenarioReport) -> String {
let output = StringBuilder()
output.write_string("MoonTusCore scenario\n")
output.write_string("result: ")
output.write_string(if report.succeeded { "success" } else { "failure" })
output.write_string("\nevents: ")
output.write_string(report.events.length().to_string())
output.write_string("\nuploads: ")
output.write_string(report.uploads.length().to_string())
output.write_char('\n')
for event in report.events {
output.write_char('#')
output.write_string(event.index.to_string())
output.write_char(' ')
output.write_string(event.operation)
match event.resource_name {
None => ()
Some(name) => {
output.write_string(" name=")
output.write_string(name)
}
}
output.write_string(" status=")
output.write_string(event.status.to_string())
match event.offset {
None => ()
Some(offset) => {
output.write_string(" offset=")
output.write_string(offset.to_string())
}
}
match event.error_code {
None => ()
Some(code) => {
output.write_string(" error=")
output.write_string(code)
}
}
output.write_char('\n')
}
for upload in report.uploads {
output.write_string("upload ")
output.write_string(upload.identifier)
output.write_string(" offset=")
output.write_string(upload.offset.to_string())
output.write_string(" length=")
output.write_string(upload_length_name(upload.length))
output.write_string(" state=")
output.write_string(lifecycle_name(upload.lifecycle))
output.write_string(" revision=")
output.write_string(upload.revision.to_string())
output.write_char('\n')
}
output.to_string()
}
///|
/// Render self-contained JSON without requiring a JSON dependency in the core
/// package. All dynamic strings pass through a conservative control escaper.
pub fn render_scenario_json(report : ScenarioReport) -> String {
let output = StringBuilder()
output.write_string("{\"schema\":\"moontuscore.scenario.v1\",\"succeeded\":")
output.write_string(if report.succeeded { "true" } else { "false" })
output.write_string(",\"events\":[")
for index, event in report.events {
if index > 0 {
output.write_char(',')
}
render_event_json(output, event)
}
output.write_string("],\"uploads\":[")
for index, upload in report.uploads {
if index > 0 {
output.write_char(',')
}
render_upload_json(output, upload)
}
output.write_string("]}")
output.to_string()
}
///|
pub fn render_response_text(response : TusResponse) -> String {
let output = StringBuilder()
output.write_string("status ")
output.write_string(response.status.to_string())
output.write_char('\n')
for field in response.headers.as_array() {
output.write_string(field.name)
output.write_string(": ")
output.write_string(field.value)
output.write_char('\n')
}
match response.error {
None => ()
Some(error) => {
output.write_string("error-code: ")
output.write_string(error.code)
output.write_char('\n')
output.write_string("error-message: ")
output.write_string(error.message)
output.write_char('\n')
}
}
for step in response.trace {
output.write_string("trace: ")
output.write_string(step.code)
output.write_string(" [")
output.write_string(step.protocol_section)
output.write_string("] ")
output.write_string(step.message)
output.write_char('\n')
}
output.to_string()
}
///|
fn render_event_json(output : StringBuilder, event : ScenarioEvent) -> Unit {
output.write_string("{\"index\":")
output.write_string(event.index.to_string())
output.write_string(",\"operation\":")
write_json_string(output, event.operation)
output.write_string(",\"resourceName\":")
write_optional_json_string(output, event.resource_name)
output.write_string(",\"identifier\":")
write_optional_json_string(output, event.identifier)
output.write_string(",\"status\":")
output.write_string(event.status.to_string())
output.write_string(",\"offset\":")
match event.offset {
None => output.write_string("null")
Some(offset) => output.write_string(offset.to_string())
}
output.write_string(",\"errorCode\":")
write_optional_json_string(output, event.error_code)
output.write_string(",\"trace\":[")
for index, step in event.traces {
if index > 0 {
output.write_char(',')
}
output.write_string("{\"code\":")
write_json_string(output, step.code)
output.write_string(",\"message\":")
write_json_string(output, step.message)
output.write_string(",\"section\":")
write_json_string(output, step.protocol_section)
output.write_char('}')
}
output.write_string("]}")
}
///|
fn render_upload_json(output : StringBuilder, upload : UploadRecord) -> Unit {
output.write_string("{\"identifier\":")
write_json_string(output, upload.identifier)
output.write_string(",\"offset\":")
output.write_string(upload.offset.to_string())
output.write_string(",\"length\":")
match upload.length {
Deferred => output.write_string("null")
Known(total) => output.write_string(total.to_string())
}
output.write_string(",\"deferred\":")
output.write_string(if upload.length == Deferred { "true" } else { "false" })
output.write_string(",\"lifecycle\":")
write_json_string(output, lifecycle_name(upload.lifecycle))
output.write_string(",\"revision\":")
output.write_string(upload.revision.to_string())
output.write_string(",\"storedBytes\":")
output.write_string(upload.data.length().to_string())
output.write_string(",\"metadata\":[")
for index, entry in upload.metadata {
if index > 0 {
output.write_char(',')
}
output.write_string("{\"key\":")
write_json_string(output, entry.key)
output.write_string(",\"base64\":")
write_json_string(output, encode_base64(entry.value))
output.write_char('}')
}
output.write_string("]}")
}
///|
fn write_optional_json_string(output : StringBuilder, value : String?) -> Unit {
match value {
None => output.write_string("null")
Some(text) => write_json_string(output, text)
}
}
///|
fn write_json_string(output : StringBuilder, value : String) -> Unit {
output.write_char('"')
for character in value {
match character {
'"' => output.write_string("\\\"")
'\\' => output.write_string("\\\\")
'\n' => output.write_string("\\n")
'\r' => output.write_string("\\r")
'\t' => output.write_string("\\t")
_ => {
let code = character.to_int()
if code < 32 {
output.write_string("\\u00")
output.write_char(hex_digit((code >> 4) & 15))
output.write_char(hex_digit(code & 15))
} else {
output.write_char(character)
}
}
}
}
output.write_char('"')
}
///|
fn hex_digit(value : Int) -> Char {
if value < 10 {
(value + '0'.to_int()).unsafe_to_char()
} else {
(value - 10 + 'a'.to_int()).unsafe_to_char()
}
}