[GO] GraphQL (gqlgen) Fehlerbehandlung

Thema

Betrachten Sie die Fehlerbehandlung auf der GraphQL Server-Seite mit gqlgen, einer von Golang erstellten GraphQL-Bibliothek, die "typsicheres GraphQL für Go" beansprucht.

Angenommener Leser

Zugehöriger Artikelindex

--11th "Antwort auf N + 1-Problem mit Datenladern"

Entwicklungsumgebung

OS - Linux(Ubuntu)

$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="18.04.5 LTS (Bionic Beaver)"

#Backend

#Language --Golang

$ go version
go version go1.15.2 linux/amd64

gqlgen

v0.13.0

IDE - Goland

GoLand 2020.2.3
Build #GO-202.7319.61, built on September 16, 2020

Alle Quellen diesmal

https://github.com/sky0621/study-gqlgen/tree/v0.2

Trainieren

Probieren wir einige Methoden zum Umgang mit GraphQL-Fehlern auf der Serverseite mit gqlgen aus.

1. 1. Grundlegende Handhabung

Listen Sie einige Muster auf.

server.go

package main

import (
	"log"
	"net/http"

	"github.com/99designs/gqlgen/graphql/handler"
	"github.com/99designs/gqlgen/graphql/playground"
	"github.com/sky0621/study-gqlgen/errorhandling/graph"
	"github.com/sky0621/study-gqlgen/errorhandling/graph/generated"
)

func main() {
	srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}}))
	http.Handle("/", playground.Handler("GraphQL playground", "/query"))
	http.Handle("/query", srv)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

schema.graphqls

type Query {
  normalReturn: [Todo!]!
  errorReturn: [Todo!]!
  customErrorReturn: [Todo!]!
  customErrorReturn2: [Todo!]!
  customErrorReturn3: [Todo!]!
  customErrorReturn4: [Todo!]!
  panicReturn: [Todo!]!
}

type Todo {
  id: ID!
  text: String!
}

Resolver

go:schema.resolvers.go


package graph

// This file will be automatically regenerated based on the schema, any resolver implementations
// will be copied through when generating and any unknown code will be moved to the end.

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/99designs/gqlgen/graphql"
	"github.com/sky0621/study-gqlgen/errorhandling/graph/generated"
	"github.com/sky0621/study-gqlgen/errorhandling/graph/model"
	"github.com/vektah/gqlparser/v2/gqlerror"
)

func (r *queryResolver) NormalReturn(ctx context.Context) ([]*model.Todo, error) {
	return []*model.Todo{
		{ID: "001", Text: "something1"},
		{ID: "002", Text: "something2"},
	}, nil
}

func (r *queryResolver) ErrorReturn(ctx context.Context) ([]*model.Todo, error) {
	return nil, errors.New("error occurred")
}

func (r *queryResolver) CustomErrorReturn(ctx context.Context) ([]*model.Todo, error) {
	return nil, gqlerror.Errorf("custom error")
}

func (r *queryResolver) CustomErrorReturn2(ctx context.Context) ([]*model.Todo, error) {
	graphql.AddError(ctx, gqlerror.Errorf("add error"))
	graphql.AddErrorf(ctx, "add error2: %s", time.Now().String())
	return nil, nil
}

func (r *queryResolver) CustomErrorReturn3(ctx context.Context) ([]*model.Todo, error) {
	return nil, &gqlerror.Error{
		Extensions: map[string]interface{}{
			"code":  "A00001",
			"field": "text",
			"value": "Toilettenreinigung",
		},
	}
}

func (r *queryResolver) CustomErrorReturn4(ctx context.Context) ([]*model.Todo, error) {
	return nil, &gqlerror.Error{
		Extensions: map[string]interface{}{
			"errors": []map[string]interface{}{
				{
					"code":  "A00001",
					"field": "text",
					"value": "Toilettenreinigung",
				},
				{
					"code":  "A00002",
					"field": "text",
					"value": "Toilettenreinigung",
				},
			},
		},
	}
}

func (r *queryResolver) PanicReturn(ctx context.Context) ([]*model.Todo, error) {
	panic(fmt.Errorf("panic occurred"))
}

// Query returns generated.QueryResolver implementation.
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }

type queryResolver struct{ *Resolver }

Erklärung nach Muster

Referenz

In Bezug auf die Antwort in GraphQL. Im Fall eines normalen Systems ist die Struktur wie folgt.

{
  "data": {
      〜〜〜〜
  }
}

Wenn der Resolver einen Fehler zurückgibt, sieht die Struktur wie folgt aus.

{
  "errors": [
    {
      "message": 〜〜〜〜,
      "path": [〜〜〜〜]
    }
  ],
  "data": null
}

Darüber hinaus wurde nachstehend darauf hingewiesen. https://gqlgen.com/reference/errors/

■ Normales System

func (r *queryResolver) NormalReturn(ctx context.Context) ([]*model.Todo, error) {
	return []*model.Todo{
		{ID: "001", Text: "something1"},
		{ID: "002", Text: "something2"},
	}, nil
}

Normales System. Die eingestellten Daten werden zurückgegeben. screenshot-localhost_8080-2020.10.17-23_48_21.png

■ Das zurückgegebene Muster ist ein Standardfehler

func (r *queryResolver) ErrorReturn(ctx context.Context) ([]*model.Todo, error) {
	return nil, errors.New("error occurred")
}

Die angegebene Fehlermeldung wird in message geladen. path wird willkürlich angegeben. screenshot-localhost_8080-2020.10.17-23_51_16.png

■ Muster, das über die von gqlgen bereitgestellte Methode einen Fehler zurückgibt

func (r *queryResolver) CustomErrorReturn(ctx context.Context) ([]*model.Todo, error) {
	return nil, gqlerror.Errorf("custom error")
}

Immerhin wird die angegebene Fehlermeldung in message geladen. Die Struktur entspricht dem Muster, das einen Go-Standardfehler zurückgibt. screenshot-localhost_8080-2020.10.18-00_08_41.png

■ Muster, das mehrere Fehler zurückgibt

func (r *queryResolver) CustomErrorReturn2(ctx context.Context) ([]*model.Todo, error) {
	graphql.AddError(ctx, gqlerror.Errorf("add error"))
	graphql.AddErrorf(ctx, "add error2: %s", time.Now().String())
	return nil, nil
}

Die zwei angegebenen Fehlertypen werden in jede "Nachricht" geladen. Ich bin ein wenig besorgt, dass "data" ein leeres Slice anstelle von "null" zurückgibt, im Gegensatz zu dem Zeitpunkt, als bisher ein Fehler aufgetreten ist. (Wahrscheinlich, weil "return" keinen Fehler zurückgegeben hat.) screenshot-localhost_8080-2020.10.18-00_19_32.png

■ Muster, die einzelne Erweiterungsbereiche verwenden

func (r *queryResolver) CustomErrorReturn3(ctx context.Context) ([]*model.Todo, error) {
	return nil, &gqlerror.Error{
		Extensions: map[string]interface{}{
			"code":  "A00001",
			"field": "text",
			"value": "Toilettenreinigung",
		},
	}
}

In message wird nichts geladen, und der Fehlerinhalt wird in den vorbereiteten Erweiterungen mit einem dienstspezifischen Ausdruck definiert. Da es sich um "map [string] interface {}" handelt, kann jede Struktur verwendet werden. Dies ermöglicht es dem Front-End, das die Antwort empfängt, eine Fehlermeldung gemäß dem "Code" zu generieren und diese dem Endbenutzer anzuzeigen. screenshot-localhost_8080-2020.10.18-00_20_47.png

■ Muster 2, das einzelne Erweiterungsbereiche verwendet

func (r *queryResolver) CustomErrorReturn4(ctx context.Context) ([]*model.Todo, error) {
	return nil, &gqlerror.Error{
		Extensions: map[string]interface{}{
			"errors": []map[string]interface{}{
				{
					"code":  "A00001",
					"field": "text",
					"value": "Toilettenreinigung",
				},
				{
					"code":  "A00002",
					"field": "text",
					"value": "Toilettenreinigung",
				},
			},
		},
	}
}

Sie möchten nicht immer einen Fehler zurückgeben. Natürlich ist es möglich, mehrere Fehler zurückzugeben, wenn Sie es in Form eines solchen Ausschnitts der Karte behalten. screenshot-localhost_8080-2020.10.18-00_24_29.png

■ Muster bei Panik

func (r *queryResolver) PanicReturn(ctx context.Context) ([]*model.Todo, error) {
	panic(fmt.Errorf("panic occurred"))
}

Die Nachricht, die geladen wird, wenn "Panik" auftritt, wird ignoriert und der "interne Systemfehler" wird in "Nachricht" geladen. screenshot-localhost_8080-2020.10.18-00_28_03.png

2. Kundenspezifische Fehlerbehandlung

Sofern es sich nicht um einen sehr kleinen Dienst handelt, ist meines Erachtens ein dienstspezifischer Ausdruck für die Fehlerbehandlung erforderlich. In gqlgen gibt es einen Mechanismus zum Hinzufügen einer Verarbeitung durch Verknüpfen von "wenn ein Fehler auftritt" und "wenn eine Panik auftritt", wenn ein Handler generiert wird. Mit diesem Mechanismus Der Resolver gibt die eindeutig definierte Fehlerstruktur an den Service zurück (wenn ein Fehler auftritt), bindet sie an den Handler, verarbeitet die Fehlerstruktur und implementiert sie als Antwort.

schema.graphqls

type Query {
  errorPresenter: [Todo!]!
  panicHandler: [Todo!]!
}

type Todo {
  id: ID!
  text: String!
}

schema.resolvers.go Erstellen Sie "AppError" als dienstspezifische Fehlerstruktur. Die Struktur wird vom Resolver zurückgegeben.

package graph

import (
	"context"
	"fmt"

	"github.com/sky0621/study-gqlgen/errorhandling2/graph/generated"
	"github.com/sky0621/study-gqlgen/errorhandling2/graph/model"
)

type ErrorCode string

const (
	ErrorCodeRequired            ErrorCode = "1001"
	ErrorCodeUnexpectedSituation ErrorCode = "9999"
)

type AppError struct {
	Code ErrorCode
	Msg  string
}

func (e AppError) Error() string {
	return fmt.Sprintf("[%s]%s", e.Code, e.Msg)
}

func (r *queryResolver) ErrorPresenter(ctx context.Context) ([]*model.Todo, error) {
	return nil, AppError{
		Code: ErrorCodeRequired,
		Msg:  "text is none",
	}
}

func (r *queryResolver) PanicHandler(ctx context.Context) ([]*model.Todo, error) {
	panic("unexpected situation")
}

// Query returns generated.QueryResolver implementation.
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }

type queryResolver struct{ *Resolver }

server.go In der von "SetErrorPresenter ()" festgelegten Funktion wird der vom Resolver ausgelöste Fehler empfangen, und wenn es sich um "AppError" handelt, wird er erneut in die Struktur von "* gqlerror.Error {}" bearbeitet. Übrigens wird "SetRecoverFunc ()" auch vorbereitet und bearbeitet, so dass angenommen wird, dass der Fehlerausdruck auch im Falle einer Panik dienstspezifisch ist.

package main

import (
	"context"
	"errors"
	"log"
	"net/http"

	"github.com/99designs/gqlgen/graphql"
	"github.com/vektah/gqlparser/v2/gqlerror"

	"github.com/99designs/gqlgen/graphql/handler"
	"github.com/99designs/gqlgen/graphql/playground"
	"github.com/sky0621/study-gqlgen/errorhandling2/graph"
	"github.com/sky0621/study-gqlgen/errorhandling2/graph/generated"
)

func main() {
	srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}}))

	srv.SetErrorPresenter(func(ctx context.Context, e error) *gqlerror.Error {
		err := graphql.DefaultErrorPresenter(ctx, e)

		var appErr graph.AppError
		if errors.As(err, &appErr) {
			return &gqlerror.Error{
				Message: appErr.Msg,
				Extensions: map[string]interface{}{
					"code": appErr.Code,
				},
			}
		}
		return err
	})

	srv.SetRecoverFunc(func(ctx context.Context, err interface{}) error {
		return &gqlerror.Error{
			Extensions: map[string]interface{}{
				"code":  graph.ErrorCodeUnexpectedSituation,
				"cause": err,
			},
		}
	})

	http.Handle("/", playground.Handler("GraphQL playground", "/query"))
	http.Handle("/query", srv)

	log.Fatal(http.ListenAndServe(":8080", nil))
}

Funktionsprüfung

Fehlerbehandlung

Gut, dass der Fehlercode und die Fehlermeldung sortiert werden können. screenshot-localhost_8080-2020.10.18-00_43_38.png

Panikbehandlung

Da der Fehler, wenn Panik auftritt, als "Ursache" hinzugefügt wird, ist der Fehlerinhalt der Quelle auch ordnungsgemäß in der Antwort enthalten. screenshot-localhost_8080-2020.10.18-00_45_38.png

3. 3. Kundenspezifische Fehlerbehandlung für Vielseitigkeit

Es gibt verschiedene Arten von Fehlern, wie z. B. Validierungsfehler, Authentifizierungsfehler, DB-Verbindungsfehler usw., und ich denke, dass sich die für die Fehlerstruktur erforderlichen Elemente ändern werden. In einigen Fällen reicht es aus, einen einzelnen Fehler zurückzugeben, in anderen Fällen muss jedes Fehlerelement (wie ein Validierungsfehler) zurückgegeben werden, und daher müssen mehrere Fehler zurückgegeben werden. Basierend auf dieser Situation werden wir versuchen, Fehler so universell wie möglich zu behandeln.

Service-spezifische Fehlerstruktur

apperror.go


package graph

import (
	"context"
	"net/http"

	"github.com/vektah/gqlparser/v2/gqlerror"

	"github.com/99designs/gqlgen/graphql"
)

type AppError struct {
	httpStatusCode int          // http.Geben Sie StatusCodeXXXXXXX ein
	appErrorCode   AppErrorCode //Dienstspezifischer Fehlercode

	/*
	 *Im Folgenden werden Elemente aufgeführt, die nicht für alle Fehlerausdrücke erforderlich sind (können optional festgelegt werden).
	 */
	field string
	value string
}

func (e *AppError) AddGraphQLError(ctx context.Context) {
	extensions := map[string]interface{}{
		"status_code": e.httpStatusCode,
		"error_code":  e.appErrorCode,
	}
	if e.field != "" {
		extensions["field"] = e.field
	}
	if e.value != "" {
		extensions["value"] = e.value
	}
	graphql.AddError(ctx, &gqlerror.Error{
		Message:    "",
		Extensions: extensions,
	})
}

func NewAppError(httpStatusCode int, appErrorCode AppErrorCode, opts ...AppErrorOption) *AppError {
	a := &AppError{
		httpStatusCode: httpStatusCode,
		appErrorCode:   appErrorCode,
	}

	for _, o := range opts {
		o(a)
	}

	return a
}

//Für Authentifizierungsfehler
func NewAuthenticationError(opts ...AppErrorOption) *AppError {
	return NewAppError(http.StatusUnauthorized, AppErrorCodeAuthenticationFailure, opts...)
}

//Für Autorisierungsfehler
func NewAuthorizationError(opts ...AppErrorOption) *AppError {
	return NewAppError(http.StatusForbidden, AppErrorCodeAuthorizationFailure, opts...)
}

//Für Validierungsfehler
func NewValidationError(field, value string, opts ...AppErrorOption) *AppError {
	options := []AppErrorOption{WithField(field), WithValue(value)}
	for _, opt := range opts {
		options = append(options, opt)
	}
	return NewAppError(http.StatusBadRequest, AppErrorCodeValidationFailure, options...)
}

//Für andere Fehler
func NewInternalServerError(opts ...AppErrorOption) *AppError {
	return NewAppError(http.StatusInternalServerError, AppErrorCodeUnexpectedFailure, opts...)
}

type AppErrorCode string

// MEMO:Abhängig von der Definition des Dienstes kann das Codesystem anstelle einer aussagekräftigen Zeichenfolge festgelegt werden.
const (
	//Authentifizierungsfehler
	AppErrorCodeAuthenticationFailure AppErrorCode = "AUTHENTICATION_FAILURE"
	//Autorisierungsfehler
	AppErrorCodeAuthorizationFailure AppErrorCode = "AUTHORIZATION_FAILURE"
	//Validierungsfehler
	AppErrorCodeValidationFailure AppErrorCode = "VALIDATION_FAILURE"

	//Andere unerwartete Fehler
	AppErrorCodeUnexpectedFailure AppErrorCode = "UNEXPECTED_FAILURE"
)

type AppErrorOption func(*AppError)

func WithField(v string) AppErrorOption {
	return func(a *AppError) {
		a.field = v
	}
}

func WithValue(v string) AppErrorOption {
	return func(a *AppError) {
		a.value = v
	}
}

Kommentar

Erstellen Sie zunächst "AppError" als dienstspezifische Fehlerstruktur. Ich denke, dass das, was Sie als Fehlerelement haben, vom Dienst abhängt, aber vorerst werden die folgenden beiden als wesentlich definiert, unabhängig vom Inhalt des Fehlers.

--HTTP-Statuscode

type AppError struct {
	httpStatusCode int          // http.Geben Sie StatusCodeXXXXXXX ein
	appErrorCode   AppErrorCode //Dienstspezifischer Fehlercode
     〜〜
}

func NewAppError(httpStatusCode int, appErrorCode AppErrorCode, opts ...AppErrorOption) *AppError {
	a := &AppError{
		httpStatusCode: httpStatusCode,
		appErrorCode:   appErrorCode,
	}
     〜〜
}

In Fällen wie Validierungsfehlern, in denen Sie Informationen zu "Welcher Wert in welchem Feld" wünschen, sollte die Struktur die erforderlichen Elemente für jedes Muster enthalten (auch wenn es redundant ist).

type AppError struct {
     〜〜
	/*
	 *Im Folgenden werden Elemente aufgeführt, die nicht für alle Fehlerausdrücke erforderlich sind (können optional festgelegt werden).
	 */
	field string
	value string
}

Ich möchte jedoch nicht jedes Mal die Funktion "Neu" ändern (dh alle Aufrufer ändern), wenn ich diese Elemente in Zukunft hinzufügen muss. Daher [Funktionsoptionsmuster](https: //commandcenter.blogspot] .com / 2014/01 / self-referential-functions-and-design.html) wird verwendet.

Definieren Sie eine Funktion zum Anwenden von Optionen und übergeben Sie sie als variables Argument in der Funktion "Neu" (dh Sie müssen dies nicht tun).

type AppErrorOption func(*AppError)

func NewAppError(httpStatusCode int, appErrorCode AppErrorCode, opts ...AppErrorOption) *AppError {
	a := &AppError{
		httpStatusCode: httpStatusCode,
		appErrorCode:   appErrorCode,
	}

	for _, o := range opts {
		o(a)
	}

	return a
}

Die folgenden beiden werden als Anwendungsbeispiele für "AppErrorOption" erstellt.

func WithField(v string) AppErrorOption {
	return func(a *AppError) {
		a.field = v
	}
}

func WithValue(v string) AppErrorOption {
	return func(a *AppError) {
		a.value = v
	}
}

Auf diese Weise kann die Fehlerstruktur erweitert werden, ohne dass der vorhandene Aufrufer geändert wird, auch wenn in Zukunft weitere Elemente zur Fehlerstruktur hinzugefügt werden.

Ich denke, es ist ziemlich schwierig, diesen Mechanismus auf den ersten Blick zu verstehen (der Hauptgrund ist, dass die Erklärung schlampig ist), daher möchte ich, dass Sie mit "Functional Option Pattern" googeln und einen einfachen Erklärungsartikel lesen. .. ..

Definieren Sie anschließend den dienstspezifischen Fehlercode wie folgt:

type AppErrorCode string

// MEMO:Abhängig von der Definition des Dienstes kann das Codesystem anstelle einer aussagekräftigen Zeichenfolge festgelegt werden.
const (
	//Authentifizierungsfehler
	AppErrorCodeAuthenticationFailure AppErrorCode = "AUTHENTICATION_FAILURE"
	//Autorisierungsfehler
	AppErrorCodeAuthorizationFailure AppErrorCode = "AUTHORIZATION_FAILURE"
	//Validierungsfehler
	AppErrorCodeValidationFailure AppErrorCode = "VALIDATION_FAILURE"

	//Andere unerwartete Fehler
	AppErrorCodeUnexpectedFailure AppErrorCode = "UNEXPECTED_FAILURE"
)

Es ist in Ordnung, wenn Sie für jeden Fehlertyp eine dedizierte "Neu" -Funktion vorbereiten.


//Für Authentifizierungsfehler
func NewAuthenticationError(opts ...AppErrorOption) *AppError {
	return NewAppError(http.StatusUnauthorized, AppErrorCodeAuthenticationFailure, opts...)
}

//Für Autorisierungsfehler
func NewAuthorizationError(opts ...AppErrorOption) *AppError {
	return NewAppError(http.StatusForbidden, AppErrorCodeAuthorizationFailure, opts...)
}

//Für Validierungsfehler
func NewValidationError(field, value string, opts ...AppErrorOption) *AppError {
	options := []AppErrorOption{WithField(field), WithValue(value)}
	for _, opt := range opts {
		options = append(options, opt)
	}
	return NewAppError(http.StatusBadRequest, AppErrorCodeValidationFailure, options...)
}

//Für andere Fehler
func NewInternalServerError(opts ...AppErrorOption) *AppError {
	return NewAppError(http.StatusInternalServerError, AppErrorCodeUnexpectedFailure, opts...)
}

Resolver

Wenn Sie als Test für jeden Typ einen Fehler generieren und ihn als GraphQL-Fehler hinzufügen, sieht er folgendermaßen aus. (Natürlich werden Authentifizierungsfehler mit Benutzer-IDs geladen, aber vorerst handelt es sich um ein Beispiel.)

go:schema.resolvers.go


package graph

import (
	"context"

	"github.com/sky0621/study-gqlgen/errorhandling3/graph/generated"
	"github.com/sky0621/study-gqlgen/errorhandling3/graph/model"
)

func (r *queryResolver) CustomErrorReturn(ctx context.Context) ([]*model.Todo, error) {
	//Authentifizierungsfehler hinzugefügt
	NewAuthenticationError().AddGraphQLError(ctx)

	//Autorisierungsfehler hinzugefügt
	NewAuthorizationError().AddGraphQLError(ctx)

	//Validierungsfehler hinzugefügt
	NewValidationError("name", "taro").AddGraphQLError(ctx)

	//Andere Fehler hinzugefügt
	NewInternalServerError().AddGraphQLError(ctx)

	return nil, nil
}

// Query returns generated.QueryResolver implementation.
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }

type queryResolver struct{ *Resolver }

schema.graphqls

type Query {
  customErrorReturn: [Todo!]!
}

type Todo {
  id: ID!
  text: String!
}

server.go Diesmal gibt es keine Vorbereitung für den Handler.

package main

import (
	"log"
	"net/http"

	"github.com/99designs/gqlgen/graphql/handler"
	"github.com/99designs/gqlgen/graphql/playground"
	"github.com/sky0621/study-gqlgen/errorhandling3/graph"
	"github.com/sky0621/study-gqlgen/errorhandling3/graph/generated"
)

func main() {
	srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}}))
	http.Handle("/", playground.Handler("GraphQL playground", "/query"))
	http.Handle("/query", srv)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Funktionsprüfung

Wie Sie sehen können, hat es ein einheitliches Format, daher sollte es auf der Empfangsseite einfach zu handhaben sein. .. .. screenshot-localhost_8080-2020.10.18-01_22_59.png

Zusammenfassung

Ich habe mehrere Vorschläge zur Fehlerbehandlung vorgestellt, von einfachen, die vorerst nur einen Fehler zurückgeben müssen, bis zu einer Methode, die unter Berücksichtigung der Vielseitigkeit eine dienstspezifische Fehlerstruktur definiert. Natürlich kann es neben den hier gezeigten auch andere Muster geben, und die hier aufgeführten befinden sich nicht auf Produktionsebene. Wenn Sie es als einen Dienst betrachten, ist auch der Umgang mit dem Fehlerinhalt, der hier am Frontend zurückgegeben wird, ein wichtiger Faktor.

Recommended Posts

GraphQL (gqlgen) Fehlerbehandlung
Fehlerbehandlung im Hauptrahmen
SikuliX-Fehlerbehandlung
django.db.migrations.exceptions.InconsistentMigrationHistory Fehlerbehandlung
Über tweepy Fehlerbehandlung
Fehlerbehandlung in PythonBox
Um Fehlerbehandlung von Feedparser
[Fehlergegenmaßnahmen] Fehlerbehandlung bei der Installation von Django-Heroku
Reaktion auf Fehler bei der Installation von mecab-python
Informationen zu FastAPI ~ Endpoint-Fehlerbehandlung ~
Memorandum zur Fehlerbehandlung bei PyCUDA-Builds
Fehlerbehandlung beim Aktualisieren der Fischschale
Fehlerbehandlung während der Django-Migration 'DIRS': [BASE_DIR / 'Templates']