Apitte – správný content-type (JSON/XML) při error

woytam
Člen | 14
+
0
-

Ahoj,
snažím se vytvořit API s podporou JSON a XML, ale errory mi to vždy vyhodí v JSON nezávisle na požadovaném formátu.
Například /api/v1/data.xml vrací XML, ale pokud nejsem přihlášen a chybu vyhodí RequestAuthenticationDecorator, odešle se JSON.
Používám Decorator a Negotiation pluginy.
Konfigurace:

services:
	apiXmlTransformer:
		factory: Controller\ApiModule\V1\Transformers\XmlTransformer
		tags: [apitte.negotiator.transformer: [suffix: xml, fallback: false]]
	decorator.request.authentication:
		class: Controller\ApiModule\V1\Decorators\RequestAuthenticationDecorator
		tags: [apitte.core.decorator: [priority: 1, type: handler.before]]
	decorator.response.example:
		class: Controller\ApiModule\V1\Decorators\ExampleResponseDecorator
		tags: [apitte.core.decorator: [priority: 50]]
	decorator.response.exampleErr:
		class: Controller\ApiModule\V1\Decorators\ExampleExceptionDecorator
		tags: [apitte.core.decorator: [priority: 49]]
api:
	debug: %debugMode%
	plugins:
		Apitte\Core\DI\Plugin\CoreDecoratorPlugin:
			unification: true
		Apitte\Negotiation\DI\NegotiationPlugin:
			unification: on
		Apitte\Core\DI\Plugin\CoreMappingPlugin:
resource:
	resources:
		Controller\ApiModule\V1\Controllers\:
			# where the classes are located
			paths: [%appDir%/ApiModule/V1/Controllers]
			decorator:
				inject: true
extensions:
	resource: Contributte\DI\Extension\ResourceExtension
	api: Apitte\Core\DI\ApiExtension

Když v RequestAuthenticationDecorator mám

public function decorateRequest(ApiRequest $request, ApiResponse $response): ApiRequest {
	throw new ClientErrorException('Invalid credentials, authentication failed.', 403);
}

odešle se vždy JSON nezávisle na nastaveném endpointu. I příkaz throw new EarlyReturnResponseException($response) odešle JSON.

Nepodařilo se mi nikde pořádně dohledat, jak nastavit Apitte, aby vždy bralo v potaz požadovaný Content-Type?

Felix
Nette Core | 1158
+
0
-

Ahoj. Nemel by jsi moznost zabalit ten projekt nebo alespon jeho minimalni cast a poslat mi to? Mrknul bych se ti na to.

Felix
Nette Core | 1158
+
0
-

Pro budouci generace davam navod, my jsme to vyresili po emailu.


Tam je problem, ze logika, ktera se stara o prevadeni vystupnich dat do ruznych formatu nepocita s tim, ze by jsi ten error vyhodil jeste predtim nez doputuje do controlleru, resp. endpointu. Protoze podle controlleru, resp. endpointu, se pozna, jake podporuje koncovky. Tim, ze ten error vyhodis jeste predtim, tak se cela takhle logika nepouzije.

Nicmene, neni to tezky udelat, posilam ukazkovou tridu.

class ContentNegotiationExceptionDecorator implements IErrorDecorator
{

   public function decorateError(ApiRequest $request, ApiResponse $response, ApiException $error): ApiResponse
   {
      if ($error instanceof ApiException) {
         $code = $error->getCode();
         $message = $error->getMessage();
      } else {
         $code = 500;
         $message = 'Application encountered an internal error. Please try again later.';
      }

      $path = $request->getUri()->getPath();

      if (Strings::endsWith($path, '.xml')) {
         $response->getBody()->write('XML error');
         $response = $response->withStatus($code);
         throw new SnapshotException($error, $request, $response);
      }

      if (Strings::endsWith($path, '.json')) {
         $response->getBody()->write('JSON error');
         $response = $response->withStatus($code);
         throw new SnapshotException($error, $request, $response);
      }

      return $response
         ->withStatus($code)
         ->withAttribute(ResponseAttributes::ATTR_ENTITY, ArrayEntity::from([
            'status' => 'error',
            'message' => $message,
         ]));
   }

}

Predpokladam, ze z kodu je to patrne. Podle toho, jestli to konci na .xml nebo .json se vyhodi SnapshotException. Pokud se nic z toho nepouzije, tak to pokracuje jako predtim.

woytam
Člen | 14
+
0
-

Super, díky, funguje.

Nicméně bych se zeptal, zda neexistuje nějaké „úhlednější“ řešení. Přijde mi to tady, že řeším dvě stejné věci na dvou různých místech. Tedy kontrolu požadovaného typu a následný převod na správný formát.
To už mi skoro přijde lepší provádět kontrolu přihlášení až v Controlleru.

A jen taková druhá otázečka, má nějaký smysl

if ($error instanceof ApiException)

když v nových verzích je stejně funkce definovaná již s apiException

public function decorateError(ApiRequest $request, ApiResponse $response, ApiException $error)

Dříve bývalo Throwable kde to smysl mělo.

Felix
Nette Core | 1158
+
0
-

Nicméně bych se zeptal, zda neexistuje nějaké „úhlednější“ řešení. Přijde mi to tady, že řeším dvě stejné věci na dvou různých místech. Tedy kontrolu požadovaného typu a následný převod na správný formát.

To už mi skoro přijde lepší provádět kontrolu přihlášení až v Controlleru.

Tak ono vracet data podle typu odpovedi (JSON a XML) je celkem narocna vec. Ale chapu te. Zkusim se zamyslet, jestli by to neslo nejak zjednodusit.

if ($error instanceof ApiException)

Uz to nema smysl. :-) Diky za hint.