//! Final error rendering for the CLI boundary.
//!
//! Why this exists: anyhow's alternate format (`{:#}`) joins the whole
//! source chain with `": "`, but thiserror messages in this workspace
//! often embed their source's text (for example `initialize handshake
//! failed: {0}` where `{0}` is the source). Rendering both the wrapper
//! and the source then prints the same words twice, like `request 1
//! timed out after 30000ms: request 1 timed out after 30000ms`
//! (WOR-1326). This renderer walks the chain itself and skips links
//! whose message the output already ends with.

/// Join an error's source chain with `": "`, skipping links whose
/// message is already the tail of the rendered text. Used at the CLI's
/// final `eprintln!` so no error prints the same words twice through
/// `{0}`-embedding error wrappers (WOR-1326).
pub fn render_error_chain(err: &anyhow::Error) -> String {
    let mut out = String::new();
    for link in err.chain() {
        let msg = link.to_string();
        if out.is_empty() {
            out = msg;
        } else if !out.ends_with(&msg) {
            out.push_str(": ");
            out.push_str(&msg);
        }
    }
    out
}

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

    #[derive(Debug, Error)]
    #[error("inner failure")]
    struct Inner;

    #[derive(Debug, Error)]
    #[error("outer wrapper: {0}")]
    struct EmbeddingWrapper(#[source] Inner);

    #[derive(Debug, Error)]
    #[error("outer wrapper")]
    struct PlainWrapper(#[source] Inner);

    #[test]
    fn embedded_source_text_is_not_repeated() {
        let err = anyhow::Error::new(EmbeddingWrapper(Inner));
        assert_eq!(render_error_chain(&err), "outer wrapper: inner failure");
    }

    #[test]
    fn distinct_source_text_is_appended() {
        let err = anyhow::Error::new(PlainWrapper(Inner));
        assert_eq!(render_error_chain(&err), "outer wrapper: inner failure");
    }

    #[test]
    fn context_layers_render_in_outermost_first_order() {
        let err = anyhow::Error::new(Inner)
            .context("middle step")
            .context("top step");
        assert_eq!(
            render_error_chain(&err),
            "top step: middle step: inner failure"
        );
    }

    #[test]
    fn single_error_renders_its_own_message() {
        let err = anyhow::anyhow!("just one message");
        assert_eq!(render_error_chain(&err), "just one message");
    }
}
