Skip Custom JSON:API Resource Types that Are Not Configurable

Created on 14 April 2022, over 2 years ago
Updated 7 June 2024, 7 months ago

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

  1. Install the JSON:API Extras module.
  2. Install the JSON:API Resources module.
  3. Create a new module called my_module.
  4. 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'
    
  5. 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];
      }
    
    }
    
  6. Install the new module (or clear caches if you installed it before adding the routing file).
  7. 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

✨ Feature request
Status

Fixed

Version

3.0

Component

Code

Created by

πŸ‡ΊπŸ‡ΈUnited States GuyPaddock

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

Production build 0.71.5 2024