UNPKG

znv

Version:

Parse your environment with Zod schemas

218 lines 9.21 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.getPreprocessorByZodType = getPreprocessorByZodType; exports.getSchemaWithPreprocessor = getSchemaWithPreprocessor; const z = __importStar(require("zod")); const type_helpers_js_1 = require("./util/type-helpers.js"); const { ZodFirstPartyTypeKind: TypeName } = z; /** * Given a Zod schema, returns a function that tries to convert a string (or * undefined!) to a valid input type for the schema. */ function getPreprocessorByZodType(schema) { const def = schema._def; const { typeName } = def; switch (typeName) { case TypeName.ZodString: case TypeName.ZodEnum: case TypeName.ZodUndefined: return (arg) => arg; case TypeName.ZodNumber: return (arg) => { if (typeof arg === "string" && /^-?\d+(\.\d+)?$/.test(arg)) { return Number(arg); } return arg; }; case TypeName.ZodBigInt: return (arg) => { if (typeof arg === "string" && /^-?\d+$/.test(arg)) { return BigInt(arg); } return arg; }; // env vars that act as flags might be declared in a number of ways, // including simply `SOME_VALUE=` (with no RHS). the latter convention // doesn't seem to be in widespread use with node, though. (that's probably // because it results in the env var being present as the empty string, // which is falsy.) // // this preprocessor is kind of a hedge -- it accepts a few different // specific values to signify true or false. i can think of two other // options: // - coerce any value that's not `undefined` to `true` (or maybe any value // that's not `undefined` or `false` or `0`, but again the complexity // piles up quickly here). // - coerce *only* 'true' and 'false' to their respective values. this could // be complemented by a custom schema called 'flag' or something else that // handles a looser coercion case (for now this is easy for users to do in // their own code according to their needs). // // for now, this hedge seems to work fine, but it might be worth revisiting. case TypeName.ZodBoolean: return (arg) => { if (typeof arg === "string") { // eslint-disable-next-line default-case switch (arg) { case "true": case "yes": case "1": return true; case "false": case "no": case "0": return false; } } return arg; }; case TypeName.ZodArray: case TypeName.ZodObject: case TypeName.ZodTuple: case TypeName.ZodRecord: case TypeName.ZodIntersection: return (arg) => { // neither `undefined` nor the empty string are valid json. if (!arg) return arg; // the one circumstance (so far) when i think a preprocessor should be // able to throw is if we're coercing to json but it's invalid -- this // way the error message will be more informative (rather than just // "expected x, got string"). in the future `getPreprocessor` could // maybe be refined to return a result type instead, but let's not // overengineer things for now. return JSON.parse(arg); }; case TypeName.ZodEffects: return getPreprocessorByZodType(def.schema); case TypeName.ZodDefault: // eslint-disable-next-line unicorn/consistent-destructuring -- false positive return getPreprocessorByZodType(def.innerType); case TypeName.ZodOptional: { const { innerType } = def; const pp = getPreprocessorByZodType(innerType); return (arg) => { if (arg === undefined) return arg; return pp(arg); }; } case TypeName.ZodNullable: { const { innerType } = def; const pp = getPreprocessorByZodType(innerType); return (arg) => { // coerce undefined to null. if (arg == null) return null; return pp(arg); }; } case TypeName.ZodDate: return (arg) => { // calling the 0-arity Date constructor makes a new Date with the // current time, which definitely isn't what we want here. but calling // the 1-arity Date constructor, even with `undefined`, should result in // "invalid date" for values that aren't parseable. we filter out // `undefined` anyway, though-- it makes typescript happier. if (arg == null) return arg; return new Date(arg); }; case TypeName.ZodLiteral: switch (typeof def.value) { case "number": return getPreprocessorByZodType({ _def: { typeName: TypeName.ZodNumber }, }); case "string": return getPreprocessorByZodType({ _def: { typeName: TypeName.ZodString }, }); case "boolean": return getPreprocessorByZodType({ _def: { typeName: TypeName.ZodBoolean }, }); default: return (arg) => arg; } case TypeName.ZodNull: return (arg) => { // coerce undefined to null. if (arg == null) return null; return arg; }; case TypeName.ZodDiscriminatedUnion: case TypeName.ZodUnion: case TypeName.ZodNativeEnum: throw new Error(`Zod type not yet supported: "${typeName}" (PRs welcome)`); case TypeName.ZodAny: case TypeName.ZodUnknown: throw new Error([ `Zod type not supported: ${typeName}`, "You can use `z.string()` or `z.string().optional()` instead of the above type.", "(Environment variables are already constrained to `string | undefined`.)", ].join("\n")); // some of these types could maybe be supported (if only via the identity // function), but don't necessarily represent something meaningful as a // top-level schema passed to znv. case TypeName.ZodVoid: case TypeName.ZodNever: case TypeName.ZodLazy: case TypeName.ZodFunction: case TypeName.ZodPromise: case TypeName.ZodMap: case TypeName.ZodSet: case TypeName.ZodNaN: case TypeName.ZodCatch: case TypeName.ZodBranded: case TypeName.ZodPipeline: case TypeName.ZodSymbol: case TypeName.ZodReadonly: throw new Error(`Zod type not supported: ${typeName}`); default: { (0, type_helpers_js_1.assertNever)(typeName); } } } /** * Given a Zod schema, return the schema wrapped in a preprocessor that tries to * convert a string to the schema's input type. */ function getSchemaWithPreprocessor(schema) { return z.preprocess(getPreprocessorByZodType(schema), schema); } //# sourceMappingURL=preprocessors.js.map