use super::*;
pub(super) struct JsonRunSession {
emitter: self::json_events::NdjsonEmitter,
}
impl JsonRunSession {
pub(super) fn new(options: RunJsonOptions, out: Box<dyn io::Write + Send>) -> Self {
Self {
emitter: NdjsonEmitter::new(out, options.quiet),
}
}
pub(super) fn sink(&self) -> Arc<dyn harn_vm::run_events::RunEventSink> {
self.emitter.sink()
}
pub(super) fn finalize_result(self, value: serde_json::Value, exit_code: i32) -> RunOutcome {
self.emitter.emit_result(value, exit_code);
RunOutcome {
stdout: String::new(),
stderr: String::new(),
exit_code,
}
}
pub(super) fn finalize_error(
self,
code: impl Into<String>,
message: impl Into<String>,
exit_code: i32,
) -> RunOutcome {
self.emitter.emit_error(code, message);
RunOutcome {
stdout: String::new(),
stderr: String::new(),
exit_code,
}
}
}
#[allow(clippy::too_many_arguments)]
pub(super) fn finalize_run_error(
stdout: String,
mut stderr: String,
json_session: Option<JsonRunSession>,
summary: Option<&RunSummaryOptions>,
phase: Option<&RunPhaseOptions>,
rusage: Option<&RunRusageOptions>,
started: Instant,
profile: Option<&harn_vm::profile::RunProfile>,
timing: Option<&RunTiming>,
main_events: u64,
cpu_ms_total: Option<u64>,
code: impl Into<String>,
message: impl Into<String>,
) -> RunOutcome {
let aux_emission = emit_run_aux_for_exit(
summary,
phase,
rusage,
started,
1,
profile,
None,
timing,
main_events,
cpu_ms_total,
json_session.is_some(),
&mut stderr,
);
if let Some(session) = json_session {
let mut outcome = session.finalize_error(code, message, aux_emission.exit_code);
outcome.stderr = aux_emission.stderr;
return outcome;
}
RunOutcome {
stdout,
stderr,
exit_code: aux_emission.exit_code,
}
}
pub(super) fn finalize_harnpack_error(
mut stderr: String,
json_session: Option<JsonRunSession>,
summary: Option<&RunSummaryOptions>,
phase: Option<&RunPhaseOptions>,
rusage: Option<&RunRusageOptions>,
started: Instant,
err: HarnpackError,
) -> RunOutcome {
let code = err.code;
let message = err.message;
stderr.push_str(&format!("error: {message}\n"));
finalize_run_error(
String::new(),
stderr,
json_session,
summary,
phase,
rusage,
started,
None,
None,
0,
None,
code,
message,
)
}
pub(super) fn finalize_harnpack_dry_run(
mut stderr: String,
json_session: Option<JsonRunSession>,
summary_options: Option<&RunSummaryOptions>,
phase_options: Option<&RunPhaseOptions>,
rusage_options: Option<&RunRusageOptions>,
started: Instant,
cpu_ms_total: Option<u64>,
prepared: &PreparedHarnpack,
) -> RunOutcome {
let summary = format!(
"[harn] harnpack verify ok: bundle_hash={}, signature_verified={}, cache_hit={}, execution_artifact={}\n",
prepared.bundle_hash,
prepared.signature_verified,
prepared.cache_hit,
prepared.execution_artifact_state
);
stderr.push_str(&summary);
let aux_emission = emit_run_aux_for_exit(
summary_options,
phase_options,
rusage_options,
started,
0,
None,
None,
None,
0,
cpu_ms_total,
json_session.is_some(),
&mut stderr,
);
if let Some(session) = json_session {
if let Some(error) = aux_emission.error {
let mut outcome = session.finalize_error(
"run_aux",
format!("failed to emit auxiliary run JSON: {error}"),
1,
);
outcome.stderr = aux_emission.stderr;
return outcome;
}
let value = serde_json::json!({
"bundle_hash": prepared.bundle_hash,
"signature_verified": prepared.signature_verified,
"key_id": prepared.key_id,
"cache_hit": prepared.cache_hit,
"dry_run_verify": true,
"execution_artifact_state": prepared.execution_artifact_state,
"fallback_reason": prepared.fallback_reason,
"artifact_decode_ms": prepared.artifact_decode_elapsed.as_millis() as u64,
"link_report": prepared.manifest.execution_artifact.as_ref().map(|artifact| &artifact.link_report),
});
let mut outcome = session.finalize_result(value, aux_emission.exit_code);
outcome.stderr = aux_emission.stderr;
return outcome;
}
RunOutcome {
stdout: String::new(),
stderr,
exit_code: aux_emission.exit_code,
}
}
pub(super) fn render_return_value_error(value: &harn_vm::VmValue) -> String {
let harn_vm::VmValue::EnumVariant(enum_variant) = value else {
return String::new();
};
if !enum_variant.is_variant("Result", "Err") {
return String::new();
}
let rendered = enum_variant
.fields
.first()
.map(|p| p.display())
.unwrap_or_default();
if rendered.is_empty() {
"error\n".to_string()
} else if rendered.ends_with('\n') {
rendered
} else {
format!("{rendered}\n")
}
}