1use std::collections::{BTreeMap, BTreeSet};
9
10use crate::{
11 AnchorRef, DocError, Document, EntityId, GroupId, ParamRef, ParamSlot, PropValue, RuleSpec,
12 builtin::{builtin_shape_kind, get_geometry_param, is_builtin, types},
13};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub struct Limits {
18 pub max_entities: usize,
20 pub max_constraints: usize,
22 pub max_layers: usize,
24 pub max_groups: usize,
26 pub max_props: usize,
28 pub max_string: usize,
30}
31
32impl Default for Limits {
33 fn default() -> Self {
34 Self {
35 max_entities: 1_000_000,
36 max_constraints: 1_000_000,
37 max_layers: 10_000,
38 max_groups: 100_000,
39 max_props: 1_000,
40 max_string: 100_000,
41 }
42 }
43}
44
45fn builtin_anchor_exists(doc: &Document, a: &AnchorRef) -> Option<bool> {
47 let e = doc.entity(a.entity)?;
48 if !is_builtin(&e.type_id) || e.type_id.as_str() == types::DIMENSION {
49 return None;
50 }
51 Some(e.geometry.as_ref().is_some_and(|g| g.anchor(&a.anchor).is_some()))
52}
53
54fn check_anchor(doc: &Document, a: &AnchorRef, out: &mut Vec<DocError>) {
55 match doc.entity(a.entity) {
56 None => out.push(DocError::BrokenReference { from: "constraint".into(), target: a.entity.to_string() }),
57 Some(_) => {
58 if builtin_anchor_exists(doc, a) == Some(false) {
59 out.push(DocError::MissingAnchor { entity: a.entity, anchor: a.anchor.clone() });
60 }
61 }
62 }
63}
64
65fn check_param(doc: &Document, p: &ParamRef, out: &mut Vec<DocError>) {
66 let Some(e) = doc.entity(p.entity) else {
67 out.push(DocError::BrokenReference { from: "constraint".into(), target: p.entity.to_string() });
68 return;
69 };
70 match &p.slot {
71 ParamSlot::Geom(name) => {
72 let ok = e.geometry.as_ref().is_some_and(|g| get_geometry_param(g, name).is_some());
73 if !ok {
74 out.push(DocError::MissingParam { entity: p.entity, param: name.clone() });
75 }
76 }
77 ParamSlot::Prop(name) => {
78 if !matches!(e.props.get(name), Some(PropValue::Number(_))) {
79 out.push(DocError::MissingParam { entity: p.entity, param: name.clone() });
80 }
81 }
82 }
83}
84
85fn check_circle(doc: &Document, id: EntityId, out: &mut Vec<DocError>) {
86 match doc.entity(id) {
87 None => out.push(DocError::BrokenReference { from: "constraint".into(), target: id.to_string() }),
88 Some(e) if is_builtin(&e.type_id) => {
89 let ok = matches!(e.type_id.as_str(), types::CIRCLE | types::ARC);
90 if !ok {
91 out.push(DocError::InvalidValue(format!("{id} is not a circle or arc")));
92 }
93 }
94 Some(_) => {}
96 }
97}
98
99impl Document {
100 pub fn validate(&self) -> Result<(), DocError> {
102 match self.validate_all(&Limits::default()).into_iter().next() {
103 Some(e) => Err(e),
104 None => Ok(()),
105 }
106 }
107
108 #[must_use]
110 pub fn validate_all(&self, limits: &Limits) -> Vec<DocError> {
111 let mut out = Vec::new();
112 self.check_limits(limits, &mut out);
113 self.check_ids(&mut out);
114 self.check_entities(limits, &mut out);
115 self.check_groups(&mut out);
116 self.check_constraints(&mut out);
117 out.truncate(1000);
118 out
119 }
120
121 fn check_limits(&self, l: &Limits, out: &mut Vec<DocError>) {
122 if self.entity_count() > l.max_entities {
123 out.push(DocError::LimitExceeded(format!("{} entities > {}", self.entity_count(), l.max_entities)));
124 }
125 if self.constraint_count() > l.max_constraints {
126 out.push(DocError::LimitExceeded(format!(
127 "{} constraints > {}",
128 self.constraint_count(),
129 l.max_constraints
130 )));
131 }
132 if self.layers().len() > l.max_layers {
133 out.push(DocError::LimitExceeded(format!("{} layers > {}", self.layers().len(), l.max_layers)));
134 }
135 if self.groups().count() > l.max_groups {
136 out.push(DocError::LimitExceeded(format!("too many groups (> {})", l.max_groups)));
137 }
138 if self.meta.title.len() > l.max_string {
139 out.push(DocError::LimitExceeded("title too long".into()));
140 }
141 }
142
143 fn check_ids(&self, out: &mut Vec<DocError>) {
144 let mut seen: BTreeMap<u64, &'static str> = BTreeMap::new();
146 let mut add = |raw: u64, kind: &'static str, out: &mut Vec<DocError>| {
147 if raw >= self.next_id() {
148 out.push(DocError::InvalidValue(format!("{kind} id {raw} is not below nextId {}", self.next_id())));
149 }
150 if let Some(prev) = seen.insert(raw, kind) {
151 out.push(DocError::DuplicateId(format!("{raw} used by {prev} and {kind}")));
152 }
153 };
154 for l in self.layers() {
155 add(l.id.0, "layer", out);
156 }
157 for e in self.entities() {
158 add(e.id.0, "entity", out);
159 }
160 for g in self.groups() {
161 add(g.id.0, "group", out);
162 }
163 for c in self.constraints() {
164 add(c.id.0, "constraint", out);
165 }
166 let order: BTreeSet<EntityId> = self.order().iter().copied().collect();
167 if order.len() != self.order().len() || order.len() != self.entity_count() {
168 out.push(DocError::BadOrder("draw order is not a permutation of the entities".into()));
169 }
170 }
171
172 fn check_entities(&self, l: &Limits, out: &mut Vec<DocError>) {
173 for e in self.entities() {
174 if self.layer(e.layer).is_none() {
175 out.push(DocError::UnknownLayer(e.layer));
176 }
177 if !e.transform.is_finite() || e.transform.inverse().is_err() {
178 out.push(DocError::InvalidValue(format!("{} has a non-finite or singular transform", e.id)));
179 }
180 if let Some(kind) = builtin_shape_kind(&e.type_id) {
181 match &e.geometry {
182 None => out.push(DocError::InvalidGeometry { entity: e.id, reason: "missing geometry".into() }),
183 Some(g) if g.kind() != kind => out.push(DocError::InvalidGeometry {
184 entity: e.id,
185 reason: format!(
186 "type {} requires {} geometry, found {}",
187 e.type_id,
188 kind.name(),
189 g.kind().name()
190 ),
191 }),
192 Some(g) => {
193 if let Err(err) = g.validate() {
194 out.push(DocError::InvalidGeometry { entity: e.id, reason: err.to_string() });
195 }
196 }
197 }
198 } else if let Some(g) = &e.geometry
199 && let Err(err) = g.validate()
200 {
201 out.push(DocError::InvalidGeometry { entity: e.id, reason: err.to_string() });
202 }
203 if e.props.len() > l.max_props {
204 out.push(DocError::LimitExceeded(format!("{} has more than {} properties", e.id, l.max_props)));
205 }
206 for (k, v) in &e.props {
207 if k.len() > 256 {
208 out.push(DocError::LimitExceeded(format!("{} property name too long", e.id)));
209 }
210 if !v.is_finite() {
211 out.push(DocError::InvalidValue(format!("{}.{k} is not finite", e.id)));
212 }
213 if let PropValue::Text(t) = v
214 && t.len() > l.max_string
215 {
216 out.push(DocError::LimitExceeded(format!("{}.{k} text too long", e.id)));
217 }
218 match v {
219 PropValue::Ref(r) if self.entity(r.entity).is_none() => {
220 out.push(DocError::BrokenReference {
221 from: format!("{}.{k}", e.id),
222 target: r.entity.to_string(),
223 });
224 }
225 PropValue::Anchor(a) => {
226 if self.entity(a.entity).is_none() {
227 out.push(DocError::BrokenReference {
228 from: format!("{}.{k}", e.id),
229 target: a.entity.to_string(),
230 });
231 } else if builtin_anchor_exists(self, a) == Some(false) {
232 out.push(DocError::MissingAnchor { entity: a.entity, anchor: a.anchor.clone() });
233 }
234 }
235 _ => {}
236 }
237 }
238 if e.name.as_ref().is_some_and(|n| n.len() > l.max_string) {
239 out.push(DocError::LimitExceeded(format!("{} name too long", e.id)));
240 }
241 if let Err(err) = e.style.validate() {
242 out.push(err);
243 }
244 for f in e.fallback.iter().flatten() {
245 if let Err(err) = f.validate() {
246 out.push(DocError::InvalidGeometry { entity: e.id, reason: format!("fallback: {err}") });
247 }
248 }
249 }
250 }
251
252 fn check_groups(&self, out: &mut Vec<DocError>) {
253 let mut entity_parent: BTreeMap<EntityId, GroupId> = BTreeMap::new();
254 let mut group_parent: BTreeMap<GroupId, GroupId> = BTreeMap::new();
255 for g in self.groups() {
256 for m in &g.members {
257 if self.entity(*m).is_none() {
258 out.push(DocError::BrokenReference { from: g.id.to_string(), target: m.to_string() });
259 }
260 if entity_parent.insert(*m, g.id).is_some() {
261 out.push(DocError::MultipleParents(m.to_string()));
262 }
263 }
264 for c in &g.children {
265 if self.group(*c).is_none() {
266 out.push(DocError::UnknownGroup(*c));
267 }
268 if group_parent.insert(*c, g.id).is_some() {
269 out.push(DocError::MultipleParents(c.to_string()));
270 }
271 }
272 }
273 for g in self.groups() {
275 let mut cur = g.id;
276 let mut steps = 0usize;
277 while let Some(p) = group_parent.get(&cur) {
278 if *p == g.id || steps > group_parent.len() {
279 out.push(DocError::GroupCycle(g.id));
280 break;
281 }
282 cur = *p;
283 steps += 1;
284 }
285 }
286 }
287
288 fn check_constraints(&self, out: &mut Vec<DocError>) {
289 for c in self.constraints() {
290 if !c.rule.values_valid() {
291 out.push(DocError::InvalidValue(format!("{} ({}) has invalid values", c.id, c.rule.kind_name())));
292 }
293 if let Some(owner) = c.owner
294 && self.entity(owner).is_none()
295 {
296 out.push(DocError::BrokenReference { from: c.id.to_string(), target: owner.to_string() });
297 }
298 for a in c.rule.anchors() {
299 check_anchor(self, a, out);
300 }
301 for p in c.rule.params() {
302 check_param(self, p, out);
303 }
304 match &c.rule {
305 RuleSpec::PointOnCircle { circle, .. }
306 | RuleSpec::Radius { circle, .. }
307 | RuleSpec::TangentLineCircle { circle, .. } => check_circle(self, *circle, out),
308 RuleSpec::Concentric { a, b }
309 | RuleSpec::EqualRadius { a, b }
310 | RuleSpec::TangentCircles { a, b, .. } => {
311 check_circle(self, *a, out);
312 check_circle(self, *b, out);
313 }
314 RuleSpec::Expression { entity, .. } if self.entity(*entity).is_none() => {
315 out.push(DocError::BrokenReference { from: c.id.to_string(), target: entity.to_string() });
316 }
317 _ => {}
318 }
319 }
320 }
321}