@webpieces/http-client-core 0.4.694 → 0.4.696

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/http-client-core",
3
- "version": "0.4.694",
3
+ "version": "0.4.696",
4
4
  "description": "Isomorphic core of the webpieces HTTP client: the decorator-driven ProxyClient, error translation, and the Proxy trap shared by http-client-node and http-client-browser",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -21,6 +21,6 @@
21
21
  "access": "public"
22
22
  },
23
23
  "dependencies": {
24
- "@webpieces/core-util": "0.4.694"
24
+ "@webpieces/core-util": "0.4.696"
25
25
  }
26
26
  }
@@ -13,6 +13,10 @@ import { TranslatedFailure } from './TranslatedFailure';
13
13
  * This achieves symmetric error handling - server throws typed exceptions,
14
14
  * client receives typed exceptions.
15
15
  *
16
+ * The symmetry is in the TYPE and the structured fields, NOT in the prose: the server sends the real
17
+ * `Error.message` for `HttpUserError` alone and a generic reason phrase for everything else. See
18
+ * {@link builtInError} and, on the server, `HttpErrorWireMapper`.
19
+ *
16
20
  * It returns a {@link TranslatedFailure} rather than a bare `Error` because the mapping is only HALF
17
21
  * the decision. It is ISOMORPHIC — the same mapping runs in a browser and in a server — and the two
18
22
  * environments must NOT do the same thing with a downstream 4xx (see
@@ -37,16 +41,34 @@ export declare class ClientErrorTranslator {
37
41
  * The built-in status → error mapping (symmetric with the server's ExpressWrapper.handleError()):
38
42
  * - 400 → HttpBadRequestError (with field, guiAlertMessage)
39
43
  * - 266 → HttpUserError (with errorCode) - 2xx code for user validation
40
- * - 401 → HttpUnauthorizedError
44
+ * - 401 → HttpUnauthorizedError (with subType)
41
45
  * - 403 → HttpForbiddenError
42
46
  * - 404 → HttpNotFoundError
43
47
  * - 408 → HttpTimeoutError
48
+ * - 429 → HttpTooManyRequestsError
44
49
  * - 500 → HttpInternalServerError
45
50
  * - 502 → HttpBadGatewayError
46
51
  * - 503 → HttpServiceUnavailableError
47
52
  * - 504 → HttpGatewayTimeoutError
48
53
  * - 598 → HttpVendorError (with waitSeconds) - custom status code
49
54
  * - other → generic HttpError
55
+ *
56
+ * # What `message` means on THIS side of the wire
57
+ *
58
+ * The reconstructed error carries whatever text the wire carried, and for every status except
59
+ * **266** a webpieces server deliberately sends only the GENERIC reason phrase — 'Not Found',
60
+ * 'Internal Server Error', … See `HttpErrorWireMapper` (http-server) for why: `Error.message` is
61
+ * an operator-facing field that routinely quotes internal detail, so it stays in the server's log
62
+ * and never reaches a caller. `HttpUserError` (266) is the one type whose message was WRITTEN for
63
+ * a human to read, and it arrives verbatim.
64
+ *
65
+ * So: branch on the TYPE, on `subType`, on `errorCode`, or on `guiAlertMessage` — never on the
66
+ * prose of `message`. It is now a constant per status by design, and treating it as diagnostic
67
+ * information will not work against a current webpieces server. The diagnosis lives in the
68
+ * server's logs, correlated by request id.
69
+ *
70
+ * (An app that publishes richer text on purpose does it through
71
+ * `ClientRegistry.addErrorTranslation()`, which is consulted before this mapping on both sides.)
50
72
  */
51
73
  private static builtInError;
52
74
  }
@@ -16,6 +16,10 @@ const TranslatedFailure_1 = require("./TranslatedFailure");
16
16
  * This achieves symmetric error handling - server throws typed exceptions,
17
17
  * client receives typed exceptions.
18
18
  *
19
+ * The symmetry is in the TYPE and the structured fields, NOT in the prose: the server sends the real
20
+ * `Error.message` for `HttpUserError` alone and a generic reason phrase for everything else. See
21
+ * {@link builtInError} and, on the server, `HttpErrorWireMapper`.
22
+ *
19
23
  * It returns a {@link TranslatedFailure} rather than a bare `Error` because the mapping is only HALF
20
24
  * the decision. It is ISOMORPHIC — the same mapping runs in a browser and in a server — and the two
21
25
  * environments must NOT do the same thing with a downstream 4xx (see
@@ -48,16 +52,34 @@ class ClientErrorTranslator {
48
52
  * The built-in status → error mapping (symmetric with the server's ExpressWrapper.handleError()):
49
53
  * - 400 → HttpBadRequestError (with field, guiAlertMessage)
50
54
  * - 266 → HttpUserError (with errorCode) - 2xx code for user validation
51
- * - 401 → HttpUnauthorizedError
55
+ * - 401 → HttpUnauthorizedError (with subType)
52
56
  * - 403 → HttpForbiddenError
53
57
  * - 404 → HttpNotFoundError
54
58
  * - 408 → HttpTimeoutError
59
+ * - 429 → HttpTooManyRequestsError
55
60
  * - 500 → HttpInternalServerError
56
61
  * - 502 → HttpBadGatewayError
57
62
  * - 503 → HttpServiceUnavailableError
58
63
  * - 504 → HttpGatewayTimeoutError
59
64
  * - 598 → HttpVendorError (with waitSeconds) - custom status code
60
65
  * - other → generic HttpError
66
+ *
67
+ * # What `message` means on THIS side of the wire
68
+ *
69
+ * The reconstructed error carries whatever text the wire carried, and for every status except
70
+ * **266** a webpieces server deliberately sends only the GENERIC reason phrase — 'Not Found',
71
+ * 'Internal Server Error', … See `HttpErrorWireMapper` (http-server) for why: `Error.message` is
72
+ * an operator-facing field that routinely quotes internal detail, so it stays in the server's log
73
+ * and never reaches a caller. `HttpUserError` (266) is the one type whose message was WRITTEN for
74
+ * a human to read, and it arrives verbatim.
75
+ *
76
+ * So: branch on the TYPE, on `subType`, on `errorCode`, or on `guiAlertMessage` — never on the
77
+ * prose of `message`. It is now a constant per status by design, and treating it as diagnostic
78
+ * information will not work against a current webpieces server. The diagnosis lives in the
79
+ * server's logs, correlated by request id.
80
+ *
81
+ * (An app that publishes richer text on purpose does it through
82
+ * `ClientRegistry.addErrorTranslation()`, which is consulted before this mapping on both sides.)
61
83
  */
62
84
  // webpieces-disable no-function-outside-class -- private helper of the static above; same reason
63
85
  static builtInError(response, protocolError) {
@@ -77,6 +99,14 @@ class ClientErrorTranslator {
77
99
  return new core_util_1.HttpNotFoundError(message);
78
100
  case 408:
79
101
  return new core_util_1.HttpTimeoutError(message);
102
+ case 429:
103
+ // The server has always been able to throw this (HttpErrorWireMapper sends
104
+ // 'Too Many Requests' for it); the client had no case for it, so it arrived as a bare
105
+ // HttpError and callers were pushed back to `err.code === 429` — the untyped pattern
106
+ // this ladder exists to replace. That gap bites harder now that `message` is a
107
+ // constant per status: branching on the TYPE is the only thing left, so every status
108
+ // the server can emit needs one.
109
+ return new core_util_1.HttpTooManyRequestsError(message);
80
110
  case 500:
81
111
  return new core_util_1.HttpInternalServerError(message);
82
112
  case 502:
@@ -1 +1 @@
1
- {"version":3,"file":"ClientErrorTranslator.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-core/src/ClientErrorTranslator.ts"],"names":[],"mappings":";;;AAAA,oDAe8B;AAC9B,2DAAwD;AAExD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAa,qBAAqB;IAC9B;;;;;;;;;;;OAWG;IACH,yNAAyN;IACzN,MAAM,CAAC,cAAc,CAAC,QAAkB,EAAE,aAA4B;QAClE,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC;QAEnC,MAAM,MAAM,GAAG,0BAAc,CAAC,oBAAoB,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;QAC9E,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACvB,OAAO,IAAI,qCAAiB,CAAC,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;QAC3D,CAAC;QAED,OAAO,IAAI,qCAAiB,CACxB,qBAAqB,CAAC,YAAY,CAAC,QAAQ,EAAE,aAAa,CAAC,EAC3D,KAAK,EACL,UAAU,CACb,CAAC;IACN,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACH,iGAAiG;IACzF,MAAM,CAAC,YAAY,CAAC,QAAkB,EAAE,aAA4B;QACxE,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC;QACnC,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,IAAI,QAAQ,CAAC,UAAU,IAAI,eAAe,CAAC;QAChF,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,CAAC;QAEtC,QAAQ,UAAU,EAAE,CAAC;YACjB,KAAK,GAAG;gBACJ,OAAO,IAAI,+BAAmB,CAC1B,OAAO,EACP,aAAa,CAAC,KAAK,EACnB,aAAa,CAAC,eAAe,CAChC,CAAC;YAEN,KAAK,GAAG,EAAE,sDAAsD;gBAC5D,OAAO,IAAI,yBAAa,CAAC,OAAO,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;YAE/D,KAAK,GAAG;gBACJ,OAAO,IAAI,iCAAqB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAEvD,KAAK,GAAG;gBACJ,OAAO,IAAI,8BAAkB,CAAC,OAAO,CAAC,CAAC;YAE3C,KAAK,GAAG;gBACJ,OAAO,IAAI,6BAAiB,CAAC,OAAO,CAAC,CAAC;YAE1C,KAAK,GAAG;gBACJ,OAAO,IAAI,4BAAgB,CAAC,OAAO,CAAC,CAAC;YAEzC,KAAK,GAAG;gBACJ,OAAO,IAAI,mCAAuB,CAAC,OAAO,CAAC,CAAC;YAEhD,KAAK,GAAG;gBACJ,OAAO,IAAI,+BAAmB,CAAC,OAAO,CAAC,CAAC;YAE5C,KAAK,GAAG;gBACJ,OAAO,IAAI,uCAA2B,CAAC,OAAO,CAAC,CAAC;YAEpD,KAAK,GAAG;gBACJ,OAAO,IAAI,mCAAuB,CAAC,OAAO,CAAC,CAAC;YAEhD,KAAK,GAAG,EAAE,0EAA0E;gBAChF,OAAO,IAAI,2BAAe,CAAC,OAAO,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;YAEnE;gBACI,oFAAoF;gBACpF,iFAAiF;gBACjF,OAAO,IAAI,qBAAS,CAChB,OAAO,IAAI,kCAAkC,UAAU,EAAE,EACzD,UAAU,EACV,OAAO,CACV,CAAC;QACV,CAAC;IACL,CAAC;CACJ;AAlGD,sDAkGC","sourcesContent":["import {\n ProtocolError,\n ClientRegistry,\n HttpError,\n HttpBadRequestError,\n HttpUserError,\n HttpVendorError,\n HttpUnauthorizedError,\n HttpForbiddenError,\n HttpNotFoundError,\n HttpTimeoutError,\n HttpInternalServerError,\n HttpBadGatewayError,\n HttpServiceUnavailableError,\n HttpGatewayTimeoutError,\n} from '@webpieces/core-util';\nimport { TranslatedFailure } from './TranslatedFailure';\n\n/**\n * ClientErrorTranslator - Translates HTTP error responses to HttpError exceptions.\n *\n * This is the CLIENT-SIDE reverse of ExpressWrapper.handleError() on the server.\n * It reconstructs typed HttpError exceptions from ProtocolError JSON responses.\n *\n * Architecture:\n * - Server: HttpError → ExpressWrapper.handleError() → ProtocolError JSON\n * - Client: ProtocolError JSON → ClientErrorTranslator.translateError() → TranslatedFailure\n *\n * This achieves symmetric error handling - server throws typed exceptions,\n * client receives typed exceptions.\n *\n * It returns a {@link TranslatedFailure} rather than a bare `Error` because the mapping is only HALF\n * the decision. It is ISOMORPHIC — the same mapping runs in a browser and in a server — and the two\n * environments must NOT do the same thing with a downstream 4xx (see\n * `ProxyClient.adaptDownstreamFailure`). The wrapper carries the one fact that hook cannot recover\n * on its own: whether the APP claimed this status, or the built-in default did.\n */\nexport class ClientErrorTranslator {\n /**\n * Parse an error response and decide which error the caller should see, and who decided it.\n *\n * App-registered translations win, so an app can reconstruct its OWN error types (e.g. a custom\n * 460) AND override built-ins. `undefined` means \"not mine\" — fall through to\n * {@link builtInError}, which stays the generic default. Symmetric with the server's\n * ExpressWrapper.handleError(), which consults ClientRegistry.tryTranslateToWire() first.\n *\n * @param response - Fetch Response object\n * @param protocolError - Parsed ProtocolError from response body\n * @returns the chosen error plus its provenance and the downstream status\n */\n // webpieces-disable no-function-outside-class -- pure, stateless status-to-type mapping with nothing to inject, called from a BROWSER bundle where no DI container exists; static is the established idiom of this class\n static translateError(response: Response, protocolError: ProtocolError): TranslatedFailure {\n const statusCode = response.status;\n\n const custom = ClientRegistry.tryTranslateFromWire(statusCode, protocolError);\n if (custom !== undefined) {\n return new TranslatedFailure(custom, true, statusCode);\n }\n\n return new TranslatedFailure(\n ClientErrorTranslator.builtInError(response, protocolError),\n false,\n statusCode,\n );\n }\n\n /**\n * The built-in status → error mapping (symmetric with the server's ExpressWrapper.handleError()):\n * - 400 → HttpBadRequestError (with field, guiAlertMessage)\n * - 266 → HttpUserError (with errorCode) - 2xx code for user validation\n * - 401 → HttpUnauthorizedError\n * - 403 → HttpForbiddenError\n * - 404 → HttpNotFoundError\n * - 408 → HttpTimeoutError\n * - 500 → HttpInternalServerError\n * - 502 → HttpBadGatewayError\n * - 503 → HttpServiceUnavailableError\n * - 504 → HttpGatewayTimeoutError\n * - 598 → HttpVendorError (with waitSeconds) - custom status code\n * - other → generic HttpError\n */\n // webpieces-disable no-function-outside-class -- private helper of the static above; same reason\n private static builtInError(response: Response, protocolError: ProtocolError): Error {\n const statusCode = response.status;\n const message = protocolError.message || response.statusText || 'Unknown error';\n const subType = protocolError.subType;\n\n switch (statusCode) {\n case 400:\n return new HttpBadRequestError(\n message,\n protocolError.field,\n protocolError.guiAlertMessage,\n );\n\n case 266: // HttpUserError - 2xx code for user validation errors\n return new HttpUserError(message, protocolError.errorCode);\n\n case 401:\n return new HttpUnauthorizedError(message, subType);\n\n case 403:\n return new HttpForbiddenError(message);\n\n case 404:\n return new HttpNotFoundError(message);\n\n case 408:\n return new HttpTimeoutError(message);\n\n case 500:\n return new HttpInternalServerError(message);\n\n case 502:\n return new HttpBadGatewayError(message);\n\n case 503:\n return new HttpServiceUnavailableError(message);\n\n case 504:\n return new HttpGatewayTimeoutError(message);\n\n case 598: // HttpVendorError - custom status code for vendor/external service errors\n return new HttpVendorError(message, protocolError.waitSeconds);\n\n default:\n // Unknown status code and no app translation claimed it: still a real HttpError (so\n // `err instanceof HttpError` holds after the RPC hop), carrying the status code.\n return new HttpError(\n message || `could not translate statusCode=${statusCode}`,\n statusCode,\n subType,\n );\n }\n }\n}\n"]}
1
+ {"version":3,"file":"ClientErrorTranslator.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-core/src/ClientErrorTranslator.ts"],"names":[],"mappings":";;;AAAA,oDAgB8B;AAC9B,2DAAwD;AAExD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAa,qBAAqB;IAC9B;;;;;;;;;;;OAWG;IACH,yNAAyN;IACzN,MAAM,CAAC,cAAc,CAAC,QAAkB,EAAE,aAA4B;QAClE,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC;QAEnC,MAAM,MAAM,GAAG,0BAAc,CAAC,oBAAoB,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;QAC9E,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACvB,OAAO,IAAI,qCAAiB,CAAC,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;QAC3D,CAAC;QAED,OAAO,IAAI,qCAAiB,CACxB,qBAAqB,CAAC,YAAY,CAAC,QAAQ,EAAE,aAAa,CAAC,EAC3D,KAAK,EACL,UAAU,CACb,CAAC;IACN,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgCG;IACH,iGAAiG;IACzF,MAAM,CAAC,YAAY,CAAC,QAAkB,EAAE,aAA4B;QACxE,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC;QACnC,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,IAAI,QAAQ,CAAC,UAAU,IAAI,eAAe,CAAC;QAChF,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,CAAC;QAEtC,QAAQ,UAAU,EAAE,CAAC;YACjB,KAAK,GAAG;gBACJ,OAAO,IAAI,+BAAmB,CAC1B,OAAO,EACP,aAAa,CAAC,KAAK,EACnB,aAAa,CAAC,eAAe,CAChC,CAAC;YAEN,KAAK,GAAG,EAAE,sDAAsD;gBAC5D,OAAO,IAAI,yBAAa,CAAC,OAAO,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;YAE/D,KAAK,GAAG;gBACJ,OAAO,IAAI,iCAAqB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAEvD,KAAK,GAAG;gBACJ,OAAO,IAAI,8BAAkB,CAAC,OAAO,CAAC,CAAC;YAE3C,KAAK,GAAG;gBACJ,OAAO,IAAI,6BAAiB,CAAC,OAAO,CAAC,CAAC;YAE1C,KAAK,GAAG;gBACJ,OAAO,IAAI,4BAAgB,CAAC,OAAO,CAAC,CAAC;YAEzC,KAAK,GAAG;gBACJ,2EAA2E;gBAC3E,sFAAsF;gBACtF,qFAAqF;gBACrF,+EAA+E;gBAC/E,qFAAqF;gBACrF,iCAAiC;gBACjC,OAAO,IAAI,oCAAwB,CAAC,OAAO,CAAC,CAAC;YAEjD,KAAK,GAAG;gBACJ,OAAO,IAAI,mCAAuB,CAAC,OAAO,CAAC,CAAC;YAEhD,KAAK,GAAG;gBACJ,OAAO,IAAI,+BAAmB,CAAC,OAAO,CAAC,CAAC;YAE5C,KAAK,GAAG;gBACJ,OAAO,IAAI,uCAA2B,CAAC,OAAO,CAAC,CAAC;YAEpD,KAAK,GAAG;gBACJ,OAAO,IAAI,mCAAuB,CAAC,OAAO,CAAC,CAAC;YAEhD,KAAK,GAAG,EAAE,0EAA0E;gBAChF,OAAO,IAAI,2BAAe,CAAC,OAAO,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;YAEnE;gBACI,oFAAoF;gBACpF,iFAAiF;gBACjF,OAAO,IAAI,qBAAS,CAChB,OAAO,IAAI,kCAAkC,UAAU,EAAE,EACzD,UAAU,EACV,OAAO,CACV,CAAC;QACV,CAAC;IACL,CAAC;CACJ;AA7HD,sDA6HC","sourcesContent":["import {\n ProtocolError,\n ClientRegistry,\n HttpError,\n HttpBadRequestError,\n HttpUserError,\n HttpVendorError,\n HttpUnauthorizedError,\n HttpForbiddenError,\n HttpNotFoundError,\n HttpTimeoutError,\n HttpInternalServerError,\n HttpBadGatewayError,\n HttpServiceUnavailableError,\n HttpGatewayTimeoutError,\n HttpTooManyRequestsError,\n} from '@webpieces/core-util';\nimport { TranslatedFailure } from './TranslatedFailure';\n\n/**\n * ClientErrorTranslator - Translates HTTP error responses to HttpError exceptions.\n *\n * This is the CLIENT-SIDE reverse of ExpressWrapper.handleError() on the server.\n * It reconstructs typed HttpError exceptions from ProtocolError JSON responses.\n *\n * Architecture:\n * - Server: HttpError → ExpressWrapper.handleError() → ProtocolError JSON\n * - Client: ProtocolError JSON → ClientErrorTranslator.translateError() → TranslatedFailure\n *\n * This achieves symmetric error handling - server throws typed exceptions,\n * client receives typed exceptions.\n *\n * The symmetry is in the TYPE and the structured fields, NOT in the prose: the server sends the real\n * `Error.message` for `HttpUserError` alone and a generic reason phrase for everything else. See\n * {@link builtInError} and, on the server, `HttpErrorWireMapper`.\n *\n * It returns a {@link TranslatedFailure} rather than a bare `Error` because the mapping is only HALF\n * the decision. It is ISOMORPHIC — the same mapping runs in a browser and in a server — and the two\n * environments must NOT do the same thing with a downstream 4xx (see\n * `ProxyClient.adaptDownstreamFailure`). The wrapper carries the one fact that hook cannot recover\n * on its own: whether the APP claimed this status, or the built-in default did.\n */\nexport class ClientErrorTranslator {\n /**\n * Parse an error response and decide which error the caller should see, and who decided it.\n *\n * App-registered translations win, so an app can reconstruct its OWN error types (e.g. a custom\n * 460) AND override built-ins. `undefined` means \"not mine\" — fall through to\n * {@link builtInError}, which stays the generic default. Symmetric with the server's\n * ExpressWrapper.handleError(), which consults ClientRegistry.tryTranslateToWire() first.\n *\n * @param response - Fetch Response object\n * @param protocolError - Parsed ProtocolError from response body\n * @returns the chosen error plus its provenance and the downstream status\n */\n // webpieces-disable no-function-outside-class -- pure, stateless status-to-type mapping with nothing to inject, called from a BROWSER bundle where no DI container exists; static is the established idiom of this class\n static translateError(response: Response, protocolError: ProtocolError): TranslatedFailure {\n const statusCode = response.status;\n\n const custom = ClientRegistry.tryTranslateFromWire(statusCode, protocolError);\n if (custom !== undefined) {\n return new TranslatedFailure(custom, true, statusCode);\n }\n\n return new TranslatedFailure(\n ClientErrorTranslator.builtInError(response, protocolError),\n false,\n statusCode,\n );\n }\n\n /**\n * The built-in status → error mapping (symmetric with the server's ExpressWrapper.handleError()):\n * - 400 → HttpBadRequestError (with field, guiAlertMessage)\n * - 266 → HttpUserError (with errorCode) - 2xx code for user validation\n * - 401 → HttpUnauthorizedError (with subType)\n * - 403 → HttpForbiddenError\n * - 404 → HttpNotFoundError\n * - 408 → HttpTimeoutError\n * - 429 → HttpTooManyRequestsError\n * - 500 → HttpInternalServerError\n * - 502 → HttpBadGatewayError\n * - 503 → HttpServiceUnavailableError\n * - 504 → HttpGatewayTimeoutError\n * - 598 → HttpVendorError (with waitSeconds) - custom status code\n * - other → generic HttpError\n *\n * # What `message` means on THIS side of the wire\n *\n * The reconstructed error carries whatever text the wire carried, and for every status except\n * **266** a webpieces server deliberately sends only the GENERIC reason phrase — 'Not Found',\n * 'Internal Server Error', … See `HttpErrorWireMapper` (http-server) for why: `Error.message` is\n * an operator-facing field that routinely quotes internal detail, so it stays in the server's log\n * and never reaches a caller. `HttpUserError` (266) is the one type whose message was WRITTEN for\n * a human to read, and it arrives verbatim.\n *\n * So: branch on the TYPE, on `subType`, on `errorCode`, or on `guiAlertMessage` — never on the\n * prose of `message`. It is now a constant per status by design, and treating it as diagnostic\n * information will not work against a current webpieces server. The diagnosis lives in the\n * server's logs, correlated by request id.\n *\n * (An app that publishes richer text on purpose does it through\n * `ClientRegistry.addErrorTranslation()`, which is consulted before this mapping on both sides.)\n */\n // webpieces-disable no-function-outside-class -- private helper of the static above; same reason\n private static builtInError(response: Response, protocolError: ProtocolError): Error {\n const statusCode = response.status;\n const message = protocolError.message || response.statusText || 'Unknown error';\n const subType = protocolError.subType;\n\n switch (statusCode) {\n case 400:\n return new HttpBadRequestError(\n message,\n protocolError.field,\n protocolError.guiAlertMessage,\n );\n\n case 266: // HttpUserError - 2xx code for user validation errors\n return new HttpUserError(message, protocolError.errorCode);\n\n case 401:\n return new HttpUnauthorizedError(message, subType);\n\n case 403:\n return new HttpForbiddenError(message);\n\n case 404:\n return new HttpNotFoundError(message);\n\n case 408:\n return new HttpTimeoutError(message);\n\n case 429:\n // The server has always been able to throw this (HttpErrorWireMapper sends\n // 'Too Many Requests' for it); the client had no case for it, so it arrived as a bare\n // HttpError and callers were pushed back to `err.code === 429` — the untyped pattern\n // this ladder exists to replace. That gap bites harder now that `message` is a\n // constant per status: branching on the TYPE is the only thing left, so every status\n // the server can emit needs one.\n return new HttpTooManyRequestsError(message);\n\n case 500:\n return new HttpInternalServerError(message);\n\n case 502:\n return new HttpBadGatewayError(message);\n\n case 503:\n return new HttpServiceUnavailableError(message);\n\n case 504:\n return new HttpGatewayTimeoutError(message);\n\n case 598: // HttpVendorError - custom status code for vendor/external service errors\n return new HttpVendorError(message, protocolError.waitSeconds);\n\n default:\n // Unknown status code and no app translation claimed it: still a real HttpError (so\n // `err instanceof HttpError` holds after the RPC hop), carrying the status code.\n return new HttpError(\n message || `could not translate statusCode=${statusCode}`,\n statusCode,\n subType,\n );\n }\n }\n}\n"]}