providers-kit 0.0.2

Portable enum-dispatch Kit over multiple model provider APIs.
Documentation
use std::error::Error as _;

use url::Url;

use crate::KitError;
use crate::error::{ErrorKind, ErrorPhase, NativeErrorDetails, Operation};

use super::execution::RequestExecution;

impl RequestExecution {
    pub(crate) fn transport_error(
        path: &str,
        timeout: std::time::Duration,
        error: reqwest::Error,
    ) -> KitError {
        let url = error.url().map(Self::redacted_url);
        let original_url = error.url().map(ToString::to_string);
        let kind = if error.is_timeout() {
            ErrorKind::Timeout
        } else {
            ErrorKind::Transport
        };
        let phase = if error.is_builder() {
            ErrorPhase::RequestBuild
        } else {
            ErrorPhase::RequestSend
        };
        let redact = |message: String| match (&original_url, &url) {
            (Some(raw), Some(redacted)) => message.replace(raw, redacted),
            _ => message,
        };
        let mut sources = Vec::new();
        let mut source = error.source();
        while let Some(error) = source {
            sources.push(redact(error.to_string()));
            source = error.source();
        }
        let mut cause = None;
        for message in sources.into_iter().rev() {
            let mut child = KitError::new(ErrorKind::Transport, Operation::Unspecified, phase)
                .with_message(message);
            if let Some(next) = cause {
                child = child.with_cause(next);
            }
            cause = Some(child);
        }
        let mut retained = KitError::new(kind, Operation::Unspecified, phase).with_native_details(
            NativeErrorDetails {
                message: Some(redact(error.to_string())),
                url,
                ..Default::default()
            },
        );
        if kind == ErrorKind::Timeout {
            retained = retained.with_timeout(timeout);
        }
        if let Some(status) = error.status() {
            retained = retained.with_http_status(status.as_u16());
        }
        if let Some(cause) = cause {
            retained = retained.with_cause(cause);
        }
        retained.with_request_target(path)
    }

    fn redacted_url(url: &Url) -> String {
        let mut redacted = url.clone();
        let _ = redacted.set_password(None);
        let _ = redacted.set_username("");
        redacted.set_query(None);
        redacted.set_fragment(None);
        redacted.to_string()
    }
}