Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions src/fastapi_swagger2/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,8 @@ def get_swagger2_operation_parameters(
schema_generator: GenerateJsonSchema,
model_name_map: ModelNameMap,
field_mapping: FieldMapping,
route: routing.APIRoute,
method: str,
) -> List[Dict[str, Any]]:
parameters = []
for param in all_route_params:
Expand All @@ -251,7 +253,26 @@ def get_swagger2_operation_parameters(
if field_info.in_.value == "body":
parameter["schema"] = schema
else:
parameter.update({k: v for (k, v) in schema.items() if k != "title"})
# according to the https://swagger.io/specification/v2/#:~:text=If%20in%20is%20any%20value%20other%20than%20%22body%22%3A
# field "type" if required
parameter["type"] = schema.get("type", "string") # fallback
if "anyOf" in schema:
any_of = schema.pop("anyOf")
if {"type": "null"} in any_of:
parameter.update({"x-nullable": True, "required": False})
any_of.remove({"type": "null"})
if len(any_of) == 1:
parameter.update(any_of[0])
else:
parameter.update({"type": "string"})
logger.warning(
f"fastapi_swagger2: Unable to handle anyOf in parameters {any_of}, use string type. Route: {route.path_format}, Method: {method}, Param: {param.alias}"
)


parameter.update(
{k: v for (k, v) in schema.items() if k not in ["title", "anyOf"]}
)
Comment on lines +256 to +275
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The current logic for handling anyOf in parameters has a flaw in the order of operations. It first determines the type from anyOf, but then overwrites it with a more generic type from the base schema dictionary. This can lead to incorrect parameter types in the generated Swagger specification (e.g., a parameter of type Optional[int] being documented as a string).

The logic should be reordered to first apply the general properties from the schema, and then let the more specific anyOf logic override them. This ensures that the final parameter schema is correct.

Consider replacing this block with the following implementation, which corrects the order of operations and also handles the case where anyOf only contains a null type more gracefully.

            # according to the https://swagger.io/specification/v2/#:~:text=If%20in%20is%20any%20value%20other%20than%20%22body%22%3A
            # field "type" is required
            parameter.update(
                {k: v for (k, v) in schema.items() if k not in ["title", "anyOf"]}
            )
            if "anyOf" in schema:
                any_of = schema.pop("anyOf")
                if {"type": "null"} in any_of:
                    parameter.update({"x-nullable": True, "required": False})
                    any_of = [item for item in any_of if item != {"type": "null"}]

                if len(any_of) == 1:
                    parameter.update(any_of[0])
                elif any_of:
                    parameter.update({"type": "string"})
                    logger.warning(
                        f"fastapi_swagger2: Unable to handle anyOf in parameters {any_of}, use string type. Route: {route.path_format}, Method: {method}, Param: {param.alias}"
                    )

            if "type" not in parameter:
                parameter["type"] = "string"  # fallback

if field_info.description:
parameter["description"] = field_info.description
if field_info.example != Undefined:
Expand Down Expand Up @@ -341,6 +362,8 @@ def get_swagger2_path(
schema_generator=schema_generator,
model_name_map=model_name_map,
field_mapping=field_mapping,
route=route,
method=method,
)
parameters.extend(operation_parameters)
if parameters:
Expand Down Expand Up @@ -571,7 +594,7 @@ def get_swagger2(
for p in properties:
if "anyOf" in properties[p].keys():
any_of = properties[p].pop("anyOf")
if len(any_of) <= 2:
if len(any_of) <= 2 and {"type": "null"} in any_of:
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@luliangce I am little unsure on this condition change. Can you please share some sample code which would trigger this condition? Thanks

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

This condition is intended to handle optional properties (Union[T, None]), but it's a bit too broad. Using <= can lead to incorrect schema generation for properties that are defined as anyOf with only a single {"type": "null"} item. In this edge case, the resulting property schema would be {"x-nullable": true} without a type, which is invalid.

Consider changing <= to == to make the logic more precise, ensuring it only applies to anyOf with exactly two items (one of which is null), which is the correct representation for an optional type. This avoids the edge case and makes the code more robust.

Suggested change
if len(any_of) <= 2 and {"type": "null"} in any_of:
if len(any_of) == 2 and {"type": "null"} in any_of:

for _any_of in any_of:
if _any_of == {"type": "null"}:
properties[p]["x-nullable"] = True
Expand Down