Skip to content

Commit ca09724

Browse files
committed
fix: zod boolean coercion
1 parent 0661ee5 commit ca09724

File tree

5 files changed

+27
-7
lines changed

5 files changed

+27
-7
lines changed

apps/supervisor/src/env.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { randomUUID } from "crypto";
22
import { env as stdEnv } from "std-env";
33
import { z } from "zod";
4-
import { AdditionalEnvVars, BoolEnv } from "./envUtil.js";
4+
import { AdditionalEnvVars, BoolEnv, CoercedBoolean } from "./envUtil.js";
55

66
const Env = z.object({
77
// This will come from `spec.nodeName` in k8s
@@ -45,7 +45,7 @@ const Env = z.object({
4545
// Used by the workload manager, e.g docker/k8s
4646
DOCKER_NETWORK: z.string().default("host"),
4747
OTEL_EXPORTER_OTLP_ENDPOINT: z.string().url(),
48-
ENFORCE_MACHINE_PRESETS: z.coerce.boolean().default(false),
48+
ENFORCE_MACHINE_PRESETS: CoercedBoolean.default(false),
4949
KUBERNETES_IMAGE_PULL_SECRETS: z.string().optional(), // csv
5050

5151
// Used by the resource monitor

apps/supervisor/src/envUtil.ts

+9
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,12 @@ export const AdditionalEnvVars = z.preprocess((val) => {
3737
return undefined;
3838
}
3939
}, z.record(z.string(), z.string()).optional());
40+
41+
/**
42+
* Zod's `z.coerce.boolean()` doesn't work as _expected_ with "true" and "false" strings.
43+
* as it coerces both to `true`. This type is a workaround for that.
44+
*/
45+
export const CoercedBoolean = z.union([
46+
z.boolean(),
47+
z.enum(["true", "false"]).transform((v) => v === "true"),
48+
]);

apps/webapp/app/components/runs/v3/RunFilters.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import {
5353
TaskRunStatusCombo,
5454
} from "./TaskRunStatus";
5555
import { TaskTriggerSourceIcon } from "./TaskTriggerSource";
56+
import { CoercedBoolean } from "~/utils/zod";
5657

5758
export const TaskAttemptStatus = z.enum(allTaskRunStatuses);
5859

@@ -83,7 +84,7 @@ export const TaskRunListSearchFilters = z.object({
8384
period: z.preprocess((value) => (value === "all" ? undefined : value), z.string().optional()),
8485
from: z.coerce.number().optional(),
8586
to: z.coerce.number().optional(),
86-
rootOnly: z.coerce.boolean().optional(),
87+
rootOnly: CoercedBoolean.optional(),
8788
batchId: z.string().optional(),
8889
runId: z.string().optional(),
8990
scheduleId: z.string().optional(),

apps/webapp/app/env.server.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { z } from "zod";
22
import { SecretStoreOptionsSchema } from "./services/secrets/secretStoreOptionsSchema.server";
33
import { isValidDatabaseUrl } from "./utils/db";
44
import { isValidRegex } from "./utils/regex";
5+
import { CoercedBoolean } from "./utils/zod";
56

67
const EnvironmentSchema = z.object({
78
NODE_ENV: z.union([z.literal("development"), z.literal("production"), z.literal("test")]),
@@ -50,7 +51,7 @@ const EnvironmentSchema = z.object({
5051
RESEND_API_KEY: z.string().optional(),
5152
SMTP_HOST: z.string().optional(),
5253
SMTP_PORT: z.coerce.number().optional(),
53-
SMTP_SECURE: z.coerce.boolean().optional(),
54+
SMTP_SECURE: CoercedBoolean.optional(),
5455
SMTP_USER: z.string().optional(),
5556
SMTP_PASSWORD: z.string().optional(),
5657

@@ -338,7 +339,7 @@ const EnvironmentSchema = z.object({
338339
ALERT_RESEND_API_KEY: z.string().optional(),
339340
ALERT_SMTP_HOST: z.string().optional(),
340341
ALERT_SMTP_PORT: z.coerce.number().optional(),
341-
ALERT_SMTP_SECURE: z.coerce.boolean().optional(),
342+
ALERT_SMTP_SECURE: CoercedBoolean.optional(),
342343
ALERT_SMTP_USER: z.string().optional(),
343344
ALERT_SMTP_PASSWORD: z.string().optional(),
344345
ALERT_RATE_LIMITER_EMISSION_INTERVAL: z.coerce.number().int().default(2_500),
@@ -378,7 +379,7 @@ const EnvironmentSchema = z.object({
378379
MAX_SEQUENTIAL_INDEX_FAILURE_COUNT: z.coerce.number().default(96),
379380

380381
LOOPS_API_KEY: z.string().optional(),
381-
MARQS_DISABLE_REBALANCING: z.coerce.boolean().default(false),
382+
MARQS_DISABLE_REBALANCING: CoercedBoolean.default(false),
382383
MARQS_VISIBILITY_TIMEOUT_MS: z.coerce
383384
.number()
384385
.int()
@@ -452,7 +453,7 @@ const EnvironmentSchema = z.object({
452453
RUN_ENGINE_TIMEOUT_PENDING_CANCEL: z.coerce.number().int().default(60_000),
453454
RUN_ENGINE_TIMEOUT_EXECUTING: z.coerce.number().int().default(60_000),
454455
RUN_ENGINE_TIMEOUT_EXECUTING_WITH_WAITPOINTS: z.coerce.number().int().default(60_000),
455-
RUN_ENGINE_DEBUG_WORKER_NOTIFICATIONS: z.coerce.boolean().default(false),
456+
RUN_ENGINE_DEBUG_WORKER_NOTIFICATIONS: CoercedBoolean.default(false),
456457
RUN_ENGINE_PARENT_QUEUE_LIMIT: z.coerce.number().int().default(1000),
457458
RUN_ENGINE_CONCURRENCY_LIMIT_BIAS: z.coerce.number().default(0.75),
458459
RUN_ENGINE_AVAILABLE_CAPACITY_BIAS: z.coerce.number().default(0.3),

apps/webapp/app/utils/zod.ts

+9
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,12 @@ export const CoercedDate = z.preprocess((arg) => {
2020

2121
return arg;
2222
}, z.date().optional());
23+
24+
/**
25+
* Zod's `z.coerce.boolean()` doesn't work as _expected_ with "true" and "false" strings.
26+
* as it coerces both to `true`. This type is a workaround for that.
27+
*/
28+
export const CoercedBoolean = z.union([
29+
z.boolean(),
30+
z.enum(["true", "false"]).transform((v) => v === "true"),
31+
]);

0 commit comments

Comments
 (0)