Skip to content

feat: preserve OR-group tokens in tags DTO#130

Open
AlejandroAkbal wants to merge 1 commit intomainfrom
auto-triage/2-feature-or-search-api
Open

feat: preserve OR-group tokens in tags DTO#130
AlejandroAkbal wants to merge 1 commit intomainfrom
auto-triage/2-feature-or-search-api

Conversation

@AlejandroAkbal
Copy link
Member

@AlejandroAkbal AlejandroAkbal commented Mar 14, 2026

Summary

API pass-through change for Feature: OR search (73 votes).

Enables OR-group tags — encoded as comma-separated alternatives (e.g. cat%2Cdog) — to pass through the tags DTO transform as single array elements, so the wrapper can translate them to engine-specific OR syntax.

How it works

The tags @Transform previously split on | to get individual tag strings. That split has been removed; each pipe-delimited segment in the URL is already a distinct tags query parameter value. The transform now only handles trimming and URL-decoding per element.

An OR group like cat,dog arrives as a single tags[] element and is forwarded intact to the wrapper, which emits { ~ cat ~ dog } for Gelbooru-family engines (or ~cat ~dog for e621/Danbooru-family).

Changes

  • src/booru/dto/booru-queries.dto.ts — remove .flatMap(split('|')), keep trim + URL-decode
  • src/booru/dto/booru-queries.dto.spec.ts — update OR-group tests to use comma encoding

Depends on

  • AlejandroAkbal/Universal-Booru-Wrapper#5 (engine OR syntax)

Part of

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Updated tag query parsing to preserve comma-delimited tag groups instead of splitting on pipe characters, improving accuracy and precision of tag-based searches.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 14, 2026

Walkthrough

The tag parsing logic in booru queries is simplified by removing pipe-character splitting. Tags are now normalized to trimmed strings without expanding on pipes, treating comma-delimited groups as single tokens. Test cases updated to reflect the new parsing behavior.

Changes

Cohort / File(s) Summary
Tag Parsing Implementation
src/booru/dto/booru-queries.dto.ts
Removed pipe-splitting logic; tags are now coerced to strings and trimmed without expanding on pipe delimiters.
Tag Parsing Tests
src/booru/dto/booru-queries.dto.spec.ts
Updated three test cases to reflect new parsing behavior: comma-delimited groups preserved as single tokens, validation rules changed to reject empty tags from repeated parameters.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: preserve OR-group tokens in tags DTO' accurately summarizes the main change: removing pipe-splitting logic to preserve comma-delimited OR-group tokens as single elements in the tags DTO.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch auto-triage/2-feature-or-search-api
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch auto-triage/2-feature-or-search-api
📝 Coding Plan
  • Generate coding plan for human review comments

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 and usage tips.

Stop splitting tags on | so that OR-group tokens (e.g. cat|dog) are
forwarded intact to the Universal Booru Wrapper, which applies the
correct engine-specific OR syntax per booru engine.

Pipelines the change through the full stack:
  App → |  (user types 'cat OR dog')
  API  → ['cat|dog']  (kept as OR-group token)
  Wrapper → '{  ~ cat  ~ dog }' (Gelbooru) / '~cat ~dog' (Danbooru/e621)
@AlejandroAkbal AlejandroAkbal force-pushed the auto-triage/2-feature-or-search-api branch from 29ca6e6 to ca0f45d Compare March 16, 2026 07:47
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In `@src/booru/dto/booru-queries.dto.spec.ts`:
- Around line 46-56: Add a new test in src/booru/dto/booru-queries.dto.spec.ts
that asserts pipe characters are not split when parsing tags: create an it()
case (e.g., "preserves pipe characters inside a single tag token") which sends a
GET to /dto-test/posts with a tags param containing an encoded pipe (e.g.,
tags=artist%3Afoo%7Cbar) and assert response.statusCode is 200 and body.tags
equals ['artist:foo|bar']; this will directly cover the regression for pipe
non-splitting and complement the existing "should normalize repeated tags..."
test.

In `@src/booru/dto/booru-queries.dto.ts`:
- Around line 189-191: The mapper currently coerces non-string tags via
String(tag) which masks bad inputs; update the transformation so non-strings are
left unchanged and only strings are trimmed. Concretely, replace the map that
does (typeof tag === 'string' ? tag : String(tag)) with logic that returns tag
as-is when typeof tag !== 'string', and only apply .trim() when typeof tag ===
'string' (e.g. map(tag => typeof tag === 'string' ? tag.trim() : tag)) so
`@IsString`({ each: true }) can properly reject non-string values.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 8cb6b83f-28cc-4a71-bb2d-822fc840b34e

📥 Commits

Reviewing files that changed from the base of the PR and between 8e8f2ac and ca0f45d.

📒 Files selected for processing (2)
  • src/booru/dto/booru-queries.dto.spec.ts
  • src/booru/dto/booru-queries.dto.ts

Comment on lines +46 to 56
it('should normalize repeated tags query params and keep each OR-group token intact', async () => {
const response = await app.inject({
method: 'GET',
url: '/dto-test/posts?baseEndpoint=gelbooru.com&tags=artist%3Afoo%7Crating%3Asafe&tags=score%3A%3E100'
url: '/dto-test/posts?baseEndpoint=gelbooru.com&tags=artist%3Afoo%2Crating%3Asafe&tags=score%3A%3E100'
})

const body = JSON.parse(response.body)

expect(response.statusCode).toBe(200)
expect(body.tags).toEqual(['artist:foo', 'rating:safe', 'score:>100'])
expect(body.tags).toEqual(['artist:foo,rating:safe', 'score:>100'])
})
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Add a direct regression test for pipe non-splitting

Given this PR removes pipe splitting, add one explicit case to assert | is preserved within a single tag token.

Suggested additional test
+  it('should not split tags containing pipe characters', async () => {
+    const response = await app.inject({
+      method: 'GET',
+      url: '/dto-test/posts?baseEndpoint=gelbooru.com&tags=artist%3Afoo%7Crating%3Asafe'
+    })
+
+    const body = JSON.parse(response.body)
+
+    expect(response.statusCode).toBe(200)
+    expect(body.tags).toEqual(['artist:foo|rating:safe'])
+  })
📝 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
it('should normalize repeated tags query params and keep each OR-group token intact', async () => {
const response = await app.inject({
method: 'GET',
url: '/dto-test/posts?baseEndpoint=gelbooru.com&tags=artist%3Afoo%7Crating%3Asafe&tags=score%3A%3E100'
url: '/dto-test/posts?baseEndpoint=gelbooru.com&tags=artist%3Afoo%2Crating%3Asafe&tags=score%3A%3E100'
})
const body = JSON.parse(response.body)
expect(response.statusCode).toBe(200)
expect(body.tags).toEqual(['artist:foo', 'rating:safe', 'score:>100'])
expect(body.tags).toEqual(['artist:foo,rating:safe', 'score:>100'])
})
it('should normalize repeated tags query params and keep each OR-group token intact', async () => {
const response = await app.inject({
method: 'GET',
url: '/dto-test/posts?baseEndpoint=gelbooru.com&tags=artist%3Afoo%2Crating%3Asafe&tags=score%3A%3E100'
})
const body = JSON.parse(response.body)
expect(response.statusCode).toBe(200)
expect(body.tags).toEqual(['artist:foo,rating:safe', 'score:>100'])
})
it('should not split tags containing pipe characters', async () => {
const response = await app.inject({
method: 'GET',
url: '/dto-test/posts?baseEndpoint=gelbooru.com&tags=artist%3Afoo%7Crating%3Asafe'
})
const body = JSON.parse(response.body)
expect(response.statusCode).toBe(200)
expect(body.tags).toEqual(['artist:foo|rating:safe'])
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/booru/dto/booru-queries.dto.spec.ts` around lines 46 - 56, Add a new test
in src/booru/dto/booru-queries.dto.spec.ts that asserts pipe characters are not
split when parsing tags: create an it() case (e.g., "preserves pipe characters
inside a single tag token") which sends a GET to /dto-test/posts with a tags
param containing an encoded pipe (e.g., tags=artist%3Afoo%7Cbar) and assert
response.statusCode is 200 and body.tags equals ['artist:foo|bar']; this will
directly cover the regression for pipe non-splitting and complement the existing
"should normalize repeated tags..." test.

Comment on lines +189 to +191
return (Array.isArray(value) ? value : [value])
.map((tag) => (typeof tag === 'string' ? tag : String(tag)))
.map((tag) => tag.trim())
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid coercing non-string tags before validation

Line 190 converts non-string values with String(tag), which can let malformed inputs pass validation as "[object Object]". Keep non-strings as-is so @IsString({ each: true }) can reject them.

Suggested fix
-    return (Array.isArray(value) ? value : [value])
-      .map((tag) => (typeof tag === 'string' ? tag : String(tag)))
-      .map((tag) => tag.trim())
+    return (Array.isArray(value) ? value : [value]).map((tag) =>
+      typeof tag === 'string' ? tag.trim() : tag
+    )
📝 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
return (Array.isArray(value) ? value : [value])
.map((tag) => (typeof tag === 'string' ? tag : String(tag)))
.map((tag) => tag.trim())
return (Array.isArray(value) ? value : [value]).map((tag) =>
typeof tag === 'string' ? tag.trim() : tag
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/booru/dto/booru-queries.dto.ts` around lines 189 - 191, The mapper
currently coerces non-string tags via String(tag) which masks bad inputs; update
the transformation so non-strings are left unchanged and only strings are
trimmed. Concretely, replace the map that does (typeof tag === 'string' ? tag :
String(tag)) with logic that returns tag as-is when typeof tag !== 'string', and
only apply .trim() when typeof tag === 'string' (e.g. map(tag => typeof tag ===
'string' ? tag.trim() : tag)) so `@IsString`({ each: true }) can properly reject
non-string values.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant