Skip to content

fix: generalize search-filter validation across all search_* methods - #18

Merged
jschfflr merged 3 commits into
mainfrom
fix/search-filter-validation
Jul 10, 2026
Merged

fix: generalize search-filter validation across all search_* methods#18
jschfflr merged 3 commits into
mainfrom
fix/search-filter-validation

Conversation

@jschfflr

Copy link
Copy Markdown
Contributor

What

Extends the search_accounts filter-key guard (shipped in 0.4.0) to every search_* method. Apollo silently drops unrecognised filter keys and returns an unfiltered default page that looks like a real match (the bug that once attached the wrong company to a deal).

A shared _validate_search_filters(filters, allowed, resource, *, strict) helper backs all of them (accounts refactored onto it too).

Strict — raise on unknown (documented, stable flat-filter vocab):
search_contacts, search_deals, search_people (+ search_accounts).

Lenient — logging.warning, still sends (undocumented activity endpoints, where an over-tight allowlist would reject valid filters):
search_notes, search_calls, search_tasks, search_emails, search_conversations, search_calendar_events.

Per-endpoint allowlists come from Apollo's public API docs + verified usage.

Why strict-vs-lenient

The docs are incomplete (e.g. opportunities/search docs omit q_keywords, which works) and the 6 activity endpoints have no filter docs at all. Raising everywhere would trade silent-wrong for loud-but-wrong on those. So: raise where the vocab is known, warn where it isn't.

Testing

  • +11 unit tests (strict raise + valid-filter pass, lenient warn-but-send, known-filter no-warn, people page/per_page).
  • Live-verified: contacts/deals valid filters return correctly; unknown keys raise before any API call; notes/tasks bogus keys warn but still return.
  • ruff ✅ · pyright ✅ · 197 tests ✅

⚠️ Separate finding (not fixed here)

Live testing surfaced that search_people's endpoint /mixed_people/search is now deprecated for API callers (422 → use /mixed_people/api_search). That's an unrelated pre-existing bug; filed to fix in a follow-up MR. This PR's people validation layer is correct for the new endpoint's params.

No version bump (left for a release PR).

🤖 Generated with Claude Code

Generalizes the search_accounts filter-key guard to every search method.
Apollo silently drops unknown filter keys and returns an unfiltered default
page that looks like a real match.

- Strict (raise on unknown), documented flat-filter endpoints: contacts,
  deals, people (+ accounts, refactored onto the shared helper).
- Lenient (logging.warning, non-breaking), undocumented activity endpoints:
  notes, calls, tasks, emails, conversations, calendar_events — where the
  full valid filter set isn't published and a hard allowlist could reject
  valid filters.

Live-verified: valid filters pass; unknown keys raise (strict) or warn
(lenient) while still returning.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@peqy peqy 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.

Overall this is a solid hardening change: strict validation where the filter vocab is stable prevents Apollo’s “silent drop → unfiltered default page” footgun, and lenient mode for activity endpoints is a good trade-off.

Found 2 issues (0 critical, 1 improvement, 1 question).

unknown = set(filters) - allowed
if not unknown:
return
msg = (

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Improvement: In lenient mode the allowlists are explicitly incomplete, but the warning text says “Supported filters: …”, which reads like a definitive list.

Fix: consider wording like “Known filters” (or omit the list entirely for strict=False), and maybe keep the full supported-list only for strict ValueError to avoid very noisy log/error messages (esp. PEOPLE_SEARCH_FILTERS).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Yep — the lenient-mode wording (“Known filters”) reads correctly now. Thanks for addressing this.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Confirmed fixed in the latest commit: lenient mode now says “Known filters” (strict still “Supported filters”). This thread can be resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Still looks good on my side — thanks again for the “Known filters” wording tweak. Can resolve this thread.

Returns:
Search results dictionary
"""
_validate_search_filters(filters, PEOPLE_SEARCH_FILTERS, "people", strict=True)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Question: search_people validates against PEOPLE_SEARCH_FILTERS, but still posts to /mixed_people/search (not the new /mixed_people/api_search mentioned in the description).

Fix: is it worth adding a small TODO / docstring note here that the endpoint is deprecated (or switching in this PR if it’s low-risk)? Mainly to avoid readers assuming the PR already addressed the deprecation.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Still seeing /mixed_people/search here. If you’re intentionally keeping the endpoint switch for the follow-up MR, I’d consider a small TODO/comment (or link to the follow-up issue) so readers don’t assume this PR fixed the deprecation. Non-blocking for this change set.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is now posting to /mixed_people/api_search (instead of /mixed_people/search), matching the deprecation note in the MR description. 👍 This thread can be resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Re-checking: search_people is now using /mixed_people/api_search, so the deprecation concern is addressed. Can resolve this thread.

peqy: lenient allowlists are intentionally incomplete, so 'Supported filters'
over-claimed. Strict mode still says 'Supported filters'; lenient (warn) mode
now says 'Known filters' so the message doesn't imply the key is definitely
invalid. (The search_people endpoint deprecation peqy asked about is handled
by the separate people-api-search PR.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@peqy

peqy Bot commented Jul 10, 2026

Copy link
Copy Markdown

Looks good — the shared filter validation + strict-vs-lenient split is clear and the tests cover the new behavior well.

Question: search_people still posts to /mixed_people/search (deprecated per the MR description). If you’re intentionally deferring the endpoint change, consider adding a small TODO/link to the follow-up so readers don’t assume this PR already addressed it.

…idation

# Conflicts:
#	src/qodev_apollo_api/client.py
@jschfflr
jschfflr merged commit 0a6e695 into main Jul 10, 2026
4 checks passed
@peqy

peqy Bot commented Jul 10, 2026

Copy link
Copy Markdown

Looks good — the generalized _validate_search_filters helper + strict-vs-lenient split is clear, and the added tests cover the intended behaviors.

The two items I previously called out are now addressed:

  • lenient mode wording uses “Known filters”
  • search_people now uses /mixed_people/api_search (avoids the deprecated /mixed_people/search).

Ship it.

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