Skip to main content

xbrl_rs/validation/
mod.rs

1//! Validation of XBRL instance documents against a taxonomy.
2
3mod calculation;
4mod dimension;
5mod schema;
6mod value;
7
8use crate::{InstanceDocument, TaxonomySet};
9
10/// Severity level of a validation message.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum Severity {
13    /// A violation of a MUST-level XBRL rule. The instance is non-conformant.
14    Error,
15    /// A calculation inconsistency or soft check. The instance may still be
16    /// technically valid but is likely incorrect.
17    Warning,
18}
19
20/// A single validation finding.
21#[derive(Debug, Clone)]
22pub struct ValidationMessage {
23    /// Error or Warning.
24    pub severity: Severity,
25    /// Machine-readable rule identifier (e.g., "schema.concept_not_found").
26    pub code: String,
27    /// Human-readable explanation.
28    pub message: String,
29    /// The concept name of the fact that triggered this, if applicable.
30    pub concept_name: Option<String>,
31    /// The context ref of the fact that triggered this, if applicable.
32    pub context_ref: Option<String>,
33}
34
35/// Collected results of validating an instance document.
36#[derive(Debug, Default)]
37pub struct ValidationResult {
38    pub messages: Vec<ValidationMessage>,
39}
40
41impl ValidationResult {
42    pub fn new() -> Self {
43        Self::default()
44    }
45
46    /// All messages with Error severity.
47    pub fn errors(&self) -> Vec<&ValidationMessage> {
48        self.messages
49            .iter()
50            .filter(|m| m.severity == Severity::Error)
51            .collect()
52    }
53
54    /// All messages with Warning severity.
55    pub fn warnings(&self) -> Vec<&ValidationMessage> {
56        self.messages
57            .iter()
58            .filter(|m| m.severity == Severity::Warning)
59            .collect()
60    }
61
62    /// True when no errors were found (warnings are acceptable).
63    pub fn is_valid(&self) -> bool {
64        !self.messages.iter().any(|m| m.severity == Severity::Error)
65    }
66
67    fn add(
68        &mut self,
69        severity: Severity,
70        code: &str,
71        message: String,
72        concept_name: Option<String>,
73        context_ref: Option<String>,
74    ) {
75        self.messages.push(ValidationMessage {
76            severity,
77            code: code.to_string(),
78            message,
79            concept_name,
80            context_ref,
81        });
82    }
83}
84
85/// Run all validation checks on schema level and linkbase level (i.e.,
86/// calculation, and dimension).
87pub fn validate_all(instance: &InstanceDocument, taxonomy: &TaxonomySet) -> ValidationResult {
88    let mut result = ValidationResult::new();
89    let prepared = value::prepare_fact_values(instance, taxonomy, &mut result);
90    schema::validate_schema(instance, taxonomy, &prepared, &mut result);
91    calculation::validate_calculations(instance, taxonomy, &prepared, &mut result);
92    dimension::validate_dimensions(instance, taxonomy, &mut result);
93    result
94}