leptos_helios/chart/
validation.rs

1//! Chart Validation System
2//!
3//! This module provides validation functionality for chart specifications.
4
5use super::{
6    ChartSpec, DataReference, Encoding, Intelligence, MarkType, Selection,
7    Transform,
8};
9// use super::{DataType, EncodingDef}; // Currently unused
10use thiserror::Error;
11
12/// Validation error types
13#[derive(Debug, Error)]
14pub enum ValidationError {
15    #[error("Missing required field: {0}")]
16    MissingField(String),
17
18    #[error("Invalid field value: {0}")]
19    InvalidValue(String),
20
21    #[error("Incompatible encoding for mark type: {0}")]
22    IncompatibleEncoding(String),
23
24    #[error("Invalid data reference: {0}")]
25    InvalidDataReference(String),
26
27    #[error("Validation failed: {0}")]
28    ValidationFailed(String),
29}
30
31/// Chart validation system
32pub struct ChartValidator;
33
34impl ChartValidator {
35    /// Validate a complete chart specification
36    pub fn validate_spec(spec: &ChartSpec) -> Result<(), ValidationError> {
37        // Validate data reference
38        spec.data.validate()?;
39
40        // Validate encoding matches mark type
41        spec.encoding.validate_for_mark(&spec.mark)?;
42
43        // Validate transforms
44        for transform in &spec.transform {
45            transform.validate()?;
46        }
47
48        // Validate selections
49        for selection in &spec.selection {
50            selection.validate()?;
51        }
52
53        // Validate intelligence features
54        if let Some(intelligence) = &spec.intelligence {
55            intelligence.validate()?;
56        }
57
58        Ok(())
59    }
60}
61
62impl DataReference {
63    /// Validate data reference
64    pub fn validate(&self) -> Result<(), ValidationError> {
65        if self.source.is_empty() {
66            return Err(ValidationError::InvalidDataReference(
67                "Source cannot be empty".to_string(),
68            ));
69        }
70
71        Ok(())
72    }
73}
74
75impl Encoding {
76    /// Validate encoding for mark type
77    pub fn validate_for_mark(&self, mark: &MarkType) -> Result<(), ValidationError> {
78        match mark {
79            MarkType::Line { .. } | MarkType::Area { .. } => {
80                if self.x.is_none() || self.y.is_none() {
81                    return Err(ValidationError::IncompatibleEncoding(
82                        "Line and area charts require both x and y encodings".to_string(),
83                    ));
84                }
85            }
86            MarkType::Bar { .. } => {
87                if self.x.is_none() || self.y.is_none() {
88                    return Err(ValidationError::IncompatibleEncoding(
89                        "Bar charts require both x and y encodings".to_string(),
90                    ));
91                }
92            }
93            MarkType::Point { .. } | MarkType::Scatter { .. } => {
94                if self.x.is_none() || self.y.is_none() {
95                    return Err(ValidationError::IncompatibleEncoding(
96                        "Point and scatter charts require both x and y encodings".to_string(),
97                    ));
98                }
99            }
100            _ => {
101                // Other mark types have different requirements
102            }
103        }
104
105        Ok(())
106    }
107
108    /// Calculate encoding complexity
109    pub fn complexity(&self) -> f64 {
110        let mut complexity = 0.0;
111
112        if self.x.is_some() {
113            complexity += 1.0;
114        }
115        if self.y.is_some() {
116            complexity += 1.0;
117        }
118        if self.color.is_some() {
119            complexity += 1.5;
120        }
121        if self.size.is_some() {
122            complexity += 1.0;
123        }
124        if self.shape.is_some() {
125            complexity += 1.0;
126        }
127        if self.opacity.is_some() {
128            complexity += 0.5;
129        }
130        if self.text.is_some() {
131            complexity += 1.0;
132        }
133        if self.tooltip.is_some() {
134            complexity += 0.5;
135        }
136        if self.detail.is_some() {
137            complexity += 1.0;
138        }
139        if self.order.is_some() {
140            complexity += 0.5;
141        }
142        if self.row.is_some() {
143            complexity += 1.5;
144        }
145        if self.column.is_some() {
146            complexity += 1.5;
147        }
148
149        complexity
150    }
151
152    /// Optimize encoding for performance
153    pub fn optimize(self) -> Self {
154        // Remove unnecessary encodings and optimize scales
155        self
156    }
157}
158
159impl Transform {
160    /// Validate transform
161    pub fn validate(&self) -> Result<(), ValidationError> {
162        // Basic validation for transforms
163        Ok(())
164    }
165
166    /// Optimize transform
167    pub fn optimize(self) -> Self {
168        // Apply optimizations
169        self
170    }
171}
172
173impl Selection {
174    /// Validate selection
175    pub fn validate(&self) -> Result<(), ValidationError> {
176        // Basic validation for selections
177        Ok(())
178    }
179}
180
181impl Intelligence {
182    /// Validate intelligence features
183    pub fn validate(&self) -> Result<(), ValidationError> {
184        // Basic validation for intelligence features
185        Ok(())
186    }
187
188    /// Calculate intelligence complexity
189    pub fn complexity(&self) -> f64 {
190        // Calculate complexity based on enabled features
191        1.0
192    }
193}
194
195impl MarkType {
196    /// Calculate mark complexity
197    pub fn complexity(&self) -> f64 {
198        match self {
199            MarkType::Point { .. } => 1.0,
200            MarkType::Line { .. } => 2.0,
201            MarkType::Bar { .. } => 1.5,
202            MarkType::Area { .. } => 3.0,
203            MarkType::Text { .. } => 0.5,
204            MarkType::Rect { .. } => 1.0,
205            MarkType::Scatter { .. } => 1.5,
206            MarkType::BoxPlot { .. } => 2.5,
207            MarkType::Violin { .. } => 3.0,
208            MarkType::Heatmap { .. } => 2.0,
209            MarkType::Histogram { .. } => 1.5,
210            MarkType::Density { .. } => 2.5,
211            MarkType::Contour { .. } => 3.5,
212            MarkType::Radar { .. } => 2.0,
213            MarkType::Sankey { .. } => 4.0,
214            MarkType::Treemap { .. } => 3.0,
215            MarkType::Composite(ref marks) => marks.iter().map(|m| m.complexity()).sum(),
216            // Phase 3 Advanced Chart Types
217            MarkType::Point3D { .. } => 4.0,
218            MarkType::Surface3D { .. } => 5.0,
219            MarkType::Choropleth { .. } => 3.5,
220            MarkType::NetworkGraph { .. } => 4.5,
221            MarkType::DotMap { .. } => 3.0,
222            MarkType::FlowMap { .. } => 4.0,
223        }
224    }
225}