์ํ: ๊ตฌํ ์๋ฃ
์ด ๋ฌธ์๋ ๊ณ๋ฆฐ๋ API์ JSON ์ฑ๊ณตยท์ค๋ฅ ์๋ต ํ์๊ณผ ์ ์ญ ์์ธ ์ฒ๋ฆฌ ๊ท์น์ ์ ์ํ๋ค. /docs, /openapi.json๊ณผ body๊ฐ ์๋ 204 No Content ์๋ต์ envelope ๋์์ด ์๋๋ค.
JSON body๊ฐ ์๋ ๋ชจ๋ ์ ์ ์๋ต์ ๋ค์ ๊ตฌ์กฐ๋ฅผ ์ฌ์ฉํ๋ค.
{
"success": true,
"data": {
"id": "6b6e46da-65db-4b06-acbc-ecf4283fb03d"
},
"error": null
}success๋ ํญ์true๋ค.data์๋ ๊ธฐ์กด endpoint์ ์ค์ ์๋ต ๊ฐ์ฒด, ๋ฐฐ์ด ๋๋ ์ํ๊ฐ์ด ๋ค์ด๊ฐ๋ค.error๋ ํญ์null์ด๋ค.- ๋ชฉ๋ก์ด ๋น์ด ์์ผ๋ฉด
data๋[]๋ค. ์ฑ๊ณต ์๋ต์data๋ฅผnull๋ก ์ฌ์ฉํ์ง ์๋๋ค. - ๊ณผ์ ์ญ์ ์ฑ๊ณต์
204 No Content์ ๋น body๋ฅผ ์ ์งํ๋ค.
์ฒ๋ฆฌ ๊ฐ๋ฅํ ๋ชจ๋ API ์ค๋ฅ๋ ๋ค์ ๊ตฌ์กฐ๋ฅผ ์ฌ์ฉํ๋ค.
{
"success": false,
"data": null,
"error": {
"code": "ASSIGNMENT_NOT_FOUND",
"message": "๊ณผ์ ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.",
"details": null
}
}success๋ ํญ์false๋ค.data๋ ํญ์null์ด๋ค.error.code๋ ํด๋ผ์ด์ธํธ ๋ถ๊ธฐ์ฉ ์์ ์ ์ธ ์๋ฌธ ๋๋ฌธ์ code๋ค.error.message๋ ์ฌ์ฉ์์๊ฒ ํ์ ๊ฐ๋ฅํ ํ๊ตญ์ด ์ค๋ช ์ด๋ค.error.details๋ ์ ๋ ฅ ๊ฒ์ฆ ์ค๋ฅ์๋ง ๋ฐฐ์ด์ด๋ฉฐ ๊ทธ ์ธ์๋null์ด๋ค.- stack trace, SQL, ๋น๋ฐ๋ฒํธ, JWT, ์์ฒญ body๋ ์๋ต๊ณผ ๋ก๊ทธ์ ํฌํจํ์ง ์๋๋ค.
FastAPI/Pydantic ๊ฒ์ฆ ์คํจ๋ 422 VALIDATION_ERROR๋ก ๋ณํํ๋ค.
{
"success": false,
"data": null,
"error": {
"code": "VALIDATION_ERROR",
"message": "์์ฒญ ๊ฐ์ด ์ฌ๋ฐ๋ฅด์ง ์์ต๋๋ค.",
"details": [
{
"field": "body.studentNumber",
"message": "String should match pattern '^[0-9]{5}$'",
"type": "string_pattern_mismatch"
}
]
}
}๊ฒ์ฆ detail์ field, message, type๋ง ์ ๊ณตํ๋ค. Pydantic์ ์
๋ ฅ ์๋ฌธ๊ณผ ๋ด๋ถ context๋ ๋
ธ์ถํ์ง ์๋๋ค.
| ๋ฐ์ ์์ธ | HTTP ์ํ | ๊ธฐ๋ณธ code | ์ฒ๋ฆฌ ๊ท์น |
|---|---|---|---|
๋ช
์์ HTTPException |
์๋ ์ํ ์ ์ง | ์์ธ์ ์ง์ ๋ code | ๊ธฐ์กด codeยทmessage์ header ๋ณด์กด |
| ๋ผ์ฐํ 404 | 404 |
NOT_FOUND |
์กด์ฌํ์ง ์๋ API ๊ฒฝ๋ก |
| ํ์ฉ๋์ง ์์ method | 405 |
METHOD_NOT_ALLOWED |
framework ์ค๋ฅ๋ฅผ ๊ณตํต ํ์์ผ๋ก ๋ณํ |
RequestValidationError |
422 |
VALIDATION_ERROR |
์์ ํ field detail๋ง ๋ฐํ |
SQLAlchemyError |
503 |
DATABASE_UNAVAILABLE |
๋ด๋ถ ์ค๋ฅ๋ฅผ ๊ธฐ๋กํ๊ณ DB ์ ๋ณด๋ ์จ๊น |
| ๊ทธ ์ธ ์ฒ๋ฆฌ๋์ง ์์ ์์ธ | 500 |
INTERNAL_SERVER_ERROR |
๋ด๋ถ ์ค๋ฅ๋ฅผ ๊ธฐ๋กํ๊ณ ๊ณ ์ ๋ฉ์์ง ๋ฐํ |
์ธ์ฆ ์ค๋ฅ์ WWW-Authenticate: Bearer์ฒ๋ผ ๊ธฐ์กด HTTP header๊ฐ ์์ผ๋ฉด ์ ์ญ handler๊ฐ ๊ทธ๋๋ก ์ ๋ฌํ๋ค.
| Code | ์ํ | ์๋ฏธ |
|---|---|---|
EMAIL_ALREADY_EXISTS |
409 |
์ด๋ฏธ ์ฌ์ฉ ์ค์ธ ์ด๋ฉ์ผ |
INVALID_CREDENTIALS |
401 |
๋ก๊ทธ์ธ ์ ๋ณด ๋ถ์ผ์น |
INVALID_TOKEN |
401 |
token ์์, ์์กฐ, ๋ง๋ฃ ๋๋ ์ฌ์ฉ์ ์์ |
ASSIGNMENT_NOT_FOUND |
404 |
๊ณผ์ ๊ฐ ์๊ฑฐ๋ ํ์ฌ ์ฌ์ฉ์ ์์ ๊ฐ ์๋ |
INVALID_IMAGE |
400 |
์ง์ํ์ง ์๊ฑฐ๋ ์ ํ์ ์ด๊ณผํ ์ด๋ฏธ์ง |
AI_RATE_LIMITED |
429 |
์ฌ์ง ๋ถ์ ํธ์ถ ๋๋ ์ธ๋ถ ์ฌ์ฉ๋ ์ ํ ๋๋ฌ |
AI_EXTRACTION_FAILED |
502 |
์ธ๋ถ ์ฌ์ง ๋ถ์ ์คํจ ๋๋ ์ ํจํ ๊ตฌ์กฐํ ๊ฒฐ๊ณผ ์์ |
AI_EXTRACTION_DISABLED |
503 |
์ฌ์ง ๋ถ์ ๊ธฐ๋ฅ ๋นํ์ฑํ ๋๋ API key ๋ฏธ์ค์ |
์
๋ฌด ์ค๋ฅ๋ฅผ ์๋ก ์ถ๊ฐํ ๋ endpoint์์๋ HTTPException์ code์ message๋ง ์ง์ ํ๊ณ , JSON envelope ์์ฑ์ ์ ์ญ handler์ ๋งก๊ธด๋ค.
app/responses.py: ์ฑ๊ณตยท์ค๋ฅ Pydantic model๊ณผ ์ฑ๊ณต helperapp/exceptions.py: HTTP, ๊ฒ์ฆ, DB, ๋ฏธ์ฒ๋ฆฌ ์์ธ handler์ ๋ฑ๋ก ํจ์app/main.py: handler๋ฅผ ์ ํ๋ฆฌ์ผ์ด์ ์ ํ ๋ฒ ๋ฑ๋ก- ๊ธฐ์กด router: ์ฑ๊ณต response model์ ๊ณตํต generic envelope๋ก ๋ณ๊ฒฝ
middleware๋ก ์๋ต body๋ฅผ ์ฌํ ๋ณํํ์ง ์๋๋ค. endpoint์ response_model ์์ฒด๊ฐ ์ค์ envelope๋ฅผ ํํํด์ผ OpenAPI์ ๋ฐํ์ ์๋ต์ด ์ผ์นํ๋ค.
| ์๋๋ฆฌ์ค | ๊ธฐ๋ ๊ฒฐ๊ณผ |
|---|---|
| ๊ฐ์ ยท๋ก๊ทธ์ธยท๊ณผ์ ยท๋์๋ณด๋ ์ฑ๊ณต | success=true, ์ค์ ๊ฐ์ data, error=null |
| ๋น ๊ณผ์ ๋ชฉ๋ก | data=[] |
| ๊ณผ์ ์ญ์ | 204, ๋น body |
| ์๋ชป๋ ์ ๋ ฅ | 422 VALIDATION_ERROR, ์์ ํ details |
| ๊ธฐ์กด ์ธ์ฆยท์ ๋ฌด ์ค๋ฅ | ๊ธฐ์กด HTTP ์ํ์ code ์ ์ง |
| ์กด์ฌํ์ง ์๋ ๊ฒฝ๋ก | 404 NOT_FOUND ๊ณตํต ์ค๋ฅ ์๋ต |
| DB ์์ธ | 503 DATABASE_UNAVAILABLE, SQL ๋ฏธ๋
ธ์ถ |
| ์์ํ์ง ๋ชปํ ์์ธ | 500 INTERNAL_SERVER_ERROR, ๋ด๋ถ ๋ฉ์์ง ๋ฏธ๋
ธ์ถ |
| OpenAPI | ์ฑ๊ณต endpoint๊ฐ ApiResponse[...] schema๋ฅผ ํ์ |
- JSON body๋ฅผ ๋ฐํํ๋ ๋ชจ๋ ํ์ฌ endpoint๊ฐ ์ฑ๊ณต envelope๋ฅผ ์ฌ์ฉํ๋ค.
- framework์ ์ ํ๋ฆฌ์ผ์ด์ ์ค๋ฅ๊ฐ ๊ณตํต ์ค๋ฅ envelope๋ฅผ ์ฌ์ฉํ๋ค.
- ์ธ์ฆ header์ ์ ๋ฌด ์ค๋ฅ code๊ฐ ๋ณด์กด๋๋ค.
- validation ์๋ต์ ์์ฒญ ์ ๋ ฅ ์๋ฌธ์ด ํฌํจ๋์ง ์๋๋ค.
204์ OpenAPI ๋ฌธ์ endpoint๊ฐ ๋ถํ์ํ๊ฒ ๊ฐ์ธ์ง์ง ์๋๋ค.make check์git diff --check๊ฐ ์ฑ๊ณตํ๋ค.