History

Validate cached OpenAPI payloads before rehydration

Rhys Sullivan committed 8 months ago

Commit 501720b1 parent 91ec1fe1 file · +33 −3Browse files

Unified · Split

executor/packages/database/src/runtime/tool_source_loading.ts+33−3
@@ -29,6 +29,33 @@ const OPENAPI_SPEC_CACHE_TTL_MS = 5 * 60 * 60_000; · 28 unmodified lines
29/** Cache version - bump when tool snapshot/registry/type-hint semantics change. */29/** Cache version - bump when tool snapshot/registry/type-hint semantics change. */
30const TOOL_SOURCE_CACHE_VERSION = "v25";30const TOOL_SOURCE_CACHE_VERSION = "v25";
31 31
32function toPreparedOpenApiSpec(value: unknown): PreparedOpenApiSpec | null {
33 const record = asPayload(value);
34 if (!Array.isArray(record.servers)) {
35 return null;
36 }
37 if (!record.paths || typeof record.paths !== "object" || Array.isArray(record.paths)) {
38 return null;
39 }
40
41 const servers = record.servers.filter((entry): entry is string => typeof entry === "string");
42 const warnings = Array.isArray(record.warnings)
43 ? record.warnings.filter((entry): entry is string => typeof entry === "string")
44 : [];
45 const dtsStatus =
46 record.dtsStatus === "ready" || record.dtsStatus === "failed" || record.dtsStatus === "skipped"
47 ? record.dtsStatus
48 : undefined;
49
50 return {
51 servers,
52 paths: asPayload(record.paths),
53 warnings,
54 dts: typeof record.dts === "string" ? record.dts : undefined,
55 dtsStatus,
56 };
57}
58
32export function sourceSignature(workspaceId: string, sources: Array<{ id: string; updatedAt: number; enabled: boolean }>): string {59export function sourceSignature(workspaceId: string, sources: Array<{ id: string; updatedAt: number; enabled: boolean }>): string {
33 const parts = sources60 const parts = sources
34 .map((source) => `${source.id}:${source.updatedAt}:${source.enabled ? 1 : 0}`)61 .map((source) => `${source.id}:${source.updatedAt}:${source.enabled ? 1 : 0}`)
@@ -235,9 +262,12 @@ async function loadCachedOpenApiSpec( · 200 unmodified lines
235 const blob = await ctx.storage.get(entry.storageId);262 const blob = await ctx.storage.get(entry.storageId);
236 if (blob) {263 if (blob) {
237 const json = await blob.text();264 const json = await blob.text();
238 const prepared = JSON.parse(json) as PreparedOpenApiSpec;265 const parsedResult = Result.try(() => JSON.parse(json));
239 if (!includeDts || getDtsStatus(prepared) !== "skipped") {266 if (parsedResult.isOk()) {
240 return prepared;267 const prepared = toPreparedOpenApiSpec(parsedResult.value);
268 if (prepared && (!includeDts || getDtsStatus(prepared) !== "skipped")) {
269 return prepared;
270 }
241 }271 }
242 }272 }
243 }273 }