
TSOA Fallstricke: Response Codes zurückgeben (Deutsch)
Dies ist der erste Teil einer Serie von Beiträgen zum Thema Fallstricke beim Einsatz von TSOA. Dabei geht es um Lösungen von Schwierigkeiten die im praktischen Einsatz dieses Frameworks auftreten. In diesem Beitrag geht es darum wie per TSOA REST Funktionen HTML Response Codes zurückgeben können.
Response Codes zurückgeben
Ein Swagger Response beschreibt das erwartete Ergebnis eines Aufrufs einer Funktion einer Webschnittstelle. Neben (meist einem) erfolgreichen Ergebnis sind oft mehrere Fehlervarianten möglich.
Eine Swagger 2.0 YAML Beschreibung einer API Funktion mit mehreren möglichen definierten Antworten könnte so aussehen.
get: tags: - Car summary: find car by car licence plate description: Returns a car by car licence plate. operationId: getCar parameters: - in: query name: plateNumber required: true type: string responses: "200": description: successful schema: $ref: '#/definitions/Car' "400": description: invalid input "404": description: not found "407": description: unknown error
Die Funktion ermittelt ein Objekt mit der Beschreibung eines Autos für ein übergebenes Nummernschild.
Wird ein Fahrzeug zum gewünschten Nummernschild gefunden, dann wird der Code 200 und die Beschreibung des Fahrzeugs (Car) zurück gegeben. Es sind jedoch auch einige Fehlerfälle definiert. Wird kein passendes Fahrzeug gefunden, wird der Fehlercode 404 zurückgegeben. Ist das Nummernschild falsch (z.B. zu kurz) meldet die Funktion den Fehlercode 400. Sollte ein ungeplanter interner Fehler auftreten, wie z.B. dass ein benötigter anderer Dienst ausgefallen ist, meldet die Funktion den Code 407.
Die Codes im Beispiel orientieren sich an den HTTP Statuscodes. Die Fehlercodes werden im Webclients meist als Exception / Fehler aufgelöst.
TSOA erlaubt die Definition der Response Codes über Annotationen (SuccessResponse, Response, DefaultResponse).
Das obere Beispiel wird in Typescript folgendermaßen umgesetzt.
@Tags('Car') @SuccessResponse('200', 'successful') @Response('400', 'invalid input') @Response('404', 'not found') @Response('407', 'unknown error') @Get('') public async getCar (@Query() plateNumber: string): Promise { if (plateNumber.length < 3) { this.setStatus(400) return // Compilerfehler } return this.findCar(plateNumber) // 404? 407? }
Responsecodes lassen sich nur mit setStatus zurückgeben
Im oberen Beispiel ist auch zu sehen, wie ein Fehlercode (hier 400) mittels der Controller-Funktion setStatus als Antwort gesetzt wird.
- 200 wird implizit von TSOA gesetzt, wenn die Funktion getCar() ein Ergebnis zurück liefert (Defaultverhalten)
- 204 wird implizit von TSOA gesetzt, wenn die Funktion kein Ergebnis zurück liefert (also null, undefinded, void)
- der Response Code kann mehrfach verändert werden, so lange die Funktion nicht beendet ist
- setStatus() bricht die Funktion nicht ab, d.h. es wird keine Exception geworfen, wenn ein Fehlercode eingetragen wird
- erst nach Ende der Funktion, wird der Response Code ausgewertet und eventuell eine Exception erzeugt
- 500 (Internal Server Error) wird implizit von TSOA gesetzt, wenn die Funktion eine Exception erzeugt, die Exception Message wird nicht übertragen
Das obere Beispiel wirft einige praktische Probleme auf.
- Je nachdem wie streng die Typescript Compiler / Linter-Einstellung gewählt sind, kompiliert der Code nicht, da das erste return kein Objekt zurückgibt. Um die Funktion bei einem Fehler abzubrechen müsste entweder zusätzlich ein Dummy-Objekt zurückgegeben werden, oder die Funktion komplett anders strukturiert werden. ⚠
- Die Funktion setStatus() erzwingt die Auswertung von Fehlern in der Controller-Klasse. Fehler die in anderen Klassen auftreten lassen sich nur aufwendig zurückmelden. Im Beispiel fehlt die Auswertung für die Codes 404 und 407. Diese müsste in der findCar() Funktion erfolgen. Würde der Controller diese Funktion bei einer anderen Klasse aufrufen
, würde die Meldung komplizierter. ⚠
Die Lösung ist eine eigene Status Exception
Statt in Fehlerfällen den Response Code mittels setStatus() einzutragen, empfielt es sich eine eigene Exception zu nutzen, die um den Statuscode erweitert wird.
export class StatusError extends Error { public status: number public name: string constructor (code: number, name: string, msg: string) { super(msg) this.status = code this.name = name } }
@Tags('Car') @SuccessResponse('200', 'successful') @Response('400', 'invalid input') @Response('404', 'not found') @Response('407', 'unknown error') @Get('') public async getCar (@Query() plateNumber: string): Promise { if (plateNumber.length < 3) { throw new StatusError(400, 'wrong plate number', 'can not find car because plate number is invalid') } return this.findCar(plateNumber) }
Die beschriebene StatusError Klasse besitzt ein Feld status für den Response Code. TSOA erkennt dadurch, dass ein Response Code gemeldet wird und gibt Code und Nachricht korrekt an den Client weiter.
- der Response Code status wird korrekt an den Client übermittelt = 400 (Error: Bad Request) ✅
- name und msg werden zu einer kompletten Fehlermeldung verbunden die an den Client übermittelt wird =
wrong plate number: can not find car because plate number is invalid
✅ - es muss bei Fehlern kein Dummy-Ergebnis zurück gegeben werden
- der Fehler kann von einer beliebigen anderen Klasse geworfen werden, die keine Abhängigkeit zur Controller-Klasse besitzt (z.B. Datenbank)