The single most common source of bugs in Wagoe projects.
Three naming styles are used at three different system boundaries:
| Wagoe | Convention | Example |
|---|
All Clojure code (everywhere) | kebab-case
| :password-hash, :created-at, :user-id
|
Database (at persistence boundary only) | snake_case
| password_hash, created_at, user_id
|
API / JSON | kebab-case
| password-hash, created-at, user-id
|
Never mix these inside application code. Convert only at the exact boundary.
JSON keys are the Clojure keys: the HTTP layer converts nothing, so a scaffolded
API, the workflow API and the tenant API all answer in kebab-case. The one
exception is the framework’s own user API (wagoe-user), which answers — and,
for MFA, reads — camelCase (createdAt, verificationCode).
(require '[wagoe.core.utils.case-conversion :as cc])
;; Persistence boundary: DB record → Clojure entity
(cc/snake-case->kebab-case-map db-record)
;; Persistence boundary: Clojure entity → DB insert/update
(cc/kebab-case->snake-case-map entity)
;; Only when a client needs camelCase JSON — Wagoe's APIs do not convert
(cc/kebab-case->camel-case-map entity)
(cc/camel-case->kebab-case-map api-input)
;; Bug: authentication failure because two places used different case
;; service.clj used :password_hash (snake)
;; user entity had :password-hash (kebab)
;; Result: nil comparison, login always fails
;; Fix: always kebab-case internally
(defn authenticate [user input]
(buddy/check (:password input) (:password-hash user))) ; kebab-case ✅