mirror of
https://github.com/hanzoai/zod.git
synced 2026-09-24 17:04:49 +00:00
feat: MAC address validation in v4 and mini (#5440)
* feat: add z.mac() and ZodMAC * feat: reject nonstandard and 64-bit mac addrs * test: MAC addr validation * docs: add z.mac() * feat(mini): add z.mac() * fix: tighten z.mac() test cases * Add delimiter, drop version --------- Co-authored-by: Colin McDonnell <[email protected]>
This commit is contained in:
committed by
GitHub
co-authored by
Colin McDonnell
parent
eae131c258
commit
775f3fe45f
@@ -1,78 +0,0 @@
|
||||
# Development Setup
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Node.js
|
||||
- **Required**: Node.js v24+ (project uses modern ES modules and features)
|
||||
- **Recommended**: Use nvm for Node.js version management
|
||||
- **Setup**: `nvm use default` to use the latest Node.js version
|
||||
|
||||
### Package Manager
|
||||
- **Required**: pnpm v10.12.1 (specified in package.json)
|
||||
- **Install**: `npm install -g [email protected]`
|
||||
|
||||
## Environment Setup
|
||||
|
||||
### 1. Install Dependencies
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### 2. Verify Setup
|
||||
```bash
|
||||
node --version # Should be v24+
|
||||
pnpm --version # Should be 10.12.1
|
||||
```
|
||||
|
||||
### 3. Build Project
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
### 4. Run Tests
|
||||
```bash
|
||||
pnpm test
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Starting Development
|
||||
```bash
|
||||
pnpm dev # Start development server
|
||||
pnpm dev:watch # Start with watch mode
|
||||
pnpm dev:play # Run play.ts for testing
|
||||
```
|
||||
|
||||
### Code Quality
|
||||
```bash
|
||||
pnpm format # Format code
|
||||
pnpm lint # Lint and fix
|
||||
pnpm fix # Format + lint
|
||||
```
|
||||
|
||||
### Testing
|
||||
```bash
|
||||
pnpm test # Run all tests
|
||||
pnpm test:watch # Run tests in watch mode
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Node.js Version Issues
|
||||
- Ensure you're using Node.js v24+
|
||||
- Use `nvm use default` to switch to correct version
|
||||
- Check with `node --version`
|
||||
|
||||
### Module Import Issues
|
||||
- Project uses ES modules (`"type": "module"`)
|
||||
- Use `.js` extensions in imports: `import { z } from "./index.js"`
|
||||
- Don't use `require()` - use `import` statements
|
||||
|
||||
### Test Issues
|
||||
- Ensure Vitest is properly configured
|
||||
- Check that test files are in correct directories
|
||||
- Use `pnpm vitest run <specific-file>` for isolated testing
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
@@ -1,12 +0,0 @@
|
||||
# Project Guidelines
|
||||
|
||||
This file contains general coding guidelines for the project. Update this file with small rules and best practices that do not merit their own dedicated rule file.
|
||||
|
||||
## Guidelines
|
||||
|
||||
- Do not leave log statements (e.g., `console.log`, `debugger`) in tests or production code.
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
- Ask before generating any new files.
|
||||
@@ -1,111 +0,0 @@
|
||||
---
|
||||
description: Testing guidelines for the Zod codebase - when to use play.ts vs test suite, TypeScript requirements, and testing patterns
|
||||
globs: *.test.ts,*.test.tsx,play.ts
|
||||
---
|
||||
|
||||
# Zod Testing Guidelines
|
||||
|
||||
## Always Use TypeScript
|
||||
- **All tests must be written in TypeScript** - never use JavaScript
|
||||
- Test files should use `.test.ts` extension
|
||||
- Import types properly: `import type { ... }`
|
||||
|
||||
## When to Use play.ts vs Test Suite
|
||||
|
||||
### Use [play.ts](mdc:play.ts) for:
|
||||
- Quick experimentation and prototyping
|
||||
- Manual testing of new features during development
|
||||
- Debugging specific behaviors
|
||||
- One-off validation of edge cases
|
||||
- Temporary verification before writing formal tests
|
||||
|
||||
### Use Test Suite for:
|
||||
- **All permanent test cases** - features must have proper tests
|
||||
- Regression testing
|
||||
- API validation
|
||||
- Edge case coverage
|
||||
- Performance benchmarks
|
||||
|
||||
## Test Organization
|
||||
|
||||
### Test File Structure
|
||||
Tests are located in:
|
||||
- `packages/zod/src/v4/classic/tests/` - V4 classic API tests
|
||||
- `packages/zod/src/v4/core/tests/` - V4 core tests
|
||||
- `packages/zod/src/v3/tests/` - V3 compatibility tests
|
||||
|
||||
### Test File Naming
|
||||
- Use descriptive names: `string.test.ts`, `object.test.ts`, `url-validation.test.ts`
|
||||
- Group related functionality in the same test file
|
||||
- Follow existing patterns in [packages/zod/src/v4/classic/tests/string.test.ts](mdc:packages/zod/src/v4/classic/tests/string.test.ts)
|
||||
|
||||
## Test Writing Patterns
|
||||
|
||||
### Framework Usage
|
||||
- Use **Vitest** as the test framework
|
||||
- Import: `import { expect, test } from "vitest";`
|
||||
- Import Zod: `import * as z from "zod/v4";`
|
||||
|
||||
### Test Structure
|
||||
```typescript
|
||||
test("descriptive test name", () => {
|
||||
const schema = z.string().url();
|
||||
|
||||
// Test valid cases
|
||||
expect(schema.parse("https://example.com")).toBe("https://example.com");
|
||||
|
||||
// Test invalid cases
|
||||
expect(() => schema.parse("invalid")).toThrow();
|
||||
});
|
||||
```
|
||||
|
||||
### Testing Preferences
|
||||
- **Keep test suites concise** - use as few test cases as needed to cover functionality
|
||||
- **Don't skip tests** due to type issues - fix the types instead
|
||||
- **Don't oversimplify tests** - maintain adequate coverage
|
||||
- Test both success and failure cases
|
||||
- Use descriptive test names that explain the behavior being tested
|
||||
|
||||
### URL Testing Example
|
||||
When testing URL validation (as seen in recent work):
|
||||
```typescript
|
||||
test("url preserves original input", () => {
|
||||
const url = z.string().url();
|
||||
const input = "https://example.com?key=value";
|
||||
expect(url.parse(input)).toBe(input);
|
||||
});
|
||||
|
||||
test("url normalize flag", () => {
|
||||
const normalizeUrl = z.url({ normalize: true });
|
||||
expect(normalizeUrl.parse("https://example.com?key=value"))
|
||||
.toBe("https://example.com/?key=value");
|
||||
});
|
||||
```
|
||||
|
||||
## Running Tests
|
||||
|
||||
### Run Specific Tests
|
||||
```bash
|
||||
pnpm vitest run src/v4/classic/tests/string.test.ts -t "url"
|
||||
```
|
||||
|
||||
### Run All Tests
|
||||
```bash
|
||||
pnpm test
|
||||
```
|
||||
|
||||
### Build Before Testing
|
||||
Always rebuild when testing core changes:
|
||||
```bash
|
||||
pnpm build && pnpm test
|
||||
```
|
||||
|
||||
## Test Development Workflow
|
||||
|
||||
1. **Start with [play.ts](mdc:play.ts)** for initial exploration
|
||||
2. **Write proper tests** once behavior is confirmed
|
||||
3. **Test edge cases** and error conditions
|
||||
4. **Verify backward compatibility**
|
||||
5. **Run full test suite** before committing
|
||||
|
||||
Remember: Features without tests are incomplete. Every new feature or bug fix should include corresponding test coverage.
|
||||
@@ -1,56 +0,0 @@
|
||||
# Testing Workflow
|
||||
|
||||
## Test Framework
|
||||
- **Vitest** is used for all testing
|
||||
- Tests are located in `packages/zod/src/v4/classic/tests/` and `packages/zod/src/v4/core/tests/`
|
||||
- Test files follow the pattern `*.test.ts`
|
||||
|
||||
## Running Tests
|
||||
|
||||
### All Tests
|
||||
```bash
|
||||
pnpm test
|
||||
```
|
||||
|
||||
### Watch Mode
|
||||
```bash
|
||||
pnpm test:watch
|
||||
```
|
||||
|
||||
### Specific Test File
|
||||
```bash
|
||||
pnpm vitest run packages/zod/src/v4/classic/tests/discriminated-unions.test.ts
|
||||
```
|
||||
|
||||
### Specific Test Pattern
|
||||
```bash
|
||||
pnpm vitest run --reporter=verbose packages/zod/src/v4/classic/tests/
|
||||
```
|
||||
|
||||
## Test Conventions
|
||||
|
||||
- Use `test()` for individual test cases
|
||||
- Use `describe()` for test groups
|
||||
- Use `expect()` for assertions
|
||||
- Test files should be co-located with source files or in dedicated test directories
|
||||
- Follow the existing test patterns in the codebase
|
||||
|
||||
## Test Structure Example
|
||||
```typescript
|
||||
import { z } from "../../index.js";
|
||||
|
||||
test("test description", () => {
|
||||
const schema = z.string();
|
||||
const result = schema.parse("test");
|
||||
expect(result).toBe("test");
|
||||
});
|
||||
```
|
||||
|
||||
## Debugging Tests
|
||||
- Use `console.log()` for debugging
|
||||
- Run specific test files to isolate issues
|
||||
- Use watch mode for rapid iteration
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
@@ -1,280 +0,0 @@
|
||||
# Zod Internals Comprehensive Guide
|
||||
|
||||
## Core Architecture
|
||||
|
||||
### Schema Type System
|
||||
All schemas extend `$ZodType` and implement `$ZodTypeInternals`. The core architecture is defined in:
|
||||
- [packages/zod/src/v4/core/schemas.ts](mdc:packages/zod/src/v4/core/schemas.ts) - All schema type definitions
|
||||
- [packages/zod/src/v4/core/core.ts](mdc:packages/zod/src/v4/core/core.ts) - Constructor system and type helpers
|
||||
- [packages/zod/src/v4/core/util.ts](mdc:packages/zod/src/v4/core/util.ts) - Utility functions and type helpers
|
||||
|
||||
### Constructor Pattern
|
||||
Zod uses a custom constructor system via `$constructor()` function:
|
||||
```typescript
|
||||
export const $ZodReadonly: core.$constructor<$ZodReadonly> = /*@__PURE__*/ core.$constructor(
|
||||
"$ZodReadonly",
|
||||
(inst, def) => {
|
||||
$ZodType.init(inst, def);
|
||||
// Schema-specific initialization
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
## Key Internal Properties
|
||||
|
||||
### Core Properties
|
||||
- `def`: Schema definition object containing type and configuration
|
||||
- `traits`: Set of trait identifiers for this schema
|
||||
- `id`: Unique identifier for the schema instance
|
||||
- `version`: Zod core version
|
||||
- `constr`: Constructor function reference
|
||||
|
||||
### Validation Properties
|
||||
- `propValues`: Used for discriminated unions to store literal values per property
|
||||
- `values`: Set of literal values that pass validation (for enums, literals, etc.)
|
||||
- `pattern`: RegExp for string validation patterns
|
||||
- `optin/optout`: Optionality flags for input/output types
|
||||
- `isst`: Set of possible issue types this schema can throw
|
||||
|
||||
### Processing Properties
|
||||
- `parse`: Function that parses input without running checks
|
||||
- `run`: Function that parses input and runs all checks
|
||||
- `deferred`: Array of deferred initializers
|
||||
- `bag`: Metadata object for schema-specific data
|
||||
|
||||
## Schema Categories
|
||||
|
||||
### Primitive Schemas
|
||||
- `$ZodString`, `$ZodNumber`, `$ZodBoolean`, `$ZodBigInt`
|
||||
- `$ZodSymbol`, `$ZodUndefined`, `$ZodNull`, `$ZodAny`, `$ZodUnknown`
|
||||
- `$ZodNever`, `$ZodVoid`, `$ZodDate`, `$ZodLiteral`
|
||||
|
||||
### Container Schemas
|
||||
- `$ZodArray`, `$ZodObject`, `$ZodTuple`, `$ZodRecord`
|
||||
- `$ZodMap`, `$ZodSet`, `$ZodUnion`, `$ZodIntersection`
|
||||
- `$ZodDiscriminatedUnion`
|
||||
|
||||
### Wrapper Schemas
|
||||
- `$ZodOptional`, `$ZodNullable`, `$ZodReadonly`
|
||||
- `$ZodDefault`, `$ZodPrefault`, `$ZodNonOptional`
|
||||
- `$ZodTransform`, `$ZodPipe`, `$ZodCatch`
|
||||
|
||||
### Special Schemas
|
||||
- `$ZodEnum`, `$ZodPromise`, `$ZodLazy`, `$ZodCustom`
|
||||
- `$ZodTemplateLiteral`, `$ZodNaN`, `$ZodSuccess`
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Wrapper Type Pattern
|
||||
Wrapper types must pass through internal properties from their inner type:
|
||||
|
||||
```typescript
|
||||
// Correct pattern for wrapper types
|
||||
util.defineLazy(inst._zod, "propValues", () => def.innerType._zod.propValues);
|
||||
util.defineLazy(inst._zod, "values", () => def.innerType._zod.values);
|
||||
util.defineLazy(inst._zod, "optin", () => def.innerType._zod.optin);
|
||||
util.defineLazy(inst._zod, "optout", () => def.innerType._zod.optout);
|
||||
```
|
||||
|
||||
### Lazy Property Definition
|
||||
Use `util.defineLazy()` to avoid circular dependencies:
|
||||
```typescript
|
||||
util.defineLazy(inst._zod, "propValues", () => {
|
||||
// Computed property logic
|
||||
return computedValue;
|
||||
});
|
||||
```
|
||||
|
||||
### Parse Function Pattern
|
||||
Standard parse function structure:
|
||||
```typescript
|
||||
inst._zod.parse = (payload, ctx) => {
|
||||
// Type checking
|
||||
if (typeof payload.value !== "expected_type") {
|
||||
payload.issues.push({
|
||||
expected: "expected_type",
|
||||
code: "invalid_type",
|
||||
input: payload.value,
|
||||
inst,
|
||||
});
|
||||
return payload;
|
||||
}
|
||||
|
||||
// Optional transformation
|
||||
if (def.coerce) {
|
||||
payload.value = transform(payload.value);
|
||||
}
|
||||
|
||||
return payload;
|
||||
};
|
||||
```
|
||||
|
||||
## Error System
|
||||
|
||||
### Error Types
|
||||
Defined in [packages/zod/src/v4/core/errors.ts](mdc:packages/zod/src/v4/core/errors.ts):
|
||||
- `$ZodIssueInvalidType` - Wrong type provided
|
||||
- `$ZodIssueTooBig/TooSmall` - Size/length constraints
|
||||
- `$ZodIssueInvalidStringFormat` - String format validation
|
||||
- `$ZodIssueInvalidUnion` - Union validation failures
|
||||
- `$ZodIssueCustom` - Custom validation errors
|
||||
|
||||
### Error Creation
|
||||
```typescript
|
||||
payload.issues.push({
|
||||
code: "invalid_type",
|
||||
expected: "string",
|
||||
input: payload.value,
|
||||
inst,
|
||||
// Optional fields
|
||||
path: ["property"],
|
||||
message: "Custom message",
|
||||
continue: false, // Stop validation
|
||||
});
|
||||
```
|
||||
|
||||
## Check System
|
||||
|
||||
### Check Types
|
||||
Defined in [packages/zod/src/v4/core/checks.ts](mdc:packages/zod/src/v4/core/checks.ts):
|
||||
- Numeric checks: `$ZodCheckLessThan`, `$ZodCheckGreaterThan`, `$ZodCheckMultipleOf`
|
||||
- Size checks: `$ZodCheckMaxSize`, `$ZodCheckMinSize`, `$ZodCheckSizeEquals`
|
||||
- String checks: `$ZodCheckStringFormat`, `$ZodCheckRegex`, `$ZodCheckIncludes`
|
||||
- Custom checks: `$ZodCheckProperty`, `$ZodCheckOverwrite`
|
||||
|
||||
### Check Pattern
|
||||
```typescript
|
||||
inst._zod.check = (payload) => {
|
||||
if (validationPasses(payload.value)) {
|
||||
return; // Success
|
||||
}
|
||||
|
||||
payload.issues.push({
|
||||
code: "validation_code",
|
||||
// ... other fields
|
||||
inst,
|
||||
continue: !def.abort,
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
## Parsing System
|
||||
|
||||
### Parse Functions
|
||||
Defined in [packages/zod/src/v4/core/parse.ts](mdc:packages/zod/src/v4/core/parse.ts):
|
||||
- `parse()` - Synchronous parsing
|
||||
- `parseAsync()` - Asynchronous parsing
|
||||
- `safeParse()` - Safe parsing (returns result object)
|
||||
- `safeParseAsync()` - Safe async parsing
|
||||
|
||||
### Parse Context
|
||||
```typescript
|
||||
interface ParseContextInternal {
|
||||
async?: boolean;
|
||||
error?: $ZodErrorMap;
|
||||
reportInput?: boolean;
|
||||
jitless?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
### Parse Payload
|
||||
```typescript
|
||||
interface ParsePayload<T = unknown> {
|
||||
value: T;
|
||||
issues: $ZodRawIssue[];
|
||||
}
|
||||
```
|
||||
|
||||
## Utility Functions
|
||||
|
||||
### Type Helpers
|
||||
- `util.defineLazy()` - Define lazy properties
|
||||
- `util.cached()` - Create cached getters
|
||||
- `util.prefixIssues()` - Add path prefixes to issues
|
||||
- `util.finalizeIssue()` - Finalize raw issues
|
||||
- `util.clone()` - Clone schemas
|
||||
|
||||
### Type Utilities
|
||||
- `util.PrimitiveSet` - Set of primitive values
|
||||
- `util.PropValues` - Record of property value sets
|
||||
- `util.MaybeAsync<T>` - T | Promise<T>
|
||||
- `util.Literal` - Union of literal types
|
||||
|
||||
## Discriminated Unions
|
||||
|
||||
### Implementation
|
||||
Discriminated unions use `propValues` for fast-path validation:
|
||||
```typescript
|
||||
// In $ZodDiscriminatedUnion
|
||||
util.defineLazy(inst._zod, "propValues", () => {
|
||||
const propValues: util.PropValues = {};
|
||||
for (const option of def.options) {
|
||||
const pv = option._zod.propValues;
|
||||
// Merge propValues from all options
|
||||
}
|
||||
return propValues;
|
||||
});
|
||||
```
|
||||
|
||||
### Requirements
|
||||
- Each option must have `propValues` with the discriminator key
|
||||
- Values must be unique across all options
|
||||
- Used for fast-path validation in `parse()`
|
||||
|
||||
## Performance Optimizations
|
||||
|
||||
### JIT Compilation
|
||||
- Fast-path generation for object parsing
|
||||
- Uses `eval()` when `jitless` is false
|
||||
- Generates optimized parsing functions
|
||||
|
||||
### Lazy Evaluation
|
||||
- Properties computed on-demand using `util.defineLazy()`
|
||||
- Avoids circular dependency issues
|
||||
- Improves initialization performance
|
||||
|
||||
### Caching
|
||||
- `util.cached()` for expensive computations
|
||||
- Schema normalization caching
|
||||
- Pattern compilation caching
|
||||
|
||||
## Recent Fixes
|
||||
|
||||
### Readonly Literal Discriminators
|
||||
- **Issue**: `$ZodReadonly` wasn't passing through `values` property
|
||||
- **Fix**: Added `values` to `$ZodReadonlyInternals` and lazy definition
|
||||
- **Impact**: Readonly literals can now be used as discriminators in discriminated unions
|
||||
- **Pattern**: Follow the wrapper type pattern for all internal properties
|
||||
|
||||
## Development Guidelines
|
||||
|
||||
### Creating New Schemas
|
||||
1. Define the schema definition interface
|
||||
2. Define the internals interface extending `$ZodTypeInternals`
|
||||
3. Define the schema interface extending `$ZodType`
|
||||
4. Create constructor using `$constructor()`
|
||||
5. Initialize with `$ZodType.init()`
|
||||
6. Implement `parse()` function
|
||||
7. Add lazy properties as needed
|
||||
8. Add tests for both public API and internal properties
|
||||
|
||||
### Testing Internals
|
||||
- Test both public API and internal property access
|
||||
- Verify `propValues` are correctly computed
|
||||
- Check that wrapper types pass through properties correctly
|
||||
- Test error scenarios and edge cases
|
||||
|
||||
### Common Pitfalls
|
||||
- Forgetting to pass through internal properties in wrapper types
|
||||
- Not using `util.defineLazy()` for computed properties
|
||||
- Missing error handling in parse functions
|
||||
- Incorrect issue code usage
|
||||
- Not testing internal property access
|
||||
|
||||
### Best Practices
|
||||
- Always check if wrapper types pass through required internal properties
|
||||
- Use `util.defineLazy()` for computed properties to avoid circular dependencies
|
||||
- Follow existing patterns in similar schema types
|
||||
- Test both the public API and internal property access
|
||||
- Use proper TypeScript types for all interfaces
|
||||
- Document complex internal logic with comments
|
||||
@@ -1,62 +0,0 @@
|
||||
# Zod Project Guide
|
||||
|
||||
This is the Zod TypeScript-first schema validation library project. The project uses **pnpm** as the package manager and **ES modules**.
|
||||
|
||||
## Project Structure
|
||||
|
||||
- **Root**: Monorepo with multiple packages
|
||||
- **Main package**: [packages/zod/](mdc:packages/zod/) - The core Zod library
|
||||
- **Package manager**: pnpm (version 10.12.1)
|
||||
- **Module system**: ES modules (`"type": "module"`)
|
||||
|
||||
## Common Commands
|
||||
|
||||
### Development
|
||||
- `pnpm dev` - Start development server with tsx
|
||||
- `pnpm dev:watch` - Start development server with watch mode
|
||||
- `pnpm dev:play` - Run play.ts file for quick experimentation.
|
||||
|
||||
### Testing
|
||||
- `pnpm test` - Run all tests with vitest
|
||||
- `pnpm test:watch` - Run tests in watch mode
|
||||
- `pnpm vitest run <file>` - Run specific test file
|
||||
- `pnpm vitest run packages/zod/src/v4/classic/tests/discriminated-unions.test.ts` - Run specific test
|
||||
|
||||
### Building
|
||||
- `pnpm build` - Build all packages (excluding docs)
|
||||
- `pnpm clean` - Clean all packages
|
||||
|
||||
### Code Quality
|
||||
- `pnpm format` - Format code with Biome
|
||||
- `pnpm format:check` - Check formatting without writing
|
||||
- `pnpm lint` - Lint and fix code with Biome
|
||||
- `pnpm lint:check` - Check linting without fixing
|
||||
- `pnpm fix` - Run both format and lint
|
||||
|
||||
### Benchmarking
|
||||
- `pnpm bench` - Run benchmarks
|
||||
- `pnpm moltar` - Run specific benchmark (object-moltar)
|
||||
|
||||
### Publishing
|
||||
- `pnpm prepublishOnly` - Run tests and build before publishing
|
||||
- `pnpm publish:jsr` - Publish to JSR (dry run)
|
||||
|
||||
## Key Files
|
||||
|
||||
- [package.json](mdc:package.json) - Root package configuration
|
||||
- [packages/zod/package.json](mdc:packages/zod/package.json) - Main Zod package
|
||||
- [pnpm-workspace.yaml](mdc:pnpm-workspace.yaml) - Workspace configuration
|
||||
- [vitest.workspace.ts](mdc:vitest.workspace.ts) - Vitest workspace config
|
||||
|
||||
## Development Notes
|
||||
|
||||
- Use **pnpm** for all package management
|
||||
- Tests use **Vitest** framework
|
||||
- Code formatting/linting uses **Biome**
|
||||
- The project supports multiple Zod versions (v3, v4, v4-mini)
|
||||
- Source code uses TypeScript with ES modules
|
||||
- Node.js v24+ is required for development
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
AGENTS.md
|
||||
@@ -0,0 +1,36 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Development Commands
|
||||
|
||||
The project uses pnpm workspaces. Key commands:
|
||||
|
||||
- `pnpm build` - Build all packages (runs recursive build command)
|
||||
- `pnpm vitest run` - Run all tests with Vitest
|
||||
- `pnpm vitest run <path>` - Run specific test file (e.g., `packages/zod/src/v4/classic/tests/string.test.ts`)
|
||||
- `pnpm vitest run <path> -t "<pattern>"` - Run specific test(s) within a file (e.g., `-t "MAC"`)
|
||||
- `pnpm vitest run --update` - Update all test snapshots
|
||||
- `pnpm vitest run <path> --update` - Update snapshots for specific test file
|
||||
- `pnpm test:watch` - Run tests in watch mode
|
||||
- `pnpm vitest run --coverage` - Run tests with coverage report
|
||||
- `pnpm dev` - Execute code with tsx under source conditions
|
||||
- `pnpm dev <file>` - Execute `<file>` with tsx & proper resolution conditions. Usually use for `play.ts`.
|
||||
- `pnpm dev:play` - Quick alias to run play.ts for experimentation
|
||||
- `pnpm lint` - Run biome linter with auto-fix
|
||||
- `pnpm format` - Format code with biome
|
||||
- `pnpm fix` - Run both format and lint
|
||||
|
||||
## Rules
|
||||
|
||||
- Node.js v24+ required (use nvm if needed); pnpm v10.12.1
|
||||
- ES modules are used throughout (`"type": "module"`)
|
||||
- All tests must be written in TypeScript - never use JavaScript
|
||||
- Use `play.ts` for quick experimentation; use proper tests for all permanent test cases
|
||||
- Features without tests are incomplete - every new feature or bug fix needs test coverage
|
||||
- Don't skip tests due to type issues - fix the types instead
|
||||
- Test both success and failure cases with edge cases
|
||||
- No log statements (`console.log`, `debugger`) in tests or production code
|
||||
- Ask before generating new files
|
||||
- Use `util.defineLazy()` for computed properties to avoid circular dependencies
|
||||
- Performance is critical - parameter reassignment is allowed for optimization
|
||||
@@ -1,109 +0,0 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Development Commands
|
||||
|
||||
The project uses pnpm workspaces. Key commands:
|
||||
|
||||
- `pnpm build` - Build all packages (runs recursive build command)
|
||||
- `pnpm test` - Run all tests with Vitest
|
||||
- `pnpm test:watch` - Run tests in watch mode
|
||||
- `pnpm dev` - Execute code with tsx under source conditions
|
||||
- `pnpm dev <file>` - Execute `<file>` with tsx & proper resolution conditions. Usually use for `play.ts`.
|
||||
- `pnpm lint` - Run biome linter with auto-fix
|
||||
- `pnpm format` - Format code with biome
|
||||
- `pnpm fix` - Run both format and lint
|
||||
|
||||
### Testing
|
||||
|
||||
- Tests use Vitest with workspace-based configuration
|
||||
- Test files are located in `src/*/tests/` directories
|
||||
- Run specific tests: `pnpm test <pattern>` or `pnpm test --filter <workspace> <pattern>`
|
||||
- Tests include type checking via `typecheck.enabled = true`
|
||||
|
||||
### Package-Specific Commands
|
||||
|
||||
In the `packages/zod/` workspace:
|
||||
- `pnpm build` - Uses zshy build tool with tsconfig.build.json
|
||||
- `pnpm clean` - Clean build artifacts (preserving node_modules)
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
Zod is a TypeScript-first schema validation library organized as a monorepo with multiple versions and variants:
|
||||
|
||||
### Repository Structure
|
||||
|
||||
- **Root**: Monorepo configuration with pnpm workspaces
|
||||
- **packages/zod/**: Main Zod package with multiple version exports
|
||||
- **packages/docs/**: Documentation website (Next.js)
|
||||
- **packages/bench/**: Performance benchmarks
|
||||
- **packages/resolution/**: Module resolution testing
|
||||
- **packages/treeshake/**: Bundle size analysis
|
||||
- **packages/tsc/**: TypeScript compilation benchmarks
|
||||
|
||||
### Version Architecture
|
||||
|
||||
The main zod package exports multiple versions:
|
||||
|
||||
1. **v4 (default)**: Current version, exports from `v4/classic/external.js`
|
||||
2. **v4/core**: Core v4 implementation without legacy compatibility
|
||||
3. **v4/mini**: Lightweight v4 variant
|
||||
4. **v3**: Legacy version for backward compatibility
|
||||
5. **mini**: General minimal build
|
||||
|
||||
### Key Implementation Files
|
||||
|
||||
- `src/v4/core/`: Core validation logic, schemas, parsing, and error handling
|
||||
- `src/v4/classic/`: v4 with legacy compatibility layer
|
||||
- `src/v4/mini/`: Minimal v4 implementation
|
||||
- `src/v3/`: Legacy v3 implementation
|
||||
- `src/locales/`: Internationalization support
|
||||
|
||||
### Export Strategy
|
||||
|
||||
The package uses conditional exports with:
|
||||
- `@zod/source`: Development condition pointing to TypeScript source
|
||||
- Standard ESM/CJS exports for distribution
|
||||
- Multiple entry points for different versions and variants
|
||||
|
||||
## Code Standards
|
||||
|
||||
### Linting and Formatting
|
||||
|
||||
- Uses Biome for both linting and formatting
|
||||
- Line width: 120 characters
|
||||
- Trailing commas: ES5 style for JavaScript, none for JSON
|
||||
- Notable lint rule relaxations:
|
||||
- `noExplicitAny: "off"` - `any` is allowed
|
||||
- `noParameterAssign: "off"` - Required for performance optimizations
|
||||
- `noNonNullAssertion: "off"` - Non-null assertions are allowed
|
||||
|
||||
### TypeScript Configuration
|
||||
|
||||
- Strict mode enabled with exact optional property types
|
||||
- Node.js module resolution (NodeNext)
|
||||
- Target: ES2020
|
||||
- Custom conditions support for `@zod/source`
|
||||
|
||||
## Development Workflow
|
||||
|
||||
1. Use `play.ts` for initial experimentation with `pnpm dev play.ts`
|
||||
2. Write tests in appropriate `tests/` directories
|
||||
3. Build with `pnpm build` before testing changes
|
||||
4. Run linting/formatting with `pnpm fix`
|
||||
5. All changes must pass tests and type checking
|
||||
|
||||
## Build System
|
||||
|
||||
- Uses `zshy` build tool for the main package
|
||||
- Generates both ES modules and CommonJS outputs
|
||||
- Supports source maps and declaration files
|
||||
- Post-build formatting with Biome
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- Performance is critical - parameter reassignment is allowed for optimization
|
||||
- Benchmarks available in `packages/bench/`
|
||||
- Bundle size monitoring in `packages/treeshake/`
|
||||
- TypeScript compilation performance tracked in `packages/tsc/`
|
||||
@@ -259,6 +259,7 @@ z.cuid2();
|
||||
z.ulid();
|
||||
z.ipv4();
|
||||
z.ipv6();
|
||||
z.mac();
|
||||
z.cidrv4(); // ipv4 CIDR block
|
||||
z.cidrv6(); // ipv6 CIDR block
|
||||
z.hash("sha256"); // or "sha1", "sha384", "sha512", "md5"
|
||||
@@ -487,10 +488,10 @@ Use the `precision` parameter to constrain the allowable decimal precision.
|
||||
|
||||
```ts
|
||||
z.iso.time({ precision: -1 }); // HH:MM (minute precision)
|
||||
z.iso.time({ precision: 0 }); // HH:MM:SS (second precision)
|
||||
z.iso.time({ precision: 1 }); // HH:MM:SS.s (decisecond precision)
|
||||
z.iso.time({ precision: 2 }); // HH:MM:SS.ss (centisecond precision)
|
||||
z.iso.time({ precision: 3 }); // HH:MM:SS.sss (millisecond precision)
|
||||
z.iso.time({ precision: 0 }); // HH:MM:SS (second precision)
|
||||
z.iso.time({ precision: 1 }); // HH:MM:SS.s (decisecond precision)
|
||||
z.iso.time({ precision: 2 }); // HH:MM:SS.ss (centisecond precision)
|
||||
z.iso.time({ precision: 3 }); // HH:MM:SS.sss (millisecond precision)
|
||||
```
|
||||
|
||||
### IP addresses
|
||||
@@ -515,6 +516,22 @@ const cidrv6 = z.string().cidrv6();
|
||||
cidrv6.parse("2001:db8::/32"); // ✅
|
||||
```
|
||||
|
||||
### MAC Addresses
|
||||
|
||||
Validate standard 48-bit MAC address [IEEE 802](https://en.wikipedia.org/wiki/MAC_address).
|
||||
|
||||
```ts
|
||||
const mac = z.mac();
|
||||
mac.parse("00:1A:2B:3C:4D:5E"); // ✅
|
||||
mac.parse("00-1a-2b-3c-4d-5e"); // ❌ colon-delimited by default
|
||||
mac.parse("001A:2B3C:4D5E"); // ❌ standard formats only
|
||||
mac.parse("00:1A:2b:3C:4d:5E"); // ❌ no mixed case
|
||||
|
||||
// custom delimiter
|
||||
const dashMac = z.mac({ delimiter: "-" });
|
||||
dashMac.parse("00-1A-2B-3C-4D-5E"); // ✅
|
||||
```
|
||||
|
||||
### JWTs
|
||||
|
||||
Validate [JSON Web Tokens](https://jwt.io/).
|
||||
|
||||
@@ -81,6 +81,7 @@ z.cuid2();
|
||||
z.ulid();
|
||||
z.cidrv4();
|
||||
z.cidrv6();
|
||||
z.mac();
|
||||
```
|
||||
|
||||
## Numeric types
|
||||
|
||||
@@ -614,6 +614,19 @@ export function ipv4(params?: string | core.$ZodIPv4Params): ZodIPv4 {
|
||||
return core._ipv4(ZodIPv4, params);
|
||||
}
|
||||
|
||||
// ZodMAC
|
||||
export interface ZodMAC extends ZodStringFormat<"mac"> {
|
||||
_zod: core.$ZodMACInternals;
|
||||
}
|
||||
export const ZodMAC: core.$constructor<ZodMAC> = /*@__PURE__*/ core.$constructor("ZodMAC", (inst, def) => {
|
||||
// ZodStringFormat.init(inst, def);
|
||||
core.$ZodMAC.init(inst, def);
|
||||
ZodStringFormat.init(inst, def);
|
||||
});
|
||||
export function mac(params?: string | core.$ZodMACParams): ZodMAC {
|
||||
return core._mac(ZodMAC, params);
|
||||
}
|
||||
|
||||
// ZodIPv6
|
||||
export interface ZodIPv6 extends ZodStringFormat<"ipv6"> {
|
||||
_zod: core.$ZodIPv6Internals;
|
||||
|
||||
@@ -195,6 +195,28 @@ test("continuability", () => {
|
||||
},
|
||||
]
|
||||
`);
|
||||
expect(
|
||||
z
|
||||
.mac()
|
||||
.refine(() => false)
|
||||
.safeParse("invalid_value").error!.issues
|
||||
).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"code": "invalid_format",
|
||||
"format": "mac",
|
||||
"message": "Invalid MAC address",
|
||||
"origin": "string",
|
||||
"path": [],
|
||||
"pattern": "/^(([0-9A-F]{2}(:)[0-9A-F]{2}(\\3[0-9A-F]{2}){4})|([0-9a-f]{2}(:)[0-9a-f]{2}(\\6[0-9a-f]{2}){4}))$/",
|
||||
},
|
||||
{
|
||||
"code": "custom",
|
||||
"message": "Invalid input",
|
||||
"path": [],
|
||||
},
|
||||
]
|
||||
`);
|
||||
expect(
|
||||
z
|
||||
.emoji()
|
||||
|
||||
@@ -775,6 +775,8 @@ test("format", () => {
|
||||
expect(z.string().date().format).toEqual("date");
|
||||
expect(z.string().time().format).toEqual("time");
|
||||
expect(z.string().duration().format).toEqual("duration");
|
||||
|
||||
expect(z.mac().format).toEqual("mac");
|
||||
});
|
||||
|
||||
test("min max getters", () => {
|
||||
@@ -885,6 +887,58 @@ test("IPv6 validation", () => {
|
||||
expect(() => ipv6.parse("254.164.77.1")).toThrow();
|
||||
});
|
||||
|
||||
test("MAC validation", () => {
|
||||
const mac = z.mac();
|
||||
|
||||
// Valid MAC addresses
|
||||
expect(mac.safeParse("00:1A:2B:3C:4D:5E").success).toBe(true);
|
||||
expect(mac.safeParse("FF:FF:FF:FF:FF:FF").success).toBe(true);
|
||||
expect(mac.safeParse("00:11:22:33:44:55").success).toBe(true);
|
||||
expect(mac.safeParse("A1:B2:C3:D4:E5:F6").success).toBe(true);
|
||||
expect(mac.safeParse("10:20:30:40:50:60").success).toBe(true);
|
||||
expect(mac.safeParse("0a:1b:2c:3d:4e:5f").success).toBe(true);
|
||||
expect(mac.safeParse("12:34:56:78:9A:BC").success).toBe(true);
|
||||
|
||||
// Invalid MAC addresses
|
||||
expect(mac.safeParse("00:1A-2B:3C-4D:5E").success).toBe(false);
|
||||
expect(mac.safeParse("00:1A:2B:3C:4D").success).toBe(false);
|
||||
expect(mac.safeParse("00:1A:2B:3C:4D").success).toBe(false);
|
||||
expect(mac.safeParse("00-1A-2B-3C-4D").success).toBe(false);
|
||||
expect(mac.safeParse("01-23-45-67-89-AB").success).toBe(false); // Dash delimiter not accepted by default
|
||||
expect(mac.safeParse("AA-BB-CC-DD-EE-FF").success).toBe(false); // Dash delimiter not accepted by default
|
||||
expect(mac.safeParse("DE-AD-BE-EF-00-01").success).toBe(false); // Dash delimiter not accepted by default
|
||||
expect(mac.safeParse("98-76-54-32-10-FF").success).toBe(false); // Dash delimiter not accepted by default
|
||||
expect(mac.safeParse("00:1A:2B:3C:4D:GZ").success).toBe(false);
|
||||
expect(mac.safeParse("00:1A:2B:3C:4D:5E:GG").success).toBe(false);
|
||||
expect(mac.safeParse("123:45:67:89:AB:CD").success).toBe(false);
|
||||
expect(mac.safeParse("00--1A:2B:3C:4D:5E").success).toBe(false);
|
||||
expect(mac.safeParse("00:1A::2B:3C:4D:5E").success).toBe(false);
|
||||
expect(mac.safeParse("00:1A:2B:3C:3C:2B:1A:00").success).toBe(false); // Disallow EUI-64
|
||||
expect(mac.safeParse("00:1a:2B:3c:4D:5e").success).toBe(false); // Disallow mixed-case
|
||||
|
||||
// MAC formats that are nonstandard but occassionally referenced, ex. https://www.postgresql.org/docs/17/datatype-net-types.html#DATATYPE-MACADDR
|
||||
expect(mac.safeParse("00:1A:2B:3C:4D:5E:FF").success).toBe(false);
|
||||
expect(mac.safeParse("001A2B:3C4D5E").success).toBe(false);
|
||||
expect(mac.safeParse("001A:2B3C:4D5E").success).toBe(false);
|
||||
expect(mac.safeParse("001A.2B3C.4D5E").success).toBe(false);
|
||||
expect(mac.safeParse("001A2B3C4D5E").success).toBe(false);
|
||||
expect(mac.safeParse("00.1A.2B.3C.4D.5E").success).toBe(false);
|
||||
});
|
||||
|
||||
test("MAC validation with custom delimiter", () => {
|
||||
const colonMac = z.mac({ delimiter: ":" });
|
||||
expect(colonMac.safeParse("00:1A:2B:3C:4D:5E").success).toBe(true);
|
||||
expect(colonMac.safeParse("00-1A-2B-3C-4D-5E").success).toBe(false);
|
||||
|
||||
const dashMac = z.mac({ delimiter: "-" });
|
||||
expect(dashMac.safeParse("00-1A-2B-3C-4D-5E").success).toBe(true);
|
||||
expect(dashMac.safeParse("00:1A:2B:3C:4D:5E").success).toBe(false);
|
||||
|
||||
const colonOnlyMac = z.mac({ delimiter: ":" });
|
||||
expect(colonOnlyMac.safeParse("00:1A:2B:3C:4D:5E").success).toBe(true);
|
||||
expect(colonOnlyMac.safeParse("00-1A-2B-3C-4D-5E").success).toBe(false);
|
||||
});
|
||||
|
||||
test("CIDR v4 validation", () => {
|
||||
const cidrV4 = z.string().cidrv4();
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ const email = z.templateLiteral(["", z.string().email()]);
|
||||
// const ip = z.templateLiteral(["", z.string().ip()]);
|
||||
const ipv4 = z.templateLiteral(["", z.string().ipv4()]);
|
||||
const ipv6 = z.templateLiteral(["", z.string().ipv6()]);
|
||||
const mac = z.templateLiteral(["", z.mac()]);
|
||||
const ulid = z.templateLiteral(["", z.string().ulid()]);
|
||||
const uuid = z.templateLiteral(["", z.string().uuid()]);
|
||||
const stringAToZ = z.templateLiteral(["", z.string().regex(/^[a-z]+$/)]);
|
||||
@@ -137,6 +138,7 @@ test("template literal type inference", () => {
|
||||
// expectTypeOf<z.infer<typeof ip>>().toEqualTypeOf<string>();
|
||||
expectTypeOf<z.infer<typeof ipv4>>().toEqualTypeOf<string>();
|
||||
expectTypeOf<z.infer<typeof ipv6>>().toEqualTypeOf<string>();
|
||||
expectTypeOf<z.infer<typeof mac>>().toEqualTypeOf<string>();
|
||||
expectTypeOf<z.infer<typeof ulid>>().toEqualTypeOf<string>();
|
||||
expectTypeOf<z.infer<typeof uuid>>().toEqualTypeOf<string>();
|
||||
expectTypeOf<z.infer<typeof stringAToZ>>().toEqualTypeOf<string>();
|
||||
@@ -361,6 +363,7 @@ test("template literal parsing - success - basic cases", () => {
|
||||
// ip.parse("c359:f57c:21e5:39eb:1187:e501:f936:b452");
|
||||
ipv4.parse("213.174.246.205");
|
||||
ipv6.parse("c359:f57c:21e5:39eb:1187:e501:f936:b452");
|
||||
mac.parse("00:1A:2B:3C:4D:5E");
|
||||
ulid.parse("01GW3D2QZJBYB6P1Z1AE997VPW");
|
||||
uuid.parse("808989fd-3a6e-4af2-b607-737323a176f6");
|
||||
stringAToZ.parse("asudgaskhdgashd");
|
||||
@@ -497,6 +500,8 @@ test("template literal parsing - failure - basic cases", () => {
|
||||
expect(() => ipv4.parse("c359:f57c:21e5:39eb:1187:e501:f936:b452")).toThrow();
|
||||
expect(() => ipv6.parse("c359:f57c:21e5:39eb:1187:e501:f936:b4521")).toThrow();
|
||||
expect(() => ipv6.parse("213.174.246.205")).toThrow();
|
||||
expect(() => mac.parse("00:1A:2B:3C:4D:5E:6A:7B")).toThrow();
|
||||
expect(() => mac.parse("00:1A:2B:3C")).toThrow();
|
||||
expect(() => ulid.parse("01GW3D2QZJBYB6P1Z1AE997VPW!")).toThrow();
|
||||
expect(() => uuid.parse("808989fd-3a6e-4af2-b607-737323a176f6Z")).toThrow();
|
||||
expect(() => uuid.parse("Z808989fd-3a6e-4af2-b607-737323a176f6")).toThrow();
|
||||
@@ -568,6 +573,9 @@ test("regexes", () => {
|
||||
expect(ipv6._zod.pattern.source).toMatchInlineSnapshot(
|
||||
`"^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$"`
|
||||
);
|
||||
expect(mac._zod.pattern.source).toMatchInlineSnapshot(
|
||||
`"^(([0-9A-F]{2}(:)[0-9A-F]{2}(\\3[0-9A-F]{2}){4})|([0-9a-f]{2}(:)[0-9a-f]{2}(\\6[0-9a-f]{2}){4}))$"`
|
||||
);
|
||||
expect(ulid._zod.pattern.source).toMatchInlineSnapshot(`"^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$"`);
|
||||
expect(uuid._zod.pattern.source).toMatchInlineSnapshot(
|
||||
`"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"`
|
||||
|
||||
@@ -132,6 +132,30 @@ describe("toJSONSchema", () => {
|
||||
"type": "string",
|
||||
}
|
||||
`);
|
||||
expect(z.toJSONSchema(z.mac())).toMatchInlineSnapshot(`
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"format": "mac",
|
||||
"pattern": "^(([0-9A-F]{2}(:)[0-9A-F]{2}(\\3[0-9A-F]{2}){4})|([0-9a-f]{2}(:)[0-9a-f]{2}(\\6[0-9a-f]{2}){4}))$",
|
||||
"type": "string",
|
||||
}
|
||||
`);
|
||||
expect(z.toJSONSchema(z.mac({ delimiter: ":" }))).toMatchInlineSnapshot(`
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"format": "mac",
|
||||
"pattern": "^(([0-9A-F]{2}(:)[0-9A-F]{2}(\\3[0-9A-F]{2}){4})|([0-9a-f]{2}(:)[0-9a-f]{2}(\\6[0-9a-f]{2}){4}))$",
|
||||
"type": "string",
|
||||
}
|
||||
`);
|
||||
expect(z.toJSONSchema(z.mac({ delimiter: "-" }))).toMatchInlineSnapshot(`
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"format": "mac",
|
||||
"pattern": "^(([0-9A-F]{2}(-)[0-9A-F]{2}(\\3[0-9A-F]{2}){4})|([0-9a-f]{2}(-)[0-9a-f]{2}(\\6[0-9a-f]{2}){4}))$",
|
||||
"type": "string",
|
||||
}
|
||||
`);
|
||||
expect(z.toJSONSchema(z.uuid())).toMatchInlineSnapshot(`
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
@@ -357,6 +381,31 @@ describe("toJSONSchema", () => {
|
||||
}
|
||||
`);
|
||||
|
||||
expect(z.toJSONSchema(z.mac())).toMatchInlineSnapshot(`
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"format": "mac",
|
||||
"pattern": "^(([0-9A-F]{2}(:)[0-9A-F]{2}(\\3[0-9A-F]{2}){4})|([0-9a-f]{2}(:)[0-9a-f]{2}(\\6[0-9a-f]{2}){4}))$",
|
||||
"type": "string",
|
||||
}
|
||||
`);
|
||||
expect(z.toJSONSchema(z.mac({ delimiter: ":" }))).toMatchInlineSnapshot(`
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"format": "mac",
|
||||
"pattern": "^(([0-9A-F]{2}(:)[0-9A-F]{2}(\\3[0-9A-F]{2}){4})|([0-9a-f]{2}(:)[0-9a-f]{2}(\\6[0-9a-f]{2}){4}))$",
|
||||
"type": "string",
|
||||
}
|
||||
`);
|
||||
expect(z.toJSONSchema(z.mac({ delimiter: "-" }))).toMatchInlineSnapshot(`
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"format": "mac",
|
||||
"pattern": "^(([0-9A-F]{2}(-)[0-9A-F]{2}(\\3[0-9A-F]{2}){4})|([0-9a-f]{2}(-)[0-9a-f]{2}(\\6[0-9a-f]{2}){4}))$",
|
||||
"type": "string",
|
||||
}
|
||||
`);
|
||||
|
||||
expect(z.toJSONSchema(z.base64())).toMatchInlineSnapshot(`
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
|
||||
@@ -345,6 +345,22 @@ export function _ipv6<T extends schemas.$ZodIPv6>(
|
||||
});
|
||||
}
|
||||
|
||||
// MAC
|
||||
export type $ZodMACParams = StringFormatParams<schemas.$ZodMAC, "pattern" | "when">;
|
||||
export type $ZodCheckMACParams = CheckStringFormatParams<schemas.$ZodMAC, "pattern" | "when">;
|
||||
export function _mac<T extends schemas.$ZodMAC>(
|
||||
Class: util.SchemaClass<T>,
|
||||
params?: string | $ZodMACParams | $ZodCheckMACParams
|
||||
): T {
|
||||
return new Class({
|
||||
type: "string",
|
||||
format: "mac",
|
||||
check: "string_format",
|
||||
abort: false,
|
||||
...util.normalizeParams(params),
|
||||
});
|
||||
}
|
||||
|
||||
// CIDRv4
|
||||
export type $ZodCIDRv4Params = StringFormatParams<schemas.$ZodCIDRv4, "pattern" | "when">;
|
||||
export type $ZodCheckCIDRv4Params = CheckStringFormatParams<schemas.$ZodCIDRv4, "pattern" | "when">;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import * as util from "./util.js";
|
||||
|
||||
export const cuid: RegExp = /^[cC][^\s-]{8,}$/;
|
||||
export const cuid2: RegExp = /^[0-9a-z]+$/;
|
||||
export const ulid: RegExp = /^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$/;
|
||||
@@ -59,7 +61,12 @@ export const ipv4: RegExp =
|
||||
/^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/;
|
||||
export const ipv6: RegExp =
|
||||
/^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;
|
||||
|
||||
export const mac = (delimiter?: string): RegExp => {
|
||||
const escapedDelim = util.escapeRegex(delimiter ?? ":");
|
||||
return new RegExp(
|
||||
`^(([0-9A-F]{2}(${escapedDelim})[0-9A-F]{2}(\\3[0-9A-F]{2}){4})|([0-9a-f]{2}(${escapedDelim})[0-9a-f]{2}(\\6[0-9a-f]{2}){4}))$`
|
||||
);
|
||||
};
|
||||
export const cidrv4: RegExp =
|
||||
/^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/;
|
||||
export const cidrv6: RegExp =
|
||||
|
||||
@@ -782,6 +782,26 @@ export const $ZodIPv6: core.$constructor<$ZodIPv6> = /*@__PURE__*/ core.$constru
|
||||
};
|
||||
});
|
||||
|
||||
////////////////////////////// ZodMAC //////////////////////////////
|
||||
export interface $ZodMACDef extends $ZodStringFormatDef<"mac"> {
|
||||
delimiter?: string;
|
||||
}
|
||||
|
||||
export interface $ZodMACInternals extends $ZodStringFormatInternals<"mac"> {
|
||||
def: $ZodMACDef;
|
||||
}
|
||||
|
||||
export interface $ZodMAC extends $ZodType {
|
||||
_zod: $ZodMACInternals;
|
||||
}
|
||||
|
||||
export const $ZodMAC: core.$constructor<$ZodMAC> = /*@__PURE__*/ core.$constructor("$ZodMAC", (inst, def): void => {
|
||||
def.pattern ??= regexes.mac(def.delimiter);
|
||||
$ZodStringFormat.init(inst, def);
|
||||
|
||||
inst._zod.bag.format = `mac`;
|
||||
});
|
||||
|
||||
////////////////////////////// ZodCIDRv4 //////////////////////////////
|
||||
|
||||
export interface $ZodCIDRv4Def extends $ZodStringFormatDef<"cidrv4"> {
|
||||
@@ -4300,6 +4320,7 @@ export type $ZodStringFormatTypes =
|
||||
| $ZodISODuration
|
||||
| $ZodIPv4
|
||||
| $ZodIPv6
|
||||
| $ZodMAC
|
||||
| $ZodCIDRv4
|
||||
| $ZodCIDRv6
|
||||
| $ZodBase64
|
||||
|
||||
@@ -60,6 +60,7 @@ const error: () => errors.$ZodErrorMap = () => {
|
||||
duration: "ISO duration",
|
||||
ipv4: "IPv4 address",
|
||||
ipv6: "IPv6 address",
|
||||
mac: "MAC address",
|
||||
cidrv4: "IPv4 range",
|
||||
cidrv6: "IPv6 range",
|
||||
base64: "base64-encoded string",
|
||||
|
||||
@@ -366,6 +366,19 @@ export function cidrv6(params?: string | core.$ZodCIDRv6Params): ZodMiniCIDRv6 {
|
||||
return core._cidrv6(ZodMiniCIDRv6, params);
|
||||
}
|
||||
|
||||
// ZodMiniMAC
|
||||
export interface ZodMiniMAC extends _ZodMiniString<core.$ZodMACInternals> {
|
||||
// _zod: core.$ZodMACInternals;
|
||||
}
|
||||
export const ZodMiniMAC: core.$constructor<ZodMiniMAC> = /*@__PURE__*/ core.$constructor("ZodMiniMAC", (inst, def) => {
|
||||
core.$ZodMAC.init(inst, def);
|
||||
ZodMiniStringFormat.init(inst, def);
|
||||
});
|
||||
|
||||
export function mac(params?: string | core.$ZodMACParams): ZodMiniMAC {
|
||||
return core._mac(ZodMiniMAC, params);
|
||||
}
|
||||
|
||||
// ZodMiniBase64
|
||||
export interface ZodMiniBase64 extends _ZodMiniString<core.$ZodBase64Internals> {
|
||||
// _zod: core.$ZodBase64Internals;
|
||||
|
||||
@@ -241,6 +241,38 @@ test("z.ipv6", () => {
|
||||
expect(() => z.parse(a, 123)).toThrow();
|
||||
});
|
||||
|
||||
test("z.mac", () => {
|
||||
const a = z.mac();
|
||||
// valid mac
|
||||
expect(z.parse(a, "00:1A:2B:3C:4D:5E")).toEqual("00:1A:2B:3C:4D:5E");
|
||||
// invalid mac (dash delimiter not accepted by default)
|
||||
expect(() => z.parse(a, "01-23-45-67-89-AB")).toThrow();
|
||||
expect(() => z.parse(a, "00:1A:2B::4D:5E")).toThrow();
|
||||
expect(() => z.parse(a, "00:1a-2B:3c-4D:5e")).toThrow();
|
||||
expect(() => z.parse(a, "hello")).toThrow();
|
||||
// wrong type
|
||||
expect(() => z.parse(a, 123)).toThrow();
|
||||
});
|
||||
|
||||
test("z.mac with custom delimiter", () => {
|
||||
const a = z.mac({ delimiter: ":" });
|
||||
// valid mac with colon
|
||||
expect(z.parse(a, "00:1A:2B:3C:4D:5E")).toEqual("00:1A:2B:3C:4D:5E");
|
||||
// invalid mac with dash
|
||||
expect(() => z.parse(a, "00-1A-2B-3C-4D-5E")).toThrow();
|
||||
|
||||
const b = z.mac({ delimiter: "-" });
|
||||
// valid mac with dash
|
||||
expect(z.parse(b, "00-1A-2B-3C-4D-5E")).toEqual("00-1A-2B-3C-4D-5E");
|
||||
// invalid mac with colon
|
||||
expect(() => z.parse(b, "00:1A:2B:3C:4D:5E")).toThrow();
|
||||
|
||||
const c = z.mac({ delimiter: ":" });
|
||||
// colon-only mac
|
||||
expect(z.parse(c, "00:1A:2B:3C:4D:5E")).toEqual("00:1A:2B:3C:4D:5E");
|
||||
expect(() => z.parse(c, "00-1A-2B-3C-4D-5E")).toThrow();
|
||||
});
|
||||
|
||||
test("z.base64", () => {
|
||||
const a = z.base64();
|
||||
// valid base64
|
||||
|
||||
@@ -1,29 +1,10 @@
|
||||
import * as z from "zod";
|
||||
|
||||
// Test the circular reference issue with z.lazy()
|
||||
const schema = z.lazy(() => z.tuple([schema]));
|
||||
z;
|
||||
|
||||
console.log("Testing circular lazy schema creation:");
|
||||
try {
|
||||
// Try to access properties that depend on innerType during construction
|
||||
// This should trigger the error if the bug exists
|
||||
const _pattern = schema._zod.pattern;
|
||||
const _optin = schema._zod.optin;
|
||||
console.log("✅ Schema created successfully");
|
||||
console.log("Pattern:", _pattern);
|
||||
console.log("Optin:", _optin);
|
||||
} catch (error) {
|
||||
console.log("❌ Error during schema creation:", error);
|
||||
if (error instanceof Error) {
|
||||
console.log("Message:", error.message);
|
||||
console.log("Stack:", error.stack);
|
||||
}
|
||||
}
|
||||
const mac = z.stringFormat(
|
||||
"mac",
|
||||
/^(([0-9A-F]{2}([-:])[0-9A-F]{2}(\3[0-9A-F]{2}){4})|([0-9a-f]{2}([-:])[0-9a-f]{2}(\6[0-9a-f]{2}){4}))$/
|
||||
);
|
||||
|
||||
console.log("\nTesting circular lazy schema parsing:");
|
||||
try {
|
||||
const result = schema.parse([[]]);
|
||||
console.log("✅ Parse Success:", result);
|
||||
} catch (error) {
|
||||
console.log("❌ Parse Error:", error);
|
||||
}
|
||||
mac.parse("00:1A:2B:3C:4D:5E");
|
||||
|
||||
Reference in New Issue
Block a user