Skip to content

API: Remove Trailing Slashes#812

Merged
Ayush8923 merged 7 commits intomainfrom
api/remove-trailing-slashes
May 5, 2026
Merged

API: Remove Trailing Slashes#812
Ayush8923 merged 7 commits intomainfrom
api/remove-trailing-slashes

Conversation

@Ayush8923
Copy link
Copy Markdown
Collaborator

@Ayush8923 Ayush8923 commented May 5, 2026

Issue: #561

Summary

  • Removed trailing slashes from all FastAPI route declarations
  • Added StripTrailingSlashMiddleware (pure-ASGI) that rewrites /foo/ to /foo before routing, so existing integrations sending the trailing-slash form keep working, no 307 redirect, no auth-header loss on redirect, no double round-trip. To be removed in a future release once integrators have migrated.
  • Updated all affected tests to use the canonical URLs and added a deprecation note in the OpenAPI description so the legacy form is visibly marked as going away in Swagger.

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.

Summary by CodeRabbit

  • New Features

    • Added middleware to normalize and strip trailing slashes for incoming API requests.
  • Bug Fixes

    • Standardized endpoint paths to remove trailing-slash variants for consistent routing and URL behavior.
  • Tests

    • Added tests verifying trailing-slash compatibility, query-string preservation, and expected responses for both slash/no-slash requests.

@Ayush8923 Ayush8923 self-assigned this May 5, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

📝 Walkthrough

Walkthrough

Introduces a StripTrailingSlashMiddleware to strip single trailing slashes from HTTP request paths, registers it in the app middleware stack, adjusts many route decorator path arguments (removing explicit "/" or trailing slashes), swaps the include order of two routers, and updates tests to use non-trailing-slash endpoints.

Changes

Trailing Slash Normalization

Layer / File(s) Summary
Middleware Implementation
backend/app/core/middleware.py
Adds StripTrailingSlashMiddleware that removes a single trailing / from scope["path"] (except /) and strips trailing b"/" from scope["raw_path"] before forwarding to the ASGI app.
Middleware Registration
backend/app/main.py
Imports and registers StripTrailingSlashMiddleware via app.add_middleware(...) in the FastAPI app middleware stack.
Router Decorator Updates
backend/app/api/routes/*, backend/app/api/routes/config/* (many files)
Changed route decorator path arguments from "/" to "" under router prefixes, and removed trailing slashes from absolute paths (e.g., "/reset-password/""/reset-password") across endpoints (api_keys, assistants, collections, config, credentials, doc_transformation_job, documents, languages, login, model_config, model_evaluation, openai_conversation, organization, private, project, user_project, users, utils).
Router Registration Order
backend/app/api/main.py
Swapped include order so doc_transformation_job.router is included before documents.router.
Config Router Mounting
backend/app/api/routes/config/__init__.py
Removed top-level prefix="/configs" from the exported router and mounted config.router and version.router explicitly under prefix="/configs".
Tests Updated
backend/app/tests/... (many test files)
Updated test request URLs to use endpoints without trailing slashes; added backend/app/tests/core/test_trailing_slash_compat.py with three tests validating compatibility, query preservation, and root-path behavior.
sequenceDiagram
    participant Client as Client (HTTP)
    participant Middleware as StripTrailingSlashMiddleware
    participant App as FastAPI App / Router
    participant Handler as Route Handler
    Client->>Middleware: HTTP GET /foo/ (or /foo)
    Middleware->>Middleware: strip trailing slash if present (not for /)
    Middleware->>App: forward modified scope (path=/foo)
    App->>Handler: route to handler for /foo
    Handler-->>App: response (200)
    App-->>Client: HTTP response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • AkhileshNegi
  • vprashrex
  • Prajna1999

Poem

🐰
I hop along the routing trail,
no trailing slash shall make me fail.
Both /foo/ and /foo find home today,
one middleware clears the way. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.51% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'API: Remove Trailing Slashes' is clear, concise, and directly describes the primary change across the entire pull request: systematically removing trailing slashes from API route declarations.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 api/remove-trailing-slashes

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.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 5, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@Ayush8923 Ayush8923 linked an issue May 5, 2026 that may be closed by this pull request
Copy link
Copy Markdown

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/app/api/routes/user_project.py (1)

122-128: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Logging entry.email without mask_string violates the coding guidelines.

Lines 123 and 127 log raw email addresses (PII) to the application logger. Per the coding guidelines, sensitive values must be wrapped with mask_string.

🛡️ Proposed fix
-                logger.info(
-                    f"[add_project_users] Invitation email sent | email: {entry.email}"
-                )
+                logger.info(
+                    f"[add_project_users] Invitation email sent | email: {mask_string(str(entry.email))}"
+                )
 ...
-            except Exception as e:
-                logger.error(
-                    f"[add_project_users] Failed to send invitation email | email: {entry.email}, error: {e}"
-                )
+            except Exception as e:
+                logger.error(
+                    f"[add_project_users] Failed to send invitation email | email: {mask_string(str(entry.email))}, error: {e}"
+                )

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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/api/routes/user_project.py` around lines 122 - 128, The logs in
add_project_users are printing raw PII (entry.email) in logger.info and
logger.error; update both calls to wrap the email with the mask_string helper
(e.g., logger.info(f"[add_project_users] Invitation email sent | email:
{mask_string(entry.email)}") and similarly for logger.error) so all sensitive
values follow the masking guideline and keep the function name prefix intact.
🤖 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 `@backend/app/core/middleware.py`:
- Around line 16-28: Add precise type annotations to both methods: annotate
StripTrailingSlashMiddleware.__init__ with the ASGI app type (e.g., app:
"ASGIApp") and return -> None, and annotate
StripTrailingSlashMiddleware.__call__ with ASGI scope, receive and send types
(e.g., scope: "Scope", receive: "Receive", send: "Send") and return -> None (it
is an async def so its signature should be async def __call__(...) -> None).
Also add the necessary imports for the ASGI/Starlette types (Scope, Receive,
Send, ASGIApp) or appropriate typing aliases at the top of the module so the
annotations resolve. Ensure you update both method signatures (function names
__init__ and __call__) accordingly without changing runtime behavior.

---

Outside diff comments:
In `@backend/app/api/routes/user_project.py`:
- Around line 122-128: The logs in add_project_users are printing raw PII
(entry.email) in logger.info and logger.error; update both calls to wrap the
email with the mask_string helper (e.g., logger.info(f"[add_project_users]
Invitation email sent | email: {mask_string(entry.email)}") and similarly for
logger.error) so all sensitive values follow the masking guideline and keep the
function name prefix intact.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 590deacc-c46e-4aea-96d7-162ff27a6e1d

📥 Commits

Reviewing files that changed from the base of the PR and between 7bdf83b and 768016f.

📒 Files selected for processing (41)
  • backend/app/api/main.py
  • backend/app/api/routes/api_keys.py
  • backend/app/api/routes/assistants.py
  • backend/app/api/routes/collections.py
  • backend/app/api/routes/config/config.py
  • backend/app/api/routes/credentials.py
  • backend/app/api/routes/doc_transformation_job.py
  • backend/app/api/routes/documents.py
  • backend/app/api/routes/languages.py
  • backend/app/api/routes/login.py
  • backend/app/api/routes/model_config.py
  • backend/app/api/routes/model_evaluation.py
  • backend/app/api/routes/openai_conversation.py
  • backend/app/api/routes/organization.py
  • backend/app/api/routes/private.py
  • backend/app/api/routes/project.py
  • backend/app/api/routes/user_project.py
  • backend/app/api/routes/users.py
  • backend/app/api/routes/utils.py
  • backend/app/core/middleware.py
  • backend/app/main.py
  • backend/app/tests/api/routes/collections/test_collection_list.py
  • backend/app/tests/api/routes/configs/test_config.py
  • backend/app/tests/api/routes/configs/test_version.py
  • backend/app/tests/api/routes/test_api_key.py
  • backend/app/tests/api/routes/test_assistants.py
  • backend/app/tests/api/routes/test_creds.py
  • backend/app/tests/api/routes/test_doc_transformation_job.py
  • backend/app/tests/api/routes/test_evaluation.py
  • backend/app/tests/api/routes/test_languages.py
  • backend/app/tests/api/routes/test_login.py
  • backend/app/tests/api/routes/test_model_config.py
  • backend/app/tests/api/routes/test_model_evaluation.py
  • backend/app/tests/api/routes/test_org.py
  • backend/app/tests/api/routes/test_private.py
  • backend/app/tests/api/routes/test_project.py
  • backend/app/tests/api/routes/test_users.py
  • backend/app/tests/api/test_auth_failures.py
  • backend/app/tests/api/test_user_project.py
  • backend/app/tests/core/test_exception_handlers.py
  • backend/app/tests/core/test_trailing_slash_compat.py

Comment thread backend/app/core/middleware.py Outdated
@Ayush8923 Ayush8923 requested a review from nishika26 May 5, 2026 11:29
Comment thread backend/app/api/routes/config/__init__.py
Comment thread backend/app/core/middleware.py Outdated
def __init__(self, app):
self.app = app

async def __call__(self, scope, receive, send):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

do add type hints as suggested by code rabbit

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done

@Ayush8923 Ayush8923 requested a review from nishika26 May 5, 2026 11:47
Copy link
Copy Markdown

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

🧹 Nitpick comments (1)
backend/app/api/routes/config/__init__.py (1)

5-8: 💤 Low value

LGTM — routing chain is correct after the refactor.

Both sub-routers resolve to {API_V1_STR}/configs/… as expected, and the design correctly avoids the FastAPI constraint that prohibits an empty-path child route when the parent APIRouter carries no prefix.

One optional note: if either config.router or version.router already declares tags=["Config Management"], the tag will be duplicated in the OpenAPI schema. Consider omitting tags from the leaf routers or from this parent to avoid the redundancy.

🤖 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 `@backend/app/api/routes/config/__init__.py` around lines 5 - 8, The OpenAPI
tag "Config Management" is being applied both on the parent APIRouter (router)
and possibly on leaf routers (config.router or version.router), which can
produce duplicate tags in the schema; remove the duplicate by ensuring the tag
is declared only once — either remove tags=["Config Management"] from the parent
APIRouter named router and keep it on config.router/version.router, or remove
tags from the leaf routers and keep it only on router so that only a single
"Config Management" tag appears in the OpenAPI output.
🤖 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.

Nitpick comments:
In `@backend/app/api/routes/config/__init__.py`:
- Around line 5-8: The OpenAPI tag "Config Management" is being applied both on
the parent APIRouter (router) and possibly on leaf routers (config.router or
version.router), which can produce duplicate tags in the schema; remove the
duplicate by ensuring the tag is declared only once — either remove
tags=["Config Management"] from the parent APIRouter named router and keep it on
config.router/version.router, or remove tags from the leaf routers and keep it
only on router so that only a single "Config Management" tag appears in the
OpenAPI output.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 84ff7a65-bce4-414f-96f4-32d0906140b6

📥 Commits

Reviewing files that changed from the base of the PR and between 768016f and ba8106f.

📒 Files selected for processing (2)
  • backend/app/api/routes/config/__init__.py
  • backend/app/core/middleware.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/app/core/middleware.py

@Ayush8923 Ayush8923 merged commit a9e2ac5 into main May 5, 2026
3 checks passed
@Ayush8923 Ayush8923 deleted the api/remove-trailing-slashes branch May 5, 2026 12:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Kaapi v1.0: Endpoint Consistency

3 participants