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

  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

Command icon 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:

Comments

GuyPaddock created an issue. See original summary.

guypaddock’s picture

Issue summary: View changes

guypaddock’s picture

Status: Active » Needs review

I've opened an MR with the fix.

bbrala made their first commit to this issue’s fork.

bbrala’s picture

Status: Needs review » Fixed

Been a while, but yes, indeed.

Don't see hhow this can break anything since later it actually assumes it is a configurableresourcetype.

Ty.

  • bbrala committed 2cf2d58c on 8.x-3.x authored by GuyPaddock
    Issue #3275196 by GuyPaddock, bbrala: Skip Custom JSON:API Resource...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.