-
-
Notifications
You must be signed in to change notification settings - Fork 893
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
feat(state): add headers to comply with LDP specification #6917
base: main
Are you sure you want to change the base?
feat(state): add headers to comply with LDP specification #6917
Conversation
To ensure compliance with the LDP specification (https://www.w3.org/TR/ldp/): - Added the "Accept-Post" header with the value "text/turtle, application/ld+json". - Added the "Allow" header with values based on the allowed operations on the queried resources.
features/main/ldp_resources.feature
Outdated
When I add "Content-Type" header equal to "application/ld+json" | ||
And I send a "GET" request to "/dummy_get_post_delete_operations" | ||
Then the header "Accept-Post" should be equal to "text/turtle, application/ld+json" | ||
And the header "Allow" should be equal to "OPTIONS, HEAD, GET, POST, DELETE" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't the value be different for item and collection URLs?
Also, Allow-Post should only be defined for URLs where POST is enabled (usually collections, but not always).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You’re right, I overlooked that. I’ll make the changes and push them soon.
@@ -88,6 +93,9 @@ public function process(mixed $data, Operation $operation, array $uriVariables = | |||
$headers['Accept-Patch'] = $acceptPatch; | |||
} | |||
|
|||
$headers['Accept-Post'] = 'text/turtle, application/ld+json'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This value cannot be hardcoded. For instance Turtle is not supported natively by API Platform and JSON-LD may or may not depending on the config.
You should inject the enabled formats here. You can retrieve this from metadata. Listing all allowed media types (not only Turtle and JSON-LD) is probably OK (and a best practice).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It sounds much better to list all the allowed media types. I'll take care of it.
|
||
private function getAllowedMethods(?string $resourceClass): string | ||
{ | ||
$allowedMethods = self::DEFAULT_ALLOWED_METHOD; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should filter here the operations with a different URI Template than the one of the current operation to fix the issue described in my 1st comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, good point—I’ll sort this out.
… "allow" has now the right method.
… to avoid sending incorrect values.
@@ -88,6 +93,16 @@ public function process(mixed $data, Operation $operation, array $uriVariables = | |||
$headers['Accept-Patch'] = $acceptPatch; | |||
} | |||
|
|||
if (!$exception) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To avoid returning incorrect values—such as in the case of a 401 error when requesting a resource—I added this condition, although I’m not entirely sure it’s the best approach. The issue arises because, in the case of a 401 error, the allowed methods and the currentUriTemplate correspond to the error itself rather than the requested resource.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The approach looks good to me.
} | ||
} | ||
|
||
return array_unique($allowedMethods); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it's necessary because it's not possible to register two different routes for the same URL and the same method.
It would remove this as we are on the hot path.
To be honest, I would inline this code and remove this method. This will also allow you to set a variable (instead of running an O(n) operation) to check if POST is defined or not for Accept-Post, and this would remove a function call.
(Yes, these are mostly a micro optimization, but this also simplify the code IMHO).
private function getAllowedMethods(?string $resourceClass, ?string $currentUriTemplate): array | ||
{ | ||
$allowedMethods = self::DEFAULT_ALLOWED_METHOD; | ||
if (null !== $currentUriTemplate && null !== $resourceClass && $this->resourceClassResolver->isResourceClass($resourceClass)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (null !== $currentUriTemplate && null !== $resourceClass && $this->resourceClassResolver->isResourceClass($resourceClass)) { | |
if (null !== $currentUriTemplate && null !== $resourceClass && $this->resourceClassResolver?->isResourceClass($resourceClass)) { |
{ | ||
$allowedMethods = self::DEFAULT_ALLOWED_METHOD; | ||
if (null !== $currentUriTemplate && null !== $resourceClass && $this->resourceClassResolver->isResourceClass($resourceClass)) { | ||
$resourceMetadataCollection = $this->resourceCollectionMetadataFactory ? $this->resourceCollectionMetadataFactory->create($resourceClass) : new ResourceMetadataCollection($resourceClass); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$resourceMetadataCollection = $this->resourceCollectionMetadataFactory ? $this->resourceCollectionMetadataFactory->create($resourceClass) : new ResourceMetadataCollection($resourceClass); | |
$resourceMetadataCollection = $this->resourceCollectionMetadataFactory ? $this->resourceCollectionMetadataFactory->create($resourceClass) : new ResourceMetadataCollection($resourceClass); |
Better not set the extra headers at all if the factory is missing.
It's an optional feature after all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As it's an optional feature, should I add a configuration variable to enable it?
} | ||
$headers['Allow'] = implode(', ', $allowedMethods); | ||
|
||
if ($isPostAllowed && \is_array($outputFormats = ($outputFormats = $operation->getOutputFormats())) && [] !== $outputFormats) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if ($isPostAllowed && \is_array($outputFormats = ($outputFormats = $operation->getOutputFormats())) && [] !== $outputFormats) { | |
if ($isPostAllowed && \is_array($outputFormats = $operation->getOutputFormats()) && [] !== $outputFormats) { |
public function __construct( | ||
private ?IriConverterInterface $iriConverter = null, | ||
private readonly ?ResourceClassResolverInterface $resourceClassResolver = null, | ||
private readonly ?OperationMetadataFactoryInterface $operationMetadataFactory = null, | ||
private readonly ?ResourceMetadataCollectionFactoryInterface $resourceCollectionMetadataFactory = null, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could probably create an LinkedDataPlatformProcessor
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the suggestion! A LinkedDataPlatformProcessor sounds like a good idea, I'll consider implementing it.
@@ -88,6 +92,27 @@ public function process(mixed $data, Operation $operation, array $uriVariables = | |||
$headers['Accept-Patch'] = $acceptPatch; | |||
} | |||
|
|||
if (!$exception) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if ($operation instanceof ApiPlatform\Metadata\Error)
foreach ($resource->getOperations() as $resourceOperation) { | ||
if ($resourceOperation->getUriTemplate() === $currentUriTemplate) { | ||
$allowedMethods[] = $operationMethod = $resourceOperation->getMethod(); | ||
$isPostAllowed = $isPostAllowed || ('POST' === $operationMethod); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you could collect outputFormats here directly? The operation below may not be the one where the formats are defined.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. Thanks.
} | ||
$headers['Allow'] = implode(', ', $allowedMethods); | ||
|
||
if ($isPostAllowed && \is_array($outputFormats = ($outputFormats = $operation->getOutputFormats())) && [] !== $outputFormats) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if ($isPostAllowed && \is_array($outputFormats = ($outputFormats = $operation->getOutputFormats())) && [] !== $outputFormats) { | |
if ($isPostAllowed && ($outputFormats = $operation->getOutputFormats())) { |
use Symfony\Component\HttpFoundation\Request; | ||
use Symfony\Component\HttpFoundation\Response; | ||
|
||
class RespondProcessorTest extends TestCase |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a RespondProcessorTest already exists it must be in tests
forgot to move it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, i will merge them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The existing RespondProcessorTest use the old way of doing test with prophecy instead of PHPunit mock system, should i refactor it ?
@@ -21,6 +21,7 @@ | |||
<argument type="service" id="api_platform.iri_converter" /> | |||
<argument type="service" id="api_platform.resource_class_resolver" /> | |||
<argument type="service" id="api_platform.metadata.operation.metadata_factory" /> | |||
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
on-invalid=ignore
use ApiPlatform\Metadata\Post; | ||
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; | ||
|
||
#[ApiResource(operations: [new Get(), new GetCollection(), new Post(), new Delete()])] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do you really need an entity/document for this? I think only the DummyGetPostDeleteOperation is enough, if you need a dummy provider you can create a static one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, i probably did that when i was struggling with behat or trying to fix some MongoDB test on CI.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes with Behat by default they both run, as this feature doesn't impact doctrine we don't need to setup any entities.
To ensure compliance with the LDP specification (https://www.w3.org/TR/ldp/):