use std::{fmt::Display, io::Write};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::{
cli::{EXIT_ERRORS, EXIT_OK},
harness::Harness,
hook::Event,
};
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "answer", rename_all = "lowercase")]
#[non_exhaustive]
pub enum Answer {
Allow {
#[serde(default, skip_serializing_if = "Option::is_none")]
stderr: Option<String>,
},
Deny {
reason: String,
},
Continue {
reason: String,
},
Context {
text: String,
},
}
impl Answer {
pub fn as_str(&self) -> &'static str {
match self {
Answer::Allow { .. } => "allow",
Answer::Deny { .. } => "deny",
Answer::Continue { .. } => "continue",
Answer::Context { .. } => "context",
}
}
pub fn stderr(&self) -> Option<&str> {
match self {
Answer::Allow { stderr } => stderr.as_deref(),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct Output {
pub stdout: String,
pub stderr: Option<String>,
pub exit: u8,
}
impl Output {
pub fn json(value: Value) -> Self {
Output {
stdout: value.to_string(),
stderr: None,
exit: EXIT_OK,
}
}
#[must_use]
pub fn stderr(mut self, text: Option<String>) -> Self {
self.stderr = text;
self
}
#[must_use]
pub fn exit(mut self, exit: u8) -> Self {
self.exit = exit;
self
}
}
pub fn emit<E: Display>(
harness: &dyn Harness,
event: Event,
result: Result<Answer, E>,
stdout: &mut impl Write,
stderr: &mut impl Write,
) -> std::io::Result<u8> {
let rendered = result
.map_err(|e| e.to_string())
.and_then(|a| harness.answer(event, &a).map_err(|e| e.to_string()));
match rendered {
Ok(out) => {
if let Some(text) = &out.stderr {
stderr.write_all(text.as_bytes())?;
}
stdout.write_all(format!("{}\n", out.stdout).as_bytes())?;
stdout.flush()?;
Ok(out.exit)
}
Err(e) => {
writeln!(stderr, "error: {e}")?;
Ok(EXIT_ERRORS)
}
}
}