(value: unknown)
| 485 | } |
| 486 | |
| 487 | export function validateRuntimeRecord(value: unknown): RuntimeValidationResult<RuntimeRecord> { |
| 488 | if (!isRecord(value)) return validationError("$", "Runtime record must be an object") |
| 489 | for (const key of Object.keys(value)) { |
| 490 | if (!RUNTIME_RECORD_KEYS.has(key)) return validationError(`$.${key}`, `${key} is not allowed in runtime record`) |
| 491 | } |
| 492 | if ("ownerType" in value) return validationError("$.ownerType", "Runtime record ownerType must be nested under scope") |
| 493 | if ("ownerId" in value) return validationError("$.ownerId", "Runtime record ownerId must be nested under scope") |
| 494 | if ("rootPath" in value) return validationError("$.rootPath", "Runtime record rootPath must be nested under scope") |
| 495 | if ("repoPath" in value) return validationError("$.repoPath", "Runtime record repoPath must be nested under scope") |
| 496 | |
| 497 | const runtimeId = requiredString(value, "runtimeId") |
| 498 | if (!runtimeId.ok) return runtimeId |
| 499 | if (!isRuntimeKind(value.kind)) return validationError("$.kind", "Runtime record kind is invalid") |
| 500 | if (!isRuntimeStatus(value.status)) return validationError("$.status", "Runtime record status is invalid") |
| 501 | |
| 502 | const scope = validateRuntimeScope(value.scope) |
| 503 | if (!scope.ok) { |
| 504 | const scopePath = scope.error.path ?? "$" |
| 505 | return { ok: false, error: { ...scope.error, path: scopePath === "$" ? "$.scope" : `$.scope${scopePath.slice(1)}` } } |
| 506 | } |
| 507 | |
| 508 | const startedAt = requiredString(value, "startedAt") |
| 509 | if (!startedAt.ok) return startedAt |
| 510 | const updatedAt = requiredString(value, "updatedAt") |
| 511 | if (!updatedAt.ok) return updatedAt |
| 512 | const lastActivityAt = requiredString(value, "lastActivityAt") |
| 513 | if (!lastActivityAt.ok) return lastActivityAt |
| 514 | |
| 515 | const output: RuntimeRecord = { |
| 516 | runtimeId: runtimeId.value, |
| 517 | kind: value.kind, |
| 518 | status: value.status, |
| 519 | scope: scope.value, |
| 520 | startedAt: startedAt.value, |
| 521 | updatedAt: updatedAt.value, |
| 522 | lastActivityAt: lastActivityAt.value, |
| 523 | } |
| 524 | |
| 525 | for (const key of ["nativeId", "processLabel", "processStartedAt", "exitedAt", "error"] as const) { |
| 526 | const field = value[key] |
| 527 | if (field === undefined) continue |
| 528 | if (typeof field !== "string") return validationError(`$.${key}`, `${key} must be a string`) |
| 529 | output[key] = field |
| 530 | } |
| 531 | |
| 532 | if (value.signal !== undefined) { |
| 533 | if (value.signal !== null && typeof value.signal !== "string") return validationError("$.signal", "signal must be a string or null") |
| 534 | output.signal = value.signal |
| 535 | } |
| 536 | |
| 537 | for (const key of ["pid", "pgid"] as const) { |
| 538 | const field = value[key] |
| 539 | if (field === undefined) continue |
| 540 | if (typeof field !== "number" || !Number.isFinite(field)) return validationError(`$.${key}`, `${key} must be a finite number`) |
| 541 | output[key] = field |
| 542 | } |
| 543 | |
| 544 | if (value.exitCode !== undefined) { |
no test coverage detected