Skip to content

Commit b886fde

Browse files
Fix ValidationError schema to match Pydantic v2 format (#232)
* fix: update ValidationError model to match Pydantic v2 format - Update ValidationErrorModel with Pydantic v2 fields (type, input, url) - Change type_ to type and make core fields required - Add comprehensive tests for schema and response validation - Resolves inconsistency between OpenAPI docs and actual responses Fixes #231 * refactor(tests): improve validation error tests naming
1 parent 6dada0e commit b886fde

File tree

2 files changed

+108
-5
lines changed

2 files changed

+108
-5
lines changed

flask_openapi3/models/validation_error.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@
77

88

99
class ValidationErrorModel(BaseModel):
10-
# More information: https://docs.pydantic.dev/latest/usage/models/#error-handling
11-
loc: Optional[list[str]] = Field(None, title="Location", description="the error's location as a list. ")
12-
msg: Optional[str] = Field(None, title="Message", description="a computer-readable identifier of the error type.")
13-
type_: Optional[str] = Field(None, title="Error Type", description="a human readable explanation of the error.")
10+
# More information: https://docs.pydantic.dev/latest/concepts/models/#error-handling
11+
type: str = Field(..., title="Error Type", description="A computer-readable identifier of the error type.")
12+
loc: list[Any] = Field(..., title="Location", description="The error's location as a list.")
13+
msg: str = Field(..., title="Message", description="A human readable explanation of the error.")
14+
input: Any = Field(..., title="Input", description="The input provided for validation.")
15+
url: Optional[str] = Field(None, title="URL", description="The URL to further information about the error.")
1416
ctx: Optional[dict[str, Any]] = Field(
1517
None,
1618
title="Error context",
17-
description="an optional object which contains values required to render the error message."
19+
description="An optional object which contains values required to render the error message.",
1820
)
1921

2022

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import pytest
2+
from pydantic import BaseModel, Field
3+
4+
from flask_openapi3 import OpenAPI
5+
6+
app = OpenAPI(__name__)
7+
app.config["TESTING"] = True
8+
9+
10+
@pytest.fixture
11+
def client():
12+
client = app.test_client()
13+
return client
14+
15+
16+
class LoginRequest(BaseModel):
17+
email: str = Field(..., description="User email")
18+
password: str = Field(..., description="User password")
19+
20+
21+
@app.post("/login")
22+
def login(body: LoginRequest):
23+
return {"message": f"Login successful for {body.email}"}
24+
25+
26+
def test_pydantic_validation_error_schema(client):
27+
resp = client.get("/openapi/openapi.json")
28+
assert resp.status_code == 200
29+
30+
schemas = resp.json["components"]["schemas"]
31+
assert "ValidationErrorModel" in schemas
32+
33+
validation_error_schema = schemas["ValidationErrorModel"]
34+
properties = validation_error_schema["properties"]
35+
36+
assert "type" in properties
37+
assert "loc" in properties
38+
assert "msg" in properties
39+
assert "input" in properties
40+
assert "url" in properties
41+
assert "ctx" in properties
42+
43+
assert properties["type"]["type"] == "string"
44+
assert properties["loc"]["type"] == "array"
45+
assert properties["msg"]["type"] == "string"
46+
47+
required_fields = validation_error_schema.get("required", [])
48+
assert "type" in required_fields
49+
assert "loc" in required_fields
50+
assert "msg" in required_fields
51+
assert "input" in required_fields
52+
assert "ctx" not in required_fields
53+
54+
55+
def test_pydantic_validation_error_response(client):
56+
resp = client.post(
57+
"/login",
58+
json={"invalid_field": "test"},
59+
content_type="application/json",
60+
)
61+
62+
assert resp.status_code == 422
63+
64+
errors = resp.json
65+
assert isinstance(errors, list)
66+
assert len(errors) > 0
67+
68+
error = errors[0]
69+
assert "type" in error
70+
assert "loc" in error
71+
assert "msg" in error
72+
assert "input" in error
73+
74+
assert isinstance(error["type"], str)
75+
assert isinstance(error["loc"], list)
76+
assert isinstance(error["msg"], str)
77+
assert error["type"] in ["missing", "string_type", "value_error", "extra_forbidden"]
78+
79+
80+
def test_pydantic_missing_field_error_response(client):
81+
resp = client.post(
82+
"/login",
83+
json={},
84+
content_type="application/json",
85+
)
86+
87+
assert resp.status_code == 422
88+
89+
errors = resp.json
90+
assert len(errors) >= 2
91+
92+
email_error = None
93+
for error in errors:
94+
if error.get("loc") == ["email"]:
95+
email_error = error
96+
break
97+
98+
assert email_error is not None
99+
assert email_error["type"] == "missing"
100+
assert email_error["msg"] == "Field required"
101+
assert email_error["input"] == {}

0 commit comments

Comments
 (0)