Repository navigation
Commit 0b5b176
fix(data-objectstack): a request that got no answer no longer reads as an answered 500 (#29915)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} -->
_Requested by **Steedos** · [project
thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9UbbLoUpNB6DHdnmokKa35qYC8)_
Fixes #29885
Clause-②: no
Metadata-protocol: none
## 维护者速读
- **改了什么**:请求根本没有得到服务器回答时(断网、主机不可达),适配器抛出的错误不再带一个编造的 500
状态码;服务器真正回答过的错误(400/401/403/404/500/503/504 等)照旧。
- **为什么改**:带着 500 的断网错误被所有读取方当成「服务器已回答」,于是界面把浏览器的 "Failed to fetch"
当作服务器原话展示,而本该提示用户检查网络连接;#29728 只在路由读取一处绕开了它,其余读取方仍然受骗。
- **风险与回滚**:风险低。只在输入没有状态码时行为变化(`statusCode` 由 500 变为未定义,错误类与 code
不变);已知读取方中无人依赖这个 500(见下方清点)。回滚即还原本 PR。
-
**先看**:`ui/packages/data-objectstack/src/errors.ts`(`createErrorFromResponse`
的状态码默认值)、`ui/packages/react/src/utils/error-message.adapter-unanswered-29885.test.ts`(真实适配器
+ 共享读取规则的边界钉)。
- **两分钟验证**:在 `ui/` 下运行 `pnpm exec vitest run
packages/react/src/utils/error-message.adapter-unanswered-29885.test.ts
packages/data-objectstack/src/errors.test.ts`,预期全部通过,其中断网用例
`answeredLoadFailure` 为 null、`classifyLoadError` 为 network。
- **需要你做的**:无,席位落地。
## What changes
`createErrorFromResponse` read `status || statusCode || 500`. A
`TypeError` from `fetch` (no answer at all) has no status, so it came
back as `ObjectStackError` `UNKNOWN_ERROR` with `statusCode: 500`.
`ObjectStackAdapter.getObjectSchema` rewraps every non-404 failure
through it, and `answeredLoadFailure` reads any numeric status as the
server's answer. So every reader of that wrapper showed the browser's
"Failed to fetch" as the server's sentence, where it should have said
"check your connection".
- **`errors.ts`**: a status-less input now comes back with `statusCode`
undefined (and `details.response.status` undefined). It is still an
`ObjectStackError` with code `UNKNOWN_ERROR`, a shape
`classifyLoadError` already reads as `network` and `answeredLoadFailure`
as no answer, so neither reader changes. An input that is an `Error` is
kept as `details.originalError`, whatever its status (a plain response
object is not, so a circular axios response never lands in `toJSON`).
Answered statuses keep today's mapping: 400, 401, 403, 404, 503, 504 and
any other status keep their class, code and `statusCode`. `statusCode`
was already declared optional, so no exported type changes.
- **`index.ts` (comment correction, no code)**: #29728's comment on the
`getObjectSchema` rewrap said `createErrorFromResponse` "stamps `500`"
on a status-less error. It now says why the cause is still needed: the
wrapper replaces the server's code and sentence with its own.
- `MetadataProvider`'s `details.originalError` unwrap (#29728) is kept,
and is not redundant: an answered 503's wrapper still reads
`CONNECTION_ERROR` "Connection error: ...", so the server's own code and
sentence live only on the cause.
## Tests
- `errors.test.ts`: the "response without status code" pin moved from
`500` to undefined (it pinned the defect). A new describe pins a fetch
`TypeError`: no status, `UNKNOWN_ERROR`, not a `ConnectionError`,
message kept, the input as `details.originalError`, and
`toJSON().statusCode` undefined. Its CONTROLs are an answered 500 and an
answered 503 that keep their status, class and cause, plus a plain
response object that carries no cause.
- `error-message.adapter-unanswered-29885.test.ts` (new, in
`@object-ui/react`): the REAL `ObjectStackAdapter.getObjectSchema` over
a fake `fetch` that rejects with a `TypeError`. The result has no
`statusCode`, `answeredLoadFailure` is `null`, and `classifyLoadError`
is `network`. CONTROLs: an answered 500 and an answered 503 keep their
status and read as answered. This file sits in `@object-ui/react`,
outside the card's named surface, because the adapter cannot import that
reader (react depends on data-objectstack).
`error-message.normalisation-boundary.test.ts` is the precedent. The
seat accepted the deviation.
- `getObjectSchema.test.ts`: **moved pin.** #29728's network-drop case
asserted `statusCode` 500, and it now asserts undefined. Class, code,
message and cause are unchanged.
<details><summary>Ablation (committed head ee241ae,
ablation-replace.mjs, restored blob == HEAD)</summary>
- Put `|| 500` back in `createErrorFromResponse`: 3 tests went red.
Those were the moved "without status code" pin, the `TypeError` pin in
`errors.test.ts`, and the react boundary pin. All 40 other tests stayed
green, including every CONTROL. The restored blob was `304622c6` ==
HEAD, with `git diff HEAD` empty.
</details>
<details><summary>Verification (final head 8923ea2)</summary>
- `pnpm exec vitest run packages/data-objectstack/
packages/react/src/utils/
packages/app-shell/src/console/__tests__/AppContent.disabledPackageDeepLink-13052.test.tsx`:
123 files and 1418 tests passed. The last file is #29728's real-adapter
route harness, and its network-drop CONTROL still draws the network
sentence.
- Consumer sweep: all 57 test files outside data-objectstack that build
a real `ObjectStackAdapter` (app-shell and console). 2 batches, 29 files
/ 291 tests and 28 files / 161 tests, all green. This ran at ee241ae,
before the main merge, and the touched source has not changed since
apart from the comment.
- `turbo run type-check --filter=@object-ui/data-objectstack
--filter=@object-ui/react`: 43/43 tasks.
- Derived gates (`dispatch-gates --commands`, 36 at 8923ea2): all exit
0, including `pnpm --dir ui exec vitest run scripts/` (170 files passed,
2 skipped).
</details>
## Acceptance notes
- **Census of readers of a 500 / >=500 status.** `RecordDetailView`'s
`feedReadVerdictOf` treats undefined and >=500 the same way (`failed`).
`plugin-chatbot` `usePendingActions` and mobile
`createOfflineDataSource` read `.status`, not `statusCode`. None
changes, and none depends on the 500. Every other reader goes through
`classifyLoadError` / `answeredLoadFailure`, and those now read a
network drop as no answer, as intended.
- **Out of scope (ruling):** the wrapper still replaces the server's
ADR-0112 `code` with its own taxonomy code (`CONNECTION_ERROR`,
`UNKNOWN_ERROR`). Carrying the server's code beside it is not done here,
and readers get it from `details.originalError`.
- **Finding, handed to the seat:** `ConnectionError`'s constructor
stamps `statusCode || 503`. The adapter builds one with no status on two
paths where nothing answered (`connect()` and `noteServerUnanswered`),
so those errors read as an answered 503.
`connectRetriesDiscovery-12318.test` pins that 503, so changing it is a
caller change, and it is left as is.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QsR2jTkV5mfMT5LzJyxGdX)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent cfd237f commit 0b5b176
6 files changed
Lines changed: 161 additions & 13 deletions
File tree
- .changeset
- ui/packages
- data-objectstack/src
- react/src/utils
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
353 | 353 | | |
354 | 354 | | |
355 | 355 | | |
356 | | - | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
357 | 407 | | |
358 | 408 | | |
359 | 409 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
217 | 217 | | |
218 | 218 | | |
219 | 219 | | |
220 | | - | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
221 | 229 | | |
222 | 230 | | |
223 | 231 | | |
224 | 232 | | |
225 | 233 | | |
226 | | - | |
| 234 | + | |
227 | 235 | | |
228 | 236 | | |
229 | 237 | | |
| |||
232 | 240 | | |
233 | 241 | | |
234 | 242 | | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
235 | 246 | | |
236 | 247 | | |
237 | 248 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
150 | 150 | | |
151 | 151 | | |
152 | 152 | | |
153 | | - | |
154 | | - | |
155 | | - | |
156 | | - | |
157 | | - | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
158 | 157 | | |
159 | 158 | | |
160 | 159 | | |
| |||
196 | 195 | | |
197 | 196 | | |
198 | 197 | | |
199 | | - | |
| 198 | + | |
200 | 199 | | |
201 | 200 | | |
202 | 201 | | |
203 | 202 | | |
204 | 203 | | |
205 | 204 | | |
206 | 205 | | |
207 | | - | |
| 206 | + | |
208 | 207 | | |
209 | 208 | | |
210 | 209 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5717 | 5717 | | |
5718 | 5718 | | |
5719 | 5719 | | |
5720 | | - | |
5721 | | - | |
5722 | | - | |
| 5720 | + | |
| 5721 | + | |
| 5722 | + | |
5723 | 5723 | | |
5724 | 5724 | | |
5725 | 5725 | | |
| |||
| 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 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
0 commit comments