錯誤處理(上)HttpError 與自定義 HTTP 回應

2024 iThome 鐵人賽

這是 Django Ninja 系列教學的第 21 篇。

在軟體開發中,錯誤處理是一個不容忽視——但常常被忽視——的環節。

不誇張地說,錯誤處理是一個「做得好沒人誇,做不好系統就慘兮兮」的議題。

沒關係,我們還是盡可能把自己做好

Django Ninja 使用 Pydantic 進行資料驗證,失敗時,預設回應「422 Unprocessable Entity」。

然而,我們有時候需要回應「400 Bad Request」或別的狀態碼,以符合現實業務需求團隊開發習慣

總之,無論出於何種原因,我們想自訂錯誤訊息、格式,以及回應的狀態碼,而不使用 Django Ninja 預設的 422 回應——不得不說,這個制式回應的資訊有點多、結構有點複雜,因為它要兼容各種情況。

本文將介紹如何自定義錯誤處理與回應——使用 Django Ninja 內建的HttpError

所有的程式碼改動,可參考這個 PR


Django Ninja 的自動錯誤處理

上一篇我們提到,如果你在 Schema 的驗證方法中,拋出ValueError錯誤,Django Ninja 將會自動捕捉並回應

事實上,不止ValueError,Django Ninja 還會替你處理以下這幾種錯誤:

  • pydantic.ValidationError,來自 Pydantic 的驗證錯誤,這是為何當 Schema 欄位有問題時,我們會直接收到 422 回應。
  • 此外,Django Ninja 還內建了一個 ninja.errors.ValidationError,這些錯誤同樣會返回 422。
  • ninja.errors.HttpError:這是本文的重點,下面會介紹。

這些都是 Django Ninja 會自動捕捉的錯誤,但不是每一種都給出制式的 422 回應——第三種就不是。

資料驗證(下)Pydantic 跨欄位驗證

2024 iThome 鐵人賽

這是 Django Ninja 系列教學的第 20 篇。

上一篇我們講完了單一欄位的自定義驗證,這篇則要來討論跨欄位之間的驗證。

跨欄位驗證同樣是 API 開發中十分常見的需求,例如註冊帳號時,要保證「密碼」與「確認密碼」兩個欄位內容相同;選擇日期期間時,開始日期不能晚於結束日期等。

這些驗證場景無法透過單一欄位驗證實現,因為它們需要同時檢查多個欄位之間的邏輯關聯,來確保整體資料的一致性和正確性

本文將介紹如何透過 Pydantic 來實現跨欄位驗證需求——以「確認密碼」為例,展示這個功能的實際應用。

本文所有的程式碼改動,可參考這個 PR


跨欄位驗證與關注點分離

其實,無論是單一欄位還是跨欄位的自定義驗證,都不一定要藉由 Pydantic 來完成。

理論上,資料驗證可以直接在 view 函式中進行,例如取出輸入的欄位值,手動驗證它的合法性。跨欄位驗證也是如此。

然而,這是一種方便但「粗糙」的做法——只適合用在驗證邏輯非常單純的情況。

透過 Pydantic 進行資料驗證,則能夠帶來一個明顯的好處:關注點分離

資料驗證(上)Pydantic 單一欄位驗證

2024 iThome 鐵人賽

這是 Django Ninja 系列教學的第 19 篇。進入第五章:資料驗證與錯誤處理

資料驗證是 API 開發中的關鍵需求之一,它負責確保從客戶端提交的資料是符合預期的,從而避免潛在的錯誤和安全問題。

有效的資料驗證可以在 API 接收到錯誤資料時,給出即時且友善的回應,提升系統的穩定性和使用者體驗

Django Ninja 中,資料驗證的核心工具是 Pydantic。它提供了強大的驗證功能,不僅能對資料型別進行檢查,還能輕鬆實現自定義驗證

本文將介紹如何在 Django Ninja 中使用 Pydantic 實作單一欄位的自定義驗證;下一篇則講述跨欄位的自定義驗證

本文所有的程式碼變動,可參考這個 PR


第五章總論

資料驗證很重要,而驗證失敗時,程式往往會拋出驗證錯誤。如何有效處理這些錯誤,則是「錯誤處理」要討論的範疇。

本章將探討這兩個密切相關的主題,共計 4 篇文章:

前兩篇,我們會學習如何實現靈活的資料驗證,以確保輸入資料符合預期,並在必要時拋出錯誤。

後兩篇,我們將討論如何處理 API 流程中可能出現的各種錯誤(不限於驗證錯誤),以提供更好的使用者體驗。

Django Ninja 的資料驗證與錯誤處理機制,相較 Django REST framework 更加複雜,因此我們得用完整的篇幅來介紹,幫助你清楚地理解它們。

API 文件(下)Pydantic Field 設定範例與預設值

2024 iThome 鐵人賽

這是 Django Ninja 系列教學的第 18 篇。

上一篇文章中,我們探討了 Django Ninja 影響 API 文件呈現的一些重要設定。它們是自動化 API 文件的基本功,不容忽視。

但這樣還不夠!我們想要讓這份文件更加生動,讀起來清晰易懂。

其中的關鍵在於 API 文件上的資料範例。好的範例讓人一讀就懂,能有效縮短理解和思考的時間。

本文將介紹如何運用 Pydantic 的Field設定,全方位提升 API 文件的清晰與可讀性。我們會探討如何為自動生成的文件加上栩栩如生的範例,讓文件更貼近真實

本文所有的程式碼變動,可參考這個 PR


Pydantic 在 Django Ninja 中的角色

Pydantic 是一個實現資料驗證、序列化的套件,廣泛應用於 FastAPI 和 Django Ninja 等框架。

在 Django Ninja 中,Pydantic 被用來定義 Schema,這些 Schema 決定了 API 如何處理 HTTP 請求和回應中的資料,並自動轉換成符合 OpenAPI 標準的文件

Pydantic 的強大之處在於,它不僅能驗證資料,還可以通過Field設定,為文件欄位提供額外的說明、範例和預設值

這些細節設定會自動反映在轉換後的 API 文件中,幫助開發者更好地理解 API 的行為與內涵。

API 文件(上)Django Ninja 文件實踐指南

2024 iThome 鐵人賽

這是 Django Ninja 系列教學的第 17 篇。我們開始第四章——API 文件

依程式碼自動產生 API 文件」是 Django Ninja 的一大賣點。

事實上,API 文件的自動化,正是我在工作上的專案從 Django REST framework 轉向 Django Ninja 的首要考量——也是我開始學習 Django Ninja 的契機。

Django Ninja 省去了大量人工撰寫(我們用 API Blueprint)API 文件的時間,特別是在 API 規格變動時,不需要再同步修改文件,大大減少了維護文件的心力。

可見這個特性有多麼重要。

教學順序的考量

那麼,為何我到了系列的第 17 篇文章——也就是本篇,才開始介紹 Django Ninja 的 API 文件功能呢?

原因在於,要產生優秀的 API 文件,需要你對 Schema 的使用有一定的了解。所以我不得不放在第三章之後。

現在,我們要開始探討如何使用 Django Ninja 產出高品質的 API 文件。

本文所有的程式碼變動,可參考這個 PR

回應(四)Resolver 方法——欄位資料格式化

2024 iThome 鐵人賽

這是 Django Ninja 系列教學的第 16 篇。

上一篇提到,API 回應常常是對 Django Model 物件內容的篩選與加工——然後 JSON 序列化。

其中「加工」部分,用更專業的說法,大概是「資料格式化」——依照一定的規則,對輸出資料進行某種轉換或重新組織,以符合特定的輸出格式

資料格式化的種類很多,例如:

  1. 時間格式轉換:將資料庫中的時間戳(timestamp),轉換為更易讀的格式。
  2. 數值轉換:將數字轉換為貨幣格式,或將小數點位數進行四捨五入。
  3. 字串處理:截斷過長的文字、加上統一的前綴等。

不論原因為何,絕大部分時候都是為了資料的「可讀性」,或符合特定業務規則。

可想而知,像資料格式化這樣的需求,不僅實務上重要,在 API 開發中也十分常見,值得我們用一整篇文章,細細探討。

本文所有的程式碼變動,可參考這個 PR


場景與需求

再次回到「取得單一文章資訊」API,這是目前的回傳格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
// http://127.0.0.1:8000/posts/2/
{
"id": 2,
"title": "Alice's Django Ninja Post 1",
"content": "Alice's Django Ninja Post 1 content",
"author": {
"id": 1,
"username": "Alice",
"email": "alice@example.com"
},
"created_at": "2024-09-12T02:28:16.801Z",
"updated_at": "2024-09-12T02:28:16.801Z"
}

我們決定簡化回應的時間字串,改採「"2024-09-12T02:28:16Z"」格式。

和舊版相比,只是少了「.801」這個小數部分而已,且依舊符合 ISO 8601 標準。

總之,回應中created_atupdated_at兩個欄位的內容,需要進行格式上的轉換。即上述提到的「資料格式化」。

回應(三)為何不用 ModelSchema?——相比 DRF,我更偏愛 Django Ninja 的理由

2024 iThome 鐵人賽

這是 Django Ninja 系列教學的第 15 篇。

Django API 回應,常常是對 Model 物件(即 db 資料)內容進行一定的篩選與加工

比如「取得單一文章資訊」API,實際上就是從Post物件挑選欄位,再進行序列化。

這個過程中,我們需要考慮如何將模型物件轉換為 API 的回應結構,同時保持程式碼的可維護性與靈活。

對此,Django REST framework(以下簡稱 DRF)提供了非常實用的「特製」序列化器——ModelSerializer,可說是 DRF 開發者必學的核心功能。

Django Ninja 雖然也有類似的實踐——ModelSchema,對我而言卻是雞肋般的存在,我幾乎不曾使用

這樣的差異,無疑是兩者的核心設計理念不同所導致。

我們曾在第 3 篇中討論過,兩者在功能上的主要區別。本文將透過「Django 模型物件的序列化」這個頗具代表性的議題,說明「為何相比於 DRF,我更喜歡寫 Django Ninja」。