Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update ErrorFormatGuesser to handle custom formats and improve guess… #1656

Merged
merged 3 commits into from Mar 13, 2018
Merged

Update ErrorFormatGuesser to handle custom formats and improve guess… #1656

merged 3 commits into from Mar 13, 2018

Conversation

natepage
Copy link
Contributor

Q A
Bug fix? yes
New feature? no
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets -
License MIT
Doc PR -

Wanted to implement custom error formats based on the format requested by the client, the issue was that whatever the request format the errors were always in the first error format configured. This PR has for purpose to improve the way the ErrorFormatGuesser is guessing the current format from the request and the configuration.

The logic is as following:
1- Check if request format has been set
2- Check if configuration for the format exist and return it if Yes
3- Otherwise guess MIME type from request headers (Accept, Content-Type)
4- Check if configuration for the MIME type exist and return it if Yes
5- Otherwise fallback to first error format configured

Example of configuration:

api_platform:
    mapping:
        paths: ['%kernel.project_dir%/src/Database/Models']
    formats:
        json:     ['application/json']
        xml:      ['application/xml', 'text/xml']
    error_formats:
        custom_error_json: ['application/json']
        custom_error_xml:  ['application/xml', 'text/xml']

Return table

Request format Accept Header Content-Type Header Return
/ / / ['key' => 'custom_error_xml', 'value' => ['application/xml', 'text/xml']]
custom_error_xml / / ['key' => 'custom_error_xml', 'value' => ['application/xml', 'text/xml']]
/ application/json / ['key' => 'custom_error_json', 'value' => ['application/json']]
/ / text/xml ['key' => 'custom_error_xml', 'value' => ['application/xml', 'text/xml']]
custom_error_xml application/json text/xml ['key' => 'custom_error_xml', 'value' => ['application/xml', 'text/xml']]

The priority order to guess the format is: Request format > Accept Header > Content-Type Header > Fallback to first error format configured.

@natepage
Copy link
Contributor Author

Travis check failed because their service was down yesterday, can we please restart the tests because it should pass. Thank you.

@@ -37,14 +37,35 @@ private function __construct()
public static function guessErrorFormat(Request $request, array $errorFormats): array
{
$requestFormat = $request->getRequestFormat('');
if ('' !== $requestFormat && isset($errorFormats[$requestFormat])) {

if ('' !== $requestFormat && array_key_exists($requestFormat, $errorFormats)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change? (isset is faster)

}
}

return ['key' => key($errorFormats), 'value' => reset($errorFormats)];
Copy link
Member

@dunglas dunglas Jan 17, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we already loop over formats, you can just store the first format in a var, line 48:

$requestMimeTypes = $request->getMimeTypes($request->getRequestFormat());
$defaultFormat = [];
foreach ($errorFormats as $format => $errorMimeTypes) {
    if ([] === $defaultFormat) {
        $defaultFormat = ['key' => $format, 'value' => $errorMimeTypes];
    }

    if (array_intersect($requestMimeTypes, $errorMimeTypes) {
        return ['key' => $format, 'value' => $errorMimeTypes];
    }
}

return $defaultFormat;

It's faster and safer in case no default format is defined.

/**
* Get MIME type from the request.
*
* @param \Symfony\Component\HttpFoundation\Request $request
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@param and @return can be removed because of the typehints.

*
* @return string|null
*/
private static function getMimeType(Request $request): ?string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove this method and just call $request->getMimeTypes($request->getRequestFormat()) because the content negotiation is already properly handled here: https://github.com/api-platform/core/blob/master/src/EventListener/AddFormatListener.php#L67

$mimeType = self::getMimeType($request);

foreach ($errorFormats as $format => $mimeTypes) {
if (in_array($mimeType, $mimeTypes, true)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sometimes they are several mime types associated with one formats (it's why you should call $request->getMimeTypes()). They all must be checked. See my alternative proposal below.

@dunglas
Copy link
Member

dunglas commented Jan 17, 2018

Can you also target the master? It's a new feature to me.

@natepage
Copy link
Contributor Author

@dunglas I've made the changes, thank you for the pieces of code you gave me. I don't know how to target master instead of 2.1, do you want me to create a new PR to master?

@natepage natepage changed the base branch from 2.1 to master January 18, 2018 01:01
@dunglas
Copy link
Member

dunglas commented Jan 22, 2018

@natepage git rebase master should do the trick.

@meyerbaptiste
Copy link
Member

Rebased and RFR @api-platform/core-team.

$defaultFormat = [];

foreach ($errorFormats as $format => $errorMimeTypes) {
if ([] === $defaultFormat) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what? this looks weird can we change to empty? or !$defaultFormat? Why can't we init above with the proper array?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we not sure the key 0 exist, it looks ok to me to do this. We indeed may use !$defaultFormat.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be 👍 for !$defaultFormat. Anyway I panicked because [] === [] is false in javascript 😆

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@dunglas dunglas merged commit 316b3f3 into api-platform:master Mar 13, 2018
@dunglas
Copy link
Member

dunglas commented Mar 13, 2018

Thanks @natepage and @meyerbaptiste!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants