use std::fmt;
use serde::{Deserialize, Serialize};
#[non_exhaustive]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ValidationError {
pub loc: Vec<LocationSegment>,
pub msg: String,
#[serde(rename = "type")]
pub error_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub input: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ctx: Option<serde_json::Map<String, serde_json::Value>>,
}
impl ValidationError {
#[must_use]
pub fn loc_path(&self) -> String {
let mut path = String::new();
for segment in &self.loc {
match segment {
LocationSegment::Integer(index) => {
path.push('[');
path.push_str(&index.to_string());
path.push(']');
}
other => {
if !path.is_empty() {
path.push('.');
}
path.push_str(&other.to_string());
}
}
}
path
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum LocationSegment {
String(String),
Integer(i64),
Other(serde_json::Value),
}
impl fmt::Display for LocationSegment {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::String(value) => f.write_str(value),
Self::Integer(value) => write!(f, "{value}"),
Self::Other(value) => write!(f, "{value}"),
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum Detail {
Errors(Vec<ValidationError>),
Message(String),
Null(()),
}
impl Default for Detail {
fn default() -> Self {
Self::Errors(Vec::new())
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct HTTPValidationError {
#[serde(default)]
pub detail: Detail,
}
impl HTTPValidationError {
#[must_use]
pub fn errors(&self) -> &[ValidationError] {
match &self.detail {
Detail::Errors(errors) => errors,
Detail::Message(_) | Detail::Null(()) => &[],
}
}
#[must_use]
pub fn message(&self) -> Option<&str> {
match &self.detail {
Detail::Message(message) => Some(message),
Detail::Errors(_) | Detail::Null(()) => None,
}
}
}