Der Server schrieb deutsche Sätze an eine Stelle, die niemand liest

„Die Anfrage war so nicht zu gebrauchen" — das ist der Ersatzsatz des
Browsers für eine Ablehnung ohne Begründung. Dabei stand die Begründung
da: „Benutzername darf nicht leer sein, das Passwort braucht mindestens 8
Zeichen." Nur eben in `errors`, und beide Clients lesen `detail`.

Zwei Formen waren betroffen. `Results.ValidationProblem` legt den Satz
unter `errors` ab, `Results.BadRequest(new { fehler = … })` unter
`fehler` — in beiden Fällen kommt er nirgends an. Zusammen 36 Stellen,
darunter jede Zeile, die ich gestern für die Meldungen geschrieben habe.
Dass die Clients `title` und `errors` auslassen, ist dabei richtig: dort
stehen sonst die englischen Prüftexte des Rahmenwerks, und „Not Found" ist
keine Auskunft.

Jetzt eine Form, hinter `Einwand.Bitte` / `.Fehlt` / `.Belegt`. Dass es
drei benannte Türen sind statt eines Aufrufs mit Zahl, hat einen Grund:
man sieht am Wort, ob der Server um eine bessere Eingabe bittet, etwas
nicht findet oder auf etwas Bestehendes stösst.

**Auffallen konnte es nicht**, und das ist der eigentliche Fund. Die
vorhandenen Tests prüften den Rumpf als Zeichenkette — der Satz *stand* ja
drin, nur im falschen Feld. Der neue Test prüft deshalb, in welchem Feld,
und ein Wächter am Quelltext schlägt an, wenn eine der beiden alten Formen
zurückkommt. Er hat beim ersten Lauf gleich eine Stelle gefunden, die mein
Suchmuster übersehen hatte.

Dazu drei Kleinigkeiten, die beim Lesen auffielen:

**Passwörter wurden getrimmt.** Der Dialog schnitt jedem Feld die Ränder
ab, auch dem Passwort — `login.ts` sendet es ungeschoren. Ein Leerzeichen
am Ende, und das angelegte Konto war für immer unbetretbar.

**Beim Einrichten stapelten sich die Fehlerzeilen**, weil `.error`
weggeräumt wurde und `.fehler` entsteht. Drei Fehlversuche, drei rote
Kästen.

**Der Benutzerdialog versprach acht Zeichen und prüfte nichts.** Jetzt
sagt er es selbst, ohne den Umweg über die Leitung.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-07 17:12:57 +02:00
co-authored by Claude Opus 5
parent 7cd48f5528
commit 8e9da1355c
15 changed files with 248 additions and 81 deletions
@@ -23,13 +23,10 @@ public static class ManagementEndpoints
{
var name = anfrage.UserName.Trim();
if (name.Length == 0 || anfrage.Password.Length < 8)
return Results.ValidationProblem(new Dictionary<string, string[]>
{
["password"] = ["Benutzername darf nicht leer sein, das Passwort braucht mindestens 8 Zeichen."],
});
return Einwand.Bitte("Benutzername darf nicht leer sein, das Passwort braucht mindestens 8 Zeichen.");
if (await db.Users.AnyAsync(u => u.UserName == name))
return Results.Problem($"„{name}“ gibt es schon.", statusCode: StatusCodes.Status409Conflict);
return Einwand.Belegt($"„{name}“ gibt es schon.");
var neu = new User
{
@@ -175,7 +172,7 @@ public static class ManagementEndpoints
ReihenfolgeRequest anfrage, FlaroContext db, CancellationToken ct) =>
{
if (anfrage.Ids is null or { Count: 0 })
return Results.BadRequest(new { fehler = "Ohne Reihenfolge gibt es nichts zu ordnen." });
return Einwand.Bitte("Ohne Reihenfolge gibt es nichts zu ordnen.");
// Ab 1 zählen: die 0 bleibt denen, die nie umsortiert wurden.
for (var i = 0; i < anfrage.Ids.Count; i++)
@@ -362,20 +359,14 @@ public static class ManagementEndpoints
bibliotheken.MapPost("/", async (CreateLibraryRequest anfrage, FlaroContext db) =>
{
if (!Enum.TryParse<LibraryKind>(anfrage.Kind, ignoreCase: true, out var art))
return Results.ValidationProblem(new Dictionary<string, string[]>
{
["kind"] = [$"Unbekannte Art „{anfrage.Kind}“. Möglich: Movies, Shows."],
});
return Einwand.Bitte($"Unbekannte Art „{anfrage.Kind}“. Möglich: Movies, Shows.");
// **Jetzt prüfen, nicht beim ersten Scan.** Ein Tippfehler im Pfad
// führte sonst zu einer Bibliothek, die dauerhaft leer bleibt, und
// die Ursache stünde allenfalls im Protokoll.
if (!Directory.Exists(anfrage.Path))
return Results.ValidationProblem(new Dictionary<string, string[]>
{
["path"] = [$"„{anfrage.Path}“ gibt es im Container nicht. " +
"Ist das Verzeichnis eingehängt? Gemeint ist der Pfad im Container, nicht der auf dem Wirt."],
});
return Einwand.Bitte($"„{anfrage.Path}“ gibt es im Container nicht. " +
"Ist das Verzeichnis eingehängt? Gemeint ist der Pfad im Container, nicht der auf dem Wirt.");
var neu = new Library
{
@@ -399,10 +390,7 @@ public static class ManagementEndpoints
{
var name = anfrage.Name.Trim();
if (name.Length == 0)
return Results.ValidationProblem(new Dictionary<string, string[]>
{
["name"] = ["Ein Name darf nicht leer sein."],
});
return Einwand.Bitte("Ein Name darf nicht leer sein.");
var vorhanden = await db.Libraries.FindAsync(id);
if (vorhanden is null) return Results.NotFound();