1use crate::binary::ArgType;
4use crate::dispatch::SharedArgs;
5use crate::DCPError;
6
7#[derive(Debug, Clone)]
9pub struct ToolSchema {
10 pub name: &'static str,
12 pub id: u16,
14 pub description: &'static str,
16 pub input: InputSchema,
18}
19
20#[derive(Debug, Clone)]
22pub struct InputSchema {
23 pub required: u64,
25 pub fields: Vec<FieldDef>,
27}
28
29#[derive(Debug, Clone)]
31pub struct FieldDef {
32 pub name: &'static str,
34 pub field_type: ArgType,
36 pub offset: u16,
38 pub size: u16,
40 pub enum_range: Option<(u8, u8)>,
42}
43
44pub struct SchemaValidator;
46
47impl SchemaValidator {
48 pub fn validate_required(schema: &InputSchema, present_mask: u64) -> Result<(), DCPError> {
51 let missing = schema.required & !present_mask;
52 if missing != 0 {
53 return Err(DCPError::ValidationFailed);
54 }
55 Ok(())
56 }
57
58 pub fn validate_enum(field: &FieldDef, value: u8) -> Result<(), DCPError> {
60 if let Some((min, max)) = field.enum_range {
61 if value < min || value > max {
62 return Err(DCPError::ValidationFailed);
63 }
64 }
65 Ok(())
66 }
67
68 pub fn validate_input(
70 schema: &InputSchema,
71 present_mask: u64,
72 field_values: &[(usize, u8)], ) -> Result<(), DCPError> {
74 Self::validate_required(schema, present_mask)?;
76
77 for &(field_idx, value) in field_values {
79 let field = schema
80 .fields
81 .get(field_idx)
82 .ok_or(DCPError::ValidationFailed)?;
83 Self::validate_enum(field, value)?;
84 }
85
86 Ok(())
87 }
88
89 pub fn validate_shared_args(schema: &InputSchema, args: &SharedArgs) -> Result<(), DCPError> {
91 let mut present_mask = 0u64;
92 let mut declared_data_len = 0usize;
93
94 for (idx, field) in schema.fields.iter().enumerate() {
95 if idx >= 16 {
96 return Err(DCPError::ValidationFailed);
97 }
98
99 let actual_type = arg_type_from_layout(args.layout(), idx)?;
100 if actual_type == ArgType::Null {
101 continue;
102 }
103
104 if actual_type != field.field_type {
105 return Err(DCPError::ValidationFailed);
106 }
107
108 let offset = field.offset as usize;
109 let size = field.size as usize;
110 let end = offset.checked_add(size).ok_or(DCPError::OutOfBounds)?;
111 if end > args.data().len() {
112 return Err(DCPError::OutOfBounds);
113 }
114 declared_data_len = declared_data_len.max(end);
115
116 if field.enum_range.is_some() {
117 let value = *args.data().get(offset).ok_or(DCPError::OutOfBounds)?;
118 Self::validate_enum(field, value)?;
119 }
120
121 present_mask |= 1 << idx;
122 }
123
124 for idx in schema.fields.len()..16 {
125 if arg_type_from_layout(args.layout(), idx)? != ArgType::Null {
126 return Err(DCPError::ValidationFailed);
127 }
128 }
129
130 if args.data().len() > declared_data_len {
131 return Err(DCPError::ValidationFailed);
132 }
133
134 Self::validate_required(schema, present_mask)
135 }
136}
137
138fn arg_type_from_layout(layout: u64, index: usize) -> Result<ArgType, DCPError> {
139 let shift = index.checked_mul(4).ok_or(DCPError::ValidationFailed)?;
140 let type_bits = ((layout >> shift) & 0xF) as u8;
141 ArgType::from_u8(type_bits).ok_or(DCPError::ValidationFailed)
142}
143
144impl InputSchema {
145 pub fn new() -> Self {
147 Self {
148 required: 0,
149 fields: Vec::new(),
150 }
151 }
152
153 pub fn add_field(&mut self, field: FieldDef) -> &mut Self {
155 self.fields.push(field);
156 self
157 }
158
159 pub fn set_required(&mut self, field_index: usize) -> &mut Self {
161 if field_index < 64 {
162 self.required |= 1 << field_index;
163 }
164 self
165 }
166
167 pub fn is_required(&self, field_index: usize) -> bool {
169 if field_index >= 64 {
170 return false;
171 }
172 self.required & (1 << field_index) != 0
173 }
174
175 pub fn required_count(&self) -> u32 {
177 self.required.count_ones()
178 }
179}
180
181impl Default for InputSchema {
182 fn default() -> Self {
183 Self::new()
184 }
185}
186
187impl FieldDef {
188 pub fn new(name: &'static str, field_type: ArgType, offset: u16, size: u16) -> Self {
190 Self {
191 name,
192 field_type,
193 offset,
194 size,
195 enum_range: None,
196 }
197 }
198
199 pub fn new_enum(name: &'static str, offset: u16, size: u16, min: u8, max: u8) -> Self {
201 Self {
202 name,
203 field_type: ArgType::I32, offset,
205 size,
206 enum_range: Some((min, max)),
207 }
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 #[test]
216 fn test_required_validation() {
217 let mut schema = InputSchema::new();
218 schema.set_required(0);
219 schema.set_required(2);
220
221 assert!(SchemaValidator::validate_required(&schema, 0b101).is_ok());
223 assert!(SchemaValidator::validate_required(&schema, 0b111).is_ok());
224
225 assert_eq!(
227 SchemaValidator::validate_required(&schema, 0b100),
228 Err(DCPError::ValidationFailed)
229 );
230
231 assert_eq!(
233 SchemaValidator::validate_required(&schema, 0b001),
234 Err(DCPError::ValidationFailed)
235 );
236 }
237
238 #[test]
239 fn test_enum_validation() {
240 let field = FieldDef::new_enum("status", 0, 1, 1, 5);
241
242 assert!(SchemaValidator::validate_enum(&field, 1).is_ok());
243 assert!(SchemaValidator::validate_enum(&field, 3).is_ok());
244 assert!(SchemaValidator::validate_enum(&field, 5).is_ok());
245
246 assert_eq!(
247 SchemaValidator::validate_enum(&field, 0),
248 Err(DCPError::ValidationFailed)
249 );
250 assert_eq!(
251 SchemaValidator::validate_enum(&field, 6),
252 Err(DCPError::ValidationFailed)
253 );
254 }
255
256 #[test]
257 fn test_complete_validation() {
258 let mut schema = InputSchema::new();
259 schema.add_field(FieldDef::new("name", ArgType::String, 0, 32));
260 schema.add_field(FieldDef::new_enum("type", 32, 1, 1, 3));
261 schema.set_required(0);
262 schema.set_required(1);
263
264 assert!(SchemaValidator::validate_input(&schema, 0b11, &[(1, 2)]).is_ok());
266
267 assert_eq!(
269 SchemaValidator::validate_input(&schema, 0b01, &[(1, 2)]),
270 Err(DCPError::ValidationFailed)
271 );
272
273 assert_eq!(
275 SchemaValidator::validate_input(&schema, 0b11, &[(1, 5)]),
276 Err(DCPError::ValidationFailed)
277 );
278 }
279
280 #[test]
281 fn test_schema_helpers() {
282 let mut schema = InputSchema::new();
283 schema.set_required(0);
284 schema.set_required(3);
285
286 assert!(schema.is_required(0));
287 assert!(!schema.is_required(1));
288 assert!(!schema.is_required(2));
289 assert!(schema.is_required(3));
290 assert_eq!(schema.required_count(), 2);
291 }
292}