Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Zaawansowane odpowiedzi - edykty cesarskie

Edykt cesarski nie kończył się na rozkazie. Zawsze wymieniał następstwa: co się stanie, gdy polecenie wykonano, co gdy petent nie ma prawa prosić, a co gdy sprawa w ogóle nie istnieje w annałach. Dopiero taki dokument dawał się stosować bez pytania kancelarii o zdanie.

Twoje API działa tak samo. W poprzedniej lekcji dokumentowaliśmy odpowiedź ogólnym

@ApiResponse
, podając kod statusu ręcznie. NestJS ma jednak osobny dekorator dla każdego kodu - i o nich jest ta lekcja.

Dekorator opisuje, nie ustawia

Zacznijmy od rzeczy, która bywa źródłem nieporozumień. Te dekoratory nie zmieniają zachowania aplikacji. Trafiają wyłącznie do dokumentacji:

1@ApiResponse({ status: 200, description: 'Lista legionów' })
2@Get()
3findAll() {}
4
5@ApiOkResponse({ description: 'Lista legionów' })
6@Get()
7findAll() {}

Oba zapisy dają w Swagger UI dokładnie to samo. Drugi jest krótszy i trudniej się w nim pomylić, bo kodu statusu nie podajesz - siedzi on w nazwie dekoratora.

O tym, jaki kod naprawdę wróci do klienta, decyduje co innego:

@Post
domyślnie zwraca 201, pozostałe metody 200, a
@HttpCode(204)
nadpisuje to jawnie. Dopisanie
@ApiCreatedResponse
nad metodą
@Get
nie sprawi, że zacznie ona zwracać 201 - sprawi tylko, że dokumentacja skłamie.

Kod w nazwie dekoratora

Reguła jest prosta: nazwa dekoratora to oficjalna nazwa kodu HTTP. Stąd cały zestaw daje się odtworzyć z pamięci:

1import {
2  ApiOkResponse,                    // 200 OK
3  ApiCreatedResponse,               // 201 Created
4  ApiBadRequestResponse,            // 400 Bad Request
5  ApiUnauthorizedResponse,          // 401 Unauthorized
6  ApiForbiddenResponse,             // 403 Forbidden
7  ApiNotFoundResponse,              // 404 Not Found
8  ApiInternalServerErrorResponse,   // 500 Internal Server Error
9} from '@nestjs/swagger';

@ApiCreatedResponse()
odpowiada kodowi 201 (Created), a
@ApiNotFoundResponse()
reprezentuje kod 404 Not Found
- nie 400, nie 401 i nie 403, bo każdy z nich ma własny dekorator z listy powyżej.

Reguła działa też w drugą stronę i pozwala odrzucić nazwy, których nie ma.

@ApiNewResponse()
ani
@ApiSuccessResponse()
nie istnieją - brzmią sensownie, ale w protokole HTTP nie ma kodu o nazwie „New" ani „Success". Jeśli nie umiesz wskazać kodu, do którego dekorator się odnosi, to znak, że taki dekorator nie istnieje.

Trzy klasy kodów

Kody układają się w klasy, a klasa mówi, kto zawinił:

  • 2xx - powodzenie.
    200 OK
    to „zrobione",
    201 Created
    - „zrobione i powstał nowy zasób".
  • 4xx - wina klienta. Żądanie było wadliwe albo niedozwolone; powtórzenie go bez zmian nic nie da.
  • 5xx - wina serwera.
    500 Internal Server Error
    oznacza, że aplikacja się wywróciła. Żądanie mogło być całkiem poprawne.

Uporządkowane od powodzenia do błędu serwera dają kolejność: 200 OK → 201 Created → 401 Unauthorized → 500 Internal Server Error.

W obrębie 4xx warto zapamiętać jedną parę. 401 Unauthorized znaczy „nie wiem, kim jesteś" - brakuje tokenu albo jest nieważny. 403 Forbidden znaczy „wiem, kim jesteś, i nie wolno ci" - token jest w porządku, ale rola nie wystarcza. Pierwsze naprawia się logowaniem, drugiego nie naprawi się wcale.

Budowa dekoratora

Dekorator przyjmuje obiekt, a kolejność jego pól jest umowna, choć w praktyce zawsze ta sama - najpierw opis, potem typ:

1@ApiOkResponse({
2  description: 'Lista legionów',
3  type: LegionResponseDto,
4})
5@Get()
6findAll(): LegionResponseDto[] {
7  return this.legionService.findAll();
8}

Czyta się to w czterech krokach:

@ApiOkResponse({
otwiera dekorator,
description: 'Lista legionów',
wyjaśnia człowiekowi, co dostanie,
type: LegionResponseDto
wskazuje klasę DTO, a
})
domyka.

description
trafia do opisu odpowiedzi w Swagger UI.
type
robi więcej: Swagger sięga po dekoratory
@ApiProperty
z tej klasy - te z poprzedniej lekcji - i buduje z nich pełny schemat odpowiedzi wraz z przykładowymi wartościami. Bez
type
czytelnik dowie się, że dostanie 200, ale nie dowie się, co w środku.

Odpowiedź, która jest tablicą

Gdy endpoint zwraca listę, masz dwa równoważne zapisy i oba działają poprawnie:

1@ApiOkResponse({ type: [LegionResponseDto] })
2@Get()
3findAll() {}
4
5@ApiOkResponse({ type: LegionResponseDto, isArray: true })
6@Get()
7findAllAgain() {}

Pierwszy - klasa w nawiasach kwadratowych - jest krótszy. Drugi, z jawnym

isArray: true
, bywa czytelniejszy, gdy obok stoją inne opcje. Wybór jest kwestią stylu; Swagger wygeneruje identyczny schemat.

Nie zadziała natomiast

type: Array<LegionResponseDto>
. Powód jest głębszy niż składnia: typy generyczne znikają przy kompilacji TypeScriptu. Dekorator dostaje wartość istniejącą w czasie działania programu, a z
Array<LegionResponseDto>
zostaje po kompilacji samo
Array
- bez śladu po tym, czego jest tablicą. Nawiasy kwadratowe i
isArray
działają właśnie dlatego, że przekazują klasę
LegionResponseDto
jako zwykłą wartość.

Komplet na jednym endpoincie

W praktyce endpoint dokumentuje wszystkie odpowiedzi, jakich klient może się spodziewać:

1@ApiOperation({ summary: 'Awansuj legionistę' })
2@ApiOkResponse({
3  description: 'Legionista został awansowany',
4  type: LegionaryResponseDto,
5})
6@ApiBadRequestResponse({ description: 'Nieprawidłowa ranga docelowa' })
7@ApiUnauthorizedResponse({ description: 'Brak ważnego tokenu JWT' })
8@ApiNotFoundResponse({ description: 'Legionista nie znaleziony' })
9@ApiBearerAuth('JWT-auth')
10@UseGuards(JwtAuthGuard)
11@Patch(':id/promote')
12promote(@Param('id') id: string, @Body() dto: PromoteLegionaryDto) {
13  return this.legionService.promote(id, dto);
14}

Zwróć uwagę na parę

@ApiBearerAuth('JWT-auth')
i
@ApiUnauthorizedResponse
. Pierwszy zaznacza endpoint jako chroniony i włącza w Swagger UI pole na token - nazwa
'JWT-auth'
musi zgadzać się z tą podaną przy konfiguracji
addBearerAuth
. Drugi opisuje, co się stanie bez tokenu. Chroniony endpoint bez
@ApiUnauthorizedResponse
obiecuje czytelnikowi, że 401 nigdy nie nastąpi - a nastąpi przy pierwszej próbie.

Podsumowanie

Edykt wymienia następstwa, @name:

  • dekoratory odpowiedzi tylko dokumentują; kod statusu ustala
    @Post
    ,
    @Get
    albo
    @HttpCode
    ,
  • nazwa dekoratora to nazwa kodu:
    @ApiOkResponse
    (200),
    @ApiCreatedResponse
    (201)
    ,
    @ApiBadRequestResponse
    (400),
    @ApiUnauthorizedResponse
    (401),
    @ApiForbiddenResponse
    (403),
    @ApiNotFoundResponse
    (404)
    ,
    @ApiInternalServerErrorResponse
    (500),
  • @ApiNewResponse
    i
    @ApiSuccessResponse
    nie istnieją - nie ma kodów HTTP o takich nazwach,
  • klasy kodów: 2xx powodzenie, 4xx wina klienta, 5xx wina serwera; od sukcesu do błędu serwera: 200 OK → 201 Created → 401 Unauthorized → 500 Internal Server Error,
  • 401 to „nie wiem, kim jesteś", 403 to „wiem i nie wolno ci",
  • budowa dekoratora:
    @ApiOkResponse({
    description: 'Lista legionów',
    type: LegionResponseDto
    })
    ,
  • type
    wiąże odpowiedź z DTO, więc Swagger zbuduje schemat z jego
    @ApiProperty
    ,
  • tablicę zapisujesz na dwa równoważne sposoby:
    type: [LegionDto]
    albo
    type: LegionDto, isArray: true
    - oba działają poprawnie,
  • type: Array<LegionDto>
    nie zadziała, bo typy generyczne znikają przy kompilacji,
  • endpoint chroniony oznaczasz parą
    @ApiBearerAuth('JWT-auth')
    i
    @ApiUnauthorizedResponse
    .

W następnej lekcji zajmiemy się wyglądem samego Forum Annałów - konfiguracją Swagger UI. A na razie zapamiętaj: dokumentacja odpowiedzi nie jest ozdobnikiem. To jedyne miejsce, w którym klient dowie się, na co ma się przygotować, zanim jego kod się o tym przekona.

Przejdź do CodeWorlds