Commit ec5a125
* fix(rest): name the conflicting field in the UNIQUE_VIOLATION 409 body (#7821)
A single-record write violating a `unique` field answered with `code:
'UNIQUE_VIOLATION'`, the `object`, and no `field` — so on an object with
several unique fields the caller was told only that *a* value was taken,
and a client that wanted to render its own localized message could not
name the field either.
The platform already resolved this. Since #6544 the bulk/import path
runs `sanitizeRowError` -> `uniqueViolationColumn` and says "A record
with this `email` already exists."; the single-record branch held the
same error object, sat one import from the same helper, and withheld it.
One rule, two implementations, one strictly worse.
`mapDataError`'s conflict branch now resolves the column and puts it on
the wire as `field`, with the default message naming it. Convergence is
upward only — the bulk path is untouched.
Handing `uniqueViolationColumn` the ERROR rather than `error.message` is
load-bearing: `sanitizeRowError` only ever holds a string, so it reads
the message channel alone, while this site's object read also covers
`detail` and one step of `cause`. Measured, that is where the column is
for the Postgres driver we ship — node-postgres keeps its `DETAIL: Key
(email)=(...)` line on `error.detail`, off the message.
Degradation is the contract, not a fallback: an index name (MySQL
always, SQLite's `index 'x'` form), a composite key, or unparseable
driver prose yields the unnamed sentence and NO `field` key at all. A
wrong field name is worse than none — it sends the user to correct an
input that was never the problem.
The withholding this branch enforces is unchanged and re-pinned per
dialect: the offending user data, the index name and the `table.`
qualifier still never reach the wire (`sys_user.email` -> `email`).
The dialect table gains a third face — the `field`, and the dialects
that must carry none — plus the node-postgres `detail`, cause-wrapped,
composite and SQLite index-form shapes. Its "one fixed sentence" pin is
widened to exactly two sentences one bare identifier apart, rather than
relaxed, so driver text leaking into `body.error` still goes red.
Not addressed: the message is still built-in English. Localizing
platform-built-in error copy is one answer owed to this string, #7307
and `sanitizeRowError`'s siblings together.
Fixes #7821
* docs(protocol): document the `field` on the UNIQUE_VIOLATION 409 (#7821)
`http-protocol.mdx` showed the 409 body this PR changes, so it went stale
the moment the field landed — it documented the unnamed sentence with no
`field` at all. Flagged by the docs-drift check on #7930.
Shows the naming shape, and gives the omission its own callout: `field`
is best-effort and absent for an index name (MySQL always), a composite
key, or an unparseable message. Clients are told to key on `code` and
treat `field` as an enhancement — present when the platform can prove it,
never guessed.
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 3140f9c commit ec5a125
4 files changed
Lines changed: 358 additions & 17 deletions
File tree
- .changeset
- content/docs/protocol/kernel
- packages/rest/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
735 | 735 | | |
736 | 736 | | |
737 | 737 | | |
738 | | - | |
| 738 | + | |
| 739 | + | |
| 740 | + | |
| 741 | + | |
| 742 | + | |
| 743 | + | |
| 744 | + | |
| 745 | + | |
| 746 | + | |
| 747 | + | |
| 748 | + | |
| 749 | + | |
| 750 | + | |
| 751 | + | |
| 752 | + | |
| 753 | + | |
| 754 | + | |
| 755 | + | |
739 | 756 | | |
740 | 757 | | |
741 | 758 | | |
| |||
745 | 762 | | |
746 | 763 | | |
747 | 764 | | |
| 765 | + | |
| 766 | + | |
| 767 | + | |
| 768 | + | |
| 769 | + | |
| 770 | + | |
| 771 | + | |
748 | 772 | | |
749 | 773 | | |
750 | 774 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| 12 | + | |
12 | 13 | | |
13 | 14 | | |
14 | 15 | | |
| |||
1072 | 1073 | | |
1073 | 1074 | | |
1074 | 1075 | | |
1075 | | - | |
1076 | | - | |
1077 | | - | |
1078 | | - | |
1079 | | - | |
1080 | | - | |
1081 | | - | |
1082 | | - | |
1083 | | - | |
| 1076 | + | |
| 1077 | + | |
| 1078 | + | |
| 1079 | + | |
| 1080 | + | |
| 1081 | + | |
| 1082 | + | |
| 1083 | + | |
| 1084 | + | |
| 1085 | + | |
| 1086 | + | |
| 1087 | + | |
| 1088 | + | |
| 1089 | + | |
| 1090 | + | |
| 1091 | + | |
| 1092 | + | |
| 1093 | + | |
| 1094 | + | |
| 1095 | + | |
| 1096 | + | |
| 1097 | + | |
| 1098 | + | |
| 1099 | + | |
| 1100 | + | |
| 1101 | + | |
| 1102 | + | |
| 1103 | + | |
| 1104 | + | |
| 1105 | + | |
| 1106 | + | |
| 1107 | + | |
| 1108 | + | |
| 1109 | + | |
| 1110 | + | |
| 1111 | + | |
| 1112 | + | |
| 1113 | + | |
| 1114 | + | |
| 1115 | + | |
| 1116 | + | |
| 1117 | + | |
| 1118 | + | |
| 1119 | + | |
| 1120 | + | |
1084 | 1121 | | |
| 1122 | + | |
1085 | 1123 | | |
1086 | 1124 | | |
1087 | 1125 | | |
1088 | | - | |
| 1126 | + | |
| 1127 | + | |
| 1128 | + | |
1089 | 1129 | | |
| 1130 | + | |
1090 | 1131 | | |
1091 | 1132 | | |
1092 | 1133 | | |
| |||
0 commit comments