Skip to content

Route GraphQL resolver throws GraphQlNoSuchEntityException instead of returning null for orphaned URL rewrites #40604

@damienwebdev

Description

@damienwebdev

Preconditions and environment

2.4.7-p9 (also affects earlier 2.4.7 releases) and 2.4-develop

Steps to reproduce

  1. Ensure a URL rewrite record exists in the url_rewrite table where:

    • entity_id = 0 and redirect_type = 0, OR
    • The target_path matches a URL that, when followed through the rewrite chain, resolves to entity_id = 0

    For example, a record like:

    request_path: "some-request-path"
    target_path:  "some-route.html"
    entity_id:    0
    redirect_type: 301
    store_id:     1
    

    where some-route.html does not exist as a request_path on store 1.

  2. Query the GraphQL route resolver:

    {
      route(url: "some-route.html") {
        type
        relative_url
        redirect_code
      }
    }

Expected result

The resolver returns null for the route, consistent with how completely unknown URLs are handled:

{
  "data": {
    "route": null
  }
}

Actual result

The resolver throws a GraphQlNoSuchEntityException:

{
  "errors": [
    {
      "message": "No such entity found with matching URL key: 10dozen.html",
      "locations": [{ "line": 1, "column": 3 }],
      "path": ["route"],
      "extensions": {
        "category": "graphql-no-such-entity"
      }
    }
  ],
  "data": {
    "route": null
  }
}

Additional information

In Magento\UrlRewriteGraphQl\Model\Resolver\AbstractEntityUrl::resolve(), lines 86-90:

if ($redirectType === 0 && !$entityId) {
    throw new GraphQlNoSuchEntityException(
        __('No such entity found with matching URL key: %url', ['url' => $url])
    );
}

This exception propagates up through Route::resolve() uncaught. The Route resolver only catches NoSuchEntityException and GraphQlNoSuchEntityException from $this->entityDataProviderComposite->getData(), but not from the parent::resolve() call which invokes AbstractEntityUrl::resolve().

// Route.php - the parent::resolve() call is NOT wrapped in try-catch
$resultArray = parent::resolve($field, $context, $info, $value, $args);

Release note

This inconsistency between "not found" (returns null) and "orphaned rewrite" (throws exception) causes problems for GraphQl consumers. For example, Apollo Client treats GraphQL errors differently from null data responses, which can cause unexpected error pages instead of proper 404 handling.

In our production store, we identified 118 URLs affected by this — all orphaned URL rewrites left over from a platform migration.


Potential Fix:

Wrap the parent::resolve() call in Route::resolve() with the same try-catch pattern already used for entityDataProviderComposite->getData():

try {
    $resultArray = parent::resolve(
        $field,
        $context,
        $info,
        $value,
        $args
    );
} catch (NoSuchEntityException | GraphQlNoSuchEntityException $th) {
    return null;
}

Alternatively, the throw in AbstractEntityUrl::resolve() could be replaced with return null to handle this consistently at the source, but that may affect other resolvers that extend AbstractEntityUrl.

Triage and priority

  • Severity: S0 - Affects critical data or functionality and leaves users without workaround.
  • Severity: S1 - Affects critical data or functionality and forces users to employ a workaround.
  • Severity: S2 - Affects non-critical data or functionality and forces users to employ a workaround.
  • Severity: S3 - Affects non-critical data or functionality and does not force users to employ a workaround.
  • Severity: S4 - Affects aesthetics, professional look and feel, “quality” or “usability”.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    Ready for Confirmation

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions