gproxy-protocol 4.1.12

GPROXY v4 connection modelling, operation declarations, vendor wire types and pairwise conversion
Documentation
use std::{error::Error, fmt};

/// A recognized upstream failure, retaining its declared vendor error payload.
/// Hosts can render a client error event without parsing a diagnostic string.
#[derive(Debug)]
pub struct UpstreamError<T>(pub T);

impl<T: serde::Serialize> fmt::Display for UpstreamError<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let json = serde_json::to_string(&self.0).map_err(|_| fmt::Error)?;
        f.write_str(&json)
    }
}

impl<T: fmt::Debug + serde::Serialize> Error for UpstreamError<T> {}

/// Why a request, result, or required host operation could not be converted.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
pub enum TransformErrorKind {
    InvalidInput,
    InvalidResult,
    Unsupported,
    MissingMetadata,
    MissingState,
    Conflict,
    Limit,
    Host,
}

/// A conversion failure with an explicit field/operation context and cause.
#[derive(Debug)]
pub struct TransformError {
    kind: TransformErrorKind,
    context: String,
    detail: String,
    source: Option<Box<dyn Error + Send + Sync + 'static>>,
}

impl TransformError {
    pub fn new(
        kind: TransformErrorKind,
        context: impl Into<String>,
        detail: impl Into<String>,
    ) -> Self {
        Self {
            kind,
            context: context.into(),
            detail: detail.into(),
            source: None,
        }
    }

    pub fn with_source(
        kind: TransformErrorKind,
        context: impl Into<String>,
        detail: impl Into<String>,
        source: impl Into<Box<dyn Error + Send + Sync + 'static>>,
    ) -> Self {
        Self {
            kind,
            context: context.into(),
            detail: detail.into(),
            source: Some(source.into()),
        }
    }

    pub fn shape(context: impl Into<String>, detail: impl Into<String>) -> Self {
        Self::new(TransformErrorKind::InvalidInput, context, detail)
    }

    pub fn unsupported(context: impl Into<String>, detail: impl Into<String>) -> Self {
        Self::new(TransformErrorKind::Unsupported, context, detail)
    }

    pub fn invalid_result(context: impl Into<String>, detail: impl Into<String>) -> Self {
        Self::new(TransformErrorKind::InvalidResult, context, detail)
    }

    /// Retain a declared upstream error for HTTP and streaming renderers.
    pub fn upstream<T>(context: impl Into<String>, payload: T) -> Self
    where
        T: fmt::Debug + serde::Serialize + Send + Sync + 'static,
    {
        let error = UpstreamError(payload);
        Self::with_source(
            TransformErrorKind::InvalidResult,
            context,
            error.to_string(),
            error,
        )
    }

    pub fn missing_metadata(field: impl Into<String>) -> Self {
        Self::new(
            TransformErrorKind::MissingMetadata,
            field,
            "required factual metadata is unavailable",
        )
    }

    pub fn kind(&self) -> TransformErrorKind {
        self.kind
    }
    pub fn context(&self) -> &str {
        &self.context
    }
    pub fn detail(&self) -> &str {
        &self.detail
    }
}

impl fmt::Display for TransformError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}: {}", self.context, self.detail)
    }
}

impl Error for TransformError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        self.source
            .as_deref()
            .map(|source| source as &(dyn Error + 'static))
    }
}

impl From<serde_json::Error> for TransformError {
    fn from(error: serde_json::Error) -> Self {
        let message = error.to_string();
        Self::with_source(TransformErrorKind::InvalidInput, "JSON", message, error)
    }
}

impl From<crate::capability::CapabilityError> for TransformError {
    fn from(error: crate::capability::CapabilityError) -> Self {
        use crate::capability::CapabilityErrorKind;
        let kind = match error.kind() {
            CapabilityErrorKind::Unsupported => TransformErrorKind::Unsupported,
            CapabilityErrorKind::NotFound | CapabilityErrorKind::Expired => {
                TransformErrorKind::Host
            }
            CapabilityErrorKind::Conflict => TransformErrorKind::Conflict,
            CapabilityErrorKind::Limit => TransformErrorKind::Limit,
            CapabilityErrorKind::Invalid => TransformErrorKind::InvalidInput,
            CapabilityErrorKind::Transport | CapabilityErrorKind::Storage => {
                TransformErrorKind::Host
            }
        };
        let message = error.to_string();
        Self::with_source(kind, "host capability", message, error)
    }
}

/// A declared field whose representation changes or cannot be retained.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
pub enum DiagnosticKind {
    /// A source field or content block omitted because it has no target
    /// representation. Other supported parts of the conversion are retained.
    OmittedOptional,
    RepresentationChanged,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Diagnostic {
    pub field: String,
    pub kind: DiagnosticKind,
    pub message: String,
}

/// Owned diagnostics for one conversion, never serialized into vendor `rest`.
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct Report {
    pub diagnostics: Vec<Diagnostic>,
}

impl Report {
    pub fn omitted(&mut self, field: impl Into<String>, message: impl Into<String>) {
        self.diagnostics.push(Diagnostic {
            field: field.into(),
            kind: DiagnosticKind::OmittedOptional,
            message: message.into(),
        });
    }

    pub fn changed(&mut self, field: impl Into<String>, message: impl Into<String>) {
        self.diagnostics.push(Diagnostic {
            field: field.into(),
            kind: DiagnosticKind::RepresentationChanged,
            message: message.into(),
        });
    }
}