Skip to main content

candid_core/model/
validation_error.rs

1use crate::diagnostics::Diagnostic;
2#[cfg(feature = "compiler")]
3use crate::diagnostics::{CompileError, DiagnosticPhase, Severity};
4use std::fmt;
5
6/// Compatibility name for the shared diagnostic item in the Contract
7/// validation domain.
8///
9/// Contract violations are [`Diagnostic`] values that always carry `path` and
10/// never carry `phase`/`severity`, so their serialized shape is unchanged:
11/// `{code, path, message, resource_limit?}` plus any optional location data.
12pub type ContractViolation = Diagnostic;
13
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct ContractValidationError {
16    pub violations: Vec<ContractViolation>,
17}
18
19impl ContractValidationError {
20    pub(crate) fn single(
21        code: impl Into<String>,
22        path: impl Into<String>,
23        message: impl Into<String>,
24    ) -> Self {
25        Self {
26            violations: vec![Diagnostic::violation(code, path, message)],
27        }
28    }
29
30    pub(crate) fn resource_limit(resource: &str, limit: usize, observed: usize) -> Self {
31        Self {
32            violations: vec![Diagnostic::resource_violation(
33                resource,
34                crate::limits::portable_count(limit),
35                crate::limits::portable_count(observed),
36            )],
37        }
38    }
39
40    /// Lossless item-by-item conversion into a [`CompileError`].
41    ///
42    /// Every violation keeps its code, structured path, span, related
43    /// locations, notes, and resource metadata, and gains the compile-domain
44    /// `phase`/`severity`. The message keeps the pre-existing `{path}:
45    /// {message}` rendering so compile output stays byte-compatible.
46    #[cfg(feature = "compiler")]
47    pub(crate) fn into_compile_error(self, phase: DiagnosticPhase) -> CompileError {
48        CompileError {
49            diagnostics: self
50                .violations
51                .into_iter()
52                .map(|violation| {
53                    let message = match &violation.path {
54                        Some(path) => format!("{}: {}", path, violation.message),
55                        None => violation.message.clone(),
56                    };
57                    Diagnostic {
58                        phase: Some(phase.clone()),
59                        severity: Some(Severity::Error),
60                        message,
61                        ..violation
62                    }
63                })
64                .collect(),
65        }
66    }
67}
68
69impl fmt::Display for ContractValidationError {
70    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
71        write!(
72            formatter,
73            "Contract validation failed with {} violation(s)",
74            self.violations.len()
75        )
76    }
77}
78
79impl std::error::Error for ContractValidationError {}
80
81#[derive(Debug, Clone, PartialEq, Eq)]
82pub enum ContractJsonError {
83    MalformedJson(String),
84    InvalidContract(ContractValidationError),
85}
86
87impl fmt::Display for ContractJsonError {
88    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
89        match self {
90            Self::MalformedJson(message) => write!(formatter, "Malformed Contract JSON: {message}"),
91            Self::InvalidContract(error) => error.fmt(formatter),
92        }
93    }
94}
95
96impl std::error::Error for ContractJsonError {}