Skip to content

Document version header in every response schema #1612

@stitch

Description

@stitch

Hi Vitalik et al!

First off: thank you for creating Django Ninja. We're using this at the internet.nl internet standards compliance checker to describe our API endpoints and it's working like a charm.

Being a governmental funded project, our API should comply to government standards. Most requirements can directly be translated to Django Ninja, except for one. One of these is a bit odd: it requires to document a header being sent on every response, so in every schema. While sending the actual header can easily be done with a decorator (as documented and seen below), the header must be documented on every response also, or the openapi.json file will not pass their linter.

How would i be able to document an "API-Version" header in every response schema under the "header" section?

Background

The specification for doing so is here: https://developer.overheid.nl/kennisbank/apis/api-design-rules/hoe-te-voldoen/version-header

{
  "headers": {
    "API-Version": {
      "description": "Semver van de API",
      "schema": {
        "type": "string",
        "example": "1.0.0"
      }
    }
  }
}

I'm now using two workarounds: specifying a decorator and adding a "headers" section in the openapi_extra field.

Decorator:

def version_header(func):
    def wrapper(request, *args, **kwargs):
        response = func(request, *args, **kwargs)
        response["API-Version"] = django_settings.OPEN_API_VERSION
        return response
    return wrapper

OpenAPI Extra:

api = NinjaAPI(
    title=django_settings.OPEN_API_TITLE,
    openapi_extra={
        "info": {
            "contact": {
                "name": django_settings.OPEN_API_CONTACT_ORGANIZATION,
                "email": django_settings.OPEN_API_CONTACT_EMAIL,
                "url": django_settings.OPEN_API_CONTACT_URL
            },
        },
        # Makeshift alternative for specifying headers everywhere.
        "headers": {
            "API-Version": django_settings.OPEN_API_VERSION
        }
    },
    version=django_settings.OPEN_API_VERSION,
    renderer=ORJSONRenderer()
)

Current workaround

I've now hacked it using some workaround proposed by an AI tool, but it looks messy and might break in a next version perhaps:

Workaround fix:

# Inject API-Version header into the generated OpenAPI schema for all responses.
_orig_get_openapi_schema = api.get_openapi_schema


def _get_openapi_schema_with_version_header(**kwargs):
    schema = _orig_get_openapi_schema()
    components = schema.setdefault("components", {})
    headers = components.setdefault("headers", {})
    headers["API-Version"] = {
        "description": f"Semantic version of the {django_settings.OPEN_API_TITLE}",
        "schema": {"type": "string", "example": django_settings.OPEN_API_VERSION},
    }
    for path_item in schema.get("paths", {}).values():
        for operation in path_item.values():
            responses = operation.get("responses", {})
            for response in responses.values():
                response_headers = response.setdefault("headers", {})
                response_headers["API-Version"] = {"$ref": "#/components/headers/API-Version"}
    return schema


api.get_openapi_schema = _get_openapi_schema_with_version_header

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions