Skip to main content

dcp/protocol/
schema.rs

1//! Schema validation for DCP protocol.
2
3use crate::binary::ArgType;
4use crate::dispatch::SharedArgs;
5use crate::DCPError;
6
7/// Compile-time tool schema
8#[derive(Debug, Clone)]
9pub struct ToolSchema {
10    /// Tool name (for MCP compatibility)
11    pub name: &'static str,
12    /// Tool ID (for DCP native)
13    pub id: u16,
14    /// Description
15    pub description: &'static str,
16    /// Input schema
17    pub input: InputSchema,
18}
19
20/// Input parameter schema
21#[derive(Debug, Clone)]
22pub struct InputSchema {
23    /// Required fields bitmask (bit N = field N is required)
24    pub required: u64,
25    /// Field definitions
26    pub fields: Vec<FieldDef>,
27}
28
29/// Field definition
30#[derive(Debug, Clone)]
31pub struct FieldDef {
32    /// Field name
33    pub name: &'static str,
34    /// Field type
35    pub field_type: ArgType,
36    /// Offset in binary layout
37    pub offset: u16,
38    /// Size in bytes
39    pub size: u16,
40    /// For enum types: valid range (min, max)
41    pub enum_range: Option<(u8, u8)>,
42}
43
44/// Schema validator
45pub struct SchemaValidator;
46
47impl SchemaValidator {
48    /// Validate that all required fields are present
49    /// `present_mask` is a bitmask where bit N indicates field N is present
50    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    /// Validate an enum value is within range
59    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    /// Validate a complete input against schema
69    pub fn validate_input(
70        schema: &InputSchema,
71        present_mask: u64,
72        field_values: &[(usize, u8)], // (field_index, enum_value) for enum fields
73    ) -> Result<(), DCPError> {
74        // Check required fields
75        Self::validate_required(schema, present_mask)?;
76
77        // Check enum ranges
78        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    /// Validate shared binary arguments against a tool input schema.
90    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    /// Create a new input schema
146    pub fn new() -> Self {
147        Self {
148            required: 0,
149            fields: Vec::new(),
150        }
151    }
152
153    /// Add a field to the schema
154    pub fn add_field(&mut self, field: FieldDef) -> &mut Self {
155        self.fields.push(field);
156        self
157    }
158
159    /// Mark a field as required by index
160    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    /// Check if a field is required
168    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    /// Get the number of required fields
176    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    /// Create a new field definition
189    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    /// Create an enum field with valid range
200    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, // Enums are represented as integers
204            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        // All required present
222        assert!(SchemaValidator::validate_required(&schema, 0b101).is_ok());
223        assert!(SchemaValidator::validate_required(&schema, 0b111).is_ok());
224
225        // Missing field 0
226        assert_eq!(
227            SchemaValidator::validate_required(&schema, 0b100),
228            Err(DCPError::ValidationFailed)
229        );
230
231        // Missing field 2
232        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        // Valid input
265        assert!(SchemaValidator::validate_input(&schema, 0b11, &[(1, 2)]).is_ok());
266
267        // Missing required field
268        assert_eq!(
269            SchemaValidator::validate_input(&schema, 0b01, &[(1, 2)]),
270            Err(DCPError::ValidationFailed)
271        );
272
273        // Invalid enum value
274        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}