Giter Club home page Giter Club logo

api-tools-api-problem's Introduction

Laminas Api Problem

Build Status

🇷🇺 Русским гражданам

Мы, участники Laminas, родились и живем в разных странах. У многих из нас есть друзья, родственники и коллеги как в России, так и в Украине. Некоторые из нас родились в России. Некоторые из нас живут в России. У некоторых бабушки и дедушки сражались с фашистами во Второй мировой войне. Здесь никто не поддерживает фашизм.

У одного из нас есть украинская родственница, которая спаслась из дома вместе с сыном. Поезд задержался из-за бомбежки на дороге впереди. У нас есть друзья, которые прячутся в бомбоубежищах. Мы с тревогой ждем весточки от них после воздушных налетов, которые беспорядочно наносят удары и попадают по больницам, школам, детским садам и домам. Мы не берем это из каких-либо СМИ. Мы наблюдаем это напрямую.

Вы доверяете нам достаточно, чтоб использовать наши программы, и мы просим вас довериться нам вновь. Мы нуждаемся в помощи. Выходите и протестуйте против этой бесполезной войны. Остановите кровопролитие. Скажите "Нет войне!"

🇺🇸 To Citizens of Russia

We at Laminas come from all over the world. Many of us have friends, family and colleagues in both Russia and Ukraine. Some of us were born in Russia. Some of us currently live in Russia. Some have grandparents who fought Nazis in World War II. Nobody here supports fascism.

One team member has a Ukrainian relative who fled her home with her son. The train was delayed due to bombing on the road ahead. We have friends who are hiding in bomb shelters. We anxiously follow up on them after the air raids, which indiscriminately fire at hospitals, schools, kindergartens and houses. We're not taking this from any media. These are our actual experiences.

You trust us enough to use our software. We ask that you trust us to say the truth on this. We need your help. Go out and protest this unnecessary war. Stop the bloodshed. Say "stop the war!"

Introduction

This module provides data structures and rendering for the API-Problem format.

Requirements

Please see the composer.json file.

Installation

Run the following composer command:

$ composer require laminas-api-tools/api-tools-api-problem

Alternately, manually add the following to your composer.json, in the require section:

"require": {
    "laminas-api-tools/api-tools-api-problem": "^1.2"
}

And then run composer update to ensure the module is installed.

Finally, add the module name to your project's config/application.config.php under the modules key:

return [
    /* ... */
    'modules' => [
        /* ... */
        'Laminas\ApiTools\ApiProblem',
    ],
    /* ... */
];

laminas-component-installer

If you use laminas-component-installer, that plugin will install api-tools-api-problem as a module for you.

Configuration

User Configuration

The top-level configuration key for user configuration of this module is api-tools-api-problem.

Key: accept_filters

An array of Accept header media types that, when matched, will result in the ApiProblemListener handling an MvcEvent::EVENT_RENDER_ERROR event.

Key: render_error_controllers

An array of controller service names that, if matched as the controller parameter in the MVC RouteMatch, will cause the ApiProblemListener to handle MvcEvent::EVENT_RENDER_ERROR events.

System Configuration

The following configuration is provided in config/module.config.php to enable the module to function:

'service_manager' => [
    'aliases'   => [
        'Laminas\ApiTools\ApiProblem\ApiProblemListener'  => 'Laminas\ApiTools\ApiProblem\Listener\ApiProblemListener',
        'Laminas\ApiTools\ApiProblem\RenderErrorListener' => 'Laminas\ApiTools\ApiProblem\Listener\RenderErrorListener',
        'Laminas\ApiTools\ApiProblem\ApiProblemRenderer'  => 'Laminas\ApiTools\ApiProblem\View\ApiProblemRenderer',
        'Laminas\ApiTools\ApiProblem\ApiProblemStrategy'  => 'Laminas\ApiTools\ApiProblem\View\ApiProblemStrategy',
    ],
    'factories' => [
        'Laminas\ApiTools\ApiProblem\Listener\ApiProblemListener'             => 'Laminas\ApiTools\ApiProblem\Factory\ApiProblemListenerFactory',
        'Laminas\ApiTools\ApiProblem\Listener\RenderErrorListener'            => 'Laminas\ApiTools\ApiProblem\Factory\RenderErrorListenerFactory',
        'Laminas\ApiTools\ApiProblem\Listener\SendApiProblemResponseListener' => 'Laminas\ApiTools\ApiProblem\Factory\SendApiProblemResponseListenerFactory',
        'Laminas\ApiTools\ApiProblem\View\ApiProblemRenderer'                 => 'Laminas\ApiTools\ApiProblem\Factory\ApiProblemRendererFactory',
        'Laminas\ApiTools\ApiProblem\View\ApiProblemStrategy'                 => 'Laminas\ApiTools\ApiProblem\Factory\ApiProblemStrategyFactory',
    ],
],
'view_manager' => [
    // Enable this in your application configuration in order to get full
    // exception stack traces in your API-Problem responses.
    'display_exceptions' => false,
],

Laminas Events

Listeners

Laminas\ApiTools\ApiProblem\Listener\ApiProblemListener

The ApiProblemListener attaches to three events in the MVC lifecycle:

  • MvcEvent::EVENT_DISPATCH as a shared listener on Laminas\Stdlib\DispatchableInterface with a priority of 100.
  • MvcEvent::EVENT_DISPATCH_ERROR with a priority of 100.
  • MvcEvent::EVENT_RENDER with a priority of 1000.

If the current Accept media type does not match the configured API-Problem media types (by default, these are application/json and application/*+json), then this listener returns without taking any action.

When this listener does take action, the purposes are threefold:

  • Before dispatching, the render_error_controllers configuration value is consulted to determine if the Laminas\ApiTools\ApiProblem\Listener\RenderErrorListener should be attached; see RenderErrorListener for more information.
  • After dispatching, detects the type of response from the controller; if it is already an ApiProblem model, it continues without doing anything. If an exception was thrown during dispatch, it converts the response to an API-Problem response with some information from the exception.
  • If a dispatch error occurred, and the Accept type is in the set defined for API-Problems, it attempts to cast the dispatch exception into an API-Problem response.

Laminas\ApiTools\ApiProblem\Listener\RenderErrorListener

This listener is attached to MvcEvent::EVENT_RENDER_ERROR at priority 100. This listener is conditionally attached by Laminas\ApiTools\ApiProblem\Listener\ApiProblemListener for controllers that require API Problem responses. With a priority of 100, this ensures that this listener runs before the default Laminas listener on this event. In cases when it does run, it will cast an exception into an API-problem response.

Laminas\ApiTools\ApiProblem\Listener\SendApiProblemResponseListener

This listener is attached to SendResponseEvent::EVENT_SEND_RESPONSE at priority -500. The primary purpose of this listener is, on detection of an API-Problem response, to send appropriate headers and the problem details as the content body. If the view_manager's display_exceptions setting is enabled, the listener will determine if the API-Problem represents an application exception, and, if so, inject the exception trace as part of the serialized response.

Laminas Services

Event Services

  • Laminas\ApiTools\ApiProblem\Listener\ApiProblemListener
  • Laminas\ApiTools\ApiProblem\Listener\RenderErrorListener
  • Laminas\ApiTools\ApiProblem\Listener\SendApiProblemResponseListener

View Services

Laminas\ApiTools\ApiProblem\View\ApiProblemRenderer

This service extends the JsonRenderer service from the Laminas MVC layer. Its primary responsibility is to decorate JSON rendering with the ability to optionally output stack traces.

Laminas\ApiTools\ApiProblem\View\ApiProblemStrategy

This service is a view strategy that detects a Laminas\ApiTools\ApiProblem\View\ApiProblemModel; when detected, it selects the ApiProblemRender, and injects the response with a Content-Type header that contains the application/problem+json media type. This is similar in nature to Laminas's JsonStrategy.

Models

Laminas\ApiTools\ApiProblem\ApiProblem

An instance of Laminas\ApiTools\ApiProblem\ApiProblem serves the purpose of modeling the kind of problem that is encountered. An instance of ApiProblem is typically wrapped in an ApiProblemResponse. Most information can be passed into the constructor:

class ApiProblem
{
    public function __construct(
        $status,
        $detail,
        $type = null,
        $title = null,
        array $additional = []
    ) {
        /* ... */
    }
}

For example:

new ApiProblem(404, 'Entity not found');

// or

new ApiProblem(424, $exceptionInstance);

Laminas\ApiTools\ApiProblem\ApiProblemResponse

An instance of Laminas\ApiTools\ApiProblem\ApiProblemResponse can be returned from any controller service or Laminas MVC event in order to short-circuit the MVC lifecycle and immediately return a response. When it is, the response will be converted to the proper JSON structure for an API-Problem, and the Content-Type header will be set to the application/problem+json media type.

For example:

use Laminas\Mvc\Controller\AbstractActionController;
use Laminas\ApiTools\ApiProblem\ApiProblem;
use Laminas\ApiTools\ApiProblem\ApiProblemResponse;

class MyController extends AbstractActionController
{
    /* ... */
    public function fetch($id)
    {
        $entity = $this->model->fetch($id);
        if (! $entity) {
            return new ApiProblemResponse(new ApiProblem(404, 'Entity not found'));
        }
        return $entity;
    }
}

api-tools-api-problem's People

Contributors

acabala avatar alexdenvir avatar artdevgame avatar bakura10 avatar bartbrinkman avatar ezimuel avatar geerteltink avatar lukash82 avatar michaelgooden avatar michalbundyra avatar neeckeloo avatar ocramius avatar ralphschindler avatar rogervila avatar rtuin avatar samsonasik avatar snapshotpl avatar svycka avatar telkins avatar weierophinney avatar wilt avatar xerkus avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

api-tools-api-problem's Issues

API Problem translation based on Accept-Language?

I was wondering, would it make sense for apigiliy and api-problem in general to support a translator service, so as to translate all messages in the ApiProblem response based on the Accept-Language HTTP header?

This way in theory a multilingual rest client could get different error messages per language set in the request.

What do you think is the best way to go about it?


Originally posted by @twmobius at zfcampus/zf-api-problem#40

Catch exceptions

I try to figure out how listeners in ApiProblem works. They attach to different events but every event has different method/code and very similar flow to create ApiProblem. I'm ready to rebuild it, but I would like to be sure it's something wrong in current code.


Originally posted by @snapshotpl at zfcampus/zf-api-problem#30

Alternative Rendering

I use the zf-api-problem, zf-mvc-auth module in combination with apigility. Since I have to ship a regular webapplication also within the project I use the zf-mvc-auth events to handle both api and webapp authentication stuff. Everything works quite nice on the apigility side.

However my webapplication is based on session for login/logout. So all protected routes within my webapp are automatically handled by zf-mvc-auth and my listeners that generate everything needed. As soon as I try to access a protected resource a 403 Error is created which is correct also, but this response is always a application/problem+json, which is obviously correct for the api but incorrect on side of the webapp where I want to have a regular error site. Is there some way to change or configure the way how the problems get rendered?

BR Daniel


Originally posted by @dhofstetter at zfcampus/zf-api-problem#25

ApiProblemResponse/RenderErrorListener issue while encoding binary data to Json

Assume displayExceptions is true.

If in the exception stack trace a function argument contains binary data that json_encode could not handle, then json_encode will return false. As a result the response content is empty.

This happens at following lines:
RenderErrorListener.php#L94
ApiProblemResponse.php#L59

This situation can occur in various debugging scenarios, e.g. uploading a file and passing its blob to a function, and it can be pretty difficult to overcome because the empty response.

Proposed solution
A better choice would be to use \Zend\Json\Encoder::encode because it can correctly handle the encoding of binary data that json_encode can not encode.

// Assuming $data['blob'] contains the binary data of a PDF file

json_encode($data); // return false

\Zend\Json\Json::encode($data); // return false if the json extension is available, otherwise \Zend\Json\Encoder::encode is used

\Zend\Json\Encoder::encode($data); // return the encoded json

Originally posted by @leogr at zfcampus/zf-api-problem#29

SendApiProblemResponseListener modifies content

I have noticed, that SendApiProblemResponseListener is modifying the response content AFTER sending headers (if display_exceptions is enabled).

I have a Content-Length header based on response content but this header is now responding a wrong length as it doesn't know if the content gets modified afterwords.

On the same time I'm automatically pretty printing the response if the request contains header X-Pretty: 1 but this will also be ignored as the content gets rewritten in this case.

What is the reason to overwrite HttpResponseSender instead of listening on EVENT_FINISH?

This is my broken code:

class Module
{
    public function onBootstrap(MvcEvent $e)
    {
        $eventManager  = $e->getApplication()->getEventManager();
        $eventManager->attach(MvcEvent::EVENT_FINISH, [$this, 'onFinish'], -1000);
    }

    public function onFinish(MvcEvent $event)
    {
        $response = $event->getResponse();
        $request  = $event->getRequest();

        if ($request instanceof HttpRequest && $response instanceof HttpResponse && !($response instanceof HttpStreamResponse)) {
            $rqHeaders = $request->getHeaders();
            $rsHeaders = $response->getHeaders();
            $rsContent = $response->getContent();

            // pretty print JSON response
            if ($rqHeaders->has('X-Pretty')
                && $rqHeaders->get('X-Pretty')->getFieldValue() === '1'
                && is_string($rsContent) && $rsContent
                && $rsHeaders->has('Content-Type')
                && preg_match('/^application\\/(.*\\-)?json/', $rsHeaders->get('Content-Type')->getFieldValue())
            ) {
                $rsContentDecode = json_decode($rsContent);
                if ($rsContentDecode !== null) {
                    $prettyOptions = JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
                    $rsContent     = json_encode($rsContentDecode, $prettyOptions);
                    $response->setContent($rsContent);

                    if ($rsHeaders->has('Content-Length')) {
                        $rsHeaders->removeHeader($rsHeaders->get('Content-Length'));
                    }
                    $rsHeaders->addHeaderLine('Content-Length', strlen($rsContent));
                }
            }

            // Send Content-Length header if possible
            if (is_string($rsContent) && !$rsHeaders->has('Content-Length')) {
                $rsHeaders->addHeaderLine('Content-Length', strlen($rsContent));
            }
        }
    }
}

Originally posted by @marc-mabe at zfcampus/zf-api-problem#54

Status code from exceptions

API Problem response with HTTP status code from exception when it's valid rage of HTTP codes. But what about situation when some library throws own exceptions, with own codes (example 302, and it means that user doesn't exist)? It does't make sense.
My solution for this is to use code from exceptions only when exception implements \ZF\ApiProblem\Exception\ExceptionInterface


Originally posted by @snapshotpl at zfcampus/zf-api-problem#26

HTTP status code != API Problem status

When I throw exception in resource:

throw new \Exception('Error', 4000010);

I get:

  • 500 in HTTP status code
  • 4000010 in API problem status (content)

Apigility documentation says (https://apigility.org/documentation/api-primer/error-reporting):

status: the HTTP status code for the current request (optional; Apigility always provides this).

And API problem documentation (https://tools.ietf.org/html/draft-nottingham-http-problem-06):

"status" (number) - The HTTP status code ([RFC2616], Section 6) generated by the origin server for this occurrence of the problem.
The status member, if present, is only advisory; it conveys the HTTP status code used for the convenience of the consumer. Generators MUST use the same status code in the actual HTTP response, to assure that generic HTTP software that does not understand this format still behaves correctly.

BTW Response after

throw new \Exception('Error', 200);

looks funny :-)


Originally posted by @snapshotpl at zfcampus/zf-api-problem#27

Can you add one JSON_FORCE_OBJECT property in ApiProblemResponse jsonFlags

In your current code ApiProblemResponse class have this constructor

public function __construct(ApiProblem $apiProblem)
    {
        $this->apiProblem = $apiProblem;
        $this->setCustomStatusCode($apiProblem->status);
        $this->setReasonPhrase($apiProblem->title);
        $this->jsonFlags = JSON_UNESCAPED_SLASHES | JSON_PARTIAL_OUTPUT_ON_ERROR;
    }

Could you please add one more property in jsonFlags JSON_FORCE_OBJECT

Like this

public function __construct(ApiProblem $apiProblem)
    {
        $this->apiProblem = $apiProblem;
        $this->setCustomStatusCode($apiProblem->status);
        $this->setReasonPhrase($apiProblem->title);
        $this->jsonFlags = JSON_UNESCAPED_SLASHES | JSON_PARTIAL_OUTPUT_ON_ERROR | JSON_FORCE_OBJECT;
    }

This will give api response to send non associated arrays keys for example during validation message generation, when keys are number and not string

like this

array(
3 =>'message1',
5 =>'message2',
7 =>'message3',
)

Originally posted by @developer-devPHP at zfcampus/zf-api-problem#56

PHP 8.0 support

Feature Request

Q A
New Feature yes

Summary

To be prepared for the december release of PHP 8.0, this repository has some additional TODOs to be tested against the new major version.

In order to make this repository compatible, one has to follow these steps:

  • Modify composer.json to provide support for PHP 8.0 by adding the constraint ~8.0.0
  • Modify composer.json to drop support for PHP less than 7.3
  • Modify composer.json to implement phpunit 9.3 which supports PHP 7.3+
  • Modify .travis.yml to ignore platform requirements when installing composer dependencies (simply add --ignore-platform-reqs to COMPOSER_ARGS env variable)
  • Modify .travis.yml to add PHP 8.0 to the matrix (NOTE: Do not allow failures as PHP 8.0 has a feature freeze since 2020-08-04!)
  • Modify source code in case there are incompatibilities with PHP 8.0

Psalm integration

Feature Request

Q A
QA yes

Summary

As decided during the Technical-Steering-Committee Meeting on August 3rd, 2020, Laminas wants to implement vimeo/psalm in all packages.

Implementing psalm is quite easy.

Required

  • Create a psalm.xml in the project root
  • Copy and paste the contents from this psalm.xml.dist
  • Run $ composer require --dev vimeo/psalm
  • Run $ vendor/bin/psalm --set-baseline=psalm-baseline.xml
  • Add a composer script static-analysis with the command psalm --shepherd --stats
  • Add a new line to script: in .travis.yml: - if [[ $TEST_COVERAGE == 'true' ]]; then composer static-analysis ; fi
  • Remove phpstan from the project (phpstan.neon.dist, .travis.yml entry, composer.json require-dev and scripts)
Optional
  • Fix as many psalm errors as possible.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.