
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.
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
Issue fork jsonapi_extras-3275196
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
- 3275196-only-process-configurable-resource-types
changes, plain diff MR !16
Comments
Comment #2
guypaddock CreditAttribution: guypaddock at Inveniem commentedComment #4
guypaddock CreditAttribution: guypaddock at Inveniem commentedI've opened an MR with the fix.
Comment #6
bbralaBeen a while, but yes, indeed.
Don't see hhow this can break anything since later it actually assumes it is a configurableresourcetype.
Ty.