Skip to content

Fix validation error related to versioning#686

Open
vprashrex wants to merge 3 commits intomainfrom
hotfix/validation-error
Open

Fix validation error related to versioning#686
vprashrex wants to merge 3 commits intomainfrom
hotfix/validation-error

Conversation

@vprashrex
Copy link
Collaborator

@vprashrex vprashrex commented Mar 18, 2026

Summary

Target issue is #688
Explain the motivation for making this change. What existing problem does the pull request solve?

The Version Create API raises validation errors as str(e) instead of e.errors(), causing the exception handler to return unstructured raw error responses instead of properly formatted ones.
like this below:

{
    "success": false,
    "data": null,
    "error": "Invalid config after merge: 2 validation errors for ConfigBlob\ncompletion.NativeCompletionConfig.provider\n  Input should be 'openai-native', 'google-native', 'sarvamai-native' or 'elevenlabs-native' [type=literal_error, input_value='something', input_type=str]\n    For further information visit https://errors.pydantic.dev/2.12/v/literal_error\ncompletion.function-after[validate_params(), KaapiCompletionConfig].provider\n  Input should be 'openai', 'google', 'sarvamai' or 'elevenlabs' [type=literal_error, input_value='something', input_type=str]\n    For further information visit https://errors.pydantic.dev/2.12/v/literal_error",
    "errors": null,
    "metadata": null
}

Now it raises the error like this after fix

{
    "success": false,
    "data": null,
    "error": "Validation failed",
    "errors": [
        {
            "field": "completion.provider",
            "message": "Input should be 'openai-native', 'google-native', 'sarvamai-native' or 'elevenlabs-native'"
        },
        {
            "field": "completion.provider",
            "message": "Input should be 'openai', 'google', 'sarvamai' or 'elevenlabs'"
        }
    ],
    "metadata": null
}

Checklist

Before submitting a pull request, please ensure that you mark these task.

  • Ran fastapi run --reload app/main.py or docker compose up in the repository root and test.
  • If you've fixed a bug or added code that is tested and has test cases.

Notes

Please add here if any other information is required for the reviewer.

Summary by CodeRabbit

  • Bug Fixes
    • Validation errors in HTTP responses are now sanitized and compressed for clearer, more concise error messages when multiple details are present.
    • Configuration validation failures now return structured validation details (including failure counts and failing fields) to make diagnosis and resolution easier.

@coderabbitai
Copy link

coderabbitai bot commented Mar 18, 2026

📝 Walkthrough

Walkthrough

Renames and refactors a validation-error helper to _sanitize_validation_errors, compresses pydantic union-branch errors to keep loc, msg, type, and applies sanitization to both validation and HTTP exception responses; also changes a version CRUD handler to raise HTTPException with raw e.errors() and updates tests.

Changes

Cohort / File(s) Summary
Exception handling core
backend/app/core/exception_handlers.py
Renamed _filter_union_branch_errors_sanitize_validation_errors; reworked sanitization to compress/filter pydantic union-branch errors (retain loc, msg, type, strip internals); validation and HTTP exception handlers now call the sanitizer when detail is a list.
CRUD version error handling
backend/app/crud/config/version.py
create_or_raise now logs validation error count and failed fields and raises HTTPException with detail=e.errors() (raw pydantic errors) instead of a static string.
Tests updated
backend/app/tests/core/test_exception_handlers.py
Renamed test class and call sites to reference _sanitize_validation_errors; assertions updated to match new name and sanitization behavior; test scenarios preserved.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I nibbled through errors, tidy and neat,
Snipped union branches down to a beat,
Loc, msg, type — the trio I keep,
Stripped the clutter, now logs sleep deep,
Hopped off pleased, with a carrot treat. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.08% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix validation error related to versioning' directly and clearly summarizes the main change: fixing how validation errors are handled in the version creation API, making them return structured errors instead of stringified exceptions.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch hotfix/validation-error
📝 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.

@vprashrex vprashrex self-assigned this Mar 18, 2026
@vprashrex vprashrex added the bug Something isn't working label Mar 18, 2026
Copy link

@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: 1

🧹 Nitpick comments (1)
backend/app/crud/config/version.py (1)

67-69: Use the current function name in the log prefix.

Line 68 logs [ConfigVersionCrud.create_from_partial] while the method is ConfigVersionCrud.create_or_raise. This makes log filtering inconsistent.

As per coding guidelines, Prefix all log messages with the function name in square brackets: logger.info(f"[function_name] Message {mask_string(sensitive_value)}").

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/crud/config/version.py` around lines 67 - 69, The log prefix is
using the wrong method name; update the logger.error call inside
ConfigVersionCrud.create_or_raise to use "[ConfigVersionCrud.create_or_raise]"
instead of "[ConfigVersionCrud.create_from_partial]" so log filtering is
consistent (locate the logger.error call in the create_or_raise method and
adjust the f-string prefix accordingly; preserve the existing message and
interpolation).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/app/crud/config/version.py`:
- Line 71: The function create_or_raise currently logs with the wrong prefix and
returns raw Pydantic errors (e.errors()), risking sensitive data leakage; update
the log message prefix from "[ConfigVersionCrud.create_from_partial]" to
"[ConfigVersionCrud.create_or_raise]" and replace the direct e.errors() response
with a sanitized list that extracts only non-sensitive fields (for each error
keep loc, msg, and type and omit input, ctx, url, etc.), then raise
HTTPException(status_code=400, detail=sanitized_errors). Ensure you reference
the exception variable e and the create_or_raise function when making these
changes.

---

Nitpick comments:
In `@backend/app/crud/config/version.py`:
- Around line 67-69: The log prefix is using the wrong method name; update the
logger.error call inside ConfigVersionCrud.create_or_raise to use
"[ConfigVersionCrud.create_or_raise]" instead of
"[ConfigVersionCrud.create_from_partial]" so log filtering is consistent (locate
the logger.error call in the create_or_raise method and adjust the f-string
prefix accordingly; preserve the existing message and interpolation).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dca19b01-227a-4790-8e9b-823351221a0e

📥 Commits

Reviewing files that changed from the base of the PR and between 824cd26 and 35c95dc.

📒 Files selected for processing (2)
  • backend/app/core/exception_handlers.py
  • backend/app/crud/config/version.py

status_code=400,
detail=f"Invalid config after merge: {str(e)}",
)
raise HTTPException(status_code=400, detail=e.errors())
Copy link

@coderabbitai coderabbitai bot Mar 18, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Pydantic v2, what fields are included by default in ValidationError.errors(), and how do include_input/include_context/include_url flags change the output?

💡 Result:

In Pydantic v2, ValidationError.errors returns a list of ErrorDetails dictionaries. By default (include_input=True, include_context=True, include_url=True), each error includes these fields when available: - 'type': computer-readable error type identifier (always) - 'loc': location tuple (always) - 'msg': human-readable message (always) - 'input': the invalid input value (default True) - 'ctx': validation context (e.g., constraints like gt=42; default True) - 'url': documentation URL (default True) The flags control inclusion as follows: - include_input=False: omits 'input' from each error - include_context=False: omits 'ctx' (called 'context' in some docs) - include_url=False: omits 'url' from each error Example from docs: [ { 'type': 'missing', 'loc': ('is_required',), 'msg': 'Field required', 'input': {...}, # omitted if include_input=False 'url': 'https://errors.pydantic.dev/2/v/missing' # omitted if include_url=False }, { 'type': 'greater_than', ... 'ctx': {'gt': 42}, # omitted if include_context=False ... } ]

Citations:


🏁 Script executed:

# Read the file context around lines 67-71
cat -n backend/app/crud/config/version.py | sed -n '65,75p'

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 603


🏁 Script executed:

# Find and check requirements files for Pydantic version
find . -name "requirements*.txt" -o -name "pyproject.toml" -o -name "setup.py" | head -20

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 99


🏁 Script executed:

# Check the Python version specified in the project
git ls-files | grep -E "(setup.py|pyproject.toml|.python-version)" | head -10

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 164


🏁 Script executed:

# Read pyproject.toml to check Pydantic version
cat backend/pyproject.toml | grep -A 50 "dependencies"

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 2487


🏁 Script executed:

# Find the function containing line 71
sed -n '50,72p' backend/app/crud/config/version.py | head -30

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 1024


🏁 Script executed:

# Find the function definition containing line 71 by searching backwards from line 71
sed -n '1,71p' backend/app/crud/config/version.py | tac | grep -m 1 "def " | head -1

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 159


🏁 Script executed:

# Get broader context to see the function definition
sed -n '40,72p' backend/app/crud/config/version.py

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 1366


Fix log prefix and sanitize Pydantic validation errors.

Line 71 returns e.errors() directly; this exposes verbose input, ctx, and url fields that may leak sensitive config values. Additionally, the log message on line 68 uses [ConfigVersionCrud.create_from_partial] but the actual function is create_or_raise, violating the log-prefix guideline.

Suggested fix
             logger.error(
-                f"[ConfigVersionCrud.create_from_partial] Validation failed | "
+                f"[ConfigVersionCrud.create_or_raise] Validation failed | "
                 f"{{'config_id': '{self.config_id}', 'error': '{str(e)}'}}"
             )
-            raise HTTPException(status_code=400, detail=e.errors())
+            safe_errors = e.errors(
+                include_input=False,
+                include_context=False,
+                include_url=False,
+            )
+            raise HTTPException(status_code=400, detail=safe_errors)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/crud/config/version.py` at line 71, The function create_or_raise
currently logs with the wrong prefix and returns raw Pydantic errors
(e.errors()), risking sensitive data leakage; update the log message prefix from
"[ConfigVersionCrud.create_from_partial]" to
"[ConfigVersionCrud.create_or_raise]" and replace the direct e.errors() response
with a sanitized list that extracts only non-sensitive fields (for each error
keep loc, msg, and type and omit input, ctx, url, etc.), then raise
HTTPException(status_code=400, detail=sanitized_errors). Ensure you reference
the exception variable e and the create_or_raise function when making these
changes.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

http_exception_handler calls _filter_union_branch_errors which sanitize the e.errors() so client would not getting any sensitive config values

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

Let me verify this by checking the actual implementation of _filter_union_branch_errors in the exception handler.


🏁 Script executed:

#!/bin/bash
cat backend/app/core/exception_handlers.py

Length of output: 3986


@vprashrex, I've read the full implementation of _filter_union_branch_errors. While the http_exception_handler does pass the list through it, that function's purpose is specifically union-branch deduplication and loc path cleanup — it does not strip sensitive fields from each error dict.

Here's what _filter_union_branch_errors does and doesn't do:

Concern Handled?
Selects the most relevant union branch ✅ Yes
Strips branch identifiers from loc ✅ Yes
Deduplicates by (loc, msg) ✅ Yes
Removes input (actual submitted value) ❌ No
Removes ctx (constraint context) ❌ No
Removes url (Pydantic docs URL) ❌ No

Since e.errors() in Pydantic v2 includes input by default — which contains the actual value that failed validation — sensitive config data (e.g. API keys, model parameters, or any nested config blob values) would still be forwarded to the client after passing through _filter_union_branch_errors.

The suggested fix remains relevant:

safe_errors = e.errors(
    include_input=False,
    include_context=False,
    include_url=False,
)
raise HTTPException(status_code=400, detail=safe_errors)

This strips input, ctx, and url at the source, before the exception handler even sees the data. The log prefix (create_from_partialcreate_or_raise) is also still a valid fix.

@codecov
Copy link

codecov bot commented Mar 18, 2026

Codecov Report

❌ Patch coverage is 88.23529% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
backend/app/core/exception_handlers.py 85.71% 1 Missing ⚠️
backend/app/crud/config/version.py 0.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

f"{{'config_id': '{self.config_id}', 'error_count': {len(e.errors())}, "
f"'fields': {['.'.join(str(l) for l in err['loc']) for err in e.errors()]}}}"
)
raise HTTPException(status_code=400, detail=e.errors())
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

When raising validation error please note that don't use str(e). use e.errors(). so that exception_handlers can pick it up and send the validation error in an structured way to the client

Copy link

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/app/crud/config/version.py`:
- Around line 67-72: The logger.error call in ConfigVersionCrud.create_or_raise
uses an ambiguous loop variable `l` and calls e.errors() multiple times; fix by
caching e.errors() to a local variable (e.g., errors = e.errors()) and replace
the comprehension to use a descriptive name like loc_part or loc_item when
joining locations, then reference errors and its length (len(errors)) in the
formatted log and use errors when raising the HTTPException (raise
HTTPException(status_code=400, detail=errors)).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 72a7213b-4ced-477c-a06e-29c43bf5516a

📥 Commits

Reviewing files that changed from the base of the PR and between 35c95dc and 5a90e0a.

📒 Files selected for processing (3)
  • backend/app/core/exception_handlers.py
  • backend/app/crud/config/version.py
  • backend/app/tests/core/test_exception_handlers.py

@vprashrex vprashrex moved this to In Review in Kaapi-dev Mar 18, 2026
@vprashrex vprashrex linked an issue Mar 18, 2026 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

Status: In Review

Development

Successfully merging this pull request may close these issues.

API: Structured error response format

1 participant