serde_field_result 0.1.0

Field-level Serde recovery for schema-drift-tolerant API clients
Documentation
use alloc::{boxed::Box, string::ToString};

use serde::{Deserialize, Deserializer, de::DeserializeOwned};
use serde_json::value::RawValue;

use crate::{Field, FieldError};

/// JSON field-level result that owns the raw value when decoding fails.
///
/// Use [`BorrowedJsonField`] when the input buffer outlives the decoded value
/// and borrowed raw JSON or borrowed nested values are useful.
///
/// Invalid fields compare by exact raw JSON text and captured error message, not by
/// semantic JSON equality.
///
/// JSON `null` always decodes as [`JsonField::Missing`], including for
/// `JsonField<Option<T>>`; use [`Field<Option<T>>`] when explicit `null` must be
/// distinguishable from an absent field.
#[derive(Clone, Debug, Default)]
pub enum JsonField<T> {
    /// The field was absent or `null`.
    #[default]
    Missing,
    /// The field was present and decoded successfully.
    Valid(T),
    /// The field was present but failed to decode.
    Invalid {
        /// Raw JSON for the malformed field value.
        raw: Box<RawValue>,
        /// Error reported by `serde_json` while decoding the raw value as `T`.
        error: FieldError,
    },
}

impl<T> PartialEq for JsonField<T>
where
    T: PartialEq,
{
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Self::Missing, Self::Missing) => true,
            (Self::Valid(left), Self::Valid(right)) => left == right,
            (
                Self::Invalid {
                    raw: left_raw,
                    error: left_error,
                },
                Self::Invalid {
                    raw: right_raw,
                    error: right_error,
                },
            ) => invalid_fields_eq(left_raw, left_error, right_raw, right_error),
            _ => false,
        }
    }
}

impl<T> Eq for JsonField<T> where T: Eq {}

impl<T> JsonField<T> {
    /// Creates an invalid JSON field from raw JSON and an error.
    #[must_use]
    pub fn invalid(raw: Box<RawValue>, error: impl Into<FieldError>) -> Self {
        Self::Invalid {
            raw,
            error: error.into(),
        }
    }

    impl_common_field_methods! {
        map_return: JsonField<U>;
        map_missing: JsonField::Missing;
        map_valid: JsonField::Valid;
        invalid_ignore: Self::Invalid { .. },
        invalid_error: Self::Invalid { error, .. } => error;
        invalid_map: Self::Invalid { raw, error } => JsonField::Invalid { raw, error };
        into_result_extra: "The raw JSON value is discarded when converting an invalid field into an error.";
    }

    /// Returns the raw invalid JSON value, if present.
    #[must_use]
    pub const fn raw_value(&self) -> Option<&RawValue> {
        match self {
            Self::Invalid { raw, .. } => Some(raw),
            Self::Missing | Self::Valid(_) => None,
        }
    }

    /// Returns the raw invalid JSON value as a string slice, if present.
    #[must_use]
    pub fn raw_json(&self) -> Option<&str> {
        self.raw_value().map(RawValue::get)
    }
}

impl<T> From<T> for JsonField<T> {
    fn from(value: T) -> Self {
        Self::Valid(value)
    }
}

impl<T> From<JsonField<T>> for Field<T> {
    fn from(field: JsonField<T>) -> Self {
        match field {
            JsonField::Missing => Self::Missing,
            JsonField::Valid(value) => Self::Valid(value),
            JsonField::Invalid { error, .. } => Self::Invalid(error),
        }
    }
}

impl<'de, T> Deserialize<'de> for JsonField<T>
where
    T: DeserializeOwned,
{
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let raw = Box::<RawValue>::deserialize(deserializer)?;
        if raw.get() == "null" {
            return Ok(Self::Missing);
        }

        Ok(match serde_json::from_str(raw.get()) {
            Ok(value) => Self::Valid(value),
            Err(error) => Self::Invalid {
                raw,
                error: FieldError::new(error.to_string()),
            },
        })
    }
}

/// JSON field-level result that borrows the raw value when decoding fails.
///
/// Unlike [`JsonField`], this type stores invalid raw JSON as `&'de RawValue`
/// and decodes valid values with `T: Deserialize<'de>`, so `T` may borrow from
/// the original JSON input.
///
/// Borrowing follows `serde_json`'s normal limitations: escaped strings may not
/// deserialize into `&'de str`. Use [`String`][alloc::string::String] or
/// [`Cow`][alloc::borrow::Cow] when string values may need unescaping.
///
/// Invalid fields compare by exact raw JSON text and captured error message, not by
/// semantic JSON equality.
///
/// JSON `null` always decodes as [`BorrowedJsonField::Missing`], including for
/// `BorrowedJsonField<Option<T>>`; use [`Field<Option<T>>`] when explicit
/// `null` must be distinguishable from an absent field.
#[derive(Clone, Debug, Default)]
pub enum BorrowedJsonField<'de, T> {
    /// The field was absent or `null`.
    #[default]
    Missing,
    /// The field was present and decoded successfully.
    Valid(T),
    /// The field was present but failed to decode.
    Invalid {
        /// Borrowed raw JSON for the malformed field value.
        raw: &'de RawValue,
        /// Error reported by `serde_json` while decoding the raw value as `T`.
        error: FieldError,
    },
}

impl<'left, 'right, T> PartialEq<BorrowedJsonField<'right, T>> for BorrowedJsonField<'left, T>
where
    T: PartialEq,
{
    fn eq(&self, other: &BorrowedJsonField<'right, T>) -> bool {
        match (self, other) {
            (Self::Missing, BorrowedJsonField::Missing) => true,
            (Self::Valid(left), BorrowedJsonField::Valid(right)) => left == right,
            (
                Self::Invalid {
                    raw: left_raw,
                    error: left_error,
                },
                BorrowedJsonField::Invalid {
                    raw: right_raw,
                    error: right_error,
                },
            ) => invalid_fields_eq(left_raw, left_error, right_raw, right_error),
            _ => false,
        }
    }
}

impl<T> Eq for BorrowedJsonField<'_, T> where T: Eq {}

impl<'de, T> BorrowedJsonField<'de, T> {
    /// Creates an invalid borrowed JSON field from raw JSON and an error.
    #[must_use]
    pub fn invalid(raw: &'de RawValue, error: impl Into<FieldError>) -> Self {
        Self::Invalid {
            raw,
            error: error.into(),
        }
    }

    impl_common_field_methods! {
        map_return: BorrowedJsonField<'de, U>;
        map_missing: BorrowedJsonField::Missing;
        map_valid: BorrowedJsonField::Valid;
        invalid_ignore: Self::Invalid { .. },
        invalid_error: Self::Invalid { error, .. } => error;
        invalid_map: Self::Invalid { raw, error } => BorrowedJsonField::Invalid { raw, error };
        into_result_extra: "The borrowed raw JSON value is discarded when converting an invalid field into an error.";
    }

    /// Returns the borrowed raw invalid JSON value, if present.
    #[must_use]
    pub const fn raw_value(&self) -> Option<&'de RawValue> {
        match self {
            Self::Invalid { raw, .. } => Some(*raw),
            Self::Missing | Self::Valid(_) => None,
        }
    }

    /// Returns the borrowed raw invalid JSON value as a string slice, if present.
    #[must_use]
    pub fn raw_json(&self) -> Option<&'de str> {
        self.raw_value().map(RawValue::get)
    }
}

impl<T> From<T> for BorrowedJsonField<'_, T> {
    fn from(value: T) -> Self {
        Self::Valid(value)
    }
}

impl<T> From<BorrowedJsonField<'_, T>> for Field<T> {
    fn from(field: BorrowedJsonField<'_, T>) -> Self {
        match field {
            BorrowedJsonField::Missing => Self::Missing,
            BorrowedJsonField::Valid(value) => Self::Valid(value),
            BorrowedJsonField::Invalid { error, .. } => Self::Invalid(error),
        }
    }
}

impl<'de: 'a, 'a, T> Deserialize<'de> for BorrowedJsonField<'a, T>
where
    T: Deserialize<'a>,
{
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let raw = <&'a RawValue>::deserialize(deserializer)?;
        if raw.get() == "null" {
            return Ok(Self::Missing);
        }

        Ok(match serde_json::from_str(raw.get()) {
            Ok(value) => Self::Valid(value),
            Err(error) => Self::Invalid {
                raw,
                error: FieldError::new(error.to_string()),
            },
        })
    }
}

fn invalid_fields_eq(
    left_raw: &RawValue,
    left_error: &FieldError,
    right_raw: &RawValue,
    right_error: &FieldError,
) -> bool {
    left_raw.get() == right_raw.get() && left_error == right_error
}