Problem/Motivation

We need an API entry point to ensure that hypermedia can be used as the engine of the application state.

Proposed resolution

The entry point could be a read-only resource that contains list of links to all the available resources (in their collection form). The logic used to list all the available resources can copied from the jsonapi_docson admin list, but turned into a JSON response.

It is OK to have a dedicated controller or core REST plugin to do this.

The output should still be JSON API compliant. I'm proposing:

{
  "data": [],
  "links": {
    "self": "http://example.org/api",
    "node--article": "http://example.org/api/node--article",
    "node--page": "http://example.org/api/node--page",
    "user--user": "http://example.org/api/user--user"
  }
}

Note that this response should contain the needed cacheability metadata.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

e0ipso created an issue. See original summary.

David Hernández’s picture

Assigned: Unassigned » David Hernández
Issue tags: +Dublin2016

Investigating a bit about this. If anyone have any ideas about this issue and is in DrupalCon Dublin, ping me and let's talk about this in person.

David Hernández’s picture

Here is a first approach to the issue to list the available resources on the base path of the API endpoint.

JSON API specification doesn't covers yet this type of entry point, and I didn't found any good example around to use as base for the patch. So, I've developed it based on this Stack Overflow issue: http://stackoverflow.com/questions/19669443/entry-point-to-rest-hateoas-...

Some issues:

  • Right now, the resources have no access callback, instead, we check the user access to each item being rendered. This was fine as we didn't expose the list of resources until now. So far, to protect the list of available resources, I added a new permission to grant access to this list.
  • JSON API requires one of the following items to be part of the document: "data", "errors", or "meta", then, it has optional items: "jsonapi", "links" and "included". See this for more info about this: http://jsonapi.org/format/#document-top-level For this issue, we needed only the "links" option, so after discussing with @e0ipso, we decided to add a list of resources inside the "meta" item.
David Hernández’s picture

Issue tags: +Needs tests

I think a test for this feature will be cool.

Status: Needs review » Needs work

The last submitted patch, 3: 2790935-2-add-controler-to-list-resources.patch, failed testing.

The last submitted patch, 3: 2790935-2-add-controler-to-list-resources.patch, failed testing.

David Hernández’s picture

David Hernández’s picture

Status: Needs work » Needs review
e0ipso’s picture

Once there are tests for this approach I'm good with merging it.

e0ipso’s picture

+++ b/src/Controller/EntryPointController.php
@@ -0,0 +1,75 @@
+      $links[$plugin_definition['type']] = $plugin_definition['data']['partialPath'];

Generating links in D8 is very complex. However, we'll need to generate full paths.

For that we will need to inject the @jsonapi.link_manager.

See ContentEntityNormalizerValue::98 for an example on how to use this. However we should link to the collection instead to 'individual'.

e0ipso’s picture

Status: Needs review » Needs work
e0ipso’s picture

Issue tags: +8.x-1.x beta blocker
e0ipso’s picture

Issue tags: -8.x-1.x beta blocker

Not blocking here.

e0ipso’s picture

Status: Needs work » Needs review
FileSize
3.94 KB

The follwing patch generates the following output in my local:

{
  "data": [],
  "links": {
    "self": "http:\/\/d8dev.local\/jsonapi",
    "block--block": "http:\/\/d8dev.local\/jsonapi\/block\/block",
    "block_content_type--block_content_type": "http:\/\/d8dev.local\/jsonapi\/block_content_type\/block_content_type",
    "block_content--basic": "http:\/\/d8dev.local\/jsonapi\/block_content\/basic",
    "comment--comment": "http:\/\/d8dev.local\/jsonapi\/comment\/comment",
    "comment_type--comment_type": "http:\/\/d8dev.local\/jsonapi\/comment_type\/comment_type",
    "contact_message--feedback": "http:\/\/d8dev.local\/jsonapi\/contact_message\/feedback",
    "contact_message--personal": "http:\/\/d8dev.local\/jsonapi\/contact_message\/personal",
    "contact_form--contact_form": "http:\/\/d8dev.local\/jsonapi\/contact_form\/contact_form",
    "editor--editor": "http:\/\/d8dev.local\/jsonapi\/editor\/editor",
    "environment_indicator--environment_indicator": "http:\/\/d8dev.local\/jsonapi\/environment_indicator\/environment_indicator",
    "field_config--field_config": "http:\/\/d8dev.local\/jsonapi\/field_config\/field_config",
    "field_storage_config--field_storage_config": "http:\/\/d8dev.local\/jsonapi\/field_storage_config\/field_storage_config",
    "file--file": "http:\/\/d8dev.local\/jsonapi\/file\/file",
    "filter_format--filter_format": "http:\/\/d8dev.local\/jsonapi\/filter_format\/filter_format",
    "image_style--image_style": "http:\/\/d8dev.local\/jsonapi\/image_style\/image_style",
    "node_type--node_type": "http:\/\/d8dev.local\/jsonapi\/node_type\/node_type",
    "node--article": "http:\/\/d8dev.local\/jsonapi\/node\/article",
    "node--page": "http:\/\/d8dev.local\/jsonapi\/node\/page",
    "rdf_mapping--rdf_mapping": "http:\/\/d8dev.local\/jsonapi\/rdf_mapping\/rdf_mapping",
    "rest_resource_config--rest_resource_config": "http:\/\/d8dev.local\/jsonapi\/rest_resource_config\/rest_resource_config",
    "search_page--search_page": "http:\/\/d8dev.local\/jsonapi\/search_page\/search_page",
    "shortcut_set--shortcut_set": "http:\/\/d8dev.local\/jsonapi\/shortcut_set\/shortcut_set",
    "shortcut--default": "http:\/\/d8dev.local\/jsonapi\/shortcut\/default",
    "oauth2_token_type--oauth2_token_type": "http:\/\/d8dev.local\/jsonapi\/oauth2_token_type\/oauth2_token_type",
    "oauth2_token--access_token": "http:\/\/d8dev.local\/jsonapi\/oauth2_token\/access_token",
    "oauth2_token--auth_code": "http:\/\/d8dev.local\/jsonapi\/oauth2_token\/auth_code",
    "oauth2_token--refresh_token": "http:\/\/d8dev.local\/jsonapi\/oauth2_token\/refresh_token",
    "oauth2_client--oauth2_client": "http:\/\/d8dev.local\/jsonapi\/oauth2_client\/oauth2_client",
    "menu--menu": "http:\/\/d8dev.local\/jsonapi\/menu\/menu",
    "action--action": "http:\/\/d8dev.local\/jsonapi\/action\/action",
    "taxonomy_term--category": "http:\/\/d8dev.local\/jsonapi\/taxonomy_term\/category",
    "taxonomy_term--islands": "http:\/\/d8dev.local\/jsonapi\/taxonomy_term\/islands",
    "taxonomy_term--tags": "http:\/\/d8dev.local\/jsonapi\/taxonomy_term\/tags",
    "taxonomy_term--types": "http:\/\/d8dev.local\/jsonapi\/taxonomy_term\/types",
    "taxonomy_vocabulary--taxonomy_vocabulary": "http:\/\/d8dev.local\/jsonapi\/taxonomy_vocabulary\/taxonomy_vocabulary",
    "tour--tour": "http:\/\/d8dev.local\/jsonapi\/tour\/tour",
    "user_role--user_role": "http:\/\/d8dev.local\/jsonapi\/user_role\/user_role",
    "user--user": "http:\/\/d8dev.local\/jsonapi\/user\/user",
    "menu_link_content--menu_link_content": "http:\/\/d8dev.local\/jsonapi\/menu_link_content\/menu_link_content",
    "view--view": "http:\/\/d8dev.local\/jsonapi\/view\/view",
    "date_format--date_format": "http:\/\/d8dev.local\/jsonapi\/date_format\/date_format",
    "base_field_override--base_field_override": "http:\/\/d8dev.local\/jsonapi\/base_field_override\/base_field_override",
    "entity_view_mode--entity_view_mode": "http:\/\/d8dev.local\/jsonapi\/entity_view_mode\/entity_view_mode",
    "entity_form_mode--entity_form_mode": "http:\/\/d8dev.local\/jsonapi\/entity_form_mode\/entity_form_mode",
    "entity_view_display--entity_view_display": "http:\/\/d8dev.local\/jsonapi\/entity_view_display\/entity_view_display",
    "entity_form_display--entity_form_display": "http:\/\/d8dev.local\/jsonapi\/entity_form_display\/entity_form_display"
  }
}

It's noteworthy that this introduces a permission for the module to be able to see this. Not sure if this is the right approach.

e0ipso’s picture

Added some kernel tests.

Wim Leers’s picture

Does the JSON API spec provide any guidance for this? It seems like it should?

e0ipso’s picture

We could not find anything in http://jsonapi.org or http://discuss.jsonapi.org.

Wim Leers’s picture

Interesting. Is this because … the people behind JSON API are more "pragmatic" and the HATEOAS people are more "academic"?

Shouldn't we ensure we have a JSON API extension spec for every addition (extension) we add?

e0ipso’s picture

Status: Needs review » Needs work

The last submitted patch, 19: 2790935--entry-point--19.patch, failed testing.

  • e0ipso committed d88d305 on 8.x-1.x
    feat(Routing) Create an entry point to ensure true HATEOAS (#2790935 by...
hampercm’s picture

Status: Needs work » Needs review

I think this was a reasonable approach. +1 for the added permission, as some sites probably won't want this information broadcast.

I see a commit; can this be Closed (fixed) at this point?

Grimreaper’s picture

Status: Needs review » Needs work

Hello,

I am testing this feature.

To authenticate, I use the basic_auth module, when trying to authenticate I got the following 403 error:

The used authentication method is not allowed on this route.

I think it is because there is not the route options

$options = [
        '_auth' => $this->authProviderList(),
        '_is_jsonapi' => TRUE,
      ];

as on the other routes provided by jsonapi.

As $this->authProviderList() is a dynamic list of authentification method, is it possible to have a callback in the routing.yml file or should it go in \Drupal\jsonapi\Routing\Routes::routes or a special callback?

Wim Leers’s picture

#23 seems correct.

e0ipso’s picture

Thanks for the bug report!

@Grimreaper I'm OK with it being in the same class \Drupal\jsonapi\Routing\Routes but with a different method. Maybe \Drupal\jsonapi\Routing\Routes::entryPoint?

Grimreaper’s picture

Status: Needs work » Needs review
FileSize
2.02 KB

Hello,

Thanks for the suggestions.

Here is a patch to fix the authentification.

Do you want me to create a test for that? If yes, should it be a kernel or functional test?

Thanks for the review.

Wim Leers’s picture

Status: Needs review » Needs work

Thanks!

  1. +++ b/src/Routing/Routes.php
    @@ -75,6 +75,31 @@ class Routes implements ContainerInjectionInterface {
    +    $route_base_path = '/jsonapi';
    ...
    +    $route_collection = (new Route($route_base_path, $defaults))
    

    It's not a base path. It's just the route's path.

    This is used only once, so it's pointless to put it in a variable.

  2. +++ b/src/Routing/Routes.php
    @@ -75,6 +75,31 @@ class Routes implements ContainerInjectionInterface {
    +    $defaults = [
    +      RouteObjectInterface::CONTROLLER_NAME => '\Drupal\jsonapi\Controller\EntryPoint::index',
    +    ];
    +    // Options that apply to all routes.
    +    $options = [
    +      '_auth' => $this->authProviderList(),
    +      '_is_jsonapi' => TRUE,
    +    ];
    

    All of this is also used only once… so again pointless to put it in a variable first.

  • e0ipso committed 12484e5 on 8.x-1.x authored by Grimreaper
    fix(Routing) Alloy dynamic authentication provider list in entry point...
e0ipso’s picture

Status: Needs work » Fixed

I made the modifications Wim suggested, and committed.

Great work everyone!!

Grimreaper’s picture

Thanks all ! :)

Wim Leers’s picture

Yay!

Status: Fixed » Closed (fixed)

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

Wim Leers’s picture