docs: ticket 04 (fehlerstrategie) aufloesen, vorgaben an ticket 11
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# 04 — Fehlerstrategie für kaputte Mods
|
||||
|
||||
Type: grilling
|
||||
Status: open
|
||||
Status: resolved
|
||||
Blocked by: 03
|
||||
|
||||
## Question
|
||||
@@ -32,3 +32,110 @@ Offen bleibt damit: kaputte TOML-Syntax (die Datei ist unlesbar, die Einheit „
|
||||
fehlende Pflichtfelder, unaufgelöste Referenzen aus Phase 3 — und wohin die Warnungen gehen, wenn der
|
||||
Bildschirm ratatui gehört. Ein Ladelauf produziert leicht ein Dutzend, und `println!` ist nach dem
|
||||
TUI-Start keine Option mehr.
|
||||
|
||||
## Answer
|
||||
|
||||
### Zwei Klassen, zwei Einheiten
|
||||
|
||||
**Syntax ist ein Datei-Fehler, alles Semantische ein Eintrags-Fehler.**
|
||||
|
||||
Das ist nicht gratis: `toml::from_str::<ModFile>()` mit `items: Vec<Item>` scheitert *komplett*, wenn
|
||||
ein einziger Eintrag ein kaputtes Feld hat — das Versprechen aus Ticket 03 wäre damit genau dann
|
||||
gebrochen, wenn 200 Gegenstände in einer Datei stehen, also im Normalfall. Deshalb wird zweistufig
|
||||
deserialisiert: erst `Vec<Spanned<toml::Value>>` je Registry-Tabelle, dann jeder Eintrag einzeln in
|
||||
seinen Typ. Etwa zehn Zeilen, und ein falsches Feld trifft nur noch seinen Eintrag.
|
||||
|
||||
Bei kaputter TOML-Syntax fällt die ganze Datei — da kommt der Parser nicht weiter, das ist keine
|
||||
Entscheidung, sondern eine Tatsache.
|
||||
|
||||
### Harte Fehler — genau drei
|
||||
|
||||
`depends` nicht installiert · `depends`-Reihenfolge verletzt · `core` nicht gefunden. Alle drei:
|
||||
Meldung auf stderr, `exit(1)`. Das TUI startet gar nicht erst, damit ist stderr genau der richtige Ort.
|
||||
|
||||
`core` ist darüber hinaus **kein Sonderfall**: Ein kaputter Eintrag darin ist eine Warnung wie überall.
|
||||
Ein Sonderfall würde die Privilegierung wieder einbauen, die Ticket 03 gerade abgeschafft hat. Die
|
||||
Absicherung gehört nicht in die Laufzeit, sondern in einen Test — siehe unten. Dass `core` fehlend
|
||||
hart ist, widerspricht dem nicht: Ohne `core` gibt es kein Attribut, keinen Slot und keinen
|
||||
Gegenstand, das ist nicht Privileg, sondern die Abwesenheit sämtlichen Inhalts.
|
||||
|
||||
Ein nicht schreibbares Log ist ausdrücklich **kein** harter Fehler. Der Schreibfehler wird verschluckt;
|
||||
ein Spiel, das wegen seiner Logdatei nicht startet, wäre die Karikatur guter Fehlerbehandlung.
|
||||
|
||||
### Warnungen
|
||||
|
||||
Während des Ladens in einem `Vec<String>` gesammelt, am Ende nach
|
||||
`data_dir()/rustkeep/logs/load-20260812-112305.log` geschrieben. Die letzten zehn bleiben liegen,
|
||||
ältere werden beim Schreiben gelöscht — streng auf das Muster `load-*.log` im eigenen `logs/`-Ordner
|
||||
beschränkt. Ein sauberer Lauf legt keine Datei an.
|
||||
|
||||
Auf dem Startbildschirm steht **eine Zeile** mit Anzahl und Dateiname. Kein Report-Screen: Das wäre
|
||||
ein Screen mit Scrolling und Navigation, den Ticket 11 mitschleppen müsste, für Inhalt, den ein
|
||||
Texteditor besser anzeigt.
|
||||
|
||||
Keine Schweregrade. Jeder Fall aus 03 und aus der Deserialisierung ist „Warnung, verworfen" — eine
|
||||
zweite Stufe wäre ein Feld, das überall denselben Wert trägt, plus die Frage, was `INFO` auslöst.
|
||||
|
||||
### Meldungsformat
|
||||
|
||||
```
|
||||
meinmod/items.toml:42: core:iron_sword — unknown field `base_pric`
|
||||
```
|
||||
|
||||
Die **Mod-ID steht vorne**: Bei zwölf installierten Mods ist sie die einzige Angabe, mit der ein
|
||||
Spieler weiß, wen er anschreiben muss.
|
||||
|
||||
`toml::de::Error` liefert `span() -> Option<Range<usize>>` und `message()`, aber keine Zeile — die ist
|
||||
`input[..span.start].lines().count()`, eine Zeile Code. Keine Spalte: `message()` nennt das Feld
|
||||
bereits im Klartext, die Spalte wäre doppelt gemoppelt und bräuchte eine zweite Rechnung. Ein Eintrag
|
||||
ohne lesbare `id` kann sich nicht benennen und heißt nach seiner Position: `items[3]`.
|
||||
|
||||
### Rust-Seite
|
||||
|
||||
Handgeschriebenes `enum LoadError` mit drei Varianten und eigenem `impl Display`.
|
||||
|
||||
`thiserror` wäre ein Proc-Macro-Dependency (zieht `syn` und `quote` in den Build) für ungefähr zehn
|
||||
gesparte Zeilen `match` — und in einem Ticket, das ausdrücklich Rust lernen soll, ist das Schreiben
|
||||
dieses `match` der Zweck. `anyhow` ist für Anwendungen gedacht, denen der konkrete Fehlertyp egal ist;
|
||||
hier ist er es nicht, es sind genau drei benannte Fälle. `impl std::error::Error` bleibt weg, solange
|
||||
niemand `Box<dyn Error>` braucht.
|
||||
|
||||
```rust
|
||||
fn load_mods() -> Result<(Registries, Vec<String>), LoadError>
|
||||
```
|
||||
|
||||
Warnungen reisen als zweiter Rückgabewert. Als Feld in `Registries` würden sie Diagnose an die
|
||||
Spieldaten hängen, die danach das ganze Spiel lang mitfahren und in jedem Save-Gedanken auftauchen;
|
||||
als `&mut Vec<String>`-Parameter wäre es das C-Muster. Ein Tupel aus zwei Dingen braucht keinen
|
||||
`LoadReport`-Typ — den gäbe es für genau eine Aufrufstelle.
|
||||
|
||||
Die `Result`-Kette endet **vor** `main`: `eprintln!("{e}")` plus `exit(1)`. `main() -> Result<…>`
|
||||
druckt über `Debug` und setzt dem Spieler `LoadError { .. }` samt „Error: " vor.
|
||||
|
||||
### Panic-Grenze
|
||||
|
||||
Die Grenze ist die **Datenherkunft**, nicht die Schwere:
|
||||
|
||||
- Alles, was aus einer TOML kommt, ist `Result`.
|
||||
- Alles, was aus dem eigenen Code kommt, darf panicken — Index in eine selbst gebaute `Vec`,
|
||||
`unreachable!` im `match` über ein eigenes Enum. Ein Panic dort ist ein Engine-Bug und soll laut
|
||||
sein; ihn in ein `Result` zu verpacken hieße, den Aufrufer nach etwas zu fragen, das er nicht
|
||||
beantworten kann.
|
||||
|
||||
`unwrap()` auf Modder-Daten ist ausnahmslos falsch — genau das tut `items.rs::parse()` heute.
|
||||
|
||||
### Check
|
||||
|
||||
Ein `#[test]`, der `mods/core/` lädt und auf **null Warnungen** prüft. Damit fällt ein kaputtes
|
||||
Kernspiel beim `cargo test` auf statt beim Spieler, und die Ladelogik bleibt frei von Sonderregeln.
|
||||
Das ist die eine runnable Prüfung, die dieses Ticket hinterlässt.
|
||||
|
||||
### Auflagen an andere Tickets
|
||||
|
||||
- **11 UI-Gerüst und Screen-Modell**: Der Startbildschirm trägt eine Zeile mit Warnungsanzahl und
|
||||
Logdateiname. Eine Zeile, kein Screen.
|
||||
|
||||
### Folgen für bestehenden Code
|
||||
|
||||
`items.rs::parse()` verliert sein `panic!`, `main()` bekommt den stderr-plus-exit-Pfad, die
|
||||
`logs/`-Verwaltung kommt neu dazu.
|
||||
|
||||
Reference in New Issue
Block a user