Skip to main content

driven/dx_integration/
legacy.rs

1//! Legacy Format Converters
2//!
3//! This module provides converters for JSON, YAML, and TOML formats via DX Serializer
4//! to maintain backward compatibility with existing configurations.
5//!
6//! ## Supported Formats
7//!
8//! - **JSON**: Standard JSON configuration files
9//! - **YAML**: YAML configuration files (common in many tools)
10//! - **TOML**: TOML configuration files (Rust ecosystem standard)
11//!
12//! ## Usage
13//!
14//! ```rust,ignore
15//! use driven::dx_integration::legacy::{LegacyFormat, LegacyConverter};
16//! use driven::DrivenConfig;
17//!
18//! // Convert from JSON
19//! let json_content = r#"{"version": "1.0", "default_editor": "cursor"}"#;
20//! let config = LegacyConverter::from_json::<DrivenConfig>(json_content)?;
21//!
22//! // Convert to JSON
23//! let json_output = LegacyConverter::to_json(&config)?;
24//!
25//! // Auto-detect format and convert
26//! let config = LegacyConverter::from_auto::<DrivenConfig>(content, "config.yaml")?;
27//! ```
28
29use crate::{DrivenConfig, DrivenError, Result};
30use std::path::Path;
31
32/// Supported legacy formats
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum LegacyFormat {
35    /// JSON format
36    Json,
37    /// YAML format
38    Yaml,
39    /// TOML format
40    Toml,
41}
42
43impl LegacyFormat {
44    /// Detect format from file extension
45    pub fn from_extension(path: &Path) -> Option<Self> {
46        path.extension()
47            .and_then(|ext| ext.to_str())
48            .and_then(|ext| match ext.to_lowercase().as_str() {
49                "json" => Some(LegacyFormat::Json),
50                "yaml" | "yml" => Some(LegacyFormat::Yaml),
51                "toml" => Some(LegacyFormat::Toml),
52                _ => None,
53            })
54    }
55
56    /// Get the file extension for this format
57    pub fn extension(&self) -> &'static str {
58        match self {
59            LegacyFormat::Json => "json",
60            LegacyFormat::Yaml => "yaml",
61            LegacyFormat::Toml => "toml",
62        }
63    }
64
65    /// Get the MIME type for this format
66    pub fn mime_type(&self) -> &'static str {
67        match self {
68            LegacyFormat::Json => "application/json",
69            LegacyFormat::Yaml => "application/x-yaml",
70            LegacyFormat::Toml => "application/toml",
71        }
72    }
73}
74
75/// Legacy format converter
76///
77/// Provides conversion between legacy formats (JSON, YAML, TOML) and DrivenConfig.
78pub struct LegacyConverter;
79
80impl LegacyConverter {
81    // ========== JSON Conversion ==========
82
83    /// Parse DrivenConfig from JSON string
84    pub fn from_json(content: &str) -> Result<DrivenConfig> {
85        serde_json::from_str(content)
86            .map_err(|e| DrivenError::Parse(format!("JSON parse error: {}", e)))
87    }
88
89    /// Serialize DrivenConfig to JSON string
90    pub fn to_json(config: &DrivenConfig) -> Result<String> {
91        serde_json::to_string_pretty(config)
92            .map_err(|e| DrivenError::Format(format!("JSON serialization error: {}", e)))
93    }
94
95    /// Serialize DrivenConfig to compact JSON string
96    pub fn to_json_compact(config: &DrivenConfig) -> Result<String> {
97        serde_json::to_string(config)
98            .map_err(|e| DrivenError::Format(format!("JSON serialization error: {}", e)))
99    }
100
101    // ========== YAML Conversion ==========
102
103    /// Parse DrivenConfig from YAML string
104    pub fn from_yaml(content: &str) -> Result<DrivenConfig> {
105        serde_yaml::from_str(content)
106            .map_err(|e| DrivenError::Parse(format!("YAML parse error: {}", e)))
107    }
108
109    /// Serialize DrivenConfig to YAML string
110    pub fn to_yaml(config: &DrivenConfig) -> Result<String> {
111        serde_yaml::to_string(config)
112            .map_err(|e| DrivenError::Format(format!("YAML serialization error: {}", e)))
113    }
114
115    // ========== TOML Conversion ==========
116
117    /// Parse DrivenConfig from TOML string
118    pub fn from_toml(content: &str) -> Result<DrivenConfig> {
119        toml::from_str(content).map_err(|e| DrivenError::Parse(format!("TOML parse error: {}", e)))
120    }
121
122    /// Serialize DrivenConfig to TOML string
123    pub fn to_toml(config: &DrivenConfig) -> Result<String> {
124        toml::to_string_pretty(config)
125            .map_err(|e| DrivenError::Format(format!("TOML serialization error: {}", e)))
126    }
127
128    // ========== Auto-Detection ==========
129
130    /// Parse DrivenConfig from content with auto-detected format
131    ///
132    /// The format is detected from the file path extension.
133    pub fn from_auto(content: &str, path: &Path) -> Result<DrivenConfig> {
134        let format = LegacyFormat::from_extension(path).ok_or_else(|| {
135            DrivenError::UnsupportedFormat(format!(
136                "Cannot detect format from path: {}",
137                path.display()
138            ))
139        })?;
140
141        Self::from_format(content, format)
142    }
143
144    /// Parse DrivenConfig from content with specified format
145    pub fn from_format(content: &str, format: LegacyFormat) -> Result<DrivenConfig> {
146        match format {
147            LegacyFormat::Json => Self::from_json(content),
148            LegacyFormat::Yaml => Self::from_yaml(content),
149            LegacyFormat::Toml => Self::from_toml(content),
150        }
151    }
152
153    /// Serialize DrivenConfig to string with specified format
154    pub fn to_format(config: &DrivenConfig, format: LegacyFormat) -> Result<String> {
155        match format {
156            LegacyFormat::Json => Self::to_json(config),
157            LegacyFormat::Yaml => Self::to_yaml(config),
158            LegacyFormat::Toml => Self::to_toml(config),
159        }
160    }
161
162    // ========== File Operations ==========
163
164    /// Load DrivenConfig from a legacy format file
165    pub fn load_file(path: &Path) -> Result<DrivenConfig> {
166        let content = std::fs::read_to_string(path)?;
167        Self::from_auto(&content, path)
168    }
169
170    /// Save DrivenConfig to a legacy format file
171    ///
172    /// The format is detected from the file path extension.
173    pub fn save_file(config: &DrivenConfig, path: &Path) -> Result<()> {
174        let format = LegacyFormat::from_extension(path).ok_or_else(|| {
175            DrivenError::UnsupportedFormat(format!(
176                "Cannot detect format from path: {}",
177                path.display()
178            ))
179        })?;
180
181        let content = Self::to_format(config, format)?;
182        std::fs::write(path, content)?;
183        Ok(())
184    }
185
186    // ========== Format Conversion ==========
187
188    /// Convert content from one legacy format to another
189    pub fn convert(
190        content: &str,
191        from_format: LegacyFormat,
192        to_format: LegacyFormat,
193    ) -> Result<String> {
194        let config = Self::from_format(content, from_format)?;
195        Self::to_format(&config, to_format)
196    }
197
198    /// Convert a file from one legacy format to another
199    pub fn convert_file(input_path: &Path, output_path: &Path) -> Result<()> {
200        let config = Self::load_file(input_path)?;
201        Self::save_file(&config, output_path)
202    }
203}
204
205/// Trait for types that can be converted from legacy formats
206pub trait LegacySerializable: Sized {
207    /// Parse from JSON string
208    fn from_legacy_json(content: &str) -> Result<Self>;
209
210    /// Serialize to JSON string
211    fn to_legacy_json(&self) -> Result<String>;
212
213    /// Parse from YAML string
214    fn from_legacy_yaml(content: &str) -> Result<Self>;
215
216    /// Serialize to YAML string
217    fn to_legacy_yaml(&self) -> Result<String>;
218
219    /// Parse from TOML string
220    fn from_legacy_toml(content: &str) -> Result<Self>;
221
222    /// Serialize to TOML string
223    fn to_legacy_toml(&self) -> Result<String>;
224}
225
226impl LegacySerializable for DrivenConfig {
227    fn from_legacy_json(content: &str) -> Result<Self> {
228        LegacyConverter::from_json(content)
229    }
230
231    fn to_legacy_json(&self) -> Result<String> {
232        LegacyConverter::to_json(self)
233    }
234
235    fn from_legacy_yaml(content: &str) -> Result<Self> {
236        LegacyConverter::from_yaml(content)
237    }
238
239    fn to_legacy_yaml(&self) -> Result<String> {
240        LegacyConverter::to_yaml(self)
241    }
242
243    fn from_legacy_toml(content: &str) -> Result<Self> {
244        LegacyConverter::from_toml(content)
245    }
246
247    fn to_legacy_toml(&self) -> Result<String> {
248        LegacyConverter::to_toml(self)
249    }
250}
251
252#[cfg(test)]
253mod tests {
254    use super::*;
255    use crate::Editor;
256
257    #[test]
258    fn test_json_roundtrip() {
259        let config = DrivenConfig::default();
260        let json = LegacyConverter::to_json(&config).unwrap();
261        let loaded = LegacyConverter::from_json(&json).unwrap();
262
263        assert_eq!(config.version, loaded.version);
264        assert_eq!(config.default_editor, loaded.default_editor);
265        assert_eq!(config.editors.cursor, loaded.editors.cursor);
266    }
267
268    #[test]
269    fn test_yaml_roundtrip() {
270        let config = DrivenConfig::default();
271        let yaml = LegacyConverter::to_yaml(&config).unwrap();
272        let loaded = LegacyConverter::from_yaml(&yaml).unwrap();
273
274        assert_eq!(config.version, loaded.version);
275        assert_eq!(config.default_editor, loaded.default_editor);
276        assert_eq!(config.editors.cursor, loaded.editors.cursor);
277    }
278
279    #[test]
280    fn test_toml_roundtrip() {
281        let config = DrivenConfig::default();
282        let toml_str = LegacyConverter::to_toml(&config).unwrap();
283        let loaded = LegacyConverter::from_toml(&toml_str).unwrap();
284
285        assert_eq!(config.version, loaded.version);
286        assert_eq!(config.default_editor, loaded.default_editor);
287        assert_eq!(config.editors.cursor, loaded.editors.cursor);
288    }
289
290    #[test]
291    fn test_format_detection() {
292        assert_eq!(
293            LegacyFormat::from_extension(Path::new("config.json")),
294            Some(LegacyFormat::Json)
295        );
296        assert_eq!(
297            LegacyFormat::from_extension(Path::new("config.yaml")),
298            Some(LegacyFormat::Yaml)
299        );
300        assert_eq!(
301            LegacyFormat::from_extension(Path::new("config.yml")),
302            Some(LegacyFormat::Yaml)
303        );
304        assert_eq!(
305            LegacyFormat::from_extension(Path::new("config.toml")),
306            Some(LegacyFormat::Toml)
307        );
308        assert_eq!(LegacyFormat::from_extension(Path::new("config.txt")), None);
309    }
310
311    #[test]
312    fn test_format_conversion() {
313        let config = DrivenConfig::default();
314
315        // JSON -> YAML
316        let json = LegacyConverter::to_json(&config).unwrap();
317        let yaml = LegacyConverter::convert(&json, LegacyFormat::Json, LegacyFormat::Yaml).unwrap();
318        let loaded = LegacyConverter::from_yaml(&yaml).unwrap();
319        assert_eq!(config.version, loaded.version);
320
321        // YAML -> TOML
322        let toml_str =
323            LegacyConverter::convert(&yaml, LegacyFormat::Yaml, LegacyFormat::Toml).unwrap();
324        let loaded = LegacyConverter::from_toml(&toml_str).unwrap();
325        assert_eq!(config.version, loaded.version);
326
327        // TOML -> JSON
328        let json2 =
329            LegacyConverter::convert(&toml_str, LegacyFormat::Toml, LegacyFormat::Json).unwrap();
330        let loaded = LegacyConverter::from_json(&json2).unwrap();
331        assert_eq!(config.version, loaded.version);
332    }
333
334    #[test]
335    fn test_legacy_serializable_trait() {
336        let config = DrivenConfig::default();
337
338        // Test JSON via trait
339        let json = config.to_legacy_json().unwrap();
340        let loaded = DrivenConfig::from_legacy_json(&json).unwrap();
341        assert_eq!(config.version, loaded.version);
342
343        // Test YAML via trait
344        let yaml = config.to_legacy_yaml().unwrap();
345        let loaded = DrivenConfig::from_legacy_yaml(&yaml).unwrap();
346        assert_eq!(config.version, loaded.version);
347
348        // Test TOML via trait
349        let toml_str = config.to_legacy_toml().unwrap();
350        let loaded = DrivenConfig::from_legacy_toml(&toml_str).unwrap();
351        assert_eq!(config.version, loaded.version);
352    }
353
354    #[test]
355    fn test_json_with_all_fields() {
356        let json = r#"{
357            "version": "2.0",
358            "default_editor": "copilot",
359            "editors": {
360                "cursor": false,
361                "copilot": true,
362                "windsurf": true,
363                "claude": false,
364                "aider": true,
365                "cline": false
366            },
367            "templates": {
368                "personas": ["developer", "reviewer"],
369                "project": "rust",
370                "standards": ["clean-code"],
371                "workflow": "agile"
372            },
373            "sync": {
374                "watch": false,
375                "auto_convert": true,
376                "source_of_truth": "custom/path.drv"
377            },
378            "context": {
379                "include": ["src/**", "lib/**"],
380                "exclude": ["target/**"],
381                "index_path": "custom/index.drv"
382            }
383        }"#;
384
385        let config = LegacyConverter::from_json(json).unwrap();
386        assert_eq!(config.version, "2.0");
387        assert_eq!(config.default_editor, Editor::Copilot);
388        assert!(!config.editors.cursor);
389        assert!(config.editors.copilot);
390        assert!(config.editors.windsurf);
391        assert!(!config.sync.watch);
392        assert_eq!(config.sync.source_of_truth, "custom/path.drv");
393    }
394}
395
396#[cfg(test)]
397mod prop_tests {
398    use super::*;
399    use crate::{ContextConfig, Editor, EditorConfig, SyncConfig, TemplateConfig};
400    use proptest::prelude::*;
401
402    /// Generate arbitrary Editor values
403    fn arb_editor() -> impl Strategy<Value = Editor> {
404        prop_oneof![
405            Just(Editor::Cursor),
406            Just(Editor::Copilot),
407            Just(Editor::Windsurf),
408            Just(Editor::Claude),
409            Just(Editor::Aider),
410            Just(Editor::Cline),
411        ]
412    }
413
414    /// Generate arbitrary EditorConfig values
415    fn arb_editor_config() -> impl Strategy<Value = EditorConfig> {
416        (
417            any::<bool>(),
418            any::<bool>(),
419            any::<bool>(),
420            any::<bool>(),
421            any::<bool>(),
422            any::<bool>(),
423        )
424            .prop_map(
425                |(cursor, copilot, windsurf, claude, aider, cline)| EditorConfig {
426                    cursor,
427                    copilot,
428                    windsurf,
429                    claude,
430                    aider,
431                    cline,
432                },
433            )
434    }
435
436    /// Generate arbitrary SyncConfig values
437    fn arb_sync_config() -> impl Strategy<Value = SyncConfig> {
438        (any::<bool>(), any::<bool>(), "[a-zA-Z0-9_./]{1,50}").prop_map(
439            |(watch, auto_convert, source)| SyncConfig {
440                watch,
441                auto_convert,
442                source_of_truth: source,
443            },
444        )
445    }
446
447    /// Generate arbitrary TemplateConfig values
448    fn arb_template_config() -> impl Strategy<Value = TemplateConfig> {
449        (
450            prop::collection::vec("[a-zA-Z0-9_-]{1,20}", 0..5),
451            prop::option::of("[a-zA-Z0-9_-]{1,20}"),
452            prop::collection::vec("[a-zA-Z0-9_-]{1,20}", 0..5),
453            prop::option::of("[a-zA-Z0-9_-]{1,20}"),
454        )
455            .prop_map(|(personas, project, standards, workflow)| TemplateConfig {
456                personas,
457                project,
458                standards,
459                workflow,
460            })
461    }
462
463    /// Generate arbitrary ContextConfig values
464    fn arb_context_config() -> impl Strategy<Value = ContextConfig> {
465        (
466            prop::collection::vec("[a-zA-Z0-9_*/.]{1,30}", 0..5),
467            prop::collection::vec("[a-zA-Z0-9_*/.]{1,30}", 0..5),
468            "[a-zA-Z0-9_./]{1,50}",
469        )
470            .prop_map(|(include, exclude, index_path)| ContextConfig {
471                include,
472                exclude,
473                index_path,
474            })
475    }
476
477    /// Generate arbitrary DrivenConfig values
478    fn arb_driven_config() -> impl Strategy<Value = DrivenConfig> {
479        (
480            "[0-9]+\\.[0-9]+",
481            arb_editor(),
482            arb_editor_config(),
483            arb_sync_config(),
484            arb_template_config(),
485            arb_context_config(),
486        )
487            .prop_map(
488                |(version, default_editor, editors, sync, templates, context)| DrivenConfig {
489                    version,
490                    default_editor,
491                    editors,
492                    sync,
493                    templates,
494                    context,
495                },
496            )
497    }
498
499    proptest! {
500        /// Property 3: Legacy Format Backward Compatibility
501        /// *For any* valid DrivenConfig, serializing to JSON and deserializing back
502        /// SHALL produce an equivalent configuration.
503        /// **Validates: Requirements 1.6**
504        #[test]
505        fn prop_json_roundtrip(config in arb_driven_config()) {
506            let json = LegacyConverter::to_json(&config).expect("JSON serialization should succeed");
507            let loaded = LegacyConverter::from_json(&json).expect("JSON deserialization should succeed");
508
509            // Verify all fields are preserved
510            prop_assert_eq!(config.version, loaded.version);
511            prop_assert_eq!(config.default_editor, loaded.default_editor);
512            prop_assert_eq!(config.editors.cursor, loaded.editors.cursor);
513            prop_assert_eq!(config.editors.copilot, loaded.editors.copilot);
514            prop_assert_eq!(config.editors.windsurf, loaded.editors.windsurf);
515            prop_assert_eq!(config.editors.claude, loaded.editors.claude);
516            prop_assert_eq!(config.editors.aider, loaded.editors.aider);
517            prop_assert_eq!(config.editors.cline, loaded.editors.cline);
518            prop_assert_eq!(config.sync.watch, loaded.sync.watch);
519            prop_assert_eq!(config.sync.auto_convert, loaded.sync.auto_convert);
520            prop_assert_eq!(config.sync.source_of_truth, loaded.sync.source_of_truth);
521            prop_assert_eq!(config.templates.personas, loaded.templates.personas);
522            prop_assert_eq!(config.templates.project, loaded.templates.project);
523            prop_assert_eq!(config.templates.standards, loaded.templates.standards);
524            prop_assert_eq!(config.templates.workflow, loaded.templates.workflow);
525            prop_assert_eq!(config.context.include, loaded.context.include);
526            prop_assert_eq!(config.context.exclude, loaded.context.exclude);
527            prop_assert_eq!(config.context.index_path, loaded.context.index_path);
528        }
529
530        /// Property 3: Legacy Format Backward Compatibility (YAML)
531        /// *For any* valid DrivenConfig, serializing to YAML and deserializing back
532        /// SHALL produce an equivalent configuration.
533        /// **Validates: Requirements 1.6**
534        #[test]
535        fn prop_yaml_roundtrip(config in arb_driven_config()) {
536            let yaml = LegacyConverter::to_yaml(&config).expect("YAML serialization should succeed");
537            let loaded = LegacyConverter::from_yaml(&yaml).expect("YAML deserialization should succeed");
538
539            // Verify all fields are preserved
540            prop_assert_eq!(config.version, loaded.version);
541            prop_assert_eq!(config.default_editor, loaded.default_editor);
542            prop_assert_eq!(config.editors.cursor, loaded.editors.cursor);
543            prop_assert_eq!(config.editors.copilot, loaded.editors.copilot);
544            prop_assert_eq!(config.editors.windsurf, loaded.editors.windsurf);
545            prop_assert_eq!(config.editors.claude, loaded.editors.claude);
546            prop_assert_eq!(config.editors.aider, loaded.editors.aider);
547            prop_assert_eq!(config.editors.cline, loaded.editors.cline);
548            prop_assert_eq!(config.sync.watch, loaded.sync.watch);
549            prop_assert_eq!(config.sync.auto_convert, loaded.sync.auto_convert);
550            prop_assert_eq!(config.sync.source_of_truth, loaded.sync.source_of_truth);
551            prop_assert_eq!(config.templates.personas, loaded.templates.personas);
552            prop_assert_eq!(config.templates.project, loaded.templates.project);
553            prop_assert_eq!(config.templates.standards, loaded.templates.standards);
554            prop_assert_eq!(config.templates.workflow, loaded.templates.workflow);
555            prop_assert_eq!(config.context.include, loaded.context.include);
556            prop_assert_eq!(config.context.exclude, loaded.context.exclude);
557            prop_assert_eq!(config.context.index_path, loaded.context.index_path);
558        }
559
560        /// Property 3: Legacy Format Backward Compatibility (TOML)
561        /// *For any* valid DrivenConfig, serializing to TOML and deserializing back
562        /// SHALL produce an equivalent configuration.
563        /// **Validates: Requirements 1.6**
564        #[test]
565        fn prop_toml_roundtrip(config in arb_driven_config()) {
566            let toml_str = LegacyConverter::to_toml(&config).expect("TOML serialization should succeed");
567            let loaded = LegacyConverter::from_toml(&toml_str).expect("TOML deserialization should succeed");
568
569            // Verify all fields are preserved
570            prop_assert_eq!(config.version, loaded.version);
571            prop_assert_eq!(config.default_editor, loaded.default_editor);
572            prop_assert_eq!(config.editors.cursor, loaded.editors.cursor);
573            prop_assert_eq!(config.editors.copilot, loaded.editors.copilot);
574            prop_assert_eq!(config.editors.windsurf, loaded.editors.windsurf);
575            prop_assert_eq!(config.editors.claude, loaded.editors.claude);
576            prop_assert_eq!(config.editors.aider, loaded.editors.aider);
577            prop_assert_eq!(config.editors.cline, loaded.editors.cline);
578            prop_assert_eq!(config.sync.watch, loaded.sync.watch);
579            prop_assert_eq!(config.sync.auto_convert, loaded.sync.auto_convert);
580            prop_assert_eq!(config.sync.source_of_truth, loaded.sync.source_of_truth);
581            prop_assert_eq!(config.templates.personas, loaded.templates.personas);
582            prop_assert_eq!(config.templates.project, loaded.templates.project);
583            prop_assert_eq!(config.templates.standards, loaded.templates.standards);
584            prop_assert_eq!(config.templates.workflow, loaded.templates.workflow);
585            prop_assert_eq!(config.context.include, loaded.context.include);
586            prop_assert_eq!(config.context.exclude, loaded.context.exclude);
587            prop_assert_eq!(config.context.index_path, loaded.context.index_path);
588        }
589
590        /// Property 3: Legacy Format Cross-Conversion
591        /// *For any* valid DrivenConfig, converting between any two legacy formats
592        /// SHALL preserve all configuration data.
593        /// **Validates: Requirements 1.6**
594        #[test]
595        fn prop_cross_format_conversion(config in arb_driven_config()) {
596            // JSON -> YAML -> TOML -> JSON should preserve data
597            let json1 = LegacyConverter::to_json(&config).expect("JSON serialization should succeed");
598            let yaml = LegacyConverter::convert(&json1, LegacyFormat::Json, LegacyFormat::Yaml)
599                .expect("JSON to YAML conversion should succeed");
600            let toml_str = LegacyConverter::convert(&yaml, LegacyFormat::Yaml, LegacyFormat::Toml)
601                .expect("YAML to TOML conversion should succeed");
602            let json2 = LegacyConverter::convert(&toml_str, LegacyFormat::Toml, LegacyFormat::Json)
603                .expect("TOML to JSON conversion should succeed");
604
605            let loaded = LegacyConverter::from_json(&json2).expect("Final JSON deserialization should succeed");
606
607            // Verify all fields are preserved after round-trip through all formats
608            prop_assert_eq!(config.version, loaded.version);
609            prop_assert_eq!(config.default_editor, loaded.default_editor);
610            prop_assert_eq!(config.editors.cursor, loaded.editors.cursor);
611            prop_assert_eq!(config.editors.copilot, loaded.editors.copilot);
612            prop_assert_eq!(config.editors.windsurf, loaded.editors.windsurf);
613            prop_assert_eq!(config.editors.claude, loaded.editors.claude);
614            prop_assert_eq!(config.editors.aider, loaded.editors.aider);
615            prop_assert_eq!(config.editors.cline, loaded.editors.cline);
616            prop_assert_eq!(config.sync.watch, loaded.sync.watch);
617            prop_assert_eq!(config.sync.auto_convert, loaded.sync.auto_convert);
618            prop_assert_eq!(config.sync.source_of_truth, loaded.sync.source_of_truth);
619        }
620    }
621}