1use super::{
4 SectionType,
5 schema::{
6 ContextSection, DrvHeader, PersonaSection, RuleEntry, StandardsSection, WorkflowSection,
7 },
8};
9use crate::{DrivenError, Result, parser::UnifiedRule};
10use bytes::{BufMut, BytesMut};
11use std::collections::HashMap;
12
13#[derive(Debug)]
15pub struct DrvEncoder {
16 string_table: Vec<String>,
18 string_map: HashMap<String, u32>,
20 buffer: BytesMut,
22}
23
24impl DrvEncoder {
25 pub fn new() -> Self {
27 Self {
28 string_table: Vec::new(),
29 string_map: HashMap::new(),
30 buffer: BytesMut::with_capacity(4096),
31 }
32 }
33
34 fn intern(&mut self, s: &str) -> u32 {
36 if let Some(&idx) = self.string_map.get(s) {
37 return idx;
38 }
39 let idx = self.string_table.len() as u32;
40 self.string_table.push(s.to_string());
41 self.string_map.insert(s.to_string(), idx);
42 idx
43 }
44
45 pub fn encode(&self, rules: &[UnifiedRule]) -> Result<Vec<u8>> {
47 let mut encoder = DrvEncoder::new();
48 encoder.encode_rules(rules)
49 }
50
51 fn encode_rules(&mut self, rules: &[UnifiedRule]) -> Result<Vec<u8>> {
53 let mut persona: Option<PersonaSection> = None;
55 let mut standards = StandardsSection::default();
56 let mut context = ContextSection::default();
57 let mut workflow: Option<WorkflowSection> = None;
58
59 for rule in rules {
60 match rule {
61 UnifiedRule::Persona {
62 name,
63 role,
64 identity,
65 style,
66 traits,
67 principles,
68 } => {
69 persona = Some(PersonaSection {
70 name_idx: self.intern(name),
71 role_idx: self.intern(role),
72 identity_idx: identity.as_ref().map(|s| self.intern(s)).unwrap_or(0),
73 style_idx: style.as_ref().map(|s| self.intern(s)).unwrap_or(0),
74 traits: traits.iter().map(|t| self.intern(t)).collect(),
75 principles: principles.iter().map(|p| self.intern(p)).collect(),
76 });
77 }
78 UnifiedRule::Standard {
79 category,
80 priority,
81 description,
82 pattern,
83 } => {
84 standards.rules.push(RuleEntry {
85 category: *category,
86 priority: *priority,
87 description_idx: self.intern(description),
88 pattern_idx: pattern.as_ref().map(|p| self.intern(p)).unwrap_or(0),
89 });
90 }
91 UnifiedRule::Context {
92 includes,
93 excludes,
94 focus,
95 } => {
96 context.include_patterns = includes.iter().map(|s| self.intern(s)).collect();
97 context.exclude_patterns = excludes.iter().map(|s| self.intern(s)).collect();
98 context.focus_areas = focus.iter().map(|s| self.intern(s)).collect();
99 }
100 UnifiedRule::Workflow { name, steps } => {
101 let mut ws = WorkflowSection {
102 name_idx: self.intern(name),
103 steps: Vec::new(),
104 };
105 for step in steps {
106 ws.steps.push(super::schema::WorkflowStep {
107 name_idx: self.intern(&step.name),
108 description_idx: self.intern(&step.description),
109 condition_idx: step
110 .condition
111 .as_ref()
112 .map(|c| self.intern(c))
113 .unwrap_or(0),
114 actions: step.actions.iter().map(|a| self.intern(a)).collect(),
115 });
116 }
117 workflow = Some(ws);
118 }
119 UnifiedRule::Raw { content } => {
120 standards.rules.push(RuleEntry {
122 category: super::schema::RuleCategory::Other,
123 priority: 100,
124 description_idx: self.intern(content),
125 pattern_idx: 0,
126 });
127 }
128 }
129 }
130
131 let mut section_count = 1; if persona.is_some() {
134 section_count += 1;
135 }
136 if !standards.rules.is_empty() {
137 section_count += 1;
138 }
139 if !context.include_patterns.is_empty() || !context.exclude_patterns.is_empty() {
140 section_count += 1;
141 }
142 if workflow.is_some() {
143 section_count += 1;
144 }
145
146 let header = DrvHeader::new(section_count);
148 self.buffer.put_slice(bytemuck::bytes_of(&header));
149
150 self.write_string_table()?;
152
153 if let Some(ref p) = persona {
155 self.write_persona_section(p)?;
156 }
157 if !standards.rules.is_empty() {
158 self.write_standards_section(&standards)?;
159 }
160 if !context.include_patterns.is_empty() || !context.exclude_patterns.is_empty() {
161 self.write_context_section(&context)?;
162 }
163 if let Some(ref w) = workflow {
164 self.write_workflow_section(w)?;
165 }
166
167 let checksum = self.calculate_checksum();
169 let header_bytes = &mut self.buffer[..16];
170 header_bytes[12..16].copy_from_slice(&checksum.to_le_bytes());
171
172 Ok(self.buffer.to_vec())
173 }
174
175 fn write_string_table(&mut self) -> Result<()> {
176 self.buffer.put_u8(SectionType::StringTable as u8);
177 self.buffer.put_u32_le(self.string_table.len() as u32);
178
179 for s in &self.string_table {
180 let bytes = s.as_bytes();
181 if bytes.len() > u16::MAX as usize {
182 return Err(DrivenError::Format(format!(
183 "String too long: {} bytes",
184 bytes.len()
185 )));
186 }
187 self.buffer.put_u16_le(bytes.len() as u16);
188 self.buffer.put_slice(bytes);
189 }
190
191 Ok(())
192 }
193
194 fn write_persona_section(&mut self, persona: &PersonaSection) -> Result<()> {
195 self.buffer.put_u8(SectionType::Persona as u8);
196 self.buffer.put_u32_le(persona.name_idx);
197 self.buffer.put_u32_le(persona.role_idx);
198 self.buffer.put_u32_le(persona.identity_idx);
199 self.buffer.put_u32_le(persona.style_idx);
200
201 self.buffer.put_u16_le(persona.traits.len() as u16);
202 for &idx in &persona.traits {
203 self.buffer.put_u32_le(idx);
204 }
205
206 self.buffer.put_u16_le(persona.principles.len() as u16);
207 for &idx in &persona.principles {
208 self.buffer.put_u32_le(idx);
209 }
210
211 Ok(())
212 }
213
214 fn write_standards_section(&mut self, standards: &StandardsSection) -> Result<()> {
215 self.buffer.put_u8(SectionType::Standards as u8);
216 self.buffer.put_u32_le(standards.rules.len() as u32);
217
218 for rule in &standards.rules {
219 self.buffer.put_u8(rule.category as u8);
220 self.buffer.put_u8(rule.priority);
221 self.buffer.put_u32_le(rule.description_idx);
222 self.buffer.put_u32_le(rule.pattern_idx);
223 }
224
225 Ok(())
226 }
227
228 fn write_context_section(&mut self, context: &ContextSection) -> Result<()> {
229 self.buffer.put_u8(SectionType::Context as u8);
230
231 self.buffer
232 .put_u16_le(context.include_patterns.len() as u16);
233 for &idx in &context.include_patterns {
234 self.buffer.put_u32_le(idx);
235 }
236
237 self.buffer
238 .put_u16_le(context.exclude_patterns.len() as u16);
239 for &idx in &context.exclude_patterns {
240 self.buffer.put_u32_le(idx);
241 }
242
243 self.buffer.put_u16_le(context.focus_areas.len() as u16);
244 for &idx in &context.focus_areas {
245 self.buffer.put_u32_le(idx);
246 }
247
248 self.buffer.put_u16_le(context.dependencies.len() as u16);
249 for &(name, version) in &context.dependencies {
250 self.buffer.put_u32_le(name);
251 self.buffer.put_u32_le(version);
252 }
253
254 Ok(())
255 }
256
257 fn write_workflow_section(&mut self, workflow: &WorkflowSection) -> Result<()> {
258 self.buffer.put_u8(SectionType::Workflow as u8);
259 self.buffer.put_u32_le(workflow.name_idx);
260 self.buffer.put_u16_le(workflow.steps.len() as u16);
261
262 for step in &workflow.steps {
263 self.buffer.put_u32_le(step.name_idx);
264 self.buffer.put_u32_le(step.description_idx);
265 self.buffer.put_u32_le(step.condition_idx);
266 self.buffer.put_u16_le(step.actions.len() as u16);
267 for &idx in &step.actions {
268 self.buffer.put_u32_le(idx);
269 }
270 }
271
272 Ok(())
273 }
274
275 fn calculate_checksum(&self) -> u32 {
276 let hash = blake3::hash(&self.buffer[16..]);
277 let bytes = hash.as_bytes();
278 u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
279 }
280}
281
282impl Default for DrvEncoder {
283 fn default() -> Self {
284 Self::new()
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291
292 #[test]
293 fn test_encoder_new() {
294 let encoder = DrvEncoder::new();
295 assert!(encoder.string_table.is_empty());
296 assert!(encoder.string_map.is_empty());
297 }
298
299 #[test]
300 fn test_string_interning() {
301 let mut encoder = DrvEncoder::new();
302 let idx1 = encoder.intern("hello");
303 let idx2 = encoder.intern("world");
304 let idx3 = encoder.intern("hello"); assert_eq!(idx1, 0);
307 assert_eq!(idx2, 1);
308 assert_eq!(idx3, 0); assert_eq!(encoder.string_table.len(), 2);
310 }
311
312 #[test]
313 fn test_encode_empty() {
314 let encoder = DrvEncoder::new();
315 let result = encoder.encode(&[]).unwrap();
316
317 assert!(result.len() >= 16);
319 assert_eq!(&result[0..4], b"DRV\0");
321 }
322}
323
324#[cfg(test)]
325mod prop_tests {
326 use super::*;
327 use crate::format::DrvDecoder;
328 use crate::format::schema::RuleCategory;
329 use proptest::prelude::*;
330
331 fn arb_rule_category() -> impl Strategy<Value = RuleCategory> {
333 prop_oneof![
334 Just(RuleCategory::Style),
335 Just(RuleCategory::Naming),
336 Just(RuleCategory::ErrorHandling),
337 Just(RuleCategory::Testing),
338 Just(RuleCategory::Documentation),
339 Just(RuleCategory::Security),
340 Just(RuleCategory::Performance),
341 Just(RuleCategory::Architecture),
342 Just(RuleCategory::Imports),
343 Just(RuleCategory::Git),
344 Just(RuleCategory::Api),
345 Just(RuleCategory::Other),
346 ]
347 }
348
349 fn arb_workflow_step() -> impl Strategy<Value = crate::parser::WorkflowStepData> {
351 (
352 "[a-zA-Z][a-zA-Z0-9_]{0,20}", "[a-zA-Z0-9 .,!?]{1,100}", proptest::option::of("[a-zA-Z0-9 .,!?]{1,50}"), proptest::collection::vec("[a-zA-Z0-9 .,!?]{1,50}", 0..5), )
357 .prop_map(|(name, description, condition, actions)| {
358 crate::parser::WorkflowStepData {
359 name,
360 description,
361 condition,
362 actions,
363 }
364 })
365 }
366
367 fn arb_unified_rule() -> impl Strategy<Value = UnifiedRule> {
369 prop_oneof![
370 (
372 "[a-zA-Z][a-zA-Z0-9_]{0,20}", "[a-zA-Z0-9 .,!?]{1,100}", proptest::option::of("[a-zA-Z0-9 .,!?]{1,100}"), proptest::option::of("[a-zA-Z0-9 .,!?]{1,50}"), proptest::collection::vec("[a-zA-Z0-9 .,!?]{1,30}", 0..5), proptest::collection::vec("[a-zA-Z0-9 .,!?]{1,50}", 0..5), )
379 .prop_map(|(name, role, identity, style, traits, principles)| {
380 UnifiedRule::Persona {
381 name,
382 role,
383 identity,
384 style,
385 traits,
386 principles,
387 }
388 }),
389 (
391 arb_rule_category(),
392 0u8..=255u8, "[a-zA-Z0-9 .,!?]{1,200}", proptest::option::of("[a-zA-Z0-9 .,!?*_/]{1,100}"), )
396 .prop_map(|(category, priority, description, pattern)| {
397 UnifiedRule::Standard {
398 category,
399 priority,
400 description,
401 pattern,
402 }
403 }),
404 (
406 proptest::collection::vec("[a-zA-Z0-9*_./]{1,50}", 0..5), proptest::collection::vec("[a-zA-Z0-9*_./]{1,50}", 0..5), proptest::collection::vec("[a-zA-Z0-9 ]{1,30}", 0..5), )
410 .prop_map(|(includes, excludes, focus)| {
411 UnifiedRule::Context {
412 includes,
413 excludes,
414 focus,
415 }
416 }),
417 (
419 "[a-zA-Z][a-zA-Z0-9_-]{0,30}", proptest::collection::vec(arb_workflow_step(), 0..5), )
422 .prop_map(|(name, steps)| { UnifiedRule::Workflow { name, steps } }),
423 "[a-zA-Z0-9 .,!?\n]{1,500}".prop_map(|content| { UnifiedRule::Raw { content } }),
425 ]
426 }
427
428 fn rules_equal(a: &UnifiedRule, b: &UnifiedRule) -> bool {
430 match (a, b) {
431 (
432 UnifiedRule::Persona {
433 name: n1,
434 role: r1,
435 identity: i1,
436 style: s1,
437 traits: t1,
438 principles: p1,
439 },
440 UnifiedRule::Persona {
441 name: n2,
442 role: r2,
443 identity: i2,
444 style: s2,
445 traits: t2,
446 principles: p2,
447 },
448 ) => n1 == n2 && r1 == r2 && i1 == i2 && s1 == s2 && t1 == t2 && p1 == p2,
449 (
450 UnifiedRule::Standard {
451 category: c1,
452 priority: p1,
453 description: d1,
454 pattern: pt1,
455 },
456 UnifiedRule::Standard {
457 category: c2,
458 priority: p2,
459 description: d2,
460 pattern: pt2,
461 },
462 ) => c1 == c2 && p1 == p2 && d1 == d2 && pt1 == pt2,
463 (
464 UnifiedRule::Context {
465 includes: i1,
466 excludes: e1,
467 focus: f1,
468 },
469 UnifiedRule::Context {
470 includes: i2,
471 excludes: e2,
472 focus: f2,
473 },
474 ) => i1 == i2 && e1 == e2 && f1 == f2,
475 (
476 UnifiedRule::Workflow {
477 name: n1,
478 steps: s1,
479 },
480 UnifiedRule::Workflow {
481 name: n2,
482 steps: s2,
483 },
484 ) => {
485 if n1 != n2 || s1.len() != s2.len() {
486 return false;
487 }
488 s1.iter().zip(s2.iter()).all(|(a, b)| {
489 a.name == b.name
490 && a.description == b.description
491 && a.condition == b.condition
492 && a.actions == b.actions
493 })
494 }
495 (UnifiedRule::Raw { content: c1 }, UnifiedRule::Raw { content: c2 }) => c1 == c2,
496 _ => false,
497 }
498 }
499
500 proptest! {
501 #[test]
512 fn prop_drv_binary_roundtrip(rules in proptest::collection::vec(arb_unified_rule(), 0..10)) {
513 let encoder = DrvEncoder::new();
514 let encoded = encoder.encode(&rules).expect("Encoding should succeed");
515
516 let decoder = DrvDecoder::new(&encoded).expect("Decoding should succeed");
517 let decoded = decoder.decode_all().expect("Decode all should succeed");
518
519 let has_persona = rules.iter().any(|r| matches!(r, UnifiedRule::Persona { .. }));
526 let has_workflow = rules.iter().any(|r| matches!(r, UnifiedRule::Workflow { .. }));
527
528 let last_context = rules.iter().rev().find(|r| matches!(r, UnifiedRule::Context { .. }));
530 let has_written_context = if let Some(UnifiedRule::Context { includes, excludes, .. }) = last_context {
531 !includes.is_empty() || !excludes.is_empty()
532 } else {
533 false
534 };
535
536 let standard_count = rules.iter().filter(|r| {
537 matches!(r, UnifiedRule::Standard { .. } | UnifiedRule::Raw { .. })
538 }).count();
539
540 let expected_count = (if has_persona { 1 } else { 0 })
541 + (if has_workflow { 1 } else { 0 })
542 + (if has_written_context { 1 } else { 0 })
543 + standard_count;
544
545 prop_assert_eq!(decoded.len(), expected_count, "Rule count mismatch");
546
547 if has_persona {
549 let last_persona = rules.iter().rev().find(|r| matches!(r, UnifiedRule::Persona { .. }));
550 let decoded_persona = decoded.iter().find(|r| matches!(r, UnifiedRule::Persona { .. }));
551 if let (Some(orig), Some(dec)) = (last_persona, decoded_persona) {
552 prop_assert!(rules_equal(orig, dec), "Persona mismatch");
553 }
554 }
555
556 if has_workflow {
558 let last_workflow = rules.iter().rev().find(|r| matches!(r, UnifiedRule::Workflow { .. }));
559 let decoded_workflow = decoded.iter().find(|r| matches!(r, UnifiedRule::Workflow { .. }));
560 if let (Some(orig), Some(dec)) = (last_workflow, decoded_workflow) {
561 prop_assert!(rules_equal(orig, dec), "Workflow mismatch");
562 }
563 }
564
565 let original_standards: Vec<_> = rules.iter().filter(|r| {
567 matches!(r, UnifiedRule::Standard { .. } | UnifiedRule::Raw { .. })
568 }).collect();
569 let decoded_standards: Vec<_> = decoded.iter().filter(|r| {
570 matches!(r, UnifiedRule::Standard { .. })
571 }).collect();
572
573 prop_assert_eq!(original_standards.len(), decoded_standards.len(), "Standard count mismatch");
574 }
575
576 #[test]
578 fn prop_string_interning_idempotent(s in "[a-zA-Z0-9 .,!?]{1,100}") {
579 let mut encoder = DrvEncoder::new();
580 let idx1 = encoder.intern(&s);
581 let idx2 = encoder.intern(&s);
582 prop_assert_eq!(idx1, idx2, "Same string should return same index");
583 }
584
585 #[test]
587 fn prop_encoding_deterministic(rules in proptest::collection::vec(arb_unified_rule(), 0..5)) {
588 let encoder1 = DrvEncoder::new();
589 let encoder2 = DrvEncoder::new();
590
591 let encoded1 = encoder1.encode(&rules).expect("First encoding should succeed");
592 let encoded2 = encoder2.encode(&rules).expect("Second encoding should succeed");
593
594 prop_assert_eq!(encoded1, encoded2, "Same rules should produce same binary");
595 }
596 }
597}