Skip to content

fix(openapi): order path parameters to match the url in generated schema#4933

Open
Bishwas-py wants to merge 9 commits into
litestar-org:mainfrom
Bishwas-py:fix/openapi-param-order-3644
Open

fix(openapi): order path parameters to match the url in generated schema#4933
Bishwas-py wants to merge 9 commits into
litestar-org:mainfrom
Bishwas-py:fix/openapi-param-order-3644

Conversation

@Bishwas-py

@Bishwas-py Bishwas-py commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Description

The generated OpenAPI schema orders path parameters by how the handler happens to consume them, not by their position in the url. A path parameter consumed through a dependency, or not consumed at all, gets pushed out of its natural url position, so refactoring code that doesn't touch the url can still reorder the public schema (and any client code generated from it).

Fixes #3644

Root cause

ParameterFactory.create_parameters_for_handler builds the parameter list by iterating the handler's function signature, recursing into dependency providers when a field is a dependency, and appending unconsumed path parameters at the very end. None of these three insertion paths follow the url's actual left-to-right order, even though that order is already available via route.path_parameters.

Fix

Added ParameterFactory._order_path_parameters, which reorders only the path-type parameters to match their position in self.path_parameters (the url's real order) before the list is returned. Query and header parameters keep their existing position; this is scoped to fixing path-parameter ordering specifically.

How was this tested

Added three tests to tests/unit/test_openapi/test_parameters.py:

  • a path parameter consumed via a dependency
  • a path parameter left unconsumed entirely
  • a path parameter interleaved with a query parameter, to confirm the reorder doesn't disturb query parameter position

Before the fix, for /users/{user_id}/posts/{post_id}/comments/{comment_id} with post_id consumed via a dependency, the generated schema ordered parameters as user_id, comment_id, post_id. After the fix, it orders them as user_id, post_id, comment_id, matching the url.


📚 Documentation preview 📚: https://litestar-org.github.io/litestar-docs-preview/4933

Path parameters were ordered by how the handler happened to consume
them (direct argument, dependency, or unconsumed), not by their
position in the url. Consuming a path parameter through a dependency
instead of a direct argument silently reordered the generated OpenAPI
schema, which reshuffles any client code generated from it.

Sort only the path-type parameters to match the url's real order;
query and header parameters keep their existing position.

Fixes litestar-org#3644
@Bishwas-py
Bishwas-py requested review from a team as code owners July 17, 2026 06:44
@codecov

codecov Bot commented Jul 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 58.33333% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.30%. Comparing base (fe47b84) to head (8772108).

Files with missing lines Patch % Lines
litestar/_openapi/parameters.py 58.33% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4933      +/-   ##
==========================================
- Coverage   67.30%   67.30%   -0.01%     
==========================================
  Files         293      293              
  Lines       15230    15239       +9     
  Branches     1728     1729       +1     
==========================================
+ Hits        10251    10256       +5     
- Misses       4832     4836       +4     
  Partials      147      147              

☔ 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.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread litestar/_openapi/parameters.py Outdated
ordered_path_parameters = sorted(
(parameters[i] for i in path_slots), key=lambda parameter: path_order[parameter.name]
)
for slot, parameter in zip(path_slots, ordered_path_parameters, strict=True):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

question: can we order all parameters? it would be a bigger change, but I guess it makes sense to have a strong and consistent order based on: type and internal logic for each type.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

scoped this to path parameters, matching #3644. ordering all parameter types is a bigger change, it would need a canonical cross-type order and would break test_unwrap_new_type's current assumption that a query param can precede a path param. am happy to open a follow-up for that if you're interested.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think that this is a good idea to split ordering in PRs. Let's focus on this problem in a single PR :)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I agree with @sobolevn. Let's try to find a general solution for this problem that provides a canonical ordering for parameters of all types.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

pushed a general solution in 7f7273d. parameters are now ordered by type: path, query, cookie, header (matching ParamType's own order), then by an internal rule per type as url position for path and alphabetical for the rest.

Per review feedback, generalize the path-only ordering fix to cover
query, cookie, and header parameters too. Parameters are now ordered
by type first (path, query, cookie, header, matching ParamType's own
declaration order), then by an internal rule per type: path parameters
by their position in the url, and query/cookie/header parameters
alphabetically by name, since those types have no positional signal
to order by otherwise.

This keeps parameter order stable and predictable regardless of how a
handler happens to consume its parameters.
Comment thread litestar/_openapi/parameters.py Outdated
"""
path_order = {name: index for index, name in enumerate(self.path_parameters)}

def sort_key(parameter: Parameter) -> tuple[int, int | str]:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why do we put this function inside the method?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

it closed over path_order, which is local there. inlined it as a lambda in 9f6737b, no nested def needed.

@Bishwas-py
Bishwas-py requested a review from sobolevn July 19, 2026 16:26
Comment thread litestar/_openapi/parameters.py Outdated
path_order = {name: index for index, name in enumerate(self.path_parameters)}
return sorted(
parameters,
key=lambda parameter: (

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's make this a module level protected named function please :)

There's not need to compile it in-place

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

moved it to a module level protected function in 7bcd4ea, bound path_order with functools.partial.

Comment thread litestar/_openapi/parameters.py Outdated
_PARAM_TYPE_ORDER = {"path": 0, "query": 1, "cookie": 2, "header": 3}


def _parameter_sort_key(parameter: Parameter, path_order: Mapping[str, int]) -> tuple[int, int | str]:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

let's put it in very bottom :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yup, moved it

Comment thread litestar/_openapi/parameters.py
@Bishwas-py
Bishwas-py requested a review from sobolevn July 20, 2026 05:01
@Bishwas-py
Bishwas-py requested a review from provinzkraut July 20, 2026 05:04
Comment thread litestar/_openapi/parameters.py Outdated
Co-authored-by: Janek Nouvertné <provinzkraut@posteo.de>
@Bishwas-py
Bishwas-py requested a review from provinzkraut July 20, 2026 12:45
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.

Bug: openapi parameter order doesn't match the order in the path

3 participants