use super::*;
use proto_types::field_descriptor_proto::Type as ProtoPrimitive;
use proto_types::protovalidate::field_path_element::Subscript;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct FieldContext {
pub name: FixedStr,
pub tag: i32,
pub subscript: Option<Subscript>,
pub map_key_type: Option<ProtoPrimitive>,
pub map_value_type: Option<ProtoPrimitive>,
pub field_type: ProtoPrimitive,
pub field_kind: FieldKind,
}
impl FieldContext {
#[inline]
#[must_use]
pub const fn new(name: FixedStr, tag: i32, field_type: ProtoPrimitive) -> Self {
Self {
name,
tag,
subscript: None,
map_key_type: None,
map_value_type: None,
field_type,
field_kind: FieldKind::Normal,
}
}
}
impl FieldContext {
#[must_use]
#[inline(never)]
#[cold]
pub fn as_path_element(&self) -> FieldPathElement {
FieldPathElement {
field_number: Some(self.tag),
field_name: Some(self.name.to_string()),
field_type: Some(self.field_type as i32),
key_type: self.map_key_type.map(|t| t as i32),
value_type: self.map_value_type.map(|t| t as i32),
subscript: self.subscript.clone(),
}
}
}
#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum FieldKind {
MapKey,
MapValue,
RepeatedItem,
Normal,
}
impl Default for FieldKind {
#[inline]
fn default() -> Self {
Self::Normal
}
}
impl FieldKind {
#[must_use]
#[inline]
pub const fn is_map_key(&self) -> bool {
matches!(self, Self::MapKey)
}
#[must_use]
#[inline]
pub const fn is_map_value(&self) -> bool {
matches!(self, Self::MapValue)
}
#[must_use]
#[inline]
pub const fn is_repeated_item(&self) -> bool {
matches!(self, Self::RepeatedItem)
}
#[must_use]
#[inline]
pub const fn is_normal(&self) -> bool {
matches!(self, Self::Normal)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct ValidationCtx {
pub field_context: Option<FieldContext>,
pub parent_elements: Vec<FieldPathElement>,
pub violations: ValidationErrors,
pub fail_fast: bool,
}
impl Default for ValidationCtx {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl ValidationCtx {
#[inline]
#[must_use]
pub const fn new() -> Self {
Self {
field_context: None,
parent_elements: vec![],
violations: ValidationErrors::new(),
fail_fast: true,
}
}
#[inline]
pub fn without_field_context(&mut self) -> &mut Self {
self.field_context = None;
self
}
#[inline]
pub fn with_field_context(&mut self, field_context: FieldContext) -> &mut Self {
self.field_context = Some(field_context);
self
}
#[inline(never)]
#[cold]
pub fn add_violation(
&mut self,
kind: ViolationKind,
error_message: impl Into<String>,
) -> ValidationResult {
self.add_violation_internal(None, kind, error_message.into())
}
#[inline]
#[must_use]
pub fn field_kind(&self) -> FieldKind {
self.field_context
.as_ref()
.map(|fc| fc.field_kind)
.unwrap_or_default()
}
#[inline(never)]
#[cold]
fn add_violation_internal(
&mut self,
rule_id: Option<String>,
kind: ViolationKind,
error_message: String,
) -> ValidationResult {
let violation = create_violation_core(
rule_id,
self.field_context.as_ref(),
&self.parent_elements,
kind.data(),
error_message,
);
self.violations.push(ViolationCtx {
data: violation,
meta: ViolationMeta {
kind,
field_kind: self.field_kind(),
},
});
if self.fail_fast {
Err(FailFast)
} else {
Ok(IsValid::No)
}
}
#[inline(never)]
#[cold]
pub fn add_violation_with_custom_id(
&mut self,
rule_id: impl Into<String>,
kind: ViolationKind,
error_message: impl Into<String>,
) -> ValidationResult {
self.add_violation_internal(Some(rule_id.into()), kind, error_message.into())
}
#[inline]
#[cold]
pub fn add_cel_violation(&mut self, rule: &CelRule) -> ValidationResult {
self.add_violation_with_custom_id(&rule.id, ViolationKind::Cel, &rule.message)
}
#[inline]
#[cold]
pub(crate) fn add_required_oneof_violation(
&mut self,
error_message: Option<String>,
) -> ValidationResult {
if let Some(msg) = error_message {
self.add_violation(ViolationKind::RequiredOneof, msg)
} else {
self.add_violation(
ViolationKind::RequiredOneof,
"at least one value must be set",
)
}
}
#[inline]
#[cold]
pub fn add_required_violation(&mut self, error_message: Option<String>) -> ValidationResult {
if let Some(msg) = error_message {
self.add_violation(ViolationKind::Required, msg)
} else {
self.add_violation(ViolationKind::Required, "is required")
}
}
#[cfg(feature = "cel")]
#[inline(never)]
#[cold]
pub(crate) fn add_cel_error_violation(&mut self, error: CelError) -> ValidationResult {
self.violations.push(ViolationCtx {
meta: ViolationMeta {
kind: ViolationKind::Cel,
field_kind: self.field_kind(),
},
data: error.into_violation(self.field_context.as_ref(), &self.parent_elements),
});
if self.fail_fast {
Err(FailFast)
} else {
Ok(IsValid::No)
}
}
}