1use crate::ast::*;
2use std::collections::HashSet;
3
4#[derive(Debug, Clone)]
5pub struct ValidationError {
6 pub message: String,
7 pub path: String,
8}
9
10pub struct Validator {
11 errors: Vec<ValidationError>,
12}
13
14impl Default for Validator {
15 fn default() -> Self {
16 Self::new()
17 }
18}
19
20impl Validator {
21 pub fn new() -> Self {
22 Self { errors: Vec::new() }
23 }
24
25 pub fn validate(mut self, doc: &CadlDocument) -> Result<(), Vec<ValidationError>> {
26 self.doc(doc);
27 if self.errors.is_empty() {
28 Ok(())
29 } else {
30 Err(self.errors)
31 }
32 }
33
34 fn push_error(&mut self, message: String, path: &str) {
35 self.errors.push(ValidationError {
36 message,
37 path: path.to_string(),
38 });
39 }
40
41 fn doc(&mut self, doc: &CadlDocument) {
42 let mut interface_names = HashSet::new();
43 for iface in &doc.interfaces {
44 if !interface_names.insert(&iface.name) {
45 self.push_error(format!("Duplicate interface name: {}", iface.name), "root");
46 }
47 self.interface(iface);
48 }
49
50 for (i, imp) in doc.implementations.iter().enumerate() {
51 self.implementation(imp, i);
52 }
53
54 for (i, con) in doc.constraints.iter().enumerate() {
55 self.constraint(con, i);
56 }
57 }
58
59 fn interface(&mut self, iface: &InterfaceDef) {
60 let mut method_names = HashSet::new();
61 for method in &iface.methods {
62 if !method_names.insert(&method.name) {
63 self.push_error(format!("Duplicate method name: {}", method.name), &iface.name);
64 }
65 }
66
67 for ann in &iface.annotations {
68 match ann {
69 Annotation::Contract(c) => self.contract(c, &iface.name),
70 Annotation::Effects(e) => self.effects(e, &iface.name),
71 Annotation::Permissions(p) => self.permissions(p, &iface.name),
72 Annotation::Resources(r) => self.resources(r, &iface.name),
73 Annotation::DataFormat(df) => self.data_format(df, &iface.name),
74 Annotation::Protocol(proto) => self.protocol(proto, &iface.name),
75 Annotation::Numerical(num) => self.numerical(num, &iface.name),
76 Annotation::Observability(obs) => self.observability(obs, &iface.name),
77 Annotation::Abi(abi) => self.abi(abi, &iface.name),
78 Annotation::Unknown(name, _) => {
79 tracing::debug!("Unknown annotation: {} on interface {}", name, iface.name);
81 }
82 }
83 }
84 }
85
86 fn implementation(&mut self, imp: &ImplDef, index: usize) {
87 if imp.name.is_empty() {
88 self.push_error(format!("Implementation at index {} has no name", index), "implementations");
89 }
90 if !imp.name.contains('.') {
92 self.push_error(format!("Implementation name '{}' should be in 'Interface.Implementation' format", imp.name), &imp.name);
93 }
94 }
95
96 fn constraint(&mut self, con: &ConstraintDef, index: usize) {
97 if con.rules.is_empty() && con.other.is_empty() {
98 self.push_error(format!("Constraint at index {} is empty", index), "constraints");
99 }
100 }
101
102 fn contract(&mut self, c: &ContractDef, context: &str) {
103 if c.codec.is_none() && c.other.is_empty() {
104 self.push_error("Contract missing descriptive fields (codec, etc.)".to_string(), context);
105 }
106 }
107
108 fn effects(&mut self, e: &EffectsDef, context: &str) {
109 if let Some(c) = &e.concurrency {
110 let valid = ["thread_safe", "single_threaded", "immutable", "reentrant"];
111 if !valid.contains(&c.as_str()) {
112 self.push_error(format!("Invalid concurrency value: {}", c), context);
113 }
114 }
115 }
116
117 fn permissions(&mut self, p: &PermissionsDef, context: &str) {
118 if p.allow.is_empty() && p.deny.is_empty() && p.sandbox.is_empty() {
119 self.push_error("Permissions annotation is empty".to_string(), context);
120 }
121 }
122
123 fn resources(&mut self, r: &ResourcesDef, context: &str) {
124 for (k, v) in &r.memory {
125 if k == "peak" && v.is_empty() {
126 self.push_error("Resource memory peak cannot be empty".to_string(), context);
127 }
128 }
129 }
130
131 fn data_format(&mut self, _df: &DataFormatDef, _context: &str) {
132 }
134
135 fn protocol(&mut self, proto: &ProtocolDef, context: &str) {
136 if !proto.states.is_empty() && proto.initial.is_none() {
137 self.push_error("Protocol with states must have an initial state".to_string(), context);
138 }
139 if let Some(initial) = &proto.initial {
140 if !proto.states.contains(initial) {
141 self.push_error(format!("Initial state '{}' not found in states list", initial), context);
142 }
143 }
144 }
145
146 fn numerical(&mut self, _num: &NumericalDef, _context: &str) {
147 }
149
150 fn observability(&mut self, _obs: &ObservabilityDef, _context: &str) {
151 }
153
154 fn abi(&mut self, abi: &AbiDef, context: &str) {
155 if let Some(encoding) = &abi.string_encoding {
156 let valid = ["utf8", "utf16", "ascii", "cesu8"];
157 if !valid.contains(&encoding.as_str()) {
158 self.push_error(format!("Invalid string encoding: {}", encoding), context);
159 }
160 }
161 }
162}
163