ownpg 0.1.1

OwnPG serves PostgreSQL DBA tools to AI clients over the Model Context Protocol, one database and one schema per run
use std::io::{self, Write};

use ownpg_core::Error;

pub(crate) fn emit<F>(render: F) -> io::Result<()>
where
    F: FnOnce(&mut dyn Write) -> io::Result<()>,
{
    let stdout = io::stdout();
    let mut handle = stdout.lock();
    match render(&mut handle).and_then(|()| handle.flush()) {
        Ok(()) => Ok(()),
        Err(error) if error.kind() == io::ErrorKind::BrokenPipe => Ok(()),
        Err(error) => Err(error),
    }
}

pub(crate) fn stdout_error(source: io::Error) -> Error {
    Error::OutputUnwritable {
        target: "stdout".to_owned(),
        source,
    }
}

pub(crate) fn cause_chain(error: &Error) -> Vec<String> {
    let mut causes = Vec::new();
    let mut source = std::error::Error::source(error);
    while let Some(cause) = source {
        causes.push(cause.to_string());
        source = cause.source();
    }
    causes
}

pub(crate) fn report_error(error: &Error) {
    let mut stderr = io::stderr().lock();
    let _ = writeln!(stderr, "error: {error}");
    for cause in cause_chain(error) {
        let _ = writeln!(stderr, "  caused by: {cause}");
    }
    let remedy = error.remedy();
    if !remedy.is_empty() {
        let _ = writeln!(stderr, "  try: {remedy}");
    }
    let _ = writeln!(stderr, "  code: {}", error.id());
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn a_closed_pipe_ends_the_write_quietly() {
        let outcome = emit(|_: &mut dyn Write| Err(io::Error::from(io::ErrorKind::BrokenPipe)));
        assert!(outcome.is_ok());
    }

    #[test]
    fn any_other_write_failure_is_reported() {
        let outcome =
            emit(|_: &mut dyn Write| Err(io::Error::from(io::ErrorKind::PermissionDenied)));
        assert_eq!(outcome.unwrap_err().kind(), io::ErrorKind::PermissionDenied);
    }

    #[test]
    fn a_stdout_failure_carries_the_output_error_id() {
        let error = stdout_error(io::Error::from(io::ErrorKind::PermissionDenied));
        assert_eq!(error.id().as_str(), "output.unwritable");
    }
}