settings_loader 1.0.0

Opinionated configuration settings load mechanism for Rust applications
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
# Phase 4: Configuration Editing & Writing - Design Specification

**Epic**: sl-m17  
**Phase**: 4  
**Status**: Design Ready - Awaiting Subtask Breakdown  
**Date Created**: December 17, 2025  
**Target**: v0.22.0 (all phases combined)

---

## Overview

Phase 4 transforms settings-loader from read-only to bidirectional, enabling applications to modify and save configuration programmatically while preserving format semantics and comments (TOML).

**Key Deliverable**: LayerEditor trait with format-specific backends and TOML comment preservation.

**Unique Feature**: TOML comment preservation via `toml_edit` crate - differentiator vs config-rs and figment.

---

## Problem Statement

### Current Limitations (Phase 3 End State)
- ✅ Can read configuration from multiple scopes
- ✅ Supports all formats (YAML, JSON, TOML, HJSON, RON)
- ❌ Cannot write configuration
- ❌ Cannot modify values programmatically
- ❌ Cannot preserve TOML comments
- ❌ Cannot integrate with TUI/CLI configuration editors

### Real-World Need: Turtle TUI
Spark Turtle needs configuration editor for TUI:
1. Display current settings
2. Allow user to edit values
3. Save changes atomically
4. Preserve comments and formatting
5. Support multiple configuration scopes

---

## High-Level Design

### New Traits

#### 1. LayerEditor Trait

```rust
/// Edit configuration layer with type safety and format awareness
pub trait LayerEditor: Send + Sync {
    /// Get a setting value by key (dotted path)
    /// Example: "database.host"
    fn get<T: DeserializeOwned>(&self, key: &str) -> Option<T>;

    /// Set a setting value by key
    /// Returns error if format-specific validation fails
    fn set<T: Serialize>(&mut self, key: &str, value: T) -> Result<(), EditorError>;

    /// Remove a setting key
    /// Returns error if key doesn't exist
    fn unset(&mut self, key: &str) -> Result<(), EditorError>;

    /// Get all available keys in this layer
    fn keys(&self) -> Vec<String>;

    /// Check if layer has unsaved changes
    fn is_dirty(&self) -> bool;

    /// Save changes back to file
    /// Atomic: writes to temp file, then renames (all-or-nothing)
    fn save(&self) -> Result<(), EditorError>;
}
```

#### 2. SettingsEditor Trait

```rust
/// Create and manage layer editors with format auto-detection
pub trait SettingsEditor {
    type Editor: LayerEditor;

    /// Open layer editor for given path
    /// Auto-detects format from file extension
    fn open(path: &Path) -> Result<Self::Editor, EditorError>;

    /// Create new configuration layer
    /// Requires explicit format specification
    fn create(path: &Path, format: ConfigFormat) -> Result<Self::Editor, EditorError>;
}
```

#### 3. ConfigFormat Enum

```rust
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConfigFormat {
    Toml,   // Via toml_edit (preserves comments)
    Json,   // Via serde_json
    Yaml,   // Via serde_yaml
    Hjson,  // Via deser_hjson
    Ron,    // Via ron
}

impl ConfigFormat {
    /// Detect format from file extension
    pub fn from_path(path: &Path) -> Option<ConfigFormat>;
}
```

### Format-Specific Backends

#### TOML Backend (toml_edit)
```rust
struct TomlLayerEditor {
    path: PathBuf,
    document: toml_edit::Document,  // Preserves comments
    dirty: bool,
}

impl LayerEditor for TomlLayerEditor {
    fn get<T: DeserializeOwned>(&self, key: &str) -> Option<T> {
        // Navigate dotted path: "a.b.c" → document["a"]["b"]["c"]
        // Deserialize to T
    }

    fn set<T: Serialize>(&mut self, key: &str, value: T) -> Result<(), EditorError> {
        // Serialize T to TOML value
        // Navigate dotted path, set value
        // Mark dirty
    }

    fn save(&self) -> Result<(), EditorError> {
        // Write to temp file
        // Rename temp → original (atomic)
        // Mark not dirty
    }
}
```

**Key Advantage**: `toml_edit::Document` preserves:
- Comments
- Whitespace
- Formatting
- Key ordering

#### JSON Backend (serde_json)
```rust
struct JsonLayerEditor {
    path: PathBuf,
    document: serde_json::Value,
    dirty: bool,
}

impl LayerEditor for JsonLayerEditor {
    // Similar pattern but JSON doesn't preserve comments
    // Comments automatically stripped on read/write
}
```

#### YAML Backend (serde_yaml)
```rust
struct YamlLayerEditor {
    path: PathBuf,
    document: serde_yaml::Value,
    dirty: bool,
}

impl LayerEditor for YamlLayerEditor {
    // Similar pattern
    // Comments may not be preserved (YAML limitation)
}
```

### Dotted Path Navigation

All editors support dotted key notation:
```rust
editor.get::<String>("database.host")?;
editor.set("database.port", 5432)?;
editor.unset("database.password")?;
```

Navigates nested structures automatically.

### Atomic Writes

All backends implement atomic writes:
1. Write to temporary file in same directory
2. Verify successful write
3. Rename temp → original (OS atomic operation)
4. If any step fails, original file untouched

Benefits:
- No partial writes
- Safe concurrent access
- Crash-safe

### Error Handling

```rust
#[derive(Debug, thiserror::Error)]
pub enum EditorError {
    #[error("IO error: {0}")]
    IoError(#[from] std::io::Error),

    #[error("Parse error: {0}")]
    ParseError(String),

    #[error("Serialization error: {0}")]
    SerializationError(String),

    #[error("Key not found: {0}")]
    KeyNotFound(String),

    #[error("Format mismatch")]
    FormatMismatch,

    #[error("Type mismatch: expected {expected}, got {actual}")]
    TypeMismatch { expected: String, actual: String },
}
```

---

## Implementation Plan (4 Subtasks)

### PHASE4.1: Test Suite & Design (sl-m28)
**Purpose**: Establish test contract before implementation

**Tests to Create**:
1. Basic layer editor operations (get, set, unset)
2. Dotted path navigation
3. Dirty flag tracking
4. Save operations
5. Format detection
6. Atomic write verification
7. Error handling
8. TOML comment preservation
9. JSON roundtrip
10. YAML roundtrip
11. Real-world Turtle scenario

**Expected**: 25+ tests guiding implementation

**Deliverable**: `tests/phase4_config_editing_tests.rs`

---

### PHASE4.2: Core Traits & TOML Backend (sl-m27)
**Purpose**: Implement LayerEditor trait and TOML editor with comment preservation

**What to Build**:
1. EditorError enum
2. ConfigFormat enum with from_path()
3. LayerEditor trait definition
4. SettingsEditor trait definition
5. TomlLayerEditor struct
6. Dotted path navigation for TOML
7. Comment preservation in toml_edit Document
8. Atomic write implementation

**Deliverables**:
- `src/editor.rs` (new module)
- `src/editor/toml.rs` (TOML backend)
- Tests: PHASE4.1 tests 1-3, 8

**Dependencies**:
- `toml_edit` crate (v0.22+)
- `thiserror` for error enum

---

### PHASE4.3: JSON & YAML Backends (sl-m26)
**Purpose**: Implement format-agnostic editors for JSON and YAML

**What to Build**:
1. JsonLayerEditor struct
2. YamlLayerEditor struct
3. Dotted path navigation for JSON/YAML
4. Format-specific error handling
5. Atomic write for each format
6. Format detection from file extension

**Deliverables**:
- `src/editor/json.rs` (JSON backend)
- `src/editor/yaml.rs` (YAML backend)
- Tests: PHASE4.1 tests 4-7, 9-10

**Dependencies**:
- Uses existing serde_json, serde_yaml

---

### PHASE4.4: Integration & Real-World Scenarios (sl-m25)
**Purpose**: Integrate editors with SettingsLoader and validate Turtle use case

**What to Build**:
1. SettingsEditor trait implementations
2. Format auto-detection system
3. Integration with existing LoadingOptions
4. Example: Turtle configuration editor
5. Integration tests with all formats
6. Performance validation

**Deliverables**:
- SettingsEditor trait implementations
- Format detection system
- Turtle usage example
- Integration tests
- Performance benchmarks

**Tests**: PHASE4.1 tests 11-25 (real-world scenarios)

---

## Design Decisions

### 1. Separate Trait from SettingsLoader

**Decision**: LayerEditor as new trait, not part of SettingsLoader

**Rationale**:
- SettingsLoader is read-only, focuses on composition
- LayerEditor is write-focused, stateful
- Separate concerns
- Apps can use SettingsLoader without importing editor
- Backward compatible (SettingsLoader unchanged)

### 2. toml_edit for TOML Comment Preservation

**Decision**: Use `toml_edit` crate instead of `toml`

**Rationale**:
- `toml` crate parses to serde Value, loses comments
- `toml_edit` preserves document structure and comments
- Differentiator vs config-rs and figment
- Required for Turtle use case (preserve user edits)

**Trade-off**:
- toml_edit API slightly different from serde
- Dotted path navigation must handle both approaches
- Worth it for comment preservation

### 3. Dotted Path Navigation

**Decision**: Support "a.b.c" notation for nested keys

**Rationale**:
- Natural for flat key notation (environment variables, CLI)
- Reduces boilerplate for common case
- Handles arbitrary nesting depth
- Consistent with config crate's key system

**Implementation**:
- Split on ".", navigate recursively
- Create intermediate objects if missing (for set)
- Error if path partially invalid (for get)

### 4. Atomic Writes

**Decision**: Temp file + rename for all formats

**Rationale**:
- Prevents partial writes
- OS-level atomicity (rename syscall)
- Safe for concurrent readers
- Crash-safe (process dies during write → original untouched)
- Standard pattern (used by git, databases, etc.)

### 5. Format Auto-Detection by Extension

**Decision**: Auto-detect from file extension, allow override

**Rationale**:
- Most common case: extension matches format
- ConfigFormat::from_path() for auto-detection
- SettingsEditor::create() requires explicit format
- Clear intent (don't guess on write)

---

## Feature Flags

### Cargo.toml

```toml
[features]
# Existing
multi-scope = ["directories"]

# New Phase 4
editor = ["toml_edit"]

# All features
full = ["multi-scope", "editor", ...]
```

**Rationale**:
- Optional dependency (toml_edit not required for read-only)
- Backward compatible (editor feature disabled by default)
- Users opt-in to writing capability

---

## Dependency Changes

### Add to [dependencies]
```toml
toml_edit = { version = "^0.22", optional = true }
thiserror = "^1.0"  # Already present
```

### Feature Gates in Code
```rust
#[cfg(feature = "editor")]
pub mod editor;

#[cfg(feature = "editor")]
pub use editor::{LayerEditor, SettingsEditor, ConfigFormat, EditorError};
```

---

## Integration with Existing Code

### No Changes to Phase 1-3

- LoadingOptions trait unchanged
- SettingsLoader trait unchanged
- ConfigScope enum unused by editors (separate concern)
- MultiScopeConfig trait unused by editors

### Complementary to Reading

Reading Phase (Phase 3):
```rust
let settings = MySettings::load(&options)?;  // Deserialize from layers
```

Writing Phase (Phase 4):
```rust
let mut editor = TomlLayerEditor::open(Path::new("settings.toml"))?;
editor.set("debug", true)?;
editor.save()?;  // Serialize back with comments preserved
```

---

## Real-World Use Case: Turtle TUI

### Current Flow (Phase 3)
```rust
1. Load settings from multiple scopes
2. Display in TUI
3. User can view only
4. No ability to save changes
```

### New Flow (Phase 4)
```rust
1. Load settings from multiple scopes (existing)
2. Display in TUI (existing)
3. User edits settings in UI
4. TUI calls editor.set("key", value)
5. User confirms
6. TUI calls editor.save()
7. Comments preserved, atomic write completes
```

### Example Code
```rust
use settings_loader::editor::{SettingsEditor, LayerEditor};

// Open config file
let mut editor = TomlLayerEditor::open(Path::new("~/.config/turtle/settings.toml"))?;

// User changed debug flag in TUI
editor.set("debug", true)?;
editor.set("log_level", "debug")?;

// Save changes
if editor.is_dirty() {
    editor.save()?;
}
```

---

## Success Criteria

### Code Quality
- ✅ All PHASE4.1 tests (25+) passing
- ✅ All PHASE4.2-4.4 tests passing (total TBD)
- ✅ All Phase 1-3 tests still passing (backward compat)
- ✅ 0 clippy warnings (Phase 4 code only)
- ✅ Code formatted

### Functionality
- ✅ LayerEditor trait works for TOML, JSON, YAML
- ✅ Dotted path navigation for all formats
- ✅ Atomic writes prevent corruption
- ✅ TOML comments preserved
- ✅ Format auto-detection working

### Integration
- ✅ Works with Phase 1-3 features
- ✅ Optional feature flag (editor = ["toml_edit"])
- ✅ Turtle TUI can use it
- ✅ No breaking changes

---

## Architecture Diagram

```
SettingsLoader (Phase 1-3: Read-Only)
    ├── LoadingOptions trait
    ├── LayerBuilder
    ├── ConfigScope enum
    └── MultiScopeConfig trait

LayerEditor (Phase 4: Read-Write)
    ├── LayerEditor trait (base)
    │   ├── TomlLayerEditor (with comment preservation)
    │   ├── JsonLayerEditor
    │   └── YamlLayerEditor
    ├── SettingsEditor trait (factory)
    ├── ConfigFormat enum
    └── EditorError enum
```

---

## Phase Dependencies

- **Depends On**: Phase 1-3 complete ✅
- **Blocks**: Phase 5 (Metadata) - Can add metadata to settings module
- **Blocks**: Phase 6 (Provenance) - Provenance can track edits
- **Blocks**: Phase 7 (Schema) - Schema export needs to be readable/writable

---

## Known Limitations & Future Work

### Phase 4 Scope
- Single-file editing only (not cross-file transactions)
- No merge/conflict resolution for concurrent edits
- No undo/redo (applications can implement if needed)
- HJSON and RON backends not in Phase 4 (can add in Phase 5+)

### Future Enhancements
- **Phase 5+**: Cross-file transaction support
- **Phase 5+**: Conflict resolution
- **Phase 5+**: Undo/redo infrastructure
- **Phase 5+**: HJSON and RON editing backends

---

## Testing Strategy

### Unit Tests (PHASE4.1-4.4)
- Individual editor operations (get, set, unset, save)
- Dotted path navigation
- Format-specific behavior
- Error handling
- Atomic write verification

### Integration Tests
- Multi-format round-trips
- Format auto-detection
- Real file I/O
- Comment preservation
- Dirty flag lifecycle

### Real-World Scenarios
- Turtle configuration editing
- Concurrent read/write patterns
- Large file handling
- Permission edge cases

---

## Performance Expectations

### Memory
- Small files (<1MB): No noticeable overhead
- Large files: toml_edit memory usage proportional to file size

### Time
- Read: ~1-5ms for typical config files
- Write: ~2-10ms including atomic write
- Comment preservation: No additional cost vs non-preserving parser

### Scalability
- Single-threaded (editors are `Send + Sync` but not `Sync` for mutation)
- Good for TUI/CLI use cases
- Not suitable for highly concurrent server-side edits (use database for that)

---

## Rollout Plan

1. **PHASE4.1** (sl-m28): Test suite drives design
2. **PHASE4.2** (sl-m27): Core traits + TOML (biggest effort)
3. **PHASE4.3** (sl-m26): JSON + YAML backends (parallel possible)
4. **PHASE4.4** (sl-m25): Integration + real-world validation

**Timeline**: 2-3 weeks estimated

---

## Risk Mitigation

### Risk: TOML Comment Loss
**Mitigation**: Use toml_edit, not toml crate

### Risk: Atomic Write Failure
**Mitigation**: Test scenarios, handle edge cases (disk full, permissions)

### Risk: Dotted Path Navigation Complexity
**Mitigation**: Extensive testing of path resolution

### Risk: Feature Interaction with Phases 5-7
**Mitigation**: Design for composition, keep traits simple

---

## Related Documentation

- `history/PHASE3_COMPLETION_SUMMARY.md` - Phase 3 completion
- `history/CONSOLIDATED_ROADMAP.md` - All 7 phases overview
- `ref/PHASE3_MULTI_SCOPE_SUPPORT.md` - Phase 3 architecture
- `PHASE_TRACKING.md` - Progress tracking

---

## Conclusion

Phase 4 transforms settings-loader from read-only to fully bidirectional with format preservation, enabling real-world use cases like configuration editors and TUI-based admin tools.

**Key differentiator**: TOML comment preservation via toml_edit.

**Ready for implementation** when subtasks are broken down and tests designed.

---

**Created**: December 17, 2025  
**Status**: ✅ Design Ready  
**Next**: Subtask creation (sl-m28, sl-m27, sl-m26, sl-m25)