shopware / shopware

Shopware 6 is an open commerce platform based on Symfony Framework and Vue and supported by a worldwide community and more than 1.500 community extensions
https://shopware.com
MIT License
2.8k stars 1.02k forks source link

cannot filter search API for empty custom fields #3925

Open dallyger opened 2 months ago

dallyger commented 2 months ago

PHP Version

8.3.10

Shopware Version

6.6.5.1

Affected area / extension

Platform(Default)

Expected behaviour

Get a result containing all entities, that match the criteria, when searching for custom fields with empty values like null or empty string. Criteria Examples:

POST /api/search/newsletter-recipient
Authorization: Bearer {{bearer_token}}
Accept: application/json

{
    "filter": [
        {
            "type": "equals",
            "field": "customFields.foo_bar",
            "value": ""
        }
    ]
}
POST /api/search/newsletter-recipient
Authorization: Bearer {{bearer_token}}
Accept: application/json

{
    "filter": [
        {
            "type": "equalsAny",
            "field": "customFields.foo_bar",
            "value": [null]
        }
    ]
}

Actual behaviour

The equalsAny filter searching for [null] returns an empty result. The equalsAny filter searching for [""] works as expected (result found). The equals filter searching for "" throws an exception (see below). The equals filter searching for null works as expected.

The error message for equals:

{
  "errors": [
    {
      "status": "400",
      "code": "FRAMEWORK__INVALID_FILTER_QUERY",
      "title": "Bad Request",
      "detail": "Parameter \"value\" for equals filter is missing.",
      "source": {
        "pointer": "/filter/0/value"
      },
      "meta": {
        "parameters": {
          "path": "/filter/0/value"
        }
      },
      "trace": "#0 /var/www/html/vendor/shopware/core/Framework/DataAbstractionLayer/Search/Parser/QueryStringParser.php(58): Shopware\\Core\\Framework\\DataAbstractionLayer\\DataAbstractionLayerException::invalidFilterQuery()\n#1 /var/www/html/vendor/shopware/core/Framework/DataAbstractionLayer/Search/RequestCriteriaBuilder.php(401): Shopware\\Core\\Framework\\DataAbstractionLayer\\Search\\Parser\\QueryStringParser::fromArray()\n#2 /var/www/html/vendor/shopware/core/Framework/DataAbstractionLayer/Search/RequestCriteriaBuilder.php(134): Shopware\\Core\\Framework\\DataAbstractionLayer\\Search\\RequestCriteriaBuilder->addFilter()\n#3 /var/www/html/vendor/shopware/core/Framework/DataAbstractionLayer/Search/RequestCriteriaBuilder.php(73): Shopware\\Core\\Framework\\DataAbstractionLayer\\Search\\RequestCriteriaBuilder->parse()\n#4 /var/www/html/vendor/shopware/core/Framework/DataAbstractionLayer/Search/RequestCriteriaBuilder.php(54): Shopware\\Core\\Framework\\DataAbstractionLayer\\Search\\RequestCriteriaBuilder->fromArray()\n#5 /var/www/html/vendor/shopware/core/Framework/Api/Controller/ApiController.php(426): Shopware\\Core\\Framework\\DataAbstractionLayer\\Search\\RequestCriteriaBuilder->handleRequest()\n#6 /var/www/html/vendor/shopware/core/Framework/Api/Controller/ApiController.php(258): Shopware\\Core\\Framework\\Api\\Controller\\ApiController->resolveSearch()\n#7 /var/www/html/vendor/symfony/http-kernel/HttpKernel.php(183): Shopware\\Core\\Framework\\Api\\Controller\\ApiController->search()\n#8 /var/www/html/vendor/symfony/http-kernel/HttpKernel.php(76): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#9 /var/www/html/vendor/shopware/core/Framework/Adapter/Kernel/HttpKernel.php(58): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#10 /var/www/html/vendor/symfony/http-kernel/HttpCache/SubRequestHandler.php(86): Shopware\\Core\\Framework\\Adapter\\Kernel\\HttpKernel->handle()\n#11 /var/www/html/vendor/symfony/http-kernel/HttpCache/HttpCache.php(458): Symfony\\Component\\HttpKernel\\HttpCache\\SubRequestHandler::handle()\n#12 /var/www/html/vendor/symfony/http-kernel/HttpCache/HttpCache.php(260): Symfony\\Component\\HttpKernel\\HttpCache\\HttpCache->forward()\n#13 /var/www/html/vendor/symfony/http-kernel/HttpCache/HttpCache.php(274): Symfony\\Component\\HttpKernel\\HttpCache\\HttpCache->pass()\n#14 /var/www/html/vendor/symfony/http-kernel/HttpCache/HttpCache.php(202): Symfony\\Component\\HttpKernel\\HttpCache\\HttpCache->invalidate()\n#15 /var/www/html/vendor/shopware/core/Framework/Adapter/Kernel/HttpCacheKernel.php(65): Symfony\\Component\\HttpKernel\\HttpCache\\HttpCache->handle()\n#16 /var/www/html/vendor/shopware/core/Kernel.php(117): Shopware\\Core\\Framework\\Adapter\\Kernel\\HttpCacheKernel->handle()\n#17 /var/www/html/vendor/symfony/runtime/Runner/Symfony/HttpKernelRunner.php(35): Shopware\\Core\\Kernel->handle()\n#18 /var/www/html/vendor/autoload_runtime.php(29): Symfony\\Component\\Runtime\\Runner\\Symfony\\HttpKernelRunner->run()\n#19 /var/www/html/public/index.php(11): require_once('...')\n#20 {main}"
    }
  ]
}

How to reproduce

  1. Setup a shop.
  2. Create a newsletter recipient
  3. POST the /api/search/newsletter-recipient endpoint with the mentioned payloads.

Below I've added some Hurl files to recreate those requests.

Example: equals empty-string

# Authenticate
POST {{base_url}}/api/oauth/token
{
    "grant_type": "client_credentials",
    "client_id":  "{{client_id}}",
    "client_secret": "{{client_secret}}"
}

HTTP 200
[Captures]
bearer_token: jsonpath "$.access_token"

# Fetch a newsletter-recipient to update
POST {{base_url}}/api/search-ids/newsletter-recipient
Authorization: Bearer {{bearer_token}}
Accept: application/json

HTTP 200
[Captures]
entity_id: jsonpath "$.data[0]"

# Set custom field on newsletter-recipient to an empty string
PATCH {{base_url}}/api/newsletter-recipient/{{entity_id}}
Authorization: Bearer {{bearer_token}}
Accept: application/json

{
    "customFields": {
        "foo_bar": ""
    }
}

HTTP 204

# Try to filter newsletter-recipient based on the previously set value
POST {{base_url}}/api/search/newsletter-recipient
Authorization: Bearer {{bearer_token}}
Accept: application/json

{
    "filter": [
        {
            "type": "equals",
            "field": "customFields.foo_bar",
            "value": ""
        }
    ],
    "limit": 4
}

Example: equals-any using null

# Authenticate
POST {{base_url}}/api/oauth/token
{
    "grant_type": "client_credentials",
    "client_id":  "{{client_id}}",
    "client_secret": "{{client_secret}}"
}

HTTP 200
[Captures]
bearer_token: jsonpath "$.access_token"

# Fetch a newsletter-recipient to update
POST {{base_url}}/api/search-ids/newsletter-recipient
Authorization: Bearer {{bearer_token}}
Accept: application/json

HTTP 200
[Captures]
entity_id: jsonpath "$.data[0]"

# Set custom field on newsletter-recipient to an empty string
PATCH {{base_url}}/api/newsletter-recipient/{{entity_id}}
Authorization: Bearer {{bearer_token}}
Accept: application/json

{
    "customFields": {
        "foo_bar": null
    }
}

HTTP 204

# Try to filter newsletter-recipient based on the previously set value
POST {{base_url}}/api/search/newsletter-recipient
Authorization: Bearer {{bearer_token}}
Accept: application/json

{
    "filter": [
        {
            "type": "equalsAny",
            "field": "customFields.foo_bar",
            "value": [null]
        }
    ],
    "limit": 4
}
shopware-issue-bot[bot] commented 2 months ago

We found the following existing issues which may help or are related to your topic:

shopwareBot commented 2 months ago

[public] Automated response: This issue is linked to the internal issue https://shopware.atlassian.net/browse/NEXT-37680.

[created from NEXT-37680, comment 484475]