mirror of
https://github.com/langgenius/dify.git
synced 2026-06-17 23:21:12 +08:00
feat(openapi): document canonical error schema in swagger via contract decorators
@accepts(query/body) now emits a 422 response with ErrorBody; @returns emits a default error response with ErrorBody. ErrorBody (and auto-promoted ErrorDetail) are registered in openapi_ns so they appear in definitions and are reachable from both error response entries.
This commit is contained in:
parent
40df3c26c6
commit
27bbbbcf4b
@ -1,7 +1,7 @@
|
|||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
from flask_restx import Namespace
|
from flask_restx import Namespace
|
||||||
|
|
||||||
from controllers.openapi._errors import OpenApiErrorFormatter
|
from controllers.openapi._errors import ErrorBody, OpenApiErrorFormatter
|
||||||
from libs.device_flow_security import attach_anti_framing
|
from libs.device_flow_security import attach_anti_framing
|
||||||
from libs.external_api import ExternalApi
|
from libs.external_api import ExternalApi
|
||||||
|
|
||||||
@ -84,6 +84,7 @@ register_schema_models(
|
|||||||
)
|
)
|
||||||
register_response_schema_models(
|
register_response_schema_models(
|
||||||
openapi_ns,
|
openapi_ns,
|
||||||
|
ErrorBody,
|
||||||
TagItem,
|
TagItem,
|
||||||
UsageInfo,
|
UsageInfo,
|
||||||
MessageMetadata,
|
MessageMetadata,
|
||||||
|
|||||||
@ -21,6 +21,7 @@ from pydantic import BaseModel, ValidationError
|
|||||||
|
|
||||||
from controllers.common.schema import query_params_from_model, query_params_from_request
|
from controllers.common.schema import query_params_from_model, query_params_from_request
|
||||||
from controllers.openapi import openapi_ns
|
from controllers.openapi import openapi_ns
|
||||||
|
from controllers.openapi._errors import ErrorBody
|
||||||
|
|
||||||
|
|
||||||
def accepts(*, query: type[BaseModel] | None = None, body: type[BaseModel] | None = None) -> Callable:
|
def accepts(*, query: type[BaseModel] | None = None, body: type[BaseModel] | None = None) -> Callable:
|
||||||
@ -51,6 +52,8 @@ def accepts(*, query: type[BaseModel] | None = None, body: type[BaseModel] | Non
|
|||||||
openapi_ns.doc(params=query_params_from_model(query))(wrapper)
|
openapi_ns.doc(params=query_params_from_model(query))(wrapper)
|
||||||
if body is not None:
|
if body is not None:
|
||||||
openapi_ns.expect(openapi_ns.models[body.__name__])(wrapper)
|
openapi_ns.expect(openapi_ns.models[body.__name__])(wrapper)
|
||||||
|
if query is not None or body is not None:
|
||||||
|
openapi_ns.response(422, "Validation error", openapi_ns.models[ErrorBody.__name__])(wrapper)
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
@ -76,6 +79,7 @@ def returns(code: int, model: type[BaseModel], description: str | None = None) -
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
openapi_ns.response(code, description or model.__name__, openapi_ns.models[model.__name__])(wrapper)
|
openapi_ns.response(code, description or model.__name__, openapi_ns.models[model.__name__])(wrapper)
|
||||||
|
openapi_ns.response("default", "Error", openapi_ns.models[ErrorBody.__name__])(wrapper)
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|||||||
@ -208,3 +208,41 @@ def test_accepts_body_emits_expect_through_guard_stack():
|
|||||||
|
|
||||||
apidoc = getattr(view, "__apidoc__", {})
|
apidoc = getattr(view, "__apidoc__", {})
|
||||||
assert apidoc.get("expect") # body schema advertised via @openapi_ns.expect
|
assert apidoc.get("expect") # body schema advertised via @openapi_ns.expect
|
||||||
|
|
||||||
|
|
||||||
|
def _response_model_name(entry) -> str:
|
||||||
|
"""Extract the model name from a flask-restx __apidoc__ response entry.
|
||||||
|
|
||||||
|
flask-restx stores responses as ``(description, model, kwargs)`` tuples
|
||||||
|
where ``model.name`` is the registered schema name.
|
||||||
|
"""
|
||||||
|
if isinstance(entry, tuple) and len(entry) >= 2:
|
||||||
|
model = entry[1]
|
||||||
|
return getattr(model, "name", "") or ""
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_accepts_documents_422_error_response(app):
|
||||||
|
from controllers.openapi._errors import ErrorBody
|
||||||
|
|
||||||
|
@accepts(query=ContractQuery)
|
||||||
|
def view(*, query):
|
||||||
|
return query
|
||||||
|
|
||||||
|
doc = getattr(view, "__apidoc__", {})
|
||||||
|
responses = doc.get("responses", {})
|
||||||
|
assert "422" in responses
|
||||||
|
assert _response_model_name(responses["422"]) == ErrorBody.__name__
|
||||||
|
|
||||||
|
|
||||||
|
def test_returns_documents_default_error_response(app):
|
||||||
|
from controllers.openapi._errors import ErrorBody
|
||||||
|
|
||||||
|
@returns(200, ContractResp)
|
||||||
|
def view():
|
||||||
|
return ContractResp(value=1)
|
||||||
|
|
||||||
|
doc = getattr(view, "__apidoc__", {})
|
||||||
|
responses = doc.get("responses", {})
|
||||||
|
assert "default" in responses
|
||||||
|
assert _response_model_name(responses["default"]) == ErrorBody.__name__
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user