Skip to content

Support filter conditions in dynamic search rules - #936

Draft
kangazhan wants to merge 1 commit into
meilisearch:mainfrom
kangazhan:agent/add-dsr-filter-condition
Draft

Support filter conditions in dynamic search rules#936
kangazhan wants to merge 1 commit into
meilisearch:mainfrom
kangazhan:agent/add-dsr-filter-condition

Conversation

@kangazhan

@kangazhan kangazhan commented Jul 28, 2026

Copy link
Copy Markdown

Summary

  • add the v1.51 filter activation condition to the dynamic search rule PHPStan shape
  • expose lastUpdatedAt as a nullable DateTimeImmutable
  • keep compatibility with responses from older Meilisearch versions where the field is absent
  • cover filter request serialization and nine-digit fractional timestamps

Fixes #935

Validation

  • docker compose run --no-deps --rm package ./vendor/bin/phpunit tests/Contracts (170 tests, 238 assertions)
  • targeted PHP CS Fixer dry-run on the three changed PHP files
  • targeted PHPStan analysis on the three changed PHP files (no errors)
  • manual Meilisearch v1.51 API smoke test confirmed filter values round-trip and lastUpdatedAt appears in both single-rule and list responses

I also attempted the full local suite in the repository Docker environment. It exceeded Composer's 300-second process timeout on the Windows bind mount before completion, so I am not representing that run as a pass; the focused contract suite above completed successfully.

AI disclosure

OpenAI Codex was used to draft the PHPDoc shape and tests. I reviewed the diff, checked it against the v1.51 server implementation, and verified it with the checks above.

Summary by CodeRabbit

  • New Features

    • Added support for filter conditions with configurable values in dynamic search rules.
    • Added access to a rule’s optional last-updated timestamp.
    • Older responses without a timestamp remain supported.
  • Bug Fixes

    • Improved handling and formatting of dynamic search rule update payloads.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bf17e6b0-f7d8-4276-8488-d3ee046f50bc

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot added the enhancement New feature or request label Jul 28, 2026
@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.87%. Comparing base (0078a8c) to head (8cb9a7b).
⚠️ Report is 189 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #936      +/-   ##
==========================================
- Coverage   89.78%   88.87%   -0.91%     
==========================================
  Files          59       93      +34     
  Lines        1449     1996     +547     
==========================================
+ Hits         1301     1774     +473     
- Misses        148      222      +74     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/Contracts/DynamicSearchRule.php`:
- Around line 28-30: Define a named FilterValue PHPStan type alias in
DynamicSearchRule.php covering the supported scalar JSON values, then update the
FilterCondition.values annotation to use array<string, FilterValue> instead of
array<string, mixed>. Keep toArray() and fromArray() aligned with the narrowed
alias.
- Around line 36-39: Update the RawDynamicSearchRule PHPStan shape so
lastUpdatedAt accepts an explicit null in addition to a non-empty string and
omission, matching the server model’s optional timestamp representation. Add or
update the relevant fixture to cover a payload where lastUpdatedAt is explicitly
null.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c61f953e-0487-4bf9-a62e-c7af61b6ae4b

📥 Commits

Reviewing files that changed from the base of the PR and between de49a38 and 8cb9a7b.

📒 Files selected for processing (3)
  • src/Contracts/DynamicSearchRule.php
  • tests/Contracts/DynamicSearchRuleTest.php
  • tests/Contracts/UpdateDynamicSearchRuleQueryTest.php

Comment on lines +28 to +30
* @phpstan-type FilterCondition array{
* values: array<string, mixed>
* }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== repo files of interest =="
git ls-files | rg '(^|/)DynamicSearchRule\.php$|composer\.(json|lock)$|phpstan' || true

echo
echo "== DynamicSearchRule outline/contents =="
if [ -f src/Contracts/DynamicSearchRule.php ]; then
  wc -l src/Contracts/DynamicSearchRule.php
  sed -n '1,220p' src/Contracts/DynamicSearchRule.php | cat -n
fi

echo
echo "== search for FilterCondition and values usages =="
rg -n "FilterCondition|FilterValue|values:\s*array<string,\s*mixed>|dynamic_search|dynamicSearch|toArray" -S .

echo
echo "== composer PHPStan config =="
if [ -f composer.json ]; then
  sed -n '1,220p' composer.json | cat -n
fi
if [ -f phpstan.neon ] || [ -f phpstan.neon.dist ] || [ -f phpstan.neon.baseline ] || [ -f phpstan-baseline.neon ]; then
  rg -n "filterCondition|FilterCondition|array<string, mixed>|strict_types|defineArrayShape|type" phpstan.neon* composer.json 2>/dev/null || true
fi

Repository: meilisearch/meilisearch-php

Length of output: 30500


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== DynamicSearchRulesFilter =="
if [ -f src/Contracts/DynamicSearchRulesFilter.php ]; then
  wc -l src/Contracts/DynamicSearchRulesFilter.php
  cat -n src/Contracts/DynamicSearchRulesFilter.php
fi

echo
echo "== UpdateDynamicSearchRuleQuery dynamic search filter area =="
sed -n '80,150p' src/Contracts/UpdateDynamicSearchRuleQuery.php | cat -n

echo
echo "== DynamicSearchRules tests relevant area =="
sed -n '120,150p' tests/Endpoints/DynamicSearchRulesTest.php | cat -n

echo
echo "== Fetch Meilisearch v1.51 dynamic_search_rules.rs relevant snippets =="
python3 - <<'PY'
import urllib.request, re
url = "https://raw.githubusercontent.com/meilisearch/meilisearch/v1.51.0/crates/meilisearch-types/src/dynamic_search_rules.rs"
try:
    text = urllib.request.urlopen(url, timeout=15).read().decode()
except Exception as e:
    print(f"FETCH_ERROR: {type(e).__name__}: {e}")
    raise SystemExit(0)
patterns = [
    r"enum\s+DynamicSearchFilter[A-Za-z0-9_]*\b.*?(?:^$)",
    r"struct\s+.*?DynamicSearch[A-Za-z0-9_]*\b.*?(?:^$)",
    r"type\s+Dynamic[A-Za-z0-9_]*Value\b.*?(?:^$)",
]
print("URL:", url)
for pat in patterns:
    print(f"\n-- matches for {pat[:80]} --")
    matches = re.findall(pat, text, flags=re.M|re.S)
    print("count", len(matches))
    for m in matches[:10]:
        print(m.replace("\n\n", "\n")[:1200])
PY

Repository: meilisearch/meilisearch-php

Length of output: 4344


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== PHPStan config =="
cat -n phpstan.dist.neon

echo
echo "== Static parse-ish evidence for FilterCondition shape and toArray doc =="
python3 - <<'PY'
from pathlib import Path
p = Path("src/Contracts/DynamicSearchRule.php")
text = p.read_text()
print("FilterCondition has array<string, mixed>:", "values: array<string, mixed>" in text)
print("FilterValue alias present:", "`@phpstan-type` FilterValue" in text)
print("toArray returns RawDynamicSearchRule:", "`@return` RawDynamicSearchRule" in text and "return $this->raw;" in text)
PY

Repository: meilisearch/meilisearch-php

Length of output: 852


Replace array<string, mixed> with a bounded filter-value type.

FilterCondition.values currently widens PHPStan analysis. Since dynamic search filter entries can be scalar JSON values, add a named FilterValue alias and use it here so toArray()/fromArray() stay precisely typed under the src/Contracts/**/*.php guideline.

Proposed shape
+ * `@phpstan-type` FilterValue scalar|array<array-key, mixed>|null
  * `@phpstan-type` FilterCondition array{
- *     values: array<string, mixed>
+ *     values: array<string, FilterValue>
  * }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
* @phpstan-type FilterCondition array{
* values: array<string, mixed>
* }
* `@phpstan-type` FilterValue scalar|array<array-key, mixed>|null
* `@phpstan-type` FilterCondition array{
* values: array<string, FilterValue>
* }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Contracts/DynamicSearchRule.php` around lines 28 - 30, Define a named
FilterValue PHPStan type alias in DynamicSearchRule.php covering the supported
scalar JSON values, then update the FilterCondition.values annotation to use
array<string, FilterValue> instead of array<string, mixed>. Keep toArray() and
fromArray() aligned with the narrowed alias.

Sources: Coding guidelines, MCP tools

Comment on lines 36 to +39
* @phpstan-type RawDynamicSearchRule array{
* uid: non-empty-string,
* description?: string|null,
* lastUpdatedAt?: non-empty-string,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Locate target and related files =="
fd -a 'DynamicSearchRule\.php$|dynamic.*search.*rule|search_rules' . | sed 's#^\./##'

echo
echo "== Inspect DynamicSearchRule target =="
if [ -f src/Contracts/DynamicSearchRule.php ]; then
  wc -l src/Contracts/DynamicSearchRule.php
  cat -n src/Contracts/DynamicSearchRule.php
fi

echo
echo "== Search for fixture/usages =="
rg -n "lastUpdatedAt|DynamicSearchRule|RawDynamicSearchRule" tests src -S

echo
echo "== Composer/phpstan references =="
rg -n '"phpstan/phpstan"|phpstan' composer.json composer.lock || true

Repository: meilisearch/meilisearch-php

Length of output: 17060


🌐 Web query:

meilisearch v1.51 dynamic_search_rules.rs lastUpdatedAt skip_serializing_if

💡 Result:

In Meilisearch, the file crates/meilisearch-types/src/dynamic_search_rules.rs (and associated modules in milli) manages dynamic search rules [1][2][3]. The serde attribute skip_serializing_if = "Option::is_none" is a standard Rust Serde pattern used throughout these structs to ensure that optional fields (like priority or certain query conditions) are omitted from the serialized JSON output when they contain no value [4][5]. Regarding your query: 1. File Context: The dynamic_search_rules.rs file defines the schema for dynamic search rules, which allow you to influence search results based on specific conditions and actions [4][5]. 2. Serializing Behavior: The use of #[serde(default, skip_serializing_if = "Option::is_none")] on fields like priority is intended to keep the API response payloads clean by excluding null fields [4][5]. 3. Regarding lastUpdatedAt: There is no field named lastUpdatedAt inherently present in the dynamic_search_rules.rs source code in the Meilisearch repository [4][2][3]. If you are encountering this field in relation to Meilisearch, it is likely part of a custom implementation, a third-party plugin (such as those used for Jellyfin or other integrations), or a different data model that may be mistaken for a core Meilisearch dynamic search rule field [6][7][8]. If you are seeing errors or unexpected behavior involving lastUpdatedAt and Meilisearch, you may be looking at an external API response or a client-side wrapper that adds metadata fields not present in the native Meilisearch dynamic search rule schema [7].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Fetch exact v1.51 dynamic_search_rules.rs =="
curl -fsSL https://raw.githubusercontent.com/meilisearch/meilisearch/v1.51.0/crates/meilisearch-types/src/dynamic_search_rules.rs \
  | sed -n '250,330p'

echo
echo "== Fetch raw line context for lastUpdatedAt =="
curl -fsSL https://raw.githubusercontent.com/meilisearch/meilisearch/v1.51.0/crates/meilisearch-types/src/dynamic_search_rules.rs \
  | rg -n "lastUpdatedAt|lastUpdated|updatedAt|skip_serializing_if|serde\\(.*default" || true

Repository: meilisearch/meilisearch-php

Length of output: 6246


🌐 Web query:

github.com/meilisearch/meilisearch v1.51 dynamic_search_rules.rs lastUpdatedAt

💡 Result:

The file dynamic_search_rules.rs is a component of the Meilisearch codebase that manages the definition, structure, and processing of dynamic search rules [1][2][3]. While there are multiple instances of files named dynamic_search_rules.rs within the repository (notably in crates/index-scheduler/src/, crates/meilisearch-types/src/, and crates/milli/src/ [3][4]), none of the public or standard definitions for the dynamic search rule structures in these files include a field explicitly named lastUpdatedAt [1][2]. The Meilisearch dynamic search rules feature underwent significant architectural changes around the v1.50.0 release [5][6]. The rules now handle fields such as precedence, active, conditions, and actions [1][2]. Changes to dynamic search rules are processed asynchronously via tasks, and responses for operations like PATCH or DELETE now return the associated task rather than the rule object itself [5][6]. If you are encountering a reference to lastUpdatedAt, it is likely part of an internal state management system, a specific database schema representation, or a custom implementation detail not present in the standard public API or primary rule definitions [1][7]. {confidence: 0.9}

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Fetch upstream structs around DynamicSearchRule definitions =="
curl -fsSL https://raw.githubusercontent.com/meilisearch/meilisearch/v1.51.0/crates/meilisearch-types/src/dynamic_search_rules.rs \
  | sed -n '1,140p'

echo
echo "== Fetch all lastUpdatedAt occurrences in v1.51 source =="
curl -fsSL https://raw.githubusercontent.com/meilisearch/meilisearch/v1.51.0/crates/meilisearch-types/src/dynamic_search_rules.rs \
  | rg -n "lastUpdatedAt|lastUpdatedAt|lastUpdated" || true

echo
echo "== Check if v1.51 is latest or contains newer dynamic search rules in another file =="
curl -fsSL https://api.github.com/repos/meilisearch/meilisearch/git/matches/data?sha=v1.51.0&path=crates/meilisearch-types/src/dynamic_search_rules.rs \
  | jq -r '.patch_url // empty' 2>/dev/null || true
git ls-data --tree v1.51.0 crates/meilisearch-types/src/dynamic_search_rules.rs || true
git ls-tree -r v1.51.0 --name-only crates/meilisearch-types/src/ | rg dynamic_search_rules || true

Repository: meilisearch/meilisearch-php

Length of output: 6134


Allow explicit null in RawDynamicSearchRule::lastUpdatedAt.

In the server model, lastUpdatedAt is Option<OffsetDateTime> with time::serde::rfc3339::option; this represents explicit null as well as an omitted field. Update the PHPStan shape and add an explicit-null fixture:

- *     lastUpdatedAt?: non-empty-string,
+ *     lastUpdatedAt?: non-empty-string|null,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
* @phpstan-type RawDynamicSearchRule array{
* uid: non-empty-string,
* description?: string|null,
* lastUpdatedAt?: non-empty-string,
* `@phpstan-type` RawDynamicSearchRule array{
* uid: non-empty-string,
* description?: string|null,
* lastUpdatedAt?: non-empty-string|null,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Contracts/DynamicSearchRule.php` around lines 36 - 39, Update the
RawDynamicSearchRule PHPStan shape so lastUpdatedAt accepts an explicit null in
addition to a non-empty string and omission, matching the server model’s
optional timestamp representation. Add or update the relevant fixture to cover a
payload where lastUpdatedAt is explicitly null.

Source: MCP tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Meilisearch v1.51.0] Add filter condition to Dynamic Search Rules

1 participant