Skip to content
Merged
Show file tree
Hide file tree
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
20 changes: 18 additions & 2 deletions src/azure_functions_openapi/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,7 @@ def get_openapi_json(
route_prefix: str = DEFAULT_ROUTE_PREFIX,
strict: bool = False,
registry: OpenAPIRegistry | None = None,
hoist_flat_schemas: bool = False,
) -> str:
"""Return the spec as pretty-printed JSON (UTF-8).

Expand All @@ -698,6 +699,11 @@ def get_openapi_json(
``""`` for hosts that disable the prefix or a custom value such
as ``"/v1"``.
strict: When ``True``, raise on any registry entry processing failure.
registry: Inject a custom :class:`OpenAPIRegistry` instead of the shared
global one. Defaults to ``None`` (the process-wide registry).
hoist_flat_schemas: When ``True`` (opt-in, #375), structured flat
schemas are promoted into ``components.schemas``. Defaults to
``False`` to preserve the existing generated spec shape.

Returns:
OpenAPI spec in JSON format.
Expand All @@ -711,6 +717,7 @@ def get_openapi_json(
security_schemes=security_schemes,
route_prefix=route_prefix,
strict=strict,
hoist_flat_schemas=hoist_flat_schemas,
registry=registry,
)
return json.dumps(spec, indent=2, ensure_ascii=False)
Expand All @@ -730,7 +737,8 @@ def get_openapi_yaml(
route_prefix: str = DEFAULT_ROUTE_PREFIX,
strict: bool = False,
registry: OpenAPIRegistry | None = None,
) -> str:
hoist_flat_schemas: bool = False,
) -> str:
"""Return the spec as YAML.

Parameters:
Expand All @@ -744,6 +752,11 @@ def get_openapi_yaml(
``""`` for hosts that disable the prefix or a custom value such
as ``"/v1"``.
strict: When ``True``, raise on any registry entry processing failure.
registry: Inject a custom :class:`OpenAPIRegistry` instead of the shared
global one. Defaults to ``None`` (the process-wide registry).
hoist_flat_schemas: When ``True`` (opt-in, #375), structured flat
schemas are promoted into ``components.schemas``. Defaults to
``False`` to preserve the existing generated spec shape.

Returns:
OpenAPI spec in YAML format.
Expand All @@ -757,6 +770,7 @@ def get_openapi_yaml(
security_schemes=security_schemes,
route_prefix=route_prefix,
strict=strict,
hoist_flat_schemas=hoist_flat_schemas,
registry=registry,
)
return yaml.safe_dump(spec, sort_keys=False, allow_unicode=True)
Expand Down Expand Up @@ -908,7 +922,8 @@ def generate_openapi_report(
route_prefix: str = DEFAULT_ROUTE_PREFIX,
strict: bool = False,
registry: OpenAPIRegistry | None = None,
) -> SpecReport:
hoist_flat_schemas: bool = False,
) -> SpecReport:
"""Generate the spec together with structured, machine-readable warnings.

Mirrors :func:`generate_openapi_spec` and returns the identical spec mapping
Expand All @@ -930,6 +945,7 @@ def generate_openapi_report(
security_schemes=security_schemes,
route_prefix=route_prefix,
strict=strict,
hoist_flat_schemas=hoist_flat_schemas,
registry=registry,
)
warnings_list = collect_spec_warnings(spec, registry=registry)
Expand Down
45 changes: 44 additions & 1 deletion tests/test_hoist_flat.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@

from azure_functions_openapi.bridge import _HANDLER_METADATA_ATTR, scan_endpoint_metadata
from azure_functions_openapi.decorator import clear_openapi_registry, get_openapi_registry
from azure_functions_openapi.spec import generate_openapi_spec
from azure_functions_openapi.spec import (
generate_openapi_report,
generate_openapi_spec,
get_openapi_json,
get_openapi_yaml,
)
from azure_functions_openapi.utils import hoist_inline_defs


Expand Down Expand Up @@ -260,3 +265,41 @@ def test_spec_hoists_flat_body_when_opted_in(_clean_registry: Any) -> None:

assert _request_schema(spec) == {"$ref": "#/components/schemas/CreateUser"}
assert spec["components"]["schemas"]["CreateUser"]["properties"] == {"name": {"type": "string"}}


# ---------------------------------------------------------------------------
# Output-API propagation (issue #378): the option must reach every public
# spec-output wrapper, not only generate_openapi_spec.
# ---------------------------------------------------------------------------


def test_get_openapi_json_forwards_hoist_flag(_clean_registry: Any) -> None:
scan_endpoint_metadata(_make_flat_app())

default_json = get_openapi_json()
hoisted_json = get_openapi_json(hoist_flat_schemas=True)

assert "#/components/schemas/CreateUser" not in default_json
assert '#/components/schemas/CreateUser' in hoisted_json


def test_get_openapi_yaml_forwards_hoist_flag(_clean_registry: Any) -> None:
scan_endpoint_metadata(_make_flat_app())

default_yaml = get_openapi_yaml()
hoisted_yaml = get_openapi_yaml(hoist_flat_schemas=True)

assert "CreateUser:" not in default_yaml
assert "#/components/schemas/CreateUser" in hoisted_yaml


def test_generate_openapi_report_forwards_hoist_flag(_clean_registry: Any) -> None:
scan_endpoint_metadata(_make_flat_app())

default_report = generate_openapi_report()
hoisted_report = generate_openapi_report(hoist_flat_schemas=True)

assert _request_schema(default_report.spec) == _FLAT_BODY
assert _request_schema(hoisted_report.spec) == {
"$ref": "#/components/schemas/CreateUser"
}
4 changes: 4 additions & 0 deletions tests/test_openapi_enhanced.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ def test_get_openapi_json_success(self) -> None:
security_schemes=None,
route_prefix="/api",
strict=False,
hoist_flat_schemas=False,
registry=None,
)

Expand Down Expand Up @@ -219,6 +220,7 @@ def test_get_openapi_json_passes_custom_description(self) -> None:
security_schemes=None,
route_prefix="/api",
strict=False,
hoist_flat_schemas=False,
registry=None,
)

Expand Down Expand Up @@ -257,6 +259,7 @@ def test_get_openapi_yaml_success(self) -> None:
security_schemes=None,
route_prefix="/api",
strict=False,
hoist_flat_schemas=False,
registry=None,
)

Expand Down Expand Up @@ -286,6 +289,7 @@ def test_get_openapi_yaml_passes_custom_description(self) -> None:
security_schemes=None,
route_prefix="/api",
strict=False,
hoist_flat_schemas=False,
registry=None,
)

Expand Down
Loading