Fehlerbehebung bei Prisma Guard-Fehlern: Ein phasenbasierter Diagnosemodell
Erfahren Sie, wie Sie generierte Prisma API-Fehler diagnostizieren können, indem Sie die Fehler der genauen Phase zuordnen – Konfiguration, Aufruferauswahl, Validierung oder Antwort –, die für sie verantwortlich ist.
Fangen Sie mit der Fehlerbehebung an, indem Sie herausfinden, in welcher Phase der Fehler tatsächlich auftritt.
Erstellte APIs können an mehreren verschiedenen Stellen versagen.
Erläuterung: Die hier beschriebenen Fehlerphasen stammen aus Projektdokumentationen und festgelegten Replikationsumgebungen, nicht aus umfassenden Nutzungstatistiken einer großen Nutzerbasis.
Ein Router kann seine eigene Konfiguration bereits vor dem Verarbeiten einer einzigen Anfrage ablehnen. Ein Prüfmechanismus kann eine fehlerhafte Struktur bereits in dem Moment ablehnen, in dem sie erstellt wird. Die Zuweisung der Anfrage an einen Router kann fehlschlagen, bevor ein für eine bestimmte Variante vorgesehener Mechanismus überhaupt ausgeführt wird. Die Validierung der Anfrage kann einen einzelnen Anfragedatenkörper ablehnen. Eine auf einen bestimmten Bereich beschränkte Operation kann fehlschlagen, einfach weil der vertrauenswürdige Kontext, auf den sie angewiesen ist, nicht vorhanden ist.
Jenseits davon gibt es eine schwierigere Kategorie: Die Anfrage ist technisch gesehen erfolgreich, doch die generierten Prisma-Argumente oder die Semantik der Antwort weichen von dem ab, was die Anwendungslogik angenommen hat.
Jede dieser Kategorien erfordert eine andere Lösung sowie einen anderen Testtyp. Das Durchsuchen der gesamten Fehlermeldung ist weitaus weniger effektiv als die Beantwortung zweier Fragen: Wann ist dieses Verhalten zum ersten Mal aufgetreten, und welche Schicht ist in der Lage, es zu beobachten?
Fangen Sie mit einer Phasenkarte an
Eine generierte Prisma-Anfrage durchläuft auf dem Weg zur Ausführung mehrere unterschiedliche Grenzen:
router construction
caller resolution
operation before-hooks
variant before-hooks
guard shape construction
request validation
Prisma argument execution
response transport
Die genaue Abfolge der mit Formen verbundenen Aufgaben kann je nachdem variieren, ob eine Form statisch ist oder vom Laufzeitkontext abhängt, doch diese diagnostische Gliederung bleibt weiterhin ein nützliches mentales Modell.
Fehler beim Start deuten auf Probleme in den Route-Deskriptoren hin. Fehler in der Aufrufstufe deuten auf die Logik zur Variantenauswahl hin. Fehler wie Invalid query und Invalid data deuten auf eine Unstimmigkeit zwischen dem Anfragekörper und der deklarierten Struktur hin. Richtlinienfehler deuten auf fehlenden vertrauenswürdigen Kontext hin. Und wenn eine Anfrage erfolgreich ist, aber das Ergebnis unerwartet ist, muss man den Statuscode völlig außer Acht lassen.
Haben Sie dabei im Hinterkopf, dass die Fehlermeldungen an bestimmte Versionen gebunden sind. Die hier erwähnten beispielhaften Anwendungen wurden mit einer festen Kombination erstellt: prisma-guard in Version 1.33.0 in Verbindung mit Zod 4.4.3 und Prisma 6.19.3. Die Beispiele für HTTP-basierte Lesevorgänge hingegen nutzen eine separate festgelegte Kombination: prisma-generator-express 1.64.4 unter Node 22.14.0 auf PostgreSQL 16.6.
Betrachten Sie die genaue Formulierung eines Fehlers als Beweis, der spezifisch für diese Versionskombination gilt. Betrachten Sie die Phase sowie die zugrunde liegende Ursache als das Debugging-Modell, das auch später noch nützlich sein wird.
Vor einer Anfrage: Die Konfiguration kann keinen Vertrag bilden
Die Router-Konstruktion ist dafür verantwortlich, die Betriebsbeschreibungen vor allem anderen zu validieren.
Eine Operation darf nicht gleichzeitig sowohl shape als auch variants konfigurieren. Ein Varianten-Map darf nicht leer bleiben. Jeder Variantenbeschreibung muss eine Form zugeordnet sein. Reservierte Formschlüssel dürfen nicht gleichzeitig als Aufrufernamen verwendet werden.
Es handelt sich dabei im Grunde um Fehler während des Bereitstellungsprozesses. Wenn das System diese Fehler erkennt und dennoch mit einem halb konfigurierten Router weiterläuft, würde es stillschweigend die Grenzen löschen, die die Anwendung eigentlich durchsetzen sollte.
Eine Operation, die weder shape noch variants definiert, stellt eine völlig andere Situation dar: Sie ist technisch gültig und ruft Prisma direkt auf, ohne jegliche Sicherheitsprüfung. Ob das akzeptabel ist, sollte eine ausdrückliche Entscheidung während der Überprüfung der Route sein – nicht ein Zufall.
Der Aufbau von Shapes weist eigene Fehlerbedingungen auf. Leere Kombinatoren, leere Projektionen, widersprüchliche erzwungene Prädikate, unvollständige Shape-Erstellungen, fehlerhafte Upsert-Strukturen sowie Bulk-Methoden ohne where-Shape werden bereits im Voraus abgelehnt, bevor Daten vom Client die Möglichkeit haben, unsicher mit ihnen zu interagieren.
Eine minimale Replikation ist am nützlichsten, wenn sie den Shape-Aufbau von der Übertragungsschicht trennt:
const query = guard.query('Plant', 'findMany', {
where: {
name: { contains: true },
},
take: { max: 50, default: 20 },
})
const args = query.parse({
where: {
name: { contains: 'fern' },
},
})
Der isolierte Pfad eignet sich gut zum Testen von Lesefiltern, Sortierungen, Paginierungsoptionen sowie der meisten Fehler bei der Strukturbildung. Er führt jedoch keine tatsächlichen Ausführungen gegen Prisma durch, wendet keine Delegierungs-Ebene für die Leseprojektion an und zeigt nicht, wie Mutationen in der Praxis funktionieren.
Egal, welche Lösung gewählt wird, sie muss in der Serverseitigen Konfiguration implementiert werden. Keine Anpassung des Anfrage-Payloads kann eine von vornherein strukturell fehlerhafte Struktur reparieren.
Vor dem Handler: Auswahl des Aufrufers fehlgeschlagen
Wenn benannte Strukturen und Varianten verwendet werden, gibt es eine zusätzliche Routing-Phase, die abläuft, bevor die Anfrage den generierten Handler erreicht.
Ein Aufrufer gilt als verloren, wenn die Variantekarte keine default-Einträge enthält. Ein Aufrufer gilt als unbekannt, wenn nichts zu ihm passt: weder eine exakte Schlüsselwerte noch ein parametrisiertes Muster und auch kein Standardwert. Zwei übereinanderliegende parametrisierte Muster werden durch die Deklarationsreihenfolge nicht gelöst; das System betrachtet die Situation als unklar und versagt stattdessen.
Die Identifizierungsdaten des Aufrufers werden über einen separaten Kanal vom Prisma-Anfragekörper übermittelt. Versuche, sie in die Anfragedaten selbst einzuschmuggeln, werden abgelehnt.
Für öffentlich zugängliche Verträge kann die Verwendung eines Headers als absichtlicher Aufrufer-Selektor eine sinnvolle Gestaltungsentscheidung sein. Für privilegierte Varianten sollte die Auswahl jedoch aus authentifizierter Logik innerhalb von resolveVariant stammen und nicht aus Client-Eingaben. Ein benutzerdefinierter Name für einen Header macht seinen Wert nicht vertrauenswürdiger.
Routing-Fehler treten auf, nachdem die operationsebene Before-Hooks ausgeführt wurden, aber noch bevor die variantspezifischen Hooks laufen. Diese Reihenfolge erklärt ein feines Verhalten: Die für die gesamte Operation geltende Authentifizierungslogik wird weiterhin ausgeführt, selbst wenn keine passende Aufrufvariante vorliegt, während in diesem Fall die variantspezifischen Hooks niemals ausgelöst werden.
Die richtige Lösung ist nicht automatisch „einfach einen Standard hinzufügen“. Ein Standard-Aufrufer akzeptiert schweigend fehlende, leere sowie nicht übereinstimmende Aufrufwerte gleichermaßen. Fügen Sie nur einen Standard hinzu, wenn dieses Ersatzverhalten in allen drei Szenarien tatsächlich akzeptabel ist.
Während der Validierung: Die Anfrage hat ihre deklarierte Grenze überschritten
Die in dieser Konfiguration festgestellten Lesfehler deuten auf den genauen Argumentpfad hin, der sie ausgelöst hat.
Ein nicht erkanntes Feld innerhalb von where bedeutet, dass dieses Feld nicht zum Filtermuster gehört. Ein nicht erkanntes Feld innerhalb von select bedeutet, dass die Anfrage versucht, die Projektion über das Zulässige hinaus zu erweitern. Ein abgelehnter skip-Wert bedeutet, dass das Überspringen von Seiten für dieses Muster niemals aktiviert wurde. Ein Fehler bei take kann bedeuten, dass der angeforderte Wert sein konfiguriertes Maximum überschritten hat oder dass er völlig im falschen skalaren Typ vorlag.
Die generierten GET-Hilfsfunktionen sind hier wichtig, denn Argumente im Prisma-Format werden nicht alle auf dieselbe Weise umgewandelt, wenn sie manuell aus Abfragesätzen erstellt werden. Numerische Filterwerte und Datumsangaben werden in den unterstützten Fällen in der Regel korrekt umgewandelt, doch boolesche Werte sowie Paginierungsangaben, die als Zeichenketten übergeben werden, können fehlerhaft umgewandelt werden. Die sicherere Wahl ist es, für GET-Anfragen den generierten Encoder zu verwenden oder alternativ über die auf POST basierende Lesevariante auf natives JSON zurückzugreifen.
Dagegen folgt die Erstellung von Validierungen einer Struktur, die für jede Prisma-Methode spezifisch ist. Create-Operationen erhalten ein data-Feld. Update-Operationen erhalten sowohl where als auch data. Upsert-Operationen erhalten where, create und update. Ein geschützter Batch-Create-Aufruf erwartet als Eingabe ein Array.
Massenoperationen können auf zwei verschiedenen Ebenen fehlschlagen. Wenn in der Struktur selbst kein where enthalten ist, handelt es sich um ein Problem während der Konstruktion. Wenn der Anfragekörper zur Laufzeit technisch gesehen ein where enthält, dieses aber keine tatsächliche Bedingung auf der Client-Seite darstellt, handelt es sich stattdessen um ein Problem zur Zeit der Anfrage.
Politikfehler bilden wiederum eine eigene Kategorie. Ein fehlender Scope-Root oder ein fehlender Kontext für eine Struktur, die von einem Laufzeitkontext abhängt, deuten beide darauf hin, dass ein bestimmter Teil des vertrauenswürdigen Zustands einfach nicht vorhanden ist. Die Einstellung des Verhaltens bei fehlendem Scope auf Fehlermodus verhindert, dass ein fehlender Kontext stillschweigend in eine unfiltrierte, oberste Ebeneanfrage umgewandelt wird.
Die Gewohnheit, die man hier entwickeln sollte, ist es, den genauen Pfad aufzubewahren, an dem etwas fehlschlug. Der Satz „Ich habe eine 400 von der Schutzfunktion erhalten“ sagt fast nichts Nützliches. Der Satz „Der Lesvorgang versuchte include.plants.take über dem konfigurierten maximalen Nestungsniveau“ weist direkt auf einen bestimmten Knoten im Vertrag hin.
Nachdem die Schutzfunktion abgeschlossen ist: Ein Status 200 verbirgt weiterhin echte Risiken
Eine erfolgreiche HTTP-Antwort sagt lediglich aus, dass der Pfad bis zum Ende abgearbeitet wurde. Sie gibt keine Auskunft darüber, ob der gesendete Wert tatsächlich berücksichtigt wurde, ob eine Bedingung innerhalb der angenommenen Abzweigung ausgeführt wurde oder ob die Antwort standardmäßig die erwartete Projektion verwendet hat.
Nehmen Sie ein vollständig erzwungenes Oberflächenprädikat: Es überschreibt alles, was der Client sendet, ohne dass ein sichtbares Zeichen dafür vorhanden ist. Wenn eine Form isPublished auf true festlegt, erhält ein Client, der false sendet, dennoch eine Erfolgsantwort, während die tatsächlich ausgeführte Abfrage den erzwungenen Wert true beibehält.
Andere erzwungene Felder verhalten sich umgekehrt und lehnen die vom Client bereitgestellten Werte entschieden ab, anstatt sie stillschweigend zu überschreiben. Da das Erzwingen je nach Anwendungsort inkonsistent wirken kann, müssen Ihre Tests überprüfen, wer tatsächlich für jedes Argument verantwortlich ist, anstatt anzunehmen, dass eine Instanz von force() auf alle Felder anwendbar ist.
Das Erzwingen wird innerhalb einer OR-Klausel noch schwieriger. Eine dort platzierte erzwungene Bedingung wird nach oben gehoben und in eine verpflichtende Konstraint auf höchster Ebene umgewandelt. Somit kann eine Struktur, die den Eindruck erweckt, „entweder die Bedingung des Clients oder die des Servers“ auszudrücken, tatsächlich als Kombination der Bedingung des Clients mit dem erzwungenen Prädikat unter Verwendung der AND-Logik ausgeführt werden. Wenn Sie wirklich eine vom Server gesteuerte Alternative benötigen, müssen Sie eine speziell dafür konstruierte Abfrage verwenden oder die Regelung stattdessen auf der Ebene der Datenbankrichtlinien durchsetzen.
Die Projektion der Antwort führt zu einer eigenen, subtilen Abweichung. Wenn ein Client bei einem geschützten Lesvorgang eine Projektion weglässt, wird die Standardprojektion der Struktur angewendet – doch diese Substitution findet zum Zeitpunkt statt, an dem der Delegat tatsächlich ausgeführt wird, und nicht, wenn guard.query().parse() läuft.
Mutationen folgen nicht denselben Regeln. Wenn enforceProjection nicht gesetzt ist, erhält ein Client, der bei einer Mutation eine Projektion weglässt, überhaupt keine select-Klausel injiziert – dadurch übernimmt stattdessen Prismas übliches Verhalten bei fehlender Projektion.
Die Durchsetzung des verschachtelten Scope ist ein weiterer Punkt, bei dem man leicht annehmen kann, es gäbe mehr Abdeckung, als tatsächlich vorhanden ist. Der automatische Scope interzipiert nur die obersten Ebene Operationen, die er ausdrücklich unterstützt. Er erreicht nicht die Beziehungen, die über eine Projektion eingebunden werden, und filtert sie nicht rekursiv. Zudem wird der Scope-Root selbst niemals durch seinen eigenen Marker gefiltert, und jeder raw SQL-Befehl umgeht vollständig die Durchsetzungsstufe der Erweiterung.
Niemandes dieser Verhaltensweisen zeigt sich, wenn man nur den Statuscode überprüft.
Wählen Sie das richtige Leseverfahren, bevor Sie die Antwortstruktur vertrauen
Die generierte Schicht bietet drei unterschiedliche Mechanismen zur Übermittlung von Leseergebnissen: paginierte Antworten, transport über POST sowie serverseitig gesendete Ereignisse über Express.
findManyPaginated gibt eine feste Außenstruktur zurück:
type PaginatedResult<T> = {
data: T[]
total: number
hasMore: boolean
}
Das Flag hasMore ist insbesondere bei forward-offset-basierter Paginierung in Kombination mit einem positiven take-Wert zuverlässig. Wenn Sie cursorbasierte Paginierung oder einen negativen take-Wert verwenden, erhalten Sie zwar weiterhin ein Booleschwert zurück, doch dieses bietet keine gleiche Garantie mehr. Ein take-Wert von 0 führt dazu, dass keine Zeilen zurückgegeben werden und das Fortsetzungsflag falsch ist, während die Gesamtanzahl unverändert bleibt.
Die Gesamtanzahl folgt einem völlig anderen logischen Ablauf. Die differenzierte Zählung berücksichtigt eine konfigurierte Obergrenze. Eine im Voraus berechnete Zählungsquelle wird nur dann verwendet, wenn die Anfrage unfiltriert, ungefiltert und nicht differenziert ist. Jeder dynamische Filter, jede differenzierte Klausel oder jede Schutzmaßnahme zwingt dazu, auf eine zur Zeit der Anfrage berechnete aktuelle Zählung zurückzugreifen.
Dieser Rückgriff bewahrt die Korrektheit, ändert jedoch sowohl den Aufwand der Operation als auch die Herkunft der Zahl. Betrachten Sie die Semantik der Gesamtanzahl als eine separate Frage im Vergleich zur Semantik des Zeilenausschnitts.
POST-basierte Lesevorgänge dienen dazu, die Größe und Kodierung des Payloads zu handhaben, nicht dazu, das Ausdrucksvermögen der Abfragesprache zu erweitern:
POST /delivery/paginated
Content-Type: application/json
{"where":{"city":{"equals":"Bangkok"}},"take":20,"skip":0}
Senden Sie den Body als natives JSON. Von den GET- und POST-Versionen derselben Route wird erwartet, dass sie denselben Schutzmechanismus anwenden. Wenn ein Hook den Anfrage-Body überschreibt, kann diese Gleichwertigkeit gestört werden, da der GET-Pfad von bereits parseten Abfrageparametern statt von einem JSON-Body gelesen wird.
Server-sent Events ändern sich dadurch, dass Daten ankommen, und nicht dadurch, welche Daten genau ankommen. Dieser Mechanismus macht nur dann Sinn, wenn der Client tatsächlich Verarbeitungsmethoden für Fortschrittsereignisse, ein endgültiges Erfolgsereignis, ein endgültiges Fehlerereignis sowie einen Fallback-Pfad implementiert hat.
{"type":"progress","stage":"relations"}
{"type":"field","field":"summary","data":{"total":6}}
{"type":"result","data":{"summary":{"total":6},"deliveries":[]}}
Manuell gesteuerte SSE-Ereignisse sind Anfragen auf Anwendungsseite, die man selbst schreibt, und sie benötigen genauso wie alles andere eine explizite Schutzbehandlung. Die automatische Einbeziehung umfasst nur jene Relationen, die dokumentiert sind und innerhalb der Grenzen des Planers liegen; alles Andere fällt auf das konfigurierte Ersatzverhalten zurück. Zudem stellen generierte Nach-Hooks kein zuverlässiges Mittel zur Bereinigung eines SSE-Streams dar.
Insgesamt kann eine „erfolgreiche“ Lesevorgang aus mehreren unabhängigen Gründen falsch sein: ein unzuverlässiges Fortsetzungsflag, eine Fehlinterpretation der Herkunft der Zählung, transportbezogenes Hook-Verhalten oder eine gesteuerte Anfrage, die nie geschützt wurde.
Richten Sie jeden Test auf die Schicht aus, die er tatsächlich überprüfen kann
Keine einzige End-to-End-Anfrage kann alle Schichten gleichzeitig validieren.
Ziehen Sie den Parser heran, wenn Sie die Validierung des Datentyps oder die Struktur der erzwungenen Zusammenführung testen. Wenden Sie den geschützten Delegaten an, wenn es um die Projektion zur Ausführungszeit oder die Endmutationss Argumente geht. Nutzen Sie den Operationspfad der Erweiterung, um zu überprüfen, ob tatsächlich eine automatische Skopoinjektion stattgefunden hat.
Ein eigenständiges Werkzeug zur Argumenterkennung ermöglicht es Ihnen, die Endmutationss Argumente ohne Berührung einer Datenbank zu inspizieren – allerdings nur, wenn Sie die Schutzerweiterung mit einem Delegaten verbinden, der tatsächlich die empfangenen Argumente zurückgibt. Das Erstellen eines getrennten, falschen Objekts beweist nichts. Solche Werkzeuge zeigen an, welche Argumente ausgesendet wurden, nicht jedoch, welche Zeilen eine Datenbank tatsächlich zurückgeben würde.
Für Fragen zu Ergebnissen auf Mieterebene, Eigentumsverhältnissen, transaktionalen Verhaltensweisen, unterschiedlichen Gesamtbeträgen oder anbieterbezogenen Besonderheiten benötigen Sie Datenbank-gestützte Testdaten. Legen Sie mindestens zwei Mieter mit Zeilen an, die deutlich voneinander abweichen, damit ein Leck sofort erkennbar ist, falls es auftritt.
Für Fragen zur generierten Routenplanung, Serialisierung, Ausführung von Hooks, Äquivalenz von GET/POST-Anfragen, Struktur der Paginierungsantworten oder Sequenzierung von SSE-Ereignissen verwenden Sie Tests auf HTTP-Ebene.
Behalten Sie mindestens einen Kontrakt-Test mit Schutzmechanismus bei, selbst wenn Ihr Browser-End-to-End-Testsuite in einem Modus läuft, der die Validierung dieser Schutzmechanismen deaktiviert. Ein Browser-Test, der in einem entspannteren Modus bestanden hat, sagt nichts darüber aus, was in der Produktion abgelehnt wird, da die Überwachungsschicht für den Test entfernt wurde.
Schreiben Sie jeden Regressionstest auf der niedrigsten Ebene, die in der Lage ist, die spezifische Behauptung zu beweisen, die er aufstellt. Kompaktere Tests bedeuten, dass bei späteren Fehlern die Fehlerquellen auf die jeweilige Phase zurückgeführt werden können, anstatt dass man den gesamten Anfragenweg von vorne untersuchen muss.
Fehler in einer einzigen Richtung bearbeiten
Eine kurze, wiederholbare Abfolge verhindert, dass man bei der Fehlerbehebung raten muss:
- Ermitteln Sie, ob es sich um einen Startvorgangsfehler, einen Fehler zur Zeit der Anfrage oder um eine erfolgreiche Antwort handelt, die Sie überrascht hat.
- Bestimmen Sie, welche Phase dafür verantwortlich ist: Router, Auflösung der Anruferadresse, Formatierung, Richtlinien, Ausführung von Prisma oder Transport.
- Einschränken Sie die Nachvollziehbarkeit auf eine einzige Operation, ein einziges Format und einen einzigen Anfragekörper.
- Überprüfen Sie den Argumentwert in der Ebene, die dem Ort des Verhaltens am nächsten liegt.
Generierte APIs sind weitaus leichter zu verstehen, wenn man ihre Phasen voneinander trennt. Konfigurationsfehler sollten bereits vor dem Bereitstellen von Traffic aufgedeckt werden. Anfragen, die eine Regel verletzen, sollten genau angeben, welchen Teil des Vertrags sie gebrochen haben. Eine erfolgreiche Antwort sollte anhand der tatsächlich übermittelten Argumente sowie der für sie dokumentierten Übertragungssemantik überprüft werden – niemals allein anhand des Statuscodes.
Verwandte Literatur
- Behebung des Fehlers „Fehlende Bibliothek libssl.so.1.1“ in Prisma auf Alpine Docker — Erfahren Sie, warum Prismas Abfragemotor in auf Alpine basierenden Docker-Images wegen eines fehlenden libssl-Fehlers abstürzt, und wie man dieses Problem endgültig behebt.
- Erstellung einer typsicheren GraphQL-API mit Prisma und Nexus in Node.js — Folgen Sie einer sieben Schritte umfassenden Anleitung zur Erstellung einer Node.js GraphQL-API, die Prismas Datenmodell mit von Nexus generierten Typen und Resolvern vereint.