Skip to content

Commit

Permalink
Merge branch '4.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
dunglas committed Sep 25, 2024
2 parents cead16a + a259d46 commit d3a6039
Show file tree
Hide file tree
Showing 17 changed files with 95 additions and 68 deletions.
16 changes: 6 additions & 10 deletions features/hydra/error.feature
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@ Feature: Error handling
And the header "Link" should contain '<http://www.w3.org/ns/hydra/error>; rel="http://www.w3.org/ns/json-ld#error"'
And the JSON node "type" should exist
And the JSON node "title" should be equal to "An error occurred"
And the JSON node "hydra:title" should be equal to "An error occurred"
And the JSON node "detail" should exist
And the JSON node "hydra:description" should exist
And the JSON node "description" should exist
And the JSON node "trace" should exist
And the JSON node "status" should exist
And the JSON node "@context" should exist
Expand Down Expand Up @@ -48,10 +47,9 @@ Feature: Error handling
}
],
"detail": "name: This value should not be blank.",
"hydra:title": "An error occurred",
"hydra:description": "name: This value should not be blank.",
"type": "/validation_errors/c1051bb4-d103-4f74-8988-acbcafc7fdc3",
"title": "An error occurred"
"title": "An error occurred",
"description": "name: This value should not be blank.",
"type": "/validation_errors/c1051bb4-d103-4f74-8988-acbcafc7fdc3"
}
"""

Expand Down Expand Up @@ -83,9 +81,8 @@ Feature: Error handling
And the JSON node "@context" should exist
And the JSON node "type" should exist
And the JSON node "title" should be equal to "An error occurred"
And the JSON node "hydra:title" should be equal to "An error occurred"
And the JSON node "detail" should exist
And the JSON node "hydra:description" should exist
And the JSON node "description" should exist

Scenario: Get an rfc 7807 bad method error
When I add "Content-Type" header equal to "application/ld+json"
Expand All @@ -101,9 +98,8 @@ Feature: Error handling
And the JSON node "@context" should exist
And the JSON node "type" should exist
And the JSON node "title" should be equal to "An error occurred"
And the JSON node "hydra:title" should be equal to "An error occurred"
And the JSON node "detail" should exist
And the JSON node "hydra:description" should exist
And the JSON node "description" should exist

Scenario: Get an rfc 7807 validation error
When I add "Content-Type" header equal to "application/ld+json"
Expand Down
2 changes: 1 addition & 1 deletion features/jsonld/input_output.feature
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ Feature: JSON-LD DTO input and output
"""
Then the response status code should be 400
And the response should be in JSON
And the JSON node "hydra:description" should be equal to "The input data is misformatted."
And the JSON node "description" should be equal to "The input data is misformatted."

@!mongodb
Scenario: Reset password through an input DTO without DataTransformer
Expand Down
2 changes: 1 addition & 1 deletion features/main/attribute_resource.feature
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ Feature: Resource attributes
And the response should be in JSON
And the header "Content-Type" should be equal to "application/problem+json; charset=utf-8"
And the header "Link" should contain '<http://www.w3.org/ns/hydra/error>; rel="http://www.w3.org/ns/json-ld#error"'
And the JSON node "hydra:description" should be equal to 'Unable to generate an IRI for the item of type "ApiPlatform\Tests\Fixtures\TestBundle\Entity\IncompleteUriVariableConfigured"'
And the JSON node "description" should be equal to 'Unable to generate an IRI for the item of type "ApiPlatform\Tests\Fixtures\TestBundle\Entity\IncompleteUriVariableConfigured"'

Scenario: Uri variables with Post operation
When I add "Content-Type" header equal to "application/ld+json"
Expand Down
15 changes: 12 additions & 3 deletions features/main/not_exposed.feature
Original file line number Diff line number Diff line change
Expand Up @@ -171,13 +171,22 @@ Feature: Expose only a collection of objects
When I send a "GET" request to "<uri>"
Then the response status code should be 404
And the response should be in JSON
And the JSON node "hydra:description" should be equal to "<hydra:description>"
And the JSON node "hydra:description" should be equal to "<description>"
Examples:
| uri | hydra:description |
| /.well-known/genid/12345 | This route is not exposed on purpose. It generates an IRI for a collection resource without identifier nor item operation. |
| uri | description |
| /tables/12345 | This route does not aim to be called. |
| /forks/12345 | This route does not aim to be called. |

Scenario Outline: Get a not exposed route returns a 404 with an explanation
When I send a "GET" request to "<uri>"
Then the response status code should be 404
And the response should be in JSON
And the JSON node "description" should be equal to "<description>"
Examples:
| uri | description |
| /.well-known/genid/12345 | This route is not exposed on purpose. It generates an IRI for a collection resource without identifier nor item operation. |


Scenario: Get a single item still works
When I send a "GET" request to "/cuillers/12345"
Then the response status code should be 200
Expand Down
12 changes: 6 additions & 6 deletions features/main/relation.feature
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ Feature: Relations support
Then the response status code should be 400
And the response should be in JSON
And the header "Content-Type" should be equal to "application/problem+json; charset=utf-8"
And the JSON node "hydra:description" should contain 'Invalid IRI "certainly not an IRI".'
And the JSON node "description" should contain 'Invalid IRI "certainly not an IRI".'

Scenario: Passing an invalid type to a relation
When I add "Content-Type" header equal to "application/ld+json"
Expand All @@ -493,20 +493,20 @@ Feature: Relations support
"properties": {
"@type": {
"type": "string",
"pattern": "^hydra:Error$"
"pattern": "^Error$"
},
"hydra:title": {
"title": {
"type": "string",
"pattern": "^An error occurred$"
},
"hydra:description": {
"description": {
"pattern": "^The type of the \"ApiPlatform\\\\Tests\\\\Fixtures\\\\TestBundle\\\\(Document|Entity)\\\\RelatedDummy\" resource must be \"array\" \\(nested document\\) or \"string\" \\(IRI\\), \"integer\" given.$"
}
},
"required": [
"@type",
"hydra:title",
"hydra:description"
"title",
"description"
]
}
"""
Expand Down
2 changes: 1 addition & 1 deletion features/main/union_intersect_types.feature
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,4 @@ Feature: Union/Intersect types
Then the response status code should be 400
And the response should be in JSON
And the header "Content-Type" should be equal to "application/problem+json; charset=utf-8"
And the JSON node "hydra:description" should be equal to 'Could not denormalize object of type "ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue5452\ActivableInterface", no supporting normalizer found.'
And the JSON node "description" should be equal to 'Could not denormalize object of type "ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue5452\ActivableInterface", no supporting normalizer found.'
2 changes: 1 addition & 1 deletion features/main/validation.feature
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ Feature: Using validations groups
And the JSON node "violations[0].message" should be equal to "This value should not be null."
And the JSON node "violations[0].propertyPath" should be equal to "test"
And the JSON node "detail" should be equal to "test: This value should not be null."
And the JSON node "hydra:description" should be equal to "test: This value should not be null."
And the JSON node "description" should be equal to "test: This value should not be null."
And the header "Content-Type" should be equal to "application/problem+json; charset=utf-8"

@!mongodb
Expand Down
10 changes: 5 additions & 5 deletions features/serializer/vo_relations.feature
Original file line number Diff line number Diff line change
Expand Up @@ -148,20 +148,20 @@ Feature: Value object as ApiResource
"properties": {
"@type": {
"type": "string",
"pattern": "^hydra:Error$"
"pattern": "^Error$"
},
"hydra:title": {
"title": {
"type": "string",
"pattern": "^An error occurred$"
},
"hydra:description": {
"description": {
"pattern": "^Cannot create an instance of \"ApiPlatform\\\\Tests\\\\Fixtures\\\\TestBundle\\\\(Document|Entity)\\\\VoDummyCar\" from serialized data because its constructor requires the following parameters to be present : \"\\$drivers\".$"
}
},
"required": [
"@type",
"hydra:title",
"hydra:description"
"title",
"description"
]
}
"""
Expand Down
3 changes: 2 additions & 1 deletion src/JsonLd/ContextBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace ApiPlatform\JsonLd;

use ApiPlatform\JsonLd\Serializer\HydraPrefixTrait;
use ApiPlatform\Metadata\Error;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\HttpOperation;
use ApiPlatform\Metadata\IriConverterInterface;
Expand Down Expand Up @@ -184,7 +185,7 @@ private function getResourceContextWithShortname(string $resourceClass, int $ref
}
}

if (false === ($this->defaultContext[self::HYDRA_CONTEXT_HAS_PREFIX] ?? true)) {
if (false === ($this->defaultContext[self::HYDRA_CONTEXT_HAS_PREFIX] ?? true) || $operation instanceof Error) {
return ['http://www.w3.org/ns/hydra/context.jsonld', $context];
}

Expand Down
45 changes: 36 additions & 9 deletions src/Laravel/ApiPlatformProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 +705,33 @@ public function register(): void
/** @var ConfigRepository */
$config = $app['config'];

return new Options(title: $config->get('api-platform.title') ?? '');
return new Options(
title: $config->get('api-platform.title', ''),
description: $config->get('api-platform.description', ''),
version: $config->get('api-platform.version', ''),
oAuthEnabled: $config->get('api-platform.swagger_ui.oauth.enabled', false),
oAuthType: $config->get('api-platform.swagger_ui.oauth.type', null),
oAuthFlow: $config->get('api-platform.swagger_ui.oauth.flow', null),
oAuthTokenUrl: $config->get('api-platform.swagger_ui.oauth.tokenUrl', null),
oAuthAuthorizationUrl: $config->get('api-platform.swagger_ui.oauth.authorizationUrl', null),
oAuthRefreshUrl: $config->get('api-platform.swagger_ui.oauth.refreshUrl', null),
oAuthScopes: $config->get('api-platform.swagger_ui.oauth.scopes', []),
apiKeys: $config->get('api-platform.swagger_ui.apiKeys', []),
);
});

$this->app->singleton(SwaggerUiProcessor::class, function (Application $app) {
/** @var ConfigRepository */
$config = $app['config'];

return new SwaggerUiProcessor(
urlGenerator: $app->make(UrlGeneratorInterface::class),
normalizer: $app->make(NormalizerInterface::class),
openApiOptions: $app->make(Options::class),
oauthClientId: $config->get('api-platform.swagger_ui.oauth.clientId'),
oauthClientSecret: $config->get('api-platform.swagger_ui.oauth.clientSecret'),
oauthPkce: $config->get('api-platform.swagger_ui.oauth.pkce', false),
);
});

$this->app->singleton(DocumentationController::class, function (Application $app) {
Expand Down Expand Up @@ -1267,14 +1293,6 @@ public function boot(ResourceNameCollectionFactoryInterface $resourceNameCollect
$route->name('api_doc')->middleware(ApiPlatformMiddleware::class);
$routeCollection->add($route);

$route = new Route(['GET'], $prefix.'/{index?}{_format?}', function (Request $request, Application $app) {
$entrypointAction = $app->make(EntrypointController::class);

return $entrypointAction->__invoke($request);
});
$route->where('index', 'index');
$route->name('api_entrypoint')->middleware(ApiPlatformMiddleware::class);
$routeCollection->add($route);
$route = new Route(['GET'], $prefix.'/.well-known/genid/{id}', function (): void {
throw new NotExposedHttpException('This route is not exposed on purpose. It generates an IRI for a collection resource without identifier nor item operation.');
});
Expand All @@ -1297,6 +1315,15 @@ public function boot(ResourceNameCollectionFactoryInterface $resourceNameCollect
$routeCollection->add($route);
}

$route = new Route(['GET'], $prefix.'/{index?}{_format?}', function (Request $request, Application $app) {
$entrypointAction = $app->make(EntrypointController::class);

return $entrypointAction->__invoke($request);
});
$route->where('index', 'index');
$route->name('api_entrypoint')->middleware(ApiPlatformMiddleware::class);
$routeCollection->add($route);

$router->setRoutes($routeCollection);
}

Expand Down
6 changes: 3 additions & 3 deletions src/Laravel/Eloquent/State/LinksHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,19 @@ public function handleLinks(Builder $builder, array $uriVariables, array $contex
$identifier = $uriVariables[$uriVariable];

if ($to = $link->getToProperty()) {
$builder = $builder->where($builder->getModel()->getTable().'.'.$builder->getModel()->{$to}()->getForeignKeyName(), $identifier);
$builder = $builder->where($builder->getModel()->{$to}()->getQualifiedForeignKeyName(), $identifier);

continue;
}

if ($from = $link->getFromProperty()) {
$relation = $this->application->make($link->getFromClass());
$builder = $builder->getModel()->where($builder->getModel()->getTable().'.'.$relation->{$from}()->getForeignKeyName(), $identifier);
$builder = $builder->getModel()->where($relation->{$from}()->getQualifiedForeignKeyName(), $identifier);

continue;
}

$builder->where($builder->getModel()->getTable().'.'.$link->getIdentifiers()[0], $identifier);
$builder->where($builder->getModel()->qualifyColumn($link->getIdentifiers()[0]), $identifier);
}

return $builder;
Expand Down
1 change: 1 addition & 0 deletions src/Laravel/State/SwaggerUiProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public function process(mixed $openApi, Operation $operation, array $uriVariable
'flow' => $this->openApiOptions->getOAuthFlow(),
'tokenUrl' => $this->openApiOptions->getOAuthTokenUrl(),
'authorizationUrl' => $this->openApiOptions->getOAuthAuthorizationUrl(),
'redirectUrl' => $request->getSchemeAndHttpHost().'/vendor/api-platform/swagger-ui/oauth2-redirect.html',
'scopes' => $this->openApiOptions->getOAuthScopes(),
'clientId' => $this->oauthClientId,
'clientSecret' => $this->oauthClientSecret,
Expand Down
19 changes: 18 additions & 1 deletion src/Laravel/config/api-platform.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,24 @@
],

'swagger_ui' => [
'enabled' => true
'enabled' => true,
//'apiKeys' => [
// 'api' => [
// 'type' => 'Bearer',
// 'name' => 'Authentication Token',
// 'in' => 'header'
// ]
//],
//'oauth' => [
// 'enabled' => true,
// 'type' => 'oauth2',
// 'flow' => 'authorizationCode',
// 'tokenUrl' => '',
// 'authorizationUrl' =>'',
// 'refreshUrl' => '',
// 'scopes' => ['scope1' => 'Description scope 1'],
// 'pkce' => true
//]
],

'url_generation_strategy' => UrlGeneratorInterface::ABS_PATH,
Expand Down
1 change: 0 additions & 1 deletion src/Serializer/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
"doctrine/collections": "^2.1",
"phpspec/prophecy-phpunit": "^2.2",
"phpunit/phpunit": "^11.2",
"sebastian/comparator": "<5.0",
"symfony/mercure-bundle": "*",
"symfony/var-dumper": "^6.4 || ^7.0",
"symfony/yaml": "^6.4 || ^7.0"
Expand Down
15 changes: 0 additions & 15 deletions src/State/ApiResource/Error.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
use Symfony\Component\WebLink\Link;

#[ErrorResource(
types: ['hydra:Error'],
openapi: false,
uriVariables: ['status'],
uriTemplate: '/errors/{status}',
Expand Down Expand Up @@ -95,21 +94,7 @@ public function __construct(
#[Groups(['trace'])]
public ?array $originalTrace = null;

#[SerializedName('hydra:title')]
#[Groups(['jsonld'])]
public function getHydraTitle(): ?string
{
return $this->title;
}

#[SerializedName('hydra:description')]
#[Groups(['jsonld'])]
public function getHydraDescription(): ?string
{
return $this->detail;
}

#[SerializedName('description')]
public function getDescription(): ?string
{
return $this->detail;
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Bundle/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public function getConfigTreeBuilder(): TreeBuilder
->scalarNode('exists_parameter_name')->defaultValue('exists')->cannotBeEmpty()->info('The name of the query parameter to filter on nullable field values.')->end()
->scalarNode('order')->defaultValue('ASC')->info('The default order of results.')->end() // Default ORDER is required for postgresql and mysql >= 5.7 when using LIMIT/OFFSET request
->scalarNode('order_parameter_name')->defaultValue('order')->cannotBeEmpty()->info('The name of the query parameter to order results.')->end()
->enumNode('order_nulls_comparison')->defaultNull()->values(array_merge(array_keys(OrderFilterInterface::NULLS_DIRECTION_MAP), [null]))->info('The nulls comparison strategy.')->end()
->enumNode('order_nulls_comparison')->defaultNull()->values(interface_exists(OrderFilterInterface::class) ? array_merge(array_keys(OrderFilterInterface::NULLS_DIRECTION_MAP), [null]) : [null])->info('The nulls comparison strategy.')->end()
->arrayNode('pagination')
->canBeDisabled()
->addDefaultsIfNotSet()
Expand Down
10 changes: 1 addition & 9 deletions src/Validator/Exception/ValidationException.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,16 +106,8 @@ public function getId(): string
return $id;
}

#[SerializedName('hydra:title')]
#[Groups(['jsonld'])]
public function getHydraTitle(): string
{
return $this->errorTitle ?? 'An error occurred';
}

#[Groups(['jsonld'])]
#[SerializedName('hydra:description')]
public function getHydraDescription(): string
public function getDescription(): string
{
return $this->detail;
}
Expand Down

0 comments on commit d3a6039

Please sign in to comment.