@superblocksteam/sdk-api 2.0.165-next.0 → 2.0.166-next.0
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/README.md +1 -2
- package/dist/errors.d.ts +36 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +244 -8
- package/dist/errors.js.map +1 -1
- package/dist/errors.test.d.ts +2 -0
- package/dist/errors.test.d.ts.map +1 -0
- package/dist/errors.test.js +189 -0
- package/dist/errors.test.js.map +1 -0
- package/dist/integrations/base/decode-worker-binary-response.d.ts.map +1 -1
- package/dist/integrations/base/decode-worker-binary-response.js +2 -5
- package/dist/integrations/base/decode-worker-binary-response.js.map +1 -1
- package/dist/integrations/base/index.d.ts +2 -1
- package/dist/integrations/base/index.d.ts.map +1 -1
- package/dist/integrations/base/index.js +1 -0
- package/dist/integrations/base/index.js.map +1 -1
- package/dist/integrations/base/request-body.d.ts +29 -0
- package/dist/integrations/base/request-body.d.ts.map +1 -0
- package/dist/integrations/base/request-body.js +238 -0
- package/dist/integrations/base/request-body.js.map +1 -0
- package/dist/integrations/base/request-body.test.d.ts +2 -0
- package/dist/integrations/base/request-body.test.d.ts.map +1 -0
- package/dist/integrations/base/request-body.test.js +466 -0
- package/dist/integrations/base/request-body.test.js.map +1 -0
- package/dist/integrations/base/rest-api-client-base.d.ts.map +1 -1
- package/dist/integrations/base/rest-api-client-base.js +15 -14
- package/dist/integrations/base/rest-api-client-base.js.map +1 -1
- package/dist/integrations/base/rest-api-integration-client.d.ts +2 -2
- package/dist/integrations/base/rest-api-integration-client.d.ts.map +1 -1
- package/dist/integrations/base/rest-api-integration-client.js +5 -6
- package/dist/integrations/base/rest-api-integration-client.js.map +1 -1
- package/dist/integrations/base/types.d.ts +55 -11
- package/dist/integrations/base/types.d.ts.map +1 -1
- package/dist/integrations/base/types.js +7 -0
- package/dist/integrations/base/types.js.map +1 -1
- package/dist/integrations/cosmosdb/client.d.ts.map +1 -1
- package/dist/integrations/cosmosdb/client.js +2 -5
- package/dist/integrations/cosmosdb/client.js.map +1 -1
- package/dist/integrations/documentation-resolver.test.js +106 -1
- package/dist/integrations/documentation-resolver.test.js.map +1 -1
- package/dist/integrations/dynamodb/client.d.ts.map +1 -1
- package/dist/integrations/dynamodb/client.js +2 -5
- package/dist/integrations/dynamodb/client.js.map +1 -1
- package/dist/integrations/gcs/client.d.ts.map +1 -1
- package/dist/integrations/gcs/client.js +2 -5
- package/dist/integrations/gcs/client.js.map +1 -1
- package/dist/integrations/gsheets/client.d.ts.map +1 -1
- package/dist/integrations/gsheets/client.js +2 -5
- package/dist/integrations/gsheets/client.js.map +1 -1
- package/dist/integrations/mongodb/client.d.ts.map +1 -1
- package/dist/integrations/mongodb/client.js +2 -5
- package/dist/integrations/mongodb/client.js.map +1 -1
- package/dist/integrations/restapiintegration/client.body-types.test.d.ts +2 -0
- package/dist/integrations/restapiintegration/client.body-types.test.d.ts.map +1 -0
- package/dist/integrations/restapiintegration/client.body-types.test.js +227 -0
- package/dist/integrations/restapiintegration/client.body-types.test.js.map +1 -0
- package/dist/integrations/restapiintegration/client.test.js +56 -30
- package/dist/integrations/restapiintegration/client.test.js.map +1 -1
- package/dist/integrations/s3/client.d.ts.map +1 -1
- package/dist/integrations/s3/client.js +2 -5
- package/dist/integrations/s3/client.js.map +1 -1
- package/dist/integrations/salesforce/client.d.ts.map +1 -1
- package/dist/integrations/salesforce/client.js +2 -5
- package/dist/integrations/salesforce/client.js.map +1 -1
- package/dist/integrations/slack/client.d.ts.map +1 -1
- package/dist/integrations/slack/client.js +4 -13
- package/dist/integrations/slack/client.js.map +1 -1
- package/dist/integrations/slack/client.test.js +20 -0
- package/dist/integrations/slack/client.test.js.map +1 -1
- package/dist/integrations/utils.d.ts +8 -3
- package/dist/integrations/utils.d.ts.map +1 -1
- package/dist/integrations/utils.js +42 -1
- package/dist/integrations/utils.js.map +1 -1
- package/package.json +2 -2
- package/src/errors.test.ts +257 -0
- package/src/errors.ts +304 -10
- package/src/integrations/base/decode-worker-binary-response.ts +5 -7
- package/src/integrations/base/index.ts +3 -0
- package/src/integrations/base/request-body.test.ts +565 -0
- package/src/integrations/base/request-body.ts +392 -0
- package/src/integrations/base/rest-api-client-base.ts +24 -19
- package/src/integrations/base/rest-api-integration-client.ts +14 -10
- package/src/integrations/base/types.ts +66 -11
- package/src/integrations/box/README.md +4 -60
- package/src/integrations/cosmosdb/client.ts +8 -7
- package/src/integrations/documentation-resolver.test.ts +130 -1
- package/src/integrations/dropbox/README.md +1 -71
- package/src/integrations/dropbox/docs.manifest.json +10 -1
- package/src/integrations/dropbox/overlays/upload-unsupported.md +3 -0
- package/src/integrations/dropbox/overlays/upload.md +89 -0
- package/src/integrations/dynamodb/client.ts +8 -7
- package/src/integrations/elasticsearch/README.md +0 -80
- package/src/integrations/elasticsearch/docs.manifest.json +10 -1
- package/src/integrations/elasticsearch/overlays/bulk-unsupported.md +3 -0
- package/src/integrations/elasticsearch/overlays/bulk.md +80 -0
- package/src/integrations/gcs/client.ts +8 -7
- package/src/integrations/googledrive/README.md +10 -36
- package/src/integrations/gsheets/client.ts +8 -7
- package/src/integrations/jira/README.md +0 -26
- package/src/integrations/jira/docs.manifest.json +10 -1
- package/src/integrations/jira/overlays/attachments-unsupported.md +3 -0
- package/src/integrations/jira/overlays/attachments.md +30 -0
- package/src/integrations/lakebase/README.md +1 -3
- package/src/integrations/mongodb/client.ts +8 -7
- package/src/integrations/postgres/README.md +1 -3
- package/src/integrations/restapiintegration/client.body-types.test.ts +323 -0
- package/src/integrations/restapiintegration/client.test.ts +68 -32
- package/src/integrations/restapiintegration/docs.manifest.json +8 -0
- package/src/integrations/restapiintegration/overlays/request-body-types-unsupported.md +7 -0
- package/src/integrations/restapiintegration/overlays/request-body-types.md +115 -0
- package/src/integrations/restapiintegration/overlays/response-types-binary.md +4 -8
- package/src/integrations/s3/client.ts +8 -7
- package/src/integrations/salesforce/client.ts +9 -7
- package/src/integrations/slack/client.test.ts +30 -0
- package/src/integrations/slack/client.ts +13 -19
- package/src/integrations/snowflakepostgres/README.md +1 -3
- package/src/integrations/stabilityai/README.md +0 -87
- package/src/integrations/stabilityai/docs.manifest.json +10 -1
- package/src/integrations/stabilityai/overlays/image-uploads-unsupported.md +3 -0
- package/src/integrations/stabilityai/overlays/image-uploads.md +120 -0
- package/src/integrations/stripe/README.md +0 -201
- package/src/integrations/stripe/docs.manifest.json +10 -1
- package/src/integrations/stripe/overlays/request-bodies-unsupported.md +3 -0
- package/src/integrations/stripe/overlays/request-bodies.md +204 -0
- package/src/integrations/utils.ts +51 -1
package/src/errors.ts
CHANGED
|
@@ -2,35 +2,309 @@
|
|
|
2
2
|
* Error classes for SDK runtime errors.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import { z } from "zod";
|
|
6
6
|
|
|
7
7
|
export const REDACTED_BINARY_RESPONSE_DATA = Object.freeze({
|
|
8
8
|
dataType: "binary",
|
|
9
9
|
redacted: true,
|
|
10
10
|
});
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
// Recursion guard for containsBinary/redactBinaryDeep: a cycle is caught by
|
|
13
|
+
// tracking visited objects, but a long non-cyclic chain has no other bound,
|
|
14
|
+
// so it would otherwise recurse until the real call stack overflows at a
|
|
15
|
+
// depth that varies by environment (e.g. the WASM sdk-api sandbox vs. Node).
|
|
16
|
+
// Cap at a fixed depth instead so the failure is deterministic and caught by
|
|
17
|
+
// the try/catch below rather than crashing the walk.
|
|
18
|
+
const MAX_BINARY_SCAN_DEPTH = 100;
|
|
19
|
+
|
|
20
|
+
export function isBinaryData(
|
|
21
|
+
value: unknown,
|
|
22
|
+
): value is ArrayBuffer | ArrayBufferView {
|
|
23
|
+
return value instanceof ArrayBuffer || ArrayBuffer.isView(value);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isWorkerBufferJson(data: object): boolean {
|
|
16
27
|
if (!("data" in data) || !("type" in data)) {
|
|
17
28
|
return false;
|
|
18
29
|
}
|
|
19
30
|
return data.type === "Buffer" && Array.isArray(data.data);
|
|
20
31
|
}
|
|
21
32
|
|
|
33
|
+
function containsBinary(
|
|
34
|
+
data: unknown,
|
|
35
|
+
seen: WeakSet<object>,
|
|
36
|
+
depth = 0,
|
|
37
|
+
): boolean {
|
|
38
|
+
if (typeof data !== "object" || data === null) return false;
|
|
39
|
+
if (isBinaryData(data) || isWorkerBufferJson(data)) return true;
|
|
40
|
+
if (seen.has(data)) return false;
|
|
41
|
+
if (depth >= MAX_BINARY_SCAN_DEPTH) {
|
|
42
|
+
throw new RangeError(`binary scan exceeded depth ${MAX_BINARY_SCAN_DEPTH}`);
|
|
43
|
+
}
|
|
44
|
+
seen.add(data);
|
|
45
|
+
if (Array.isArray(data)) {
|
|
46
|
+
return data.some((item) => containsBinary(item, seen, depth + 1));
|
|
47
|
+
}
|
|
48
|
+
if (data instanceof Map) {
|
|
49
|
+
return (
|
|
50
|
+
[...data.keys()].some((key) => containsBinary(key, seen, depth + 1)) ||
|
|
51
|
+
[...data.values()].some((value) => containsBinary(value, seen, depth + 1))
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
if (data instanceof Set) {
|
|
55
|
+
return [...data.values()].some((value) =>
|
|
56
|
+
containsBinary(value, seen, depth + 1),
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
return Object.keys(data).some((key) =>
|
|
60
|
+
containsBinary(Reflect.get(data, key), seen, depth + 1),
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Key path to the first value inside `data` — including `data` itself —
|
|
66
|
+
* that satisfies `isBinary`, walking into plain objects, arrays, Map keys
|
|
67
|
+
* and values, and Set values. Shares `containsBinary`'s cycle guard and
|
|
68
|
+
* depth cap; a chain deeper than `MAX_BINARY_SCAN_DEPTH` throws the same
|
|
69
|
+
* way.
|
|
70
|
+
*/
|
|
71
|
+
export function findBinaryDataPath(
|
|
72
|
+
data: unknown,
|
|
73
|
+
isBinary: (value: object) => boolean,
|
|
74
|
+
): (string | number)[] | undefined {
|
|
75
|
+
return findBinaryDataPathIn(data, isBinary, [], new WeakSet(), 0);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function findBinaryDataPathIn(
|
|
79
|
+
data: unknown,
|
|
80
|
+
isBinary: (value: object) => boolean,
|
|
81
|
+
path: (string | number)[],
|
|
82
|
+
seen: WeakSet<object>,
|
|
83
|
+
depth: number,
|
|
84
|
+
): (string | number)[] | undefined {
|
|
85
|
+
if (typeof data !== "object" || data === null) return undefined;
|
|
86
|
+
if (isBinary(data)) return path;
|
|
87
|
+
if (seen.has(data)) return undefined;
|
|
88
|
+
if (depth >= MAX_BINARY_SCAN_DEPTH) {
|
|
89
|
+
throw new RangeError(`binary scan exceeded depth ${MAX_BINARY_SCAN_DEPTH}`);
|
|
90
|
+
}
|
|
91
|
+
seen.add(data);
|
|
92
|
+
if (Array.isArray(data)) {
|
|
93
|
+
for (let index = 0; index < data.length; index++) {
|
|
94
|
+
const found = findBinaryDataPathIn(
|
|
95
|
+
data[index],
|
|
96
|
+
isBinary,
|
|
97
|
+
[...path, index],
|
|
98
|
+
seen,
|
|
99
|
+
depth + 1,
|
|
100
|
+
);
|
|
101
|
+
if (found !== undefined) return found;
|
|
102
|
+
}
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
if (data instanceof Map) {
|
|
106
|
+
for (const [key, value] of data) {
|
|
107
|
+
const keyFound = findBinaryDataPathIn(
|
|
108
|
+
key,
|
|
109
|
+
isBinary,
|
|
110
|
+
[...path, "<map-key>"],
|
|
111
|
+
seen,
|
|
112
|
+
depth + 1,
|
|
113
|
+
);
|
|
114
|
+
if (keyFound !== undefined) return keyFound;
|
|
115
|
+
const valueFound = findBinaryDataPathIn(
|
|
116
|
+
value,
|
|
117
|
+
isBinary,
|
|
118
|
+
[...path, "<map-value>"],
|
|
119
|
+
seen,
|
|
120
|
+
depth + 1,
|
|
121
|
+
);
|
|
122
|
+
if (valueFound !== undefined) return valueFound;
|
|
123
|
+
}
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
if (data instanceof Set) {
|
|
127
|
+
let index = 0;
|
|
128
|
+
for (const value of data) {
|
|
129
|
+
const found = findBinaryDataPathIn(
|
|
130
|
+
value,
|
|
131
|
+
isBinary,
|
|
132
|
+
[...path, index],
|
|
133
|
+
seen,
|
|
134
|
+
depth + 1,
|
|
135
|
+
);
|
|
136
|
+
if (found !== undefined) return found;
|
|
137
|
+
index++;
|
|
138
|
+
}
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
for (const key of Object.keys(data)) {
|
|
142
|
+
const found = findBinaryDataPathIn(
|
|
143
|
+
Reflect.get(data, key),
|
|
144
|
+
isBinary,
|
|
145
|
+
[...path, key],
|
|
146
|
+
seen,
|
|
147
|
+
depth + 1,
|
|
148
|
+
);
|
|
149
|
+
if (found !== undefined) return found;
|
|
150
|
+
}
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
// `invalid_literal` is the only issue whose `received` is the raw input
|
|
154
|
+
// (`unknown`); `invalid_type`/`invalid_enum_value` only ever carry a type
|
|
155
|
+
// name or a string|number enum member, never bytes. `invalid_union` nests a
|
|
156
|
+
// full ZodError per attempted branch. `custom` issues carry an arbitrary
|
|
157
|
+
// caller-defined `params` bag from `ctx.addIssue`, which a hand-written
|
|
158
|
+
// `bodySchema.superRefine()` can populate with anything, including bytes.
|
|
159
|
+
// Either can carry the same binary payloads `containsBinary` detects in
|
|
160
|
+
// `data`, so any message or zodError callers derive from an issue list must
|
|
161
|
+
// be scrubbed the same way.
|
|
162
|
+
function sanitizeZodIssue(issue: z.ZodIssue): z.ZodIssue {
|
|
163
|
+
if (
|
|
164
|
+
issue.code === z.ZodIssueCode.invalid_literal &&
|
|
165
|
+
safeContainsBinary(issue.received)
|
|
166
|
+
) {
|
|
167
|
+
return { ...issue, received: REDACTED_BINARY_RESPONSE_DATA };
|
|
168
|
+
}
|
|
169
|
+
if (
|
|
170
|
+
issue.code === z.ZodIssueCode.custom &&
|
|
171
|
+
issue.params !== undefined &&
|
|
172
|
+
safeContainsBinary(issue.params)
|
|
173
|
+
) {
|
|
174
|
+
return { ...issue, params: REDACTED_BINARY_RESPONSE_DATA };
|
|
175
|
+
}
|
|
176
|
+
if (issue.code === z.ZodIssueCode.invalid_union) {
|
|
177
|
+
return {
|
|
178
|
+
...issue,
|
|
179
|
+
unionErrors: issue.unionErrors.map(redactBinaryFromZodError),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
if (issue.code === z.ZodIssueCode.invalid_arguments) {
|
|
183
|
+
return {
|
|
184
|
+
...issue,
|
|
185
|
+
argumentsError: redactBinaryFromZodError(issue.argumentsError),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
if (issue.code === z.ZodIssueCode.invalid_return_type) {
|
|
189
|
+
return {
|
|
190
|
+
...issue,
|
|
191
|
+
returnTypeError: redactBinaryFromZodError(issue.returnTypeError),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
return issue;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Returns a ZodError whose issues never expose binary bytes, so the
|
|
199
|
+
* returned error's own `.message` (JSON.stringify of the issues) is safe
|
|
200
|
+
* to embed in a caller-built error string. Callers must build their
|
|
201
|
+
* message from this return value, not the original `zodError` argument;
|
|
202
|
+
* `restApiValidationErrorFromZodError` does this for `RestApiValidationError`.
|
|
203
|
+
*/
|
|
204
|
+
export function redactBinaryFromZodError(zodError: z.ZodError): z.ZodError {
|
|
205
|
+
return new z.ZodError(zodError.issues.map(sanitizeZodIssue));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// A walk that cannot finish (a throwing getter, an unusual structure) fails
|
|
209
|
+
// closed: treat it as binary so every caller redacts rather than risks
|
|
210
|
+
// leaking bytes. Shared by data and zodError redaction so both paths fail
|
|
211
|
+
// the same way.
|
|
212
|
+
function safeContainsBinary(data: unknown): boolean {
|
|
213
|
+
try {
|
|
214
|
+
return containsBinary(data, new WeakSet());
|
|
215
|
+
} catch {
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Binary can arrive at the top level (binary responses, binary request bodies)
|
|
221
|
+
// or nested inside a multipart body's { value, filename } parts. Only the
|
|
222
|
+
// binary leaf is replaced, not the whole payload, so sibling fields (e.g. a
|
|
223
|
+
// multipart part's `filename`, or an unrelated field in the same body) stay
|
|
224
|
+
// visible for debugging. `seen` maps each visited object to its output
|
|
225
|
+
// container, registered *before* that container is filled in, so a repeat
|
|
226
|
+
// reference to the same object - a second, non-cyclic pointer (one
|
|
227
|
+
// multipart part reused under two keys) or a genuine cycle's back-edge -
|
|
228
|
+
// always resolves to the redacted copy being built, never to the raw
|
|
229
|
+
// original. Only called once `redactBinaryValidationData` has confirmed the
|
|
230
|
+
// payload contains binary somewhere, so every container along the way is
|
|
231
|
+
// rebuilt; reference identity for the no-binary-anywhere case is handled by
|
|
232
|
+
// that caller instead.
|
|
233
|
+
function redactBinaryDeep(
|
|
234
|
+
data: unknown,
|
|
235
|
+
seen: Map<object, unknown>,
|
|
236
|
+
depth: number,
|
|
237
|
+
): unknown {
|
|
238
|
+
if (typeof data !== "object" || data === null) return data;
|
|
239
|
+
if (isBinaryData(data) || isWorkerBufferJson(data)) {
|
|
240
|
+
return REDACTED_BINARY_RESPONSE_DATA;
|
|
241
|
+
}
|
|
242
|
+
if (seen.has(data)) return seen.get(data);
|
|
243
|
+
if (depth >= MAX_BINARY_SCAN_DEPTH) {
|
|
244
|
+
throw new RangeError(`binary scan exceeded depth ${MAX_BINARY_SCAN_DEPTH}`);
|
|
245
|
+
}
|
|
246
|
+
if (Array.isArray(data)) {
|
|
247
|
+
const next: unknown[] = [];
|
|
248
|
+
seen.set(data, next);
|
|
249
|
+
for (const item of data) {
|
|
250
|
+
next.push(redactBinaryDeep(item, seen, depth + 1));
|
|
251
|
+
}
|
|
252
|
+
return next;
|
|
253
|
+
}
|
|
254
|
+
if (data instanceof Map) {
|
|
255
|
+
const next = new Map<unknown, unknown>();
|
|
256
|
+
seen.set(data, next);
|
|
257
|
+
for (const [key, value] of data) {
|
|
258
|
+
next.set(
|
|
259
|
+
redactBinaryDeep(key, seen, depth + 1),
|
|
260
|
+
redactBinaryDeep(value, seen, depth + 1),
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
return next;
|
|
264
|
+
}
|
|
265
|
+
if (data instanceof Set) {
|
|
266
|
+
const next = new Set<unknown>();
|
|
267
|
+
seen.set(data, next);
|
|
268
|
+
for (const value of data) {
|
|
269
|
+
next.add(redactBinaryDeep(value, seen, depth + 1));
|
|
270
|
+
}
|
|
271
|
+
return next;
|
|
272
|
+
}
|
|
273
|
+
const next: Record<string, unknown> = {};
|
|
274
|
+
seen.set(data, next);
|
|
275
|
+
for (const key of Object.keys(data)) {
|
|
276
|
+
next[key] = redactBinaryDeep(Reflect.get(data, key), seen, depth + 1);
|
|
277
|
+
}
|
|
278
|
+
return next;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// A walk that cannot finish (a throwing getter, an unusual structure, or a
|
|
282
|
+
// depth-capped chain) fails closed: redact the whole payload rather than
|
|
283
|
+
// risk leaking bytes from a partially-completed walk. The `containsBinary`
|
|
284
|
+
// check runs first so a payload with no binary anywhere - including a
|
|
285
|
+
// circular one - comes back as the exact same object, matching how the rest
|
|
286
|
+
// of this module treats data with nothing to redact.
|
|
22
287
|
function redactBinaryValidationData(data: unknown): unknown {
|
|
23
|
-
|
|
288
|
+
try {
|
|
289
|
+
if (!containsBinary(data, new WeakSet())) return data;
|
|
290
|
+
return redactBinaryDeep(data, new Map(), 0);
|
|
291
|
+
} catch {
|
|
24
292
|
return REDACTED_BINARY_RESPONSE_DATA;
|
|
25
293
|
}
|
|
26
|
-
return data;
|
|
27
294
|
}
|
|
28
295
|
|
|
29
296
|
/**
|
|
30
297
|
* Error thrown when REST API response or request body validation fails.
|
|
31
298
|
*
|
|
32
|
-
* Contains the
|
|
33
|
-
*
|
|
299
|
+
* Contains the actual data that failed validation (redacted when it
|
|
300
|
+
* contains binary) and a Zod error scrubbed of any binary `received`
|
|
301
|
+
* values, providing full context for debugging schema mismatches without
|
|
302
|
+
* leaking bytes into logs or telemetry via `details`.
|
|
303
|
+
*
|
|
304
|
+
* The constructor only scrubs `details`; a `message` built from the raw
|
|
305
|
+
* `ZodError` before construction still carries bytes. Build from a Zod
|
|
306
|
+
* failure through `restApiValidationErrorFromZodError` so `.message` is
|
|
307
|
+
* scrubbed too, not just `details.zodError`.
|
|
34
308
|
*/
|
|
35
309
|
export class RestApiValidationError extends Error {
|
|
36
310
|
readonly details: {
|
|
@@ -49,12 +323,32 @@ export class RestApiValidationError extends Error {
|
|
|
49
323
|
this.name = "RestApiValidationError";
|
|
50
324
|
this.details = {
|
|
51
325
|
data: redactBinaryValidationData(details.data),
|
|
52
|
-
zodError: details.zodError,
|
|
326
|
+
zodError: redactBinaryFromZodError(details.zodError),
|
|
53
327
|
};
|
|
54
328
|
Object.setPrototypeOf(this, RestApiValidationError.prototype);
|
|
55
329
|
}
|
|
56
330
|
}
|
|
57
331
|
|
|
332
|
+
/**
|
|
333
|
+
* Builds a `RestApiValidationError` from a Zod validation failure. Redacts
|
|
334
|
+
* `zodError` before composing `message`, so the thrown error's `message`
|
|
335
|
+
* can never carry bytes that `details.zodError` already scrubs. This is
|
|
336
|
+
* the only site that needs to reason about redaction order; every caller
|
|
337
|
+
* with a `ZodError` from a `safeParse` failure should use this instead of
|
|
338
|
+
* constructing `RestApiValidationError` directly.
|
|
339
|
+
*/
|
|
340
|
+
export function restApiValidationErrorFromZodError(
|
|
341
|
+
prefix: string,
|
|
342
|
+
zodError: z.ZodError,
|
|
343
|
+
data: unknown,
|
|
344
|
+
): RestApiValidationError {
|
|
345
|
+
const sanitized = redactBinaryFromZodError(zodError);
|
|
346
|
+
return new RestApiValidationError(`${prefix}: ${sanitized.message}`, {
|
|
347
|
+
zodError: sanitized,
|
|
348
|
+
data,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
58
352
|
/**
|
|
59
353
|
* Error thrown when query results fail schema validation.
|
|
60
354
|
*
|
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
REDACTED_BINARY_RESPONSE_DATA,
|
|
5
|
-
|
|
5
|
+
restApiValidationErrorFromZodError,
|
|
6
6
|
} from "../../errors.js";
|
|
7
7
|
|
|
8
8
|
export { REDACTED_BINARY_RESPONSE_DATA };
|
|
@@ -50,12 +50,10 @@ const workerBinaryResponseSchema = z.union([
|
|
|
50
50
|
export function decodeWorkerBinaryResponse(value: unknown): Uint8Array {
|
|
51
51
|
const result = workerBinaryResponseSchema.safeParse(value);
|
|
52
52
|
if (!result.success) {
|
|
53
|
-
throw
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
zodError: result.error,
|
|
58
|
-
},
|
|
53
|
+
throw restApiValidationErrorFromZodError(
|
|
54
|
+
"Binary response is malformed",
|
|
55
|
+
result.error,
|
|
56
|
+
REDACTED_BINARY_RESPONSE_DATA,
|
|
59
57
|
);
|
|
60
58
|
}
|
|
61
59
|
return result.data;
|
|
@@ -6,9 +6,12 @@ export { RestApiClientBase } from "./rest-api-client-base.js";
|
|
|
6
6
|
export { RestApiIntegrationClient } from "./rest-api-integration-client.js";
|
|
7
7
|
export type { RestApiRequest } from "./rest-api-client-base.js";
|
|
8
8
|
export { GraphQLIntegrationClient } from "./graphql-integration-client.js";
|
|
9
|
+
export { REST_API_BODY_TYPES } from "./types.js";
|
|
9
10
|
export type {
|
|
10
11
|
ApiRequestOptions,
|
|
11
12
|
ApiRequestSchema,
|
|
13
|
+
BinaryApiRequestSchema,
|
|
14
|
+
RestApiBodyType,
|
|
12
15
|
RestApiResponseType,
|
|
13
16
|
SupportsApiRequest,
|
|
14
17
|
} from "./types.js";
|