Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: z.envbool() #3906

Open
colinhacks opened this issue Dec 11, 2024 · 12 comments
Open

RFC: z.envbool() #3906

colinhacks opened this issue Dec 11, 2024 · 12 comments

Comments

@colinhacks
Copy link
Owner

colinhacks commented Dec 11, 2024

Alternative name: z.stringbool()

A customizable, first-party way to coerce string inputs to boolean, inspired by common environment variable conventions.

z.envbool()

Default behavior:

  • Returns true for "true"|"1"|"on"|"yes"|"y"|"enabled" (case insensitive)
  • Returns false for "false"|"0"|"off"|"no"|"n"|"disabled" (case insensitive)
  • Throws otherwise

The choice to throw on unrecognized values is intentional. This API will be used for parsing potentially sensitive environment variables, so this API intentionally avoids any non-explicit defaults (e.g. defaulting to false) to avoid potentially unexpected behavior. To specify a value to be returned when an uexpected input is encountered, users can use the pre-existing .catch() method.

z.envbool().catch(false)

To customize the set of truthy and falsy strings:

z.envbool({
  true: ["y"],
  false: ["n"]
})

To make it case sensitive:

z.envbool({
  case: "sensitive" // default "insensitive"
})
@nathggns
Copy link

nathggns commented Dec 11, 2024

Love it. This would replace something we've had to build ourselves, and far more robustly too. We also built it only after discovering why coerce didn't do what we needed here, so it would help teams to avoid bugs.

@crisshaker
Copy link

Ohh so glad this is going to get native support. Curious though, having 'env' in the name seems a little too specific/suggesting? I know there's nothing preventing using it any other way but just curious if it's really intended.

@kevinbailey25
Copy link

This is handy, we have something to had to build to solve this. Specifically when handling checkboxes on posted forms. We'd do a transform to see if the value was === "TRUE" or whatever value our checkbox was.

I'm not sure how I feel about the "envbool" name tho.

@4lve
Copy link

4lve commented Dec 11, 2024

Good idea, though it might make it clearer if you named it something like 'commonbool', 'humanbool', 'stringbool'

@quantizor
Copy link

normalizedBool might be easier to immediately understand

@Vinlock
Copy link

Vinlock commented Dec 11, 2024

Would be interesting if it was something like

z.coerce.boolean({
  acceptedTruthyValues: [...],
  acceptedFalsyValues: [...],
  case: '...',
})

@capaj
Copy link

capaj commented Dec 12, 2024

envbool sounds like the usage is limited to env vars, which is not the case. Please rename the proposed method to stringBool or stringAsBool()

@yousefelgoharyx
Copy link

aliasedBool is so much better to understand

@scotttrinh
Copy link
Collaborator

As far as this proposal is concerned, as-is, I agree that "env" is a little too suggestive since I think the most common case I've seen users hit this snag is in search parameter parsing, which wants an even subtly different set of defaults (like "" being truthy...). What if we exposed just the more manual API with no "default" and exported some common configurations like:

import { z } from "zod";

// The current defaults
z.boolFrom(z.ENV_BOOL_FROM);

// URLSearchParams value
z.boolFrom(z.SEARCH_PARAMS_BOOL_FROM);

I don't actually like that (maybe separate methods on boolFrom?) but I think that avoids this being hard to intuit.


I wonder if there is a more general solution to "map from some input domain into some enumerated domain"? I guess that's just union-of-pipe, right? But, that would need to be combined with some kind of easy-to-use case-insensitive literal schema to approach the same ergonomics.

import { z } from "zod";

z.union([
  z.stringOf(["y", "true", "yes"]).pipe(z.true()),
  z.stringOf(["n", "false", "no"]).pipe(z.false()),
]);

@Ashwin-Mahadevan
Copy link

This proposal feels like it should be in the z.coerce api. I agree with @Vinlock, although I'd prefer this syntax:

import { z } from "zod";

const first = z.coerce.envbool();

const second = z.coerce.envbool({
    truthy: ["true", "1", "..."],
    falsy: ["false", "0", "..."],
    case: "sensitive",
});

@soulchild
Copy link

boolish? 😄

@joshuayoes
Copy link

This feels like this should be a part of the String collection

z.string().bool()

Especially since there are other string parsed types like Datetime which can be passed options like this:

const datetime = z.string().datetime({ offset: true });

This would feel pretty natural since these extra string validations already can be passed options.

z.string().bool({
  true: ["y"],
  false: ["n"]
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests