Problem/Motivation
When using the
JSON:API Resources β
module and returning a resource that has relationships, an assertion failure is triggered in Drupal\jsonapi_extras\Normalizer\ResourceIdentifierNormalizer->guessFieldName()
because the resource type is not configurable. As it would be burdensome for a custom resource to have to implement configurability, it makes more sense for JSON:API Extras to skip processing these resource types.
<!--break-->
Steps to reproduce
- Install the JSON:API Extras module.
- Install the JSON:API Resources module.
- Create a new module called
my_module
.
- In
my_module/my_module.routing.yml
, place the following code:
jsonapi.current_user--current_user.get:
path: '/%jsonapi%/current_user'
defaults:
_jsonapi_resource: '\Drupal\my_module\Resource\CurrentUserInfoBugRepro'
requirements:
_user_is_logged_in: 'TRUE'
jsonapi.current_user--current_user.roles.relationship.get:
path: '/%jsonapi%/current_user/{entity}/relationships/roles'
defaults:
_controller: 'jsonapi.entity_resource:getRelationship'
resource_type: 'user--user'
jsonapi.current_user--current_user.roles.related:
path: '/%jsonapi%/current_user/{entity}/roles'
defaults:
_controller: 'jsonapi.entity_resource:getRelated'
related: 'roles'
resource_type: 'user--user'
- In
my_module/src/Resource/CurrentUserInfoBugRepro.php
, place the following code:
namespace Drupal\my_module\Resource;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\jsonapi\JsonApiResource\LinkCollection;
use Drupal\jsonapi\JsonApiResource\ResourceObject;
use Drupal\jsonapi\JsonApiResource\ResourceObjectData;
use Drupal\jsonapi\ResourceType\ResourceType;
use Drupal\jsonapi\ResourceType\ResourceTypeAttribute;
use Drupal\jsonapi\ResourceType\ResourceTypeRelationship;
use Drupal\jsonapi_resources\Resource\EntityResourceBase;
use Drupal\user\UserInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
/**
* Processes a request for the authenticated user's information.
*/
class CurrentUserInfoBugRepro extends EntityResourceBase implements ContainerInjectionInterface {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The current user account.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Constructs a new instance.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* Tne entity type manager.
* @param \Drupal\Core\Session\AccountInterface $account
* The current user.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager,
AccountInterface $account) {
$this->entityTypeManager = $entity_type_manager;
$this->currentUser = $account;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container): CurrentUserInfoBugRepro {
return new static(
$container->get('entity_type.manager'),
$container->get('current_user')
);
}
/**
* Process the resource request.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request.
* @param \Drupal\jsonapi\ResourceType\ResourceType[] $resource_types
* The route resource types.
*
* @return \Drupal\jsonapi\ResourceResponse
* The response.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
public function process(Request $request, array $resource_types) {
$user_storage = $this->entityTypeManager->getStorage('user');
$current_user = $user_storage->load($this->currentUser->id());
assert($current_user instanceof UserInterface);
$resource_type = reset($resource_types);
$user_fields = $current_user->getFields();
$user_roles = $user_fields['roles'];
$links = new LinkCollection([]);
$primary_data = new ResourceObject(
$current_user,
$resource_type,
$current_user->uuid(),
NULL,
[
'displayName' => $current_user->getDisplayName(),
'roles' => $user_roles,
],
$links
);
$top_level_data = new ResourceObjectData([$primary_data], 1);
$response = $this->createJsonapiResponse($top_level_data, $request);
$response->addCacheableDependency(
(new CacheableMetadata())->addCacheContexts(['user'])
);
return $response;
}
/**
* {@inheritdoc}
*/
public function getRouteResourceTypes(Route $route,
string $route_name): array {
$fields = [
'displayName' => new ResourceTypeAttribute('displayName'),
'token' => new ResourceTypeAttribute('token'),
'roles' => new ResourceTypeRelationship('roles', NULL, TRUE, FALSE),
];
$resource_type =
new ResourceType(
'current_user',
'current_user',
NULL,
FALSE,
TRUE,
TRUE,
FALSE,
$fields
);
$resource_type->setRelatableResourceTypes([
'roles' => $this->getResourceTypesByEntityTypeId('user_role'),
]);
return [$resource_type];
}
}
- Install the new module (or clear caches if you installed it before adding the routing file).
- Request
/jsonapi/current_user
on your site.
You will receive the following assertion failure:
The website encountered an unexpected error. Please try again later.
AssertionError: assert($resource_type instanceof ConfigurableResourceType) in assert() (line 100 of modules/contrib/jsonapi_extras/src/Normalizer/ResourceIdentifierNormalizer.php).
assert(, 'assert($resource_type instanceof ConfigurableResourceType)') (Line: 100)
Drupal\jsonapi_extras\Normalizer\ResourceIdentifierNormalizer->guessFieldName('fb173256-fda7-4be8-8ae2-d73d4a8b26bb', Object) (Line: 60)
Drupal\jsonapi_extras\Normalizer\ResourceIdentifierNormalizer->normalize(Object, 'api_json', Array) (Line: 153)
Symfony\Component\Serializer\Serializer->normalize(Object, 'api_json', Array) (Line: 62)
Drupal\jsonapi\Serializer\Serializer->normalize(Object, 'api_json', Array) (Line: 26)
Drupal\jsonapi\Normalizer\DataNormalizer->Drupal\jsonapi\Normalizer\{closure}(Object)
array_map(Object, Array) (Line: 27)
Drupal\jsonapi\Normalizer\DataNormalizer->normalize(Object, 'api_json', Array) (Line: 153)
Symfony\Component\Serializer\Serializer->normalize(Object, 'api_json', Array) (Line: 62)
Drupal\jsonapi\Serializer\Serializer->normalize(Object, 'api_json', Array) (Line: 26)
Drupal\jsonapi\Normalizer\RelationshipNormalizer->normalize(Object, 'api_json', Array) (Line: 153)
Symfony\Component\Serializer\Serializer->normalize(Object, 'api_json', Array) (Line: 62)
Drupal\jsonapi\Serializer\Serializer->normalize(Object, 'api_json', Array) (Line: 187)
Drupal\jsonapi\Normalizer\ResourceObjectNormalizer->serializeField(Object, Array, 'api_json') (Line: 121)
Drupal\jsonapi\Normalizer\ResourceObjectNormalizer->getNormalization(Array, Object, 'api_json', Array) (Line: 73)
Drupal\jsonapi\Normalizer\ResourceObjectNormalizer->normalize(Object, 'api_json', Array) (Line: 38)
Drupal\jsonapi_extras\Normalizer\JsonApiNormalizerDecoratorBase->normalize(Object, 'api_json', Array) (Line: 24)
Drupal\jsonapi_extras\Normalizer\ResourceObjectNormalizer->normalize(Object, 'api_json', Array) (Line: 153)
Symfony\Component\Serializer\Serializer->normalize(Object, 'api_json', Array) (Line: 62)
Drupal\jsonapi\Serializer\Serializer->normalize(Object, 'api_json', Array) (Line: 26)
Drupal\jsonapi\Normalizer\DataNormalizer->Drupal\jsonapi\Normalizer\{closure}(Object)
array_map(Object, Array) (Line: 27)
Drupal\jsonapi\Normalizer\DataNormalizer->normalize(Object, 'api_json', Array) (Line: 153)
Symfony\Component\Serializer\Serializer->normalize(Object, 'api_json', Array) (Line: 62)
Drupal\jsonapi\Serializer\Serializer->normalize(Object, 'api_json', Array) (Line: 191)
Drupal\jsonapi\Normalizer\JsonApiDocumentTopLevelNormalizer->normalize(Object, 'api_json', Array) (Line: 153)
Symfony\Component\Serializer\Serializer->normalize(Object, 'api_json', Array) (Line: 62)
Drupal\jsonapi\Serializer\Serializer->normalize(Object, 'api_json', Array) (Line: 120)
Drupal\jsonapi\EventSubscriber\ResourceResponseSubscriber->renderResponseBody(Object, Object, Object, 'api_json') (Line: 85)
Drupal\jsonapi\EventSubscriber\ResourceResponseSubscriber->onResponse(Object, 'kernel.response', Object)
call_user_func(Array, Object, 'kernel.response', Object) (Line: 142)
Proposed resolution
\Drupal\jsonapi_extras\Normalizer\ResourceIdentifierNormalizer::normalize()
should avoid processing resource types that are not configurable.
Remaining tasks
User interface changes
API changes
Data model changes