@apifuse/provider-sdk 2.2.0-beta.26 → 2.2.0-beta.28
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/CHANGELOG.md +8 -0
- package/bin/apifuse-dev.ts +2 -2
- package/bin/apifuse-pack-smoke.ts +1 -1
- package/bin/apifuse-pack-types.ts +2 -1
- package/bin/apifuse-perf.ts +2 -4
- package/bin/apifuse-record.ts +1 -1
- package/dist/auth-turn/index.d.ts +1 -1
- package/dist/auth-turn/index.js +1 -1
- package/dist/ceremonies/index.js +52 -11
- package/dist/config/loader.d.ts +1 -1
- package/dist/config/loader.js +4 -2
- package/dist/contract-serialization.d.ts +1 -20
- package/dist/contract-serialization.js +8 -587
- package/dist/contract.d.ts +0 -2
- package/dist/contract.js +5 -9
- package/dist/index.d.ts +9 -8
- package/dist/index.js +6 -8
- package/dist/provider.d.ts +3 -2
- package/dist/provider.js +2 -2
- package/dist/runtime/auth-flow.js +1 -1
- package/dist/runtime/http.d.ts +1 -0
- package/dist/runtime/http.js +135 -12
- package/dist/runtime/instrumentation.js +1 -1
- package/dist/runtime/native-network-errors.d.ts +33 -0
- package/dist/runtime/native-network-errors.js +69 -0
- package/dist/runtime/native-network.d.ts +2 -33
- package/dist/runtime/native-network.js +2 -68
- package/dist/runtime/redis.d.ts +1 -1
- package/dist/runtime/redis.js +4 -2
- package/dist/runtime/resolver-config.d.ts +6 -0
- package/dist/runtime/resolver-config.js +6 -0
- package/dist/runtime/resolver-shared.d.ts +3 -0
- package/dist/runtime/resolver-shared.js +12 -0
- package/dist/runtime/resolver-vendors/twocaptcha.d.ts +2 -1
- package/dist/runtime/resolver-vendors/twocaptcha.js +80 -43
- package/dist/runtime/resolver-vendors/types.d.ts +1 -1
- package/dist/runtime/resolver.d.ts +6 -10
- package/dist/runtime/resolver.js +19 -18
- package/dist/runtime/state.js +5 -115
- package/dist/runtime/stealth-cookies.d.ts +20 -0
- package/dist/runtime/stealth-cookies.js +111 -0
- package/dist/runtime/stealth.js +7 -130
- package/dist/schema.d.ts +0 -63
- package/dist/schema.js +8 -808
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/self-test.d.ts +13 -0
- package/dist/server/self-test.js +124 -46
- package/dist/server/serve-implementation.d.ts +199 -0
- package/dist/server/serve-implementation.js +2054 -0
- package/dist/server/serve.d.ts +1 -187
- package/dist/server/serve.js +1 -1827
- package/dist/stateful/errors.d.ts +5 -0
- package/dist/stateful/errors.js +10 -0
- package/dist/stateful/stateful-provider-session-routing.d.ts +1 -5
- package/dist/stateful/stateful-provider-session-routing.js +2 -10
- package/dist/stream.js +7 -1
- package/package.json +27 -2
- package/src/auth-turn/index.ts +1 -1
- package/src/ceremonies/index.ts +68 -18
- package/src/config/loader.ts +5 -2
- package/src/contract-serialization.ts +8 -859
- package/src/contract.ts +5 -16
- package/src/index.ts +18 -34
- package/src/provider.ts +12 -24
- package/src/runtime/auth-flow.ts +1 -1
- package/src/runtime/http.ts +155 -11
- package/src/runtime/instrumentation.ts +1 -1
- package/src/runtime/native-network-errors.ts +99 -0
- package/src/runtime/native-network.ts +16 -97
- package/src/runtime/redis.ts +7 -2
- package/src/runtime/resolver-config.ts +6 -0
- package/src/runtime/resolver-shared.ts +17 -0
- package/src/runtime/resolver-vendors/twocaptcha.ts +100 -49
- package/src/runtime/resolver-vendors/types.ts +1 -0
- package/src/runtime/resolver.ts +47 -22
- package/src/runtime/state.ts +5 -144
- package/src/runtime/stealth-cookies.ts +132 -0
- package/src/runtime/stealth.ts +14 -157
- package/src/schema.ts +9 -1060
- package/src/serve.ts +6 -1
- package/src/server/index.ts +1 -0
- package/src/server/self-test.ts +184 -59
- package/src/server/serve-implementation.ts +3024 -0
- package/src/server/serve.ts +1 -2661
- package/src/stateful/errors.ts +12 -0
- package/src/stateful/stateful-provider-session-routing.ts +2 -11
- package/src/stream.ts +8 -1
package/src/schema.ts
CHANGED
|
@@ -83,42 +83,8 @@ export function safeParseSchemaSync(
|
|
|
83
83
|
export const APIFUSE_SENSITIVE_META_KEY = "x-apifuse-sensitive";
|
|
84
84
|
export const APIFUSE_SENSITIVE_KIND_META_KEY = "x-apifuse-sensitive-kind";
|
|
85
85
|
export const APIFUSE_DESCRIPTION_KEY_META_KEY = "x-apifuse-description-key";
|
|
86
|
-
export const APIFUSE_TEXT_TRUST_META_KEY = "x-apifuse-text-trust";
|
|
87
86
|
export const APIFUSE_REDACTION_MARKER = "<redacted>";
|
|
88
87
|
|
|
89
|
-
export type TextTrust = "trusted" | "untrusted";
|
|
90
|
-
|
|
91
|
-
export interface TextTrustMetadata {
|
|
92
|
-
readonly v: 1;
|
|
93
|
-
readonly trust: TextTrust;
|
|
94
|
-
readonly carrier?: "container";
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export type OutputTextTrustMap = Readonly<Record<string, TextTrust>>;
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Auto-trusted output leaves are string-valued `z.literal()` / `z.enum()`,
|
|
101
|
-
* patterns whose every top-level alternative is anchored at both ends and
|
|
102
|
-
* whose bodies use only finite quantifiers, have a maximum match length of 32,
|
|
103
|
-
* and contain only digits and safe punctuation in positive whitespace-free
|
|
104
|
-
* character classes, literals/escapes, and ordinary or non-capturing groups,
|
|
105
|
-
* plus the structurally constrained formats listed here. A brand is trusted
|
|
106
|
-
* only when its underlying schema meets one of those rules. Letter-bearing
|
|
107
|
-
* identifiers, length-only strings, and other formats (including email, URL,
|
|
108
|
-
* base64, and JWT) are not trusted.
|
|
109
|
-
*/
|
|
110
|
-
export const AUTO_TRUSTED_ZOD_STRING_FORMATS = [
|
|
111
|
-
"cidrv4",
|
|
112
|
-
"cidrv6",
|
|
113
|
-
"date",
|
|
114
|
-
"datetime",
|
|
115
|
-
"duration",
|
|
116
|
-
"e164",
|
|
117
|
-
"ipv4",
|
|
118
|
-
"ipv6",
|
|
119
|
-
"time",
|
|
120
|
-
] as const;
|
|
121
|
-
|
|
122
88
|
export type SensitivePathSegment = string | "*";
|
|
123
89
|
export type SensitivePath = readonly SensitivePathSegment[];
|
|
124
90
|
export type SensitiveFieldKind =
|
|
@@ -161,19 +127,9 @@ export function describeKey<TSchema extends ZodType>(
|
|
|
161
127
|
});
|
|
162
128
|
}
|
|
163
129
|
|
|
164
|
-
/** Attach output text-trust authoring metadata without changing validation. */
|
|
165
|
-
export function textTrust<TSchema extends ZodType>(schema: TSchema, trust: TextTrust): TSchema {
|
|
166
|
-
const metadata = schema.meta() ?? {};
|
|
167
|
-
return schema.meta({
|
|
168
|
-
...metadata,
|
|
169
|
-
[APIFUSE_TEXT_TRUST_META_KEY]: { v: 1, trust } satisfies TextTrustMetadata,
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
|
|
173
130
|
declare module "zod" {
|
|
174
131
|
interface ZodType {
|
|
175
132
|
describeKey(key: ProviderLocaleKey | string): this;
|
|
176
|
-
textTrust(trust: TextTrust): this;
|
|
177
133
|
}
|
|
178
134
|
}
|
|
179
135
|
|
|
@@ -184,101 +140,16 @@ const describeKeyMethod = function <TSchema extends ZodType>(
|
|
|
184
140
|
return describeKey(this, key);
|
|
185
141
|
};
|
|
186
142
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
): TSchema {
|
|
191
|
-
return textTrust(this, trust);
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
interface StaticOutputFallback {
|
|
195
|
-
readonly value: unknown;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const staticOutputFallbacks = new WeakMap<object, StaticOutputFallback>();
|
|
199
|
-
let suppressDynamicOutputFallbacks = 0;
|
|
200
|
-
|
|
201
|
-
function trackOutputFallback(schema: unknown, fallback: unknown): void {
|
|
202
|
-
if (!(schema instanceof z.ZodType)) return;
|
|
203
|
-
const def = schema._zod.def;
|
|
204
|
-
if (typeof fallback !== "function") {
|
|
205
|
-
staticOutputFallbacks.set(def, { value: fallback });
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
if (def.type === "default") {
|
|
209
|
-
const descriptor = Object.getOwnPropertyDescriptor(def, "defaultValue");
|
|
210
|
-
if (!descriptor?.get) return;
|
|
211
|
-
const originalGetter = descriptor.get;
|
|
212
|
-
Object.defineProperty(def, "defaultValue", {
|
|
213
|
-
...descriptor,
|
|
214
|
-
get(this: object) {
|
|
215
|
-
if (suppressDynamicOutputFallbacks > 0) return null;
|
|
216
|
-
return Reflect.apply(originalGetter, this, []);
|
|
217
|
-
},
|
|
218
|
-
});
|
|
143
|
+
function installDescribeKeyOnPrototype(prototype: unknown): void {
|
|
144
|
+
const target = prototype as (Record<string, unknown> & { describeKey?: unknown }) | null;
|
|
145
|
+
if (!target || typeof target.describeKey === "function") {
|
|
219
146
|
return;
|
|
220
147
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
return Reflect.apply(catchValue, this, args) as never;
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const trackedFallbackPrototypes = new WeakSet<object>();
|
|
232
|
-
|
|
233
|
-
function installOutputFallbackTrackingOnPrototype(prototype: unknown): void {
|
|
234
|
-
if (!prototype || typeof prototype !== "object" || trackedFallbackPrototypes.has(prototype)) {
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
trackedFallbackPrototypes.add(prototype);
|
|
238
|
-
Reflect.apply(z.ZodType.init, z.ZodType, [Object.create(prototype), { type: "custom" }]);
|
|
239
|
-
for (const methodName of ["default", "catch"] as const) {
|
|
240
|
-
const descriptor = Object.getOwnPropertyDescriptor(prototype, methodName);
|
|
241
|
-
if (!descriptor?.configurable || !descriptor.get) continue;
|
|
242
|
-
const originalGetter = descriptor.get;
|
|
243
|
-
Object.defineProperty(prototype, methodName, {
|
|
244
|
-
...descriptor,
|
|
245
|
-
get(this: object) {
|
|
246
|
-
const originalMethod = Reflect.apply(originalGetter, this, []);
|
|
247
|
-
if (typeof originalMethod !== "function") return originalMethod;
|
|
248
|
-
const trackedMethod = function (this: unknown, fallback: unknown) {
|
|
249
|
-
const result = Reflect.apply(originalMethod, this, [fallback]);
|
|
250
|
-
trackOutputFallback(result, fallback);
|
|
251
|
-
return result;
|
|
252
|
-
};
|
|
253
|
-
Object.defineProperty(this, methodName, {
|
|
254
|
-
configurable: true,
|
|
255
|
-
enumerable: true,
|
|
256
|
-
value: trackedMethod,
|
|
257
|
-
writable: true,
|
|
258
|
-
});
|
|
259
|
-
return trackedMethod;
|
|
260
|
-
},
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
function installSchemaMetadataMethodsOnPrototype(prototype: unknown): void {
|
|
266
|
-
const target = prototype as Record<string, unknown> | null;
|
|
267
|
-
if (!target) return;
|
|
268
|
-
if (typeof target.describeKey !== "function") {
|
|
269
|
-
Object.defineProperty(target, "describeKey", {
|
|
270
|
-
configurable: true,
|
|
271
|
-
value: describeKeyMethod,
|
|
272
|
-
writable: true,
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
if (typeof target.textTrust !== "function") {
|
|
276
|
-
Object.defineProperty(target, "textTrust", {
|
|
277
|
-
configurable: true,
|
|
278
|
-
value: textTrustMethod,
|
|
279
|
-
writable: true,
|
|
280
|
-
});
|
|
281
|
-
}
|
|
148
|
+
Object.defineProperty(target, "describeKey", {
|
|
149
|
+
configurable: true,
|
|
150
|
+
value: describeKeyMethod,
|
|
151
|
+
writable: true,
|
|
152
|
+
});
|
|
282
153
|
}
|
|
283
154
|
|
|
284
155
|
for (const [name, value] of Object.entries(z)) {
|
|
@@ -288,8 +159,7 @@ for (const [name, value] of Object.entries(z)) {
|
|
|
288
159
|
if (typeof value !== "function") {
|
|
289
160
|
continue;
|
|
290
161
|
}
|
|
291
|
-
|
|
292
|
-
installOutputFallbackTrackingOnPrototype(value.prototype);
|
|
162
|
+
installDescribeKeyOnPrototype(value.prototype);
|
|
293
163
|
}
|
|
294
164
|
|
|
295
165
|
const RESERVED_SENSITIVE_KEYS = new Set([
|
|
@@ -368,927 +238,6 @@ export const fields = {
|
|
|
368
238
|
sensitiveString("token", "Provider access or refresh token.", options),
|
|
369
239
|
} as const;
|
|
370
240
|
|
|
371
|
-
type TextTrustDeclaration = TextTrust | "absent" | "invalid";
|
|
372
|
-
|
|
373
|
-
type InternalZodSchema = z.core.$ZodTypes;
|
|
374
|
-
type InternalZodDef = InternalZodSchema["_zod"]["def"];
|
|
375
|
-
|
|
376
|
-
interface OutputTextLeaf {
|
|
377
|
-
readonly classification: TextTrust;
|
|
378
|
-
readonly classified: boolean;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
interface OutputTextTrustCollection {
|
|
382
|
-
readonly leaves: Array<OutputTextLeaf & { readonly path: string }>;
|
|
383
|
-
readonly debtPaths: Set<string>;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
export class OutputTextTrustSchemaError extends TypeError {
|
|
387
|
-
readonly code = "invalid_output_text_trust_schema";
|
|
388
|
-
|
|
389
|
-
constructor(value: unknown) {
|
|
390
|
-
const receivedType = value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
|
|
391
|
-
super(`Output text-trust collection requires a Zod schema; received ${receivedType}.`);
|
|
392
|
-
this.name = "OutputTextTrustSchemaError";
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
export class OutputTextTrustCollectionError extends Error {
|
|
397
|
-
readonly classification = "untrusted" as const;
|
|
398
|
-
readonly code = "output_text_trust_collection_failed";
|
|
399
|
-
|
|
400
|
-
constructor(
|
|
401
|
-
public readonly schemaPath: string,
|
|
402
|
-
cause: unknown,
|
|
403
|
-
) {
|
|
404
|
-
super(`Output text-trust collection failed at schema path ${schemaPath}.`);
|
|
405
|
-
this.name = "OutputTextTrustCollectionError";
|
|
406
|
-
this.cause = cause;
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
/**
|
|
411
|
-
* Collect every textual leaf in a Zod output schema as a deterministic
|
|
412
|
-
* schema-path-to-classification object. The path grammar is rooted at `$`:
|
|
413
|
-
* object keys are `["key"]`, arrays and record values are `[*]`, tuple slots
|
|
414
|
-
* are `[n]`, union/intersection alternatives are `<union:n>` / `<intersection:n>`,
|
|
415
|
-
* record keys are `<record-key>`, and one bounded cycle expansion is marked
|
|
416
|
-
* `<recursive>`.
|
|
417
|
-
*/
|
|
418
|
-
export function collectOutputTextTrust(schema: ZodType): OutputTextTrustMap {
|
|
419
|
-
const { leaves } = collectOutputTextLeaves(requireOutputTextTrustSchema(schema));
|
|
420
|
-
return Object.fromEntries(
|
|
421
|
-
leaves
|
|
422
|
-
.map(({ path, classification }) => [path, classification] as const)
|
|
423
|
-
.sort(([left], [right]) => left.localeCompare(right)),
|
|
424
|
-
);
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
/** Report textual output paths that lack a valid explicit or auto-derived classification. */
|
|
428
|
-
export function findUnclassifiedOutputTextPaths(schema: ZodType): string[] {
|
|
429
|
-
const { debtPaths, leaves } = collectOutputTextLeaves(requireOutputTextTrustSchema(schema));
|
|
430
|
-
for (const { classified, path } of leaves) {
|
|
431
|
-
if (!classified) debtPaths.add(path);
|
|
432
|
-
}
|
|
433
|
-
return [...debtPaths].sort((left, right) => left.localeCompare(right));
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
/** @internal Used by contract JSON Schema projection. */
|
|
437
|
-
export function resolveOutputTextTrust(
|
|
438
|
-
schema: z.core.$ZodType,
|
|
439
|
-
inheritedUntrusted = false,
|
|
440
|
-
): TextTrust | undefined {
|
|
441
|
-
const resolved = resolveOutputTextTrustProjection(schema);
|
|
442
|
-
return resolved?.[inheritedUntrusted ? "inherited" : "local"];
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
/** @internal Resolve both projection states from one stable schema traversal. */
|
|
446
|
-
export function resolveOutputTextTrustProjection(
|
|
447
|
-
schema: z.core.$ZodType,
|
|
448
|
-
): { readonly inherited: TextTrust; readonly local: TextTrust } | undefined {
|
|
449
|
-
const resolved = resolveOutputTextLeafPair(asInternalZodSchema(schema), [], true);
|
|
450
|
-
return resolved
|
|
451
|
-
? {
|
|
452
|
-
inherited: resolved.inherited.classification,
|
|
453
|
-
local: resolved.local.classification,
|
|
454
|
-
}
|
|
455
|
-
: undefined;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
/** @internal Whether this node can bypass or mutate descendant validation guarantees. */
|
|
459
|
-
export function invalidatesDescendantOutputTextAutoTrust(schema: z.core.$ZodType): boolean {
|
|
460
|
-
return invalidatesOutputTextAutoTrust(asInternalZodSchema(schema)._zod.def);
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
/** @internal Run output projection without evaluating dynamic fallback callbacks. */
|
|
464
|
-
export function suppressDynamicOutputFallbacksDuring<T>(project: () => T): T {
|
|
465
|
-
suppressDynamicOutputFallbacks += 1;
|
|
466
|
-
try {
|
|
467
|
-
return project();
|
|
468
|
-
} finally {
|
|
469
|
-
suppressDynamicOutputFallbacks -= 1;
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
/** @internal Whether this mutator has a static fallback that cannot contain text. */
|
|
474
|
-
export function mutatorReturnIsProvablyNonText(schema: z.core.$ZodType): boolean {
|
|
475
|
-
const def = asInternalZodSchema(schema)._zod.def;
|
|
476
|
-
if (def.type !== "default" && def.type !== "catch") return false;
|
|
477
|
-
const fallback = staticOutputFallbacks.get(def);
|
|
478
|
-
return fallback !== undefined && outputFallbackValueIsProvablyNonText(fallback.value);
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
/** @internal Whether this mutator has a static array fallback. */
|
|
482
|
-
export function mutatorUsesStaticArrayFallback(schema: z.core.$ZodType): boolean {
|
|
483
|
-
const def = asInternalZodSchema(schema)._zod.def;
|
|
484
|
-
if (def.type !== "default" && def.type !== "catch") return false;
|
|
485
|
-
const fallback = staticOutputFallbacks.get(def);
|
|
486
|
-
return fallback !== undefined && Array.isArray(fallback.value);
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
/** @internal Whether this default or catch was authored with a callback. */
|
|
490
|
-
export function hasDynamicOutputFallback(schema: z.core.$ZodType): boolean {
|
|
491
|
-
const def = asInternalZodSchema(schema)._zod.def;
|
|
492
|
-
return (def.type === "default" || def.type === "catch") && !staticOutputFallbacks.has(def);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
/** @internal Used by contract JSON Schema projection. */
|
|
496
|
-
export function inheritsUntrustedOutputTextTrust(schema: z.core.$ZodType): boolean {
|
|
497
|
-
let current = asInternalZodSchema(schema);
|
|
498
|
-
while (true) {
|
|
499
|
-
if (readTextTrustDeclaration(current) === "untrusted") return true;
|
|
500
|
-
const inner = flattenedOutputSchema(current._zod.def);
|
|
501
|
-
if (!inner) return false;
|
|
502
|
-
current = inner;
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
function outputFallbackValueIsProvablyNonText(value: unknown): boolean {
|
|
507
|
-
return (
|
|
508
|
-
value === null ||
|
|
509
|
-
typeof value === "boolean" ||
|
|
510
|
-
typeof value === "number" ||
|
|
511
|
-
(Array.isArray(value) && value.every(outputFallbackValueIsProvablyNonText))
|
|
512
|
-
);
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
function collectOutputTextLeaves(schema: InternalZodSchema): OutputTextTrustCollection {
|
|
516
|
-
const leaves: Array<OutputTextLeaf & { readonly path: string }> = [];
|
|
517
|
-
const debtPaths = new Set<string>();
|
|
518
|
-
const emittedPaths = new Set<string>();
|
|
519
|
-
walkOutputSchema(schema, "$", leaves, debtPaths, emittedPaths, new Set(), false, false, true);
|
|
520
|
-
return { leaves, debtPaths };
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
function walkOutputSchema(
|
|
524
|
-
schema: InternalZodSchema,
|
|
525
|
-
path: string,
|
|
526
|
-
out: Array<OutputTextLeaf & { readonly path: string }>,
|
|
527
|
-
debtPaths: Set<string>,
|
|
528
|
-
emittedPaths: Set<string>,
|
|
529
|
-
activeSchemas: Set<InternalZodSchema>,
|
|
530
|
-
expandingCycle: boolean,
|
|
531
|
-
inheritedUntrusted: boolean,
|
|
532
|
-
autoTrustAllowed: boolean,
|
|
533
|
-
): void {
|
|
534
|
-
try {
|
|
535
|
-
walkOutputSchemaUnchecked(
|
|
536
|
-
schema,
|
|
537
|
-
path,
|
|
538
|
-
out,
|
|
539
|
-
debtPaths,
|
|
540
|
-
emittedPaths,
|
|
541
|
-
activeSchemas,
|
|
542
|
-
expandingCycle,
|
|
543
|
-
inheritedUntrusted,
|
|
544
|
-
autoTrustAllowed,
|
|
545
|
-
);
|
|
546
|
-
} catch (error) {
|
|
547
|
-
if (error instanceof OutputTextTrustCollectionError) throw error;
|
|
548
|
-
throw new OutputTextTrustCollectionError(path, error);
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
function walkOutputSchemaUnchecked(
|
|
553
|
-
schema: InternalZodSchema,
|
|
554
|
-
path: string,
|
|
555
|
-
out: Array<OutputTextLeaf & { readonly path: string }>,
|
|
556
|
-
debtPaths: Set<string>,
|
|
557
|
-
emittedPaths: Set<string>,
|
|
558
|
-
activeSchemas: Set<InternalZodSchema>,
|
|
559
|
-
expandingCycle: boolean,
|
|
560
|
-
inheritedUntrusted: boolean,
|
|
561
|
-
autoTrustAllowed: boolean,
|
|
562
|
-
): void {
|
|
563
|
-
const alreadyActive = activeSchemas.has(schema);
|
|
564
|
-
if (alreadyActive) {
|
|
565
|
-
if (!expandingCycle) {
|
|
566
|
-
walkOutputSchema(
|
|
567
|
-
schema,
|
|
568
|
-
`${path}<recursive>`,
|
|
569
|
-
out,
|
|
570
|
-
debtPaths,
|
|
571
|
-
emittedPaths,
|
|
572
|
-
new Set(),
|
|
573
|
-
true,
|
|
574
|
-
inheritedUntrusted,
|
|
575
|
-
autoTrustAllowed,
|
|
576
|
-
);
|
|
577
|
-
}
|
|
578
|
-
return;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
const leaf = resolveOutputTextLeaf(schema, [], autoTrustAllowed, inheritedUntrusted);
|
|
582
|
-
if (leaf) {
|
|
583
|
-
if (!emittedPaths.has(path)) {
|
|
584
|
-
emittedPaths.add(path);
|
|
585
|
-
out.push({ path, ...leaf });
|
|
586
|
-
}
|
|
587
|
-
return;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
const def = schema._zod.def;
|
|
591
|
-
const declaration = readTextTrustDeclaration(schema);
|
|
592
|
-
if (declaration === "trusted" || declaration === "invalid") debtPaths.add(path);
|
|
593
|
-
const descendantUntrusted = inheritedUntrusted || declaration === "untrusted";
|
|
594
|
-
const descendantAutoTrustAllowed = autoTrustAllowed && !invalidatesOutputTextAutoTrust(def);
|
|
595
|
-
if (
|
|
596
|
-
invalidatesOutputTextAutoTrust(def) &&
|
|
597
|
-
!mutatorReturnIsProvablyNonText(schema) &&
|
|
598
|
-
!mutatorCanUseCollectedTextItems(schema) &&
|
|
599
|
-
!emittedPaths.has(path)
|
|
600
|
-
) {
|
|
601
|
-
emittedPaths.add(path);
|
|
602
|
-
out.push({
|
|
603
|
-
classification: "untrusted",
|
|
604
|
-
classified: descendantUntrusted,
|
|
605
|
-
path,
|
|
606
|
-
});
|
|
607
|
-
}
|
|
608
|
-
if (!alreadyActive) activeSchemas.add(schema);
|
|
609
|
-
try {
|
|
610
|
-
switch (def.type) {
|
|
611
|
-
case "object": {
|
|
612
|
-
for (const [key, child] of Object.entries(def.shape).sort(([left], [right]) =>
|
|
613
|
-
left.localeCompare(right),
|
|
614
|
-
)) {
|
|
615
|
-
walkOutputSchema(
|
|
616
|
-
asInternalZodSchema(child),
|
|
617
|
-
`${path}[${JSON.stringify(key)}]`,
|
|
618
|
-
out,
|
|
619
|
-
debtPaths,
|
|
620
|
-
emittedPaths,
|
|
621
|
-
activeSchemas,
|
|
622
|
-
expandingCycle,
|
|
623
|
-
descendantUntrusted,
|
|
624
|
-
descendantAutoTrustAllowed,
|
|
625
|
-
);
|
|
626
|
-
}
|
|
627
|
-
if (def.catchall) {
|
|
628
|
-
if (asInternalZodSchema(def.catchall)._zod.def.type !== "never") {
|
|
629
|
-
const catchallKeyPath = `${path}<catchall-key>`;
|
|
630
|
-
if (!emittedPaths.has(catchallKeyPath)) {
|
|
631
|
-
emittedPaths.add(catchallKeyPath);
|
|
632
|
-
out.push({
|
|
633
|
-
classification: "untrusted",
|
|
634
|
-
classified: descendantUntrusted,
|
|
635
|
-
path: catchallKeyPath,
|
|
636
|
-
});
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
walkOutputSchema(
|
|
640
|
-
asInternalZodSchema(def.catchall),
|
|
641
|
-
`${path}[*]`,
|
|
642
|
-
out,
|
|
643
|
-
debtPaths,
|
|
644
|
-
emittedPaths,
|
|
645
|
-
activeSchemas,
|
|
646
|
-
expandingCycle,
|
|
647
|
-
descendantUntrusted,
|
|
648
|
-
descendantAutoTrustAllowed,
|
|
649
|
-
);
|
|
650
|
-
}
|
|
651
|
-
break;
|
|
652
|
-
}
|
|
653
|
-
case "array":
|
|
654
|
-
walkOutputSchema(
|
|
655
|
-
asInternalZodSchema(def.element),
|
|
656
|
-
`${path}[*]`,
|
|
657
|
-
out,
|
|
658
|
-
debtPaths,
|
|
659
|
-
emittedPaths,
|
|
660
|
-
activeSchemas,
|
|
661
|
-
expandingCycle,
|
|
662
|
-
descendantUntrusted,
|
|
663
|
-
descendantAutoTrustAllowed,
|
|
664
|
-
);
|
|
665
|
-
break;
|
|
666
|
-
case "tuple": {
|
|
667
|
-
def.items.forEach((child, index) => {
|
|
668
|
-
walkOutputSchema(
|
|
669
|
-
asInternalZodSchema(child),
|
|
670
|
-
`${path}[${index}]`,
|
|
671
|
-
out,
|
|
672
|
-
debtPaths,
|
|
673
|
-
emittedPaths,
|
|
674
|
-
activeSchemas,
|
|
675
|
-
expandingCycle,
|
|
676
|
-
descendantUntrusted,
|
|
677
|
-
descendantAutoTrustAllowed,
|
|
678
|
-
);
|
|
679
|
-
});
|
|
680
|
-
if (def.rest) {
|
|
681
|
-
walkOutputSchema(
|
|
682
|
-
asInternalZodSchema(def.rest),
|
|
683
|
-
`${path}[*]`,
|
|
684
|
-
out,
|
|
685
|
-
debtPaths,
|
|
686
|
-
emittedPaths,
|
|
687
|
-
activeSchemas,
|
|
688
|
-
expandingCycle,
|
|
689
|
-
descendantUntrusted,
|
|
690
|
-
descendantAutoTrustAllowed,
|
|
691
|
-
);
|
|
692
|
-
}
|
|
693
|
-
break;
|
|
694
|
-
}
|
|
695
|
-
case "record":
|
|
696
|
-
walkOutputSchema(
|
|
697
|
-
asInternalZodSchema(def.keyType),
|
|
698
|
-
`${path}<record-key>`,
|
|
699
|
-
out,
|
|
700
|
-
debtPaths,
|
|
701
|
-
emittedPaths,
|
|
702
|
-
activeSchemas,
|
|
703
|
-
expandingCycle,
|
|
704
|
-
descendantUntrusted,
|
|
705
|
-
descendantAutoTrustAllowed,
|
|
706
|
-
);
|
|
707
|
-
walkOutputSchema(
|
|
708
|
-
asInternalZodSchema(def.valueType),
|
|
709
|
-
`${path}[*]`,
|
|
710
|
-
out,
|
|
711
|
-
debtPaths,
|
|
712
|
-
emittedPaths,
|
|
713
|
-
activeSchemas,
|
|
714
|
-
expandingCycle,
|
|
715
|
-
descendantUntrusted,
|
|
716
|
-
descendantAutoTrustAllowed,
|
|
717
|
-
);
|
|
718
|
-
break;
|
|
719
|
-
case "union":
|
|
720
|
-
def.options.forEach((child, index) => {
|
|
721
|
-
walkOutputSchema(
|
|
722
|
-
asInternalZodSchema(child),
|
|
723
|
-
`${path}<union:${index}>`,
|
|
724
|
-
out,
|
|
725
|
-
debtPaths,
|
|
726
|
-
emittedPaths,
|
|
727
|
-
activeSchemas,
|
|
728
|
-
expandingCycle,
|
|
729
|
-
descendantUntrusted,
|
|
730
|
-
descendantAutoTrustAllowed,
|
|
731
|
-
);
|
|
732
|
-
});
|
|
733
|
-
break;
|
|
734
|
-
case "intersection":
|
|
735
|
-
for (const [index, side] of [def.left, def.right].entries()) {
|
|
736
|
-
walkOutputSchema(
|
|
737
|
-
asInternalZodSchema(side),
|
|
738
|
-
`${path}<intersection:${index}>`,
|
|
739
|
-
out,
|
|
740
|
-
debtPaths,
|
|
741
|
-
emittedPaths,
|
|
742
|
-
activeSchemas,
|
|
743
|
-
expandingCycle,
|
|
744
|
-
descendantUntrusted,
|
|
745
|
-
descendantAutoTrustAllowed,
|
|
746
|
-
);
|
|
747
|
-
}
|
|
748
|
-
break;
|
|
749
|
-
case "optional":
|
|
750
|
-
case "nullable":
|
|
751
|
-
case "default":
|
|
752
|
-
case "prefault":
|
|
753
|
-
case "catch":
|
|
754
|
-
case "readonly":
|
|
755
|
-
case "nonoptional":
|
|
756
|
-
case "promise":
|
|
757
|
-
walkOutputSchema(
|
|
758
|
-
asInternalZodSchema(def.innerType),
|
|
759
|
-
path,
|
|
760
|
-
out,
|
|
761
|
-
debtPaths,
|
|
762
|
-
emittedPaths,
|
|
763
|
-
activeSchemas,
|
|
764
|
-
expandingCycle,
|
|
765
|
-
descendantUntrusted,
|
|
766
|
-
descendantAutoTrustAllowed,
|
|
767
|
-
);
|
|
768
|
-
break;
|
|
769
|
-
case "pipe":
|
|
770
|
-
walkOutputSchema(
|
|
771
|
-
asInternalZodSchema(def.out),
|
|
772
|
-
path,
|
|
773
|
-
out,
|
|
774
|
-
debtPaths,
|
|
775
|
-
emittedPaths,
|
|
776
|
-
activeSchemas,
|
|
777
|
-
expandingCycle,
|
|
778
|
-
descendantUntrusted,
|
|
779
|
-
descendantAutoTrustAllowed,
|
|
780
|
-
);
|
|
781
|
-
break;
|
|
782
|
-
case "lazy":
|
|
783
|
-
walkOutputSchema(
|
|
784
|
-
asInternalZodSchema(def.getter()),
|
|
785
|
-
path,
|
|
786
|
-
out,
|
|
787
|
-
debtPaths,
|
|
788
|
-
emittedPaths,
|
|
789
|
-
activeSchemas,
|
|
790
|
-
expandingCycle,
|
|
791
|
-
descendantUntrusted,
|
|
792
|
-
descendantAutoTrustAllowed,
|
|
793
|
-
);
|
|
794
|
-
break;
|
|
795
|
-
case "string":
|
|
796
|
-
case "template_literal":
|
|
797
|
-
case "literal":
|
|
798
|
-
case "enum":
|
|
799
|
-
case "number":
|
|
800
|
-
case "bigint":
|
|
801
|
-
case "boolean":
|
|
802
|
-
case "date":
|
|
803
|
-
case "symbol":
|
|
804
|
-
case "undefined":
|
|
805
|
-
case "null":
|
|
806
|
-
case "any":
|
|
807
|
-
case "unknown":
|
|
808
|
-
case "never":
|
|
809
|
-
case "void":
|
|
810
|
-
case "map":
|
|
811
|
-
case "set":
|
|
812
|
-
case "function":
|
|
813
|
-
case "custom":
|
|
814
|
-
case "transform":
|
|
815
|
-
case "nan":
|
|
816
|
-
case "success":
|
|
817
|
-
case "file":
|
|
818
|
-
break;
|
|
819
|
-
default:
|
|
820
|
-
assertNeverZodDef(def);
|
|
821
|
-
}
|
|
822
|
-
} finally {
|
|
823
|
-
if (!alreadyActive) activeSchemas.delete(schema);
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
function mutatorCanUseCollectedTextItems(schema: InternalZodSchema): boolean {
|
|
828
|
-
if (!mutatorUsesStaticArrayFallback(schema)) return false;
|
|
829
|
-
const inner = flattenedOutputSchema(schema._zod.def);
|
|
830
|
-
if (inner?._zod.def.type !== "array") return false;
|
|
831
|
-
return collectOutputTextLeaves(asInternalZodSchema(inner._zod.def.element)).leaves.length > 0;
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
function resolveOutputTextLeaf(
|
|
835
|
-
schema: InternalZodSchema,
|
|
836
|
-
outerDeclarations: readonly TextTrustDeclaration[],
|
|
837
|
-
autoTrustAllowed: boolean,
|
|
838
|
-
inheritedUntrusted: boolean,
|
|
839
|
-
): OutputTextLeaf | undefined {
|
|
840
|
-
const resolved = resolveOutputTextLeafPair(schema, outerDeclarations, autoTrustAllowed);
|
|
841
|
-
return resolved?.[inheritedUntrusted ? "inherited" : "local"];
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
function resolveOutputTextLeafPair(
|
|
845
|
-
schema: InternalZodSchema,
|
|
846
|
-
outerDeclarations: readonly TextTrustDeclaration[],
|
|
847
|
-
autoTrustAllowed: boolean,
|
|
848
|
-
): { readonly inherited: OutputTextLeaf; readonly local: OutputTextLeaf } | undefined {
|
|
849
|
-
const declarations = [...outerDeclarations, readTextTrustDeclaration(schema)];
|
|
850
|
-
const def = schema._zod.def;
|
|
851
|
-
if (!isOutputTextLeafDef(def)) {
|
|
852
|
-
const inner = flattenedOutputSchema(def);
|
|
853
|
-
return inner
|
|
854
|
-
? resolveOutputTextLeafPair(
|
|
855
|
-
inner,
|
|
856
|
-
declarations,
|
|
857
|
-
autoTrustAllowed && !invalidatesOutputTextAutoTrust(def),
|
|
858
|
-
)
|
|
859
|
-
: undefined;
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
const autoTrusted =
|
|
863
|
-
autoTrustAllowed && !invalidatesOutputTextAutoTrust(def) && isAutoTrustedOutputTextLeaf(schema);
|
|
864
|
-
const explicitTrustAllowed = autoTrustAllowed && !invalidatesOutputTextAutoTrust(def);
|
|
865
|
-
return {
|
|
866
|
-
inherited: classifyOutputTextLeaf(declarations, autoTrusted, explicitTrustAllowed, true),
|
|
867
|
-
local: classifyOutputTextLeaf(declarations, autoTrusted, explicitTrustAllowed, false),
|
|
868
|
-
};
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
function classifyOutputTextLeaf(
|
|
872
|
-
declarations: readonly TextTrustDeclaration[],
|
|
873
|
-
autoTrusted: boolean,
|
|
874
|
-
explicitTrustAllowed: boolean,
|
|
875
|
-
inheritedUntrusted: boolean,
|
|
876
|
-
): OutputTextLeaf {
|
|
877
|
-
if (declarations.includes("invalid")) {
|
|
878
|
-
return { classification: "untrusted", classified: false };
|
|
879
|
-
}
|
|
880
|
-
if (declarations.includes("untrusted")) {
|
|
881
|
-
return { classification: "untrusted", classified: true };
|
|
882
|
-
}
|
|
883
|
-
if (declarations.includes("trusted")) {
|
|
884
|
-
return {
|
|
885
|
-
classification: explicitTrustAllowed ? "trusted" : "untrusted",
|
|
886
|
-
classified: explicitTrustAllowed,
|
|
887
|
-
};
|
|
888
|
-
}
|
|
889
|
-
if (inheritedUntrusted) {
|
|
890
|
-
return { classification: "untrusted", classified: true };
|
|
891
|
-
}
|
|
892
|
-
return autoTrusted
|
|
893
|
-
? { classification: "trusted", classified: true }
|
|
894
|
-
: { classification: "untrusted", classified: false };
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
function flattenedOutputSchema(def: InternalZodDef): InternalZodSchema | undefined {
|
|
898
|
-
switch (def.type) {
|
|
899
|
-
case "optional":
|
|
900
|
-
case "nullable":
|
|
901
|
-
case "default":
|
|
902
|
-
case "prefault":
|
|
903
|
-
case "catch":
|
|
904
|
-
case "readonly":
|
|
905
|
-
case "nonoptional":
|
|
906
|
-
case "promise":
|
|
907
|
-
return asInternalZodSchema(def.innerType);
|
|
908
|
-
case "pipe":
|
|
909
|
-
return asInternalZodSchema(def.out);
|
|
910
|
-
case "lazy":
|
|
911
|
-
return asInternalZodSchema(def.getter());
|
|
912
|
-
case "string":
|
|
913
|
-
case "template_literal":
|
|
914
|
-
case "literal":
|
|
915
|
-
case "enum":
|
|
916
|
-
case "number":
|
|
917
|
-
case "bigint":
|
|
918
|
-
case "boolean":
|
|
919
|
-
case "date":
|
|
920
|
-
case "symbol":
|
|
921
|
-
case "undefined":
|
|
922
|
-
case "null":
|
|
923
|
-
case "any":
|
|
924
|
-
case "unknown":
|
|
925
|
-
case "never":
|
|
926
|
-
case "void":
|
|
927
|
-
case "object":
|
|
928
|
-
case "array":
|
|
929
|
-
case "tuple":
|
|
930
|
-
case "record":
|
|
931
|
-
case "union":
|
|
932
|
-
case "intersection":
|
|
933
|
-
case "map":
|
|
934
|
-
case "set":
|
|
935
|
-
case "function":
|
|
936
|
-
case "custom":
|
|
937
|
-
case "transform":
|
|
938
|
-
case "nan":
|
|
939
|
-
case "success":
|
|
940
|
-
case "file":
|
|
941
|
-
return undefined;
|
|
942
|
-
default:
|
|
943
|
-
return assertNeverZodDef(def);
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
function isOutputTextLeafDef(def: InternalZodDef): boolean {
|
|
948
|
-
switch (def.type) {
|
|
949
|
-
case "string":
|
|
950
|
-
case "template_literal":
|
|
951
|
-
return true;
|
|
952
|
-
case "literal":
|
|
953
|
-
return def.values.some((value) => typeof value === "string");
|
|
954
|
-
case "enum":
|
|
955
|
-
return Object.values(def.entries).some((value) => typeof value === "string");
|
|
956
|
-
case "any":
|
|
957
|
-
case "unknown":
|
|
958
|
-
case "custom":
|
|
959
|
-
case "transform":
|
|
960
|
-
return true;
|
|
961
|
-
default:
|
|
962
|
-
return false;
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
function isAutoTrustedOutputTextLeaf(schema: InternalZodSchema): boolean {
|
|
967
|
-
const def = schema._zod.def;
|
|
968
|
-
if (hasUnsafeOutputCheck(def)) return false;
|
|
969
|
-
if (def.type === "literal" || def.type === "enum") return isOutputTextLeafDef(def);
|
|
970
|
-
if (def.type !== "string" && def.type !== "template_literal") return false;
|
|
971
|
-
|
|
972
|
-
const { bag, pattern } = schema._zod;
|
|
973
|
-
if (hasSdkOwnedStringFormatValidator(schema)) {
|
|
974
|
-
return true;
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
if (pattern instanceof RegExp && isRestrictiveAnchoredPattern(pattern)) return true;
|
|
978
|
-
const patterns = bag.patterns;
|
|
979
|
-
return (
|
|
980
|
-
patterns instanceof Set &&
|
|
981
|
-
patterns.size > 0 &&
|
|
982
|
-
[...patterns].every(
|
|
983
|
-
(candidate) => candidate instanceof RegExp && isRestrictiveAnchoredPattern(candidate),
|
|
984
|
-
)
|
|
985
|
-
);
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
function hasUnsafeOutputCheck(def: InternalZodDef): boolean {
|
|
989
|
-
const checks = Reflect.get(def, "checks");
|
|
990
|
-
return (
|
|
991
|
-
Array.isArray(checks) &&
|
|
992
|
-
checks.some((check) => {
|
|
993
|
-
if (!check || typeof check !== "object") return false;
|
|
994
|
-
const internals = Reflect.get(check, "_zod");
|
|
995
|
-
if (!internals || typeof internals !== "object") return false;
|
|
996
|
-
const checkDef = Reflect.get(internals, "def");
|
|
997
|
-
if (!checkDef || typeof checkDef !== "object") return false;
|
|
998
|
-
const kind = Reflect.get(checkDef, "check");
|
|
999
|
-
return kind === "overwrite" || kind === "custom";
|
|
1000
|
-
})
|
|
1001
|
-
);
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
function invalidatesOutputTextAutoTrust(def: InternalZodDef): boolean {
|
|
1005
|
-
return (
|
|
1006
|
-
def.type === "default" ||
|
|
1007
|
-
def.type === "catch" ||
|
|
1008
|
-
def.type === "transform" ||
|
|
1009
|
-
hasUnsafeOutputCheck(def)
|
|
1010
|
-
);
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
interface OwnedStringFormatValidator {
|
|
1014
|
-
readonly constructor: unknown;
|
|
1015
|
-
readonly patternFlags: string;
|
|
1016
|
-
readonly patternSource: string;
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
const SDK_OWNED_STRING_FORMAT_VALIDATORS: readonly OwnedStringFormatValidator[] = [
|
|
1020
|
-
z.cidrv4(),
|
|
1021
|
-
z.cidrv6(),
|
|
1022
|
-
z.iso.date(),
|
|
1023
|
-
z.iso.datetime(),
|
|
1024
|
-
z.iso.duration(),
|
|
1025
|
-
z.e164(),
|
|
1026
|
-
z.ipv4(),
|
|
1027
|
-
z.ipv6(),
|
|
1028
|
-
z.iso.time(),
|
|
1029
|
-
].map((validator) => {
|
|
1030
|
-
const pattern = Reflect.get(validator._zod.def, "pattern");
|
|
1031
|
-
if (!(pattern instanceof RegExp)) {
|
|
1032
|
-
throw new TypeError("SDK-owned Zod string format validator is missing its pattern.");
|
|
1033
|
-
}
|
|
1034
|
-
return {
|
|
1035
|
-
constructor: validator.constructor,
|
|
1036
|
-
patternFlags: pattern.flags,
|
|
1037
|
-
patternSource: pattern.source,
|
|
1038
|
-
};
|
|
1039
|
-
});
|
|
1040
|
-
|
|
1041
|
-
function hasSdkOwnedStringFormatValidator(schema: InternalZodSchema): boolean {
|
|
1042
|
-
const candidates: unknown[] = [schema];
|
|
1043
|
-
const checks = Reflect.get(schema._zod.def, "checks");
|
|
1044
|
-
if (Array.isArray(checks)) candidates.push(...checks);
|
|
1045
|
-
return candidates.some((candidate) => {
|
|
1046
|
-
if (!candidate || typeof candidate !== "object") return false;
|
|
1047
|
-
const internals = Reflect.get(candidate, "_zod");
|
|
1048
|
-
if (!internals || typeof internals !== "object") return false;
|
|
1049
|
-
const def = Reflect.get(internals, "def");
|
|
1050
|
-
if (!def || typeof def !== "object" || Reflect.has(def, "fn")) return false;
|
|
1051
|
-
const pattern = Reflect.get(def, "pattern");
|
|
1052
|
-
if (!(pattern instanceof RegExp)) return false;
|
|
1053
|
-
return SDK_OWNED_STRING_FORMAT_VALIDATORS.some(
|
|
1054
|
-
(owned) =>
|
|
1055
|
-
Reflect.get(candidate, "constructor") === owned.constructor &&
|
|
1056
|
-
pattern.source === owned.patternSource &&
|
|
1057
|
-
pattern.flags === owned.patternFlags,
|
|
1058
|
-
);
|
|
1059
|
-
});
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
function isRestrictiveAnchoredPattern(pattern: RegExp): boolean {
|
|
1063
|
-
if (pattern.multiline) return false;
|
|
1064
|
-
const alternatives = splitTopLevelRegexAlternatives(pattern.source);
|
|
1065
|
-
if (alternatives.length === 0) return false;
|
|
1066
|
-
let maximumLength = 0;
|
|
1067
|
-
for (const alternative of alternatives) {
|
|
1068
|
-
if (!alternative.startsWith("^") || !alternative.endsWith("$")) return false;
|
|
1069
|
-
const analysis = analyzeProvablyRestrictedRegexBody(alternative.slice(1, -1));
|
|
1070
|
-
if (!analysis) return false;
|
|
1071
|
-
maximumLength = Math.max(maximumLength, analysis.maximumLength);
|
|
1072
|
-
}
|
|
1073
|
-
return maximumLength <= MAXIMUM_AUTO_TRUSTED_REGEX_LENGTH;
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
function splitTopLevelRegexAlternatives(source: string): string[] {
|
|
1077
|
-
const alternatives: string[] = [];
|
|
1078
|
-
let start = 0;
|
|
1079
|
-
let escaped = false;
|
|
1080
|
-
let inCharacterClass = false;
|
|
1081
|
-
let groupDepth = 0;
|
|
1082
|
-
for (let index = 0; index < source.length; index += 1) {
|
|
1083
|
-
const character = source[index];
|
|
1084
|
-
if (escaped) {
|
|
1085
|
-
escaped = false;
|
|
1086
|
-
continue;
|
|
1087
|
-
}
|
|
1088
|
-
if (character === "\\") {
|
|
1089
|
-
escaped = true;
|
|
1090
|
-
continue;
|
|
1091
|
-
}
|
|
1092
|
-
if (character === "[") {
|
|
1093
|
-
inCharacterClass = true;
|
|
1094
|
-
continue;
|
|
1095
|
-
}
|
|
1096
|
-
if (character === "]") {
|
|
1097
|
-
inCharacterClass = false;
|
|
1098
|
-
continue;
|
|
1099
|
-
}
|
|
1100
|
-
if (inCharacterClass) continue;
|
|
1101
|
-
if (character === "(") {
|
|
1102
|
-
groupDepth += 1;
|
|
1103
|
-
continue;
|
|
1104
|
-
}
|
|
1105
|
-
if (character === ")") {
|
|
1106
|
-
groupDepth -= 1;
|
|
1107
|
-
continue;
|
|
1108
|
-
}
|
|
1109
|
-
if (character === "|" && groupDepth === 0) {
|
|
1110
|
-
alternatives.push(source.slice(start, index));
|
|
1111
|
-
start = index + 1;
|
|
1112
|
-
}
|
|
1113
|
-
}
|
|
1114
|
-
alternatives.push(source.slice(start));
|
|
1115
|
-
return alternatives;
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
interface RestrictedRegexAnalysis {
|
|
1119
|
-
readonly maximumLength: number;
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1122
|
-
interface SafeRegexCharacterClassAnalysis extends RestrictedRegexAnalysis {
|
|
1123
|
-
readonly consumedLength: number;
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
const MAXIMUM_AUTO_TRUSTED_REGEX_LENGTH = 32;
|
|
1127
|
-
|
|
1128
|
-
function analyzeProvablyRestrictedRegexBody(source: string): RestrictedRegexAnalysis | undefined {
|
|
1129
|
-
let index = 0;
|
|
1130
|
-
const parseAlternatives = (): RestrictedRegexAnalysis | undefined => {
|
|
1131
|
-
let maximumLength = 0;
|
|
1132
|
-
while (true) {
|
|
1133
|
-
const sequence = parseSequence();
|
|
1134
|
-
if (!sequence) return undefined;
|
|
1135
|
-
maximumLength = Math.max(maximumLength, sequence.maximumLength);
|
|
1136
|
-
if (source[index] !== "|") break;
|
|
1137
|
-
index += 1;
|
|
1138
|
-
}
|
|
1139
|
-
return { maximumLength };
|
|
1140
|
-
};
|
|
1141
|
-
const parseSequence = (): RestrictedRegexAnalysis | undefined => {
|
|
1142
|
-
let maximumLength = 0;
|
|
1143
|
-
while (index < source.length && source[index] !== "|" && source[index] !== ")") {
|
|
1144
|
-
const atom = parseAtom();
|
|
1145
|
-
if (!atom) return undefined;
|
|
1146
|
-
let maximumRepetitions = 1;
|
|
1147
|
-
if (source[index] === "?") {
|
|
1148
|
-
index += 1;
|
|
1149
|
-
} else if (source[index] === "{") {
|
|
1150
|
-
const match = /^\{(\d+)(?:,(\d+))?\}/.exec(source.slice(index));
|
|
1151
|
-
if (!match) return undefined;
|
|
1152
|
-
const minimum = Number(match[1]);
|
|
1153
|
-
maximumRepetitions = match[2] === undefined ? minimum : Number(match[2]);
|
|
1154
|
-
if (
|
|
1155
|
-
!Number.isSafeInteger(minimum) ||
|
|
1156
|
-
!Number.isSafeInteger(maximumRepetitions) ||
|
|
1157
|
-
maximumRepetitions < minimum
|
|
1158
|
-
)
|
|
1159
|
-
return undefined;
|
|
1160
|
-
index += match[0].length;
|
|
1161
|
-
}
|
|
1162
|
-
if (source[index] === "?") index += 1;
|
|
1163
|
-
maximumLength = addRestrictedRegexLengths(
|
|
1164
|
-
maximumLength,
|
|
1165
|
-
multiplyRestrictedRegexLength(atom.maximumLength, maximumRepetitions),
|
|
1166
|
-
);
|
|
1167
|
-
}
|
|
1168
|
-
return { maximumLength };
|
|
1169
|
-
};
|
|
1170
|
-
const parseAtom = (): RestrictedRegexAnalysis | undefined => {
|
|
1171
|
-
if (source[index] === "(") {
|
|
1172
|
-
index += 1;
|
|
1173
|
-
if (source[index] === "?") {
|
|
1174
|
-
if (source.slice(index, index + 2) !== "?:") return undefined;
|
|
1175
|
-
index += 2;
|
|
1176
|
-
}
|
|
1177
|
-
const group = parseAlternatives();
|
|
1178
|
-
if (!group || source[index] !== ")") return undefined;
|
|
1179
|
-
index += 1;
|
|
1180
|
-
return group;
|
|
1181
|
-
}
|
|
1182
|
-
if (source[index] === "[") {
|
|
1183
|
-
const characterClass = analyzeSafeRegexCharacterClass(source.slice(index));
|
|
1184
|
-
if (!characterClass) return undefined;
|
|
1185
|
-
index += characterClass.consumedLength;
|
|
1186
|
-
return characterClass;
|
|
1187
|
-
}
|
|
1188
|
-
const atomLength = safeRegexAtomLength(source.slice(index));
|
|
1189
|
-
if (atomLength === 0) return undefined;
|
|
1190
|
-
index += atomLength;
|
|
1191
|
-
return { maximumLength: 1 };
|
|
1192
|
-
};
|
|
1193
|
-
|
|
1194
|
-
const analysis = parseAlternatives();
|
|
1195
|
-
return analysis && index === source.length ? analysis : undefined;
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
function analyzeSafeRegexCharacterClass(
|
|
1199
|
-
source: string,
|
|
1200
|
-
): SafeRegexCharacterClassAnalysis | undefined {
|
|
1201
|
-
if (!source.startsWith("[") || source[1] === "^") return undefined;
|
|
1202
|
-
for (let index = 1; index < source.length; index += 1) {
|
|
1203
|
-
const character = source[index];
|
|
1204
|
-
if (character === "]") {
|
|
1205
|
-
return { consumedLength: index + 1, maximumLength: 1 };
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
const atomLength = safeRegexAtomLength(source.slice(index));
|
|
1209
|
-
if (atomLength === 0) return undefined;
|
|
1210
|
-
const rangeSeparator = index + atomLength;
|
|
1211
|
-
if (source[rangeSeparator] === "-" && source[rangeSeparator + 1] !== "]") {
|
|
1212
|
-
const endIndex = rangeSeparator + 1;
|
|
1213
|
-
const endLength = safeRegexAtomLength(source.slice(endIndex));
|
|
1214
|
-
if (endLength === 0 || !isSafeRegexCharacterRange(source, index, endIndex)) return undefined;
|
|
1215
|
-
index = endIndex + endLength - 1;
|
|
1216
|
-
continue;
|
|
1217
|
-
}
|
|
1218
|
-
index += atomLength - 1;
|
|
1219
|
-
}
|
|
1220
|
-
return undefined;
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
function addRestrictedRegexLengths(left: number, right: number): number {
|
|
1224
|
-
return left > MAXIMUM_AUTO_TRUSTED_REGEX_LENGTH - right
|
|
1225
|
-
? MAXIMUM_AUTO_TRUSTED_REGEX_LENGTH + 1
|
|
1226
|
-
: left + right;
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
|
-
function multiplyRestrictedRegexLength(length: number, repetitions: number): number {
|
|
1230
|
-
if (length === 0 || repetitions === 0) return 0;
|
|
1231
|
-
return repetitions > Math.floor(MAXIMUM_AUTO_TRUSTED_REGEX_LENGTH / length)
|
|
1232
|
-
? MAXIMUM_AUTO_TRUSTED_REGEX_LENGTH + 1
|
|
1233
|
-
: length * repetitions;
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
function safeRegexAtomLength(source: string): number {
|
|
1237
|
-
const character = source[0];
|
|
1238
|
-
if (character === undefined) return 0;
|
|
1239
|
-
if (/^[0-9]$/.test(character)) return 1;
|
|
1240
|
-
if (character === "-") return 1;
|
|
1241
|
-
if (character !== "\\") return 0;
|
|
1242
|
-
const escaped = source[1];
|
|
1243
|
-
if (escaped === "d") return 2;
|
|
1244
|
-
return escaped !== undefined && SAFE_REGEX_ESCAPED_PUNCTUATION.includes(escaped) ? 2 : 0;
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
function isSafeRegexCharacterRange(source: string, startIndex: number, endIndex: number): boolean {
|
|
1248
|
-
const start = regexCharacterClassLiteralCodePoint(source, startIndex);
|
|
1249
|
-
const end = regexCharacterClassLiteralCodePoint(source, endIndex);
|
|
1250
|
-
if (start === undefined || end === undefined || start > end) return false;
|
|
1251
|
-
const minimum = "0".codePointAt(0);
|
|
1252
|
-
const maximum = "9".codePointAt(0);
|
|
1253
|
-
return minimum !== undefined && maximum !== undefined && start >= minimum && end <= maximum;
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
function regexCharacterClassLiteralCodePoint(source: string, index: number): number | undefined {
|
|
1257
|
-
const character = source[index];
|
|
1258
|
-
if (character !== "\\") return character?.codePointAt(0);
|
|
1259
|
-
const escaped = source[index + 1];
|
|
1260
|
-
if (escaped === undefined || escaped === "d" || escaped === "w") return undefined;
|
|
1261
|
-
return escaped.codePointAt(0);
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
const SAFE_REGEX_ESCAPED_PUNCTUATION = "\\.-^$*+?()[]{}|";
|
|
1265
|
-
|
|
1266
|
-
function requireOutputTextTrustSchema(schema: unknown): InternalZodSchema {
|
|
1267
|
-
if (!(schema instanceof z.ZodType)) throw new OutputTextTrustSchemaError(schema);
|
|
1268
|
-
return asInternalZodSchema(schema);
|
|
1269
|
-
}
|
|
1270
|
-
|
|
1271
|
-
function asInternalZodSchema(schema: z.core.$ZodType): InternalZodSchema {
|
|
1272
|
-
// Zod's child-definition fields use the base type and erase the concrete
|
|
1273
|
-
// union member. This is the only cast boundary; all callers immediately
|
|
1274
|
-
// narrow the returned discriminated definition by `def.type`.
|
|
1275
|
-
return schema as InternalZodSchema;
|
|
1276
|
-
}
|
|
1277
|
-
|
|
1278
|
-
function assertNeverZodDef(def: never): never {
|
|
1279
|
-
throw new Error(`Unsupported Zod definition: ${String((def as { type?: unknown }).type)}`);
|
|
1280
|
-
}
|
|
1281
|
-
|
|
1282
|
-
function readTextTrustDeclaration(schema: unknown): TextTrustDeclaration {
|
|
1283
|
-
const metadata = readZodMetadata(schema);
|
|
1284
|
-
if (!metadata || !Reflect.has(metadata, APIFUSE_TEXT_TRUST_META_KEY)) return "absent";
|
|
1285
|
-
const value = Reflect.get(metadata, APIFUSE_TEXT_TRUST_META_KEY);
|
|
1286
|
-
if (!value || typeof value !== "object") return "invalid";
|
|
1287
|
-
if (Reflect.get(value, "v") !== 1) return "invalid";
|
|
1288
|
-
const trust = Reflect.get(value, "trust");
|
|
1289
|
-
return trust === "trusted" || trust === "untrusted" ? trust : "invalid";
|
|
1290
|
-
}
|
|
1291
|
-
|
|
1292
241
|
export function isSensitiveSchema(schema: unknown): boolean {
|
|
1293
242
|
const metadata = readZodMetadata(schema);
|
|
1294
243
|
return metadata !== undefined && Reflect.get(metadata, APIFUSE_SENSITIVE_META_KEY) === true;
|