├── README.md ├── pnpm-workspace.yaml ├── vitest.setup.ts ├── packages ├── protobuf-zod │ ├── .eslintignore │ ├── tsconfig.json │ ├── .eslintrc.yaml │ ├── tsup.config.ts │ └── package.json ├── protobuf-zod-vitest │ ├── .eslintignore │ ├── tsconfig.json │ ├── .eslintrc.yaml │ ├── tsup.config.ts │ ├── src │ │ ├── types.ts │ │ └── index.ts │ └── package.json ├── protoc-gen-validate-zod │ ├── .eslintignore │ ├── tests │ │ ├── types.ts │ │ └── harness │ │ │ └── cases │ │ │ ├── yet_another_package │ │ │ ├── embed_zod.ts │ │ │ └── embed_pb.ts │ │ │ ├── bool_zod.ts │ │ │ ├── bool.test.ts │ │ │ ├── other_package │ │ │ ├── embed_zod.ts │ │ │ └── embed_pb.ts │ │ │ ├── wkt_nested_zod.ts │ │ │ ├── wkt_any_zod.ts │ │ │ ├── kitchen_sink.test.ts │ │ │ ├── wkt_any.test.ts │ │ │ ├── oneofs.test.ts │ │ │ ├── messages_zod.ts │ │ │ ├── kitchen_sink_zod.ts │ │ │ ├── bool_pb.ts │ │ │ ├── messages.test.ts │ │ │ ├── oneofs_zod.ts │ │ │ ├── wkt_duration_zod.ts │ │ │ ├── wkt_wrappers_zod.ts │ │ │ ├── wkt_nested_pb.ts │ │ │ ├── maps_zod.ts │ │ │ ├── wkt_timestamp_zod.ts │ │ │ ├── wkt_any_pb.ts │ │ │ ├── bytes_zod.ts │ │ │ ├── maps.test.ts │ │ │ ├── enums.test.ts │ │ │ ├── kitchen_sink_pb.ts │ │ │ ├── enums_zod.ts │ │ │ ├── wkt_wrappers.test.ts │ │ │ ├── bytes.test.ts │ │ │ ├── repeated_zod.ts │ │ │ ├── oneofs_pb.ts │ │ │ └── wkt_duration.test.ts │ ├── .eslintrc.yaml │ ├── tsconfig.build.json │ ├── tsconfig.json │ ├── src │ │ ├── index.ts │ │ ├── generate.ts │ │ ├── enum.ts │ │ ├── map.ts │ │ ├── wkt.ts │ │ ├── rules.ts │ │ ├── message.ts │ │ ├── field.ts │ │ └── utils.ts │ ├── bin │ │ ├── protoc-gen-validate-zod.js │ │ └── protoc-gen-validate-zod.ts │ └── package.json └── protoc-gen-validate-zod-test │ ├── .eslintignore │ ├── proto │ ├── buf.lock │ ├── buf.yaml │ └── harness.proto │ ├── .eslintrc.yaml │ ├── tsconfig.json │ ├── tsconfig.build.json │ ├── bin │ ├── protoc-gen-validate-zod-test.js │ └── protoc-gen-validate-zod-test.ts │ ├── src │ ├── index.ts │ ├── generated │ │ └── harness_pb.ts │ ├── cases.ts │ └── generate.ts │ └── package.json ├── vendor ├── cases.bin └── validate.bin ├── buf.work.yaml ├── buf.gen.yaml ├── .changeset ├── config.json └── README.md ├── vitest.config.ts ├── .prettierignore ├── .vscode └── settings.json ├── tsconfig.json ├── .gitignore ├── buf.gen.cases.yaml ├── turbo.json ├── .github └── workflows │ ├── changeset.yaml │ └── ci.yaml ├── tsconfig.base.json └── package.json /README.md: -------------------------------------------------------------------------------- 1 | # Work in progress 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - packages/* 3 | -------------------------------------------------------------------------------- /vitest.setup.ts: -------------------------------------------------------------------------------- 1 | import "protobuf-zod-vitest"; 2 | -------------------------------------------------------------------------------- /packages/protobuf-zod/.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.js 2 | 3 | dist/ 4 | coverage/ 5 | -------------------------------------------------------------------------------- /packages/protobuf-zod-vitest/.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.js 2 | 3 | dist/ 4 | coverage/ 5 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.js 2 | 3 | dist/ 4 | coverage/ 5 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/types.ts: -------------------------------------------------------------------------------- 1 | import "protobuf-zod-vitest/types"; 2 | -------------------------------------------------------------------------------- /vendor/cases.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fubhy/protobuf-zod/HEAD/vendor/cases.bin -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod-test/.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.js 2 | 3 | dist/ 4 | coverage/ 5 | -------------------------------------------------------------------------------- /vendor/validate.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fubhy/protobuf-zod/HEAD/vendor/validate.bin -------------------------------------------------------------------------------- /buf.work.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | directories: 3 | - packages/protoc-gen-validate-zod-test/proto 4 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod-test/proto/buf.lock: -------------------------------------------------------------------------------- 1 | # Generated by buf. DO NOT EDIT. 2 | version: v1 3 | -------------------------------------------------------------------------------- /packages/protobuf-zod/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["**/*.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /buf.gen.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | plugins: 3 | - name: es 4 | out: src/generated 5 | opt: 6 | - target=ts 7 | -------------------------------------------------------------------------------- /packages/protobuf-zod-vitest/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["**/*.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod-test/proto/buf.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | breaking: 3 | use: 4 | - FILE 5 | lint: 6 | allow_comment_ignores: true 7 | use: 8 | - DEFAULT 9 | -------------------------------------------------------------------------------- /packages/protobuf-zod/.eslintrc.yaml: -------------------------------------------------------------------------------- 1 | root: true 2 | extends: 3 | - 'eslint:recommended' 4 | - 'plugin:@typescript-eslint/strict' 5 | parserOptions: 6 | project: './tsconfig.json' 7 | -------------------------------------------------------------------------------- /packages/protobuf-zod-vitest/.eslintrc.yaml: -------------------------------------------------------------------------------- 1 | root: true 2 | extends: 3 | - 'eslint:recommended' 4 | - 'plugin:@typescript-eslint/strict' 5 | parserOptions: 6 | project: './tsconfig.json' 7 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/.eslintrc.yaml: -------------------------------------------------------------------------------- 1 | root: true 2 | extends: 3 | - 'eslint:recommended' 4 | - 'plugin:@typescript-eslint/strict' 5 | parserOptions: 6 | project: './tsconfig.json' 7 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod-test/.eslintrc.yaml: -------------------------------------------------------------------------------- 1 | root: true 2 | extends: 3 | - 'eslint:recommended' 4 | - 'plugin:@typescript-eslint/strict' 5 | parserOptions: 6 | project: './tsconfig.json' 7 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod-test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["**/*.ts"], 4 | "compilerOptions": { 5 | "outDir": "dist", 6 | "declaration": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "include": ["src/**/*.ts"], 4 | "compilerOptions": { 5 | "rootDir": "src", 6 | "outDir": "dist" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod-test/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "include": ["src/**/*.ts"], 4 | "compilerOptions": { 5 | "rootDir": "src", 6 | "outDir": "dist" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.2.0/schema.json", 3 | "commit": false, 4 | "access": "public", 5 | "linked": [["*"]], 6 | "baseBranch": "main", 7 | "updateInternalDependencies": "patch" 8 | } 9 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitest/config"; 2 | import paths from "vite-tsconfig-paths"; 3 | 4 | export default defineConfig({ 5 | plugins: [paths()], 6 | test: { 7 | setupFiles: ["./vitest.setup.ts"], 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["**/*.ts"], 4 | "compilerOptions": { 5 | "outDir": "dist", 6 | // Necessary for lazy message schemas using `z.lazy`, 7 | "noImplicitAny": false 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod-test/proto/harness.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package tests.harness; 4 | 5 | import "google/protobuf/any.proto"; 6 | 7 | message TestCase { 8 | string name = 1; 9 | uint32 failures = 2; 10 | google.protobuf.Any message = 3; 11 | } 12 | -------------------------------------------------------------------------------- /packages/protobuf-zod/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup"; 2 | 3 | export default defineConfig(() => ({ 4 | entry: ["src/index.ts"], 5 | format: ["cjs", "esm"], 6 | splitting: true, 7 | sourcemap: true, 8 | clean: true, 9 | dts: true, 10 | silent: true, 11 | minify: false, 12 | })); 13 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore the pnpm lock file 2 | pnpm-lock.yaml 3 | 4 | # Ignore generated changelog 5 | CHANGELOG.md 6 | 7 | # Ignore build output 8 | **/dist/ 9 | **/build/ 10 | **/coverage/ 11 | 12 | # Ignore generated code 13 | **/generated/ 14 | **/tests/harness/ 15 | 16 | # Ignore dot files and directories 17 | **/.* 18 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.format.enable": true, 3 | "eslint.packageManager": "pnpm", 4 | "eslint.workingDirectories": [{ "mode": "auto" }], 5 | "editor.defaultFormatter": "esbenp.prettier-vscode", 6 | "editor.formatOnSave": true, 7 | "editor.codeActionsOnSave": { 8 | "source.fixAll": true 9 | }, 10 | } 11 | -------------------------------------------------------------------------------- /packages/protobuf-zod-vitest/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup"; 2 | 3 | export default defineConfig(() => ({ 4 | entry: ["src/index.ts", "src/types.ts"], 5 | format: ["cjs", "esm"], 6 | splitting: false, 7 | sourcemap: true, 8 | clean: true, 9 | dts: true, 10 | silent: true, 11 | minify: false, 12 | })); 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "paths": { 6 | "protobuf-zod": ["./packages/protobuf-zod/src"], 7 | "protobuf-zod-vitest": ["./packages/protobuf-zod-vitest/src"], 8 | "protobuf-zod-vitest/types": ["./packages/protobuf-zod-vitest/src/types"] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/src/index.ts: -------------------------------------------------------------------------------- 1 | import { createEcmaScriptPlugin } from "@bufbuild/protoplugin"; 2 | import { generateTs } from "./generate.js"; 3 | 4 | export function createPlugin(version: string = "0.0.0") { 5 | return createEcmaScriptPlugin({ 6 | name: "protoc-gen-validate-zod", 7 | version: `v${version}`, 8 | generateTs, 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/bin/protoc-gen-validate-zod.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { runNodeJs } from "@bufbuild/protoplugin"; 4 | import { readFileSync } from "node:fs"; 5 | import { createPlugin } from "../dist/index.js"; 6 | 7 | const { version } = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")); 8 | 9 | runNodeJs(createPlugin(version)); 10 | -------------------------------------------------------------------------------- /packages/protobuf-zod-vitest/src/types.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | /* eslint-disable no-unused-vars */ 4 | declare global { 5 | namespace Vi { 6 | interface JestAssertion extends jest.Matchers { 7 | toBeValid(schema: z.ZodTypeAny): void; 8 | toBeInvalid(schema: z.ZodTypeAny, failureCount: number): void; 9 | } 10 | } 11 | } 12 | 13 | export {}; 14 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod-test/bin/protoc-gen-validate-zod-test.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { runNodeJs } from "@bufbuild/protoplugin"; 4 | import { readFileSync } from "node:fs"; 5 | import { createPlugin } from "../dist/index.js"; 6 | 7 | const { version } = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")); 8 | 9 | runNodeJs(createPlugin(version)); 10 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/bin/protoc-gen-validate-zod.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | 3 | import { runNodeJs } from "@bufbuild/protoplugin"; 4 | import { readFileSync } from "node:fs"; 5 | import { createPlugin } from "../src/index.js"; 6 | 7 | const { version } = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")); 8 | 9 | runNodeJs(createPlugin(`${version}-dev`)); 10 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod-test/bin/protoc-gen-validate-zod-test.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | 3 | import { runNodeJs } from "@bufbuild/protoplugin"; 4 | import { readFileSync } from "node:fs"; 5 | import { createPlugin } from "../src/index.js"; 6 | 7 | const { version } = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")); 8 | 9 | runNodeJs(createPlugin(`${version}-dev`)); 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore all dot files 2 | .* 3 | 4 | # Ignore changeset changelogs 5 | CHANGELOG.md 6 | 7 | # Ignore all dotfiles except the whitelisted ones 8 | !.changeset 9 | !.github 10 | !.gitignore 11 | !.eslintrc.yaml 12 | !.eslintignore 13 | !.prettierignore 14 | !.prettierrc 15 | !.vscode 16 | !.node-version 17 | !.npmrc 18 | 19 | # Ignore log output 20 | *.log 21 | 22 | # Ignore build output 23 | dist/ 24 | build/ 25 | coverage/ 26 | 27 | # Ignore dependencies 28 | node_modules/ 29 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod-test/src/index.ts: -------------------------------------------------------------------------------- 1 | import { createEcmaScriptPlugin } from "@bufbuild/protoplugin"; 2 | import { generateTs } from "./generate.js"; 3 | 4 | export function createPlugin(version: string = "0.0.0") { 5 | return createEcmaScriptPlugin({ 6 | name: "protoc-gen-validate-zod-test", 7 | version: `v${version}`, 8 | generateTs, 9 | parseOption: (key) => { 10 | if (key !== "cases" && key !== "descriptor") { 11 | throw new Error("Unknown option"); 12 | } 13 | }, 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/master/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /buf.gen.cases.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | plugins: 3 | - name: es 4 | out: . 5 | opt: 6 | - target=ts 7 | - name: validate-zod 8 | path: packages/protoc-gen-validate-zod/bin/protoc-gen-validate-zod.ts 9 | out: . 10 | opt: 11 | - target=ts 12 | - name: validate-zod-test-dev 13 | path: packages/protoc-gen-validate-zod-test/bin/protoc-gen-validate-zod-test.ts 14 | out: . 15 | opt: 16 | - target=ts 17 | - cases=packages/protoc-gen-validate-zod/tests/cases.txt 18 | - descriptor=vendor/cases.bin 19 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/src/generate.ts: -------------------------------------------------------------------------------- 1 | import { Schema } from "@bufbuild/protoplugin"; 2 | import { generateEnumSchema } from "./enum.js"; 3 | import { generateMessageSchema } from "./message.js"; 4 | import { createFile } from "./utils.js"; 5 | 6 | export function generateTs(schema: Schema) { 7 | for (const file of schema.files) { 8 | const f = createFile(schema, file); 9 | f.preamble(file); 10 | 11 | for (const enumeration of file.enums) { 12 | generateEnumSchema(f, enumeration); 13 | } 14 | 15 | for (const message of file.messages) { 16 | generateMessageSchema(f, message); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turborepo.org/schema.json", 3 | "pipeline": { 4 | "build": { 5 | "dependsOn": ["^build"], 6 | "outputs": ["dist/**"], 7 | "inputs": ["src/**/*.ts", "*.config.ts", "tsconfig.json"] 8 | }, 9 | "typecheck": { 10 | "outputs": [], 11 | "inputs": ["src/**/*.ts", "tests/**/*.ts", "*.config.ts", "tsconfig.json"] 12 | }, 13 | "lint": { 14 | "outputs": [], 15 | "inputs": ["src/**/*.ts", "tests/**/*.ts", ".eslintrc.yaml", ".eslintignore"] 16 | }, 17 | "format": { 18 | "outputs": [], 19 | "inputs": ["src/**/*.ts", "tests/**/*.ts", ".eslintrc.yaml", ".eslintignore"] 20 | } 21 | }, 22 | "globalDependencies": ["tsconfig.json", "tsconfig.base.json"] 23 | } 24 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/harness/cases/yet_another_package/embed_zod.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-validate-zod v0.0.2-dev with parameter "target=ts" 2 | // @generated from file tests/harness/cases/yet_another_package/embed.proto (package tests.harness.cases.yet_another_package, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import {z} from "zod"; 7 | import {Embed_Enumerated} from "./embed_pb.js"; 8 | import {int64, numberGt} from "protobuf-zod"; 9 | import {protoInt64} from "@bufbuild/protobuf"; 10 | 11 | /** 12 | * @generated from enum tests.harness.cases.yet_another_package.Embed.Enumerated 13 | */ 14 | export const Embed_EnumeratedSchema = z.nativeEnum(Embed_Enumerated); 15 | 16 | /** 17 | * Validate message embedding across packages. 18 | * 19 | * @generated from message tests.harness.cases.yet_another_package.Embed 20 | */ 21 | export const EmbedSchema = z.object({ 22 | /** 23 | * @generated from field: int64 val = 1; 24 | * @validate {"int64":{"gt":"0"}} 25 | */ 26 | val: int64.refine(numberGt(protoInt64.zero)), 27 | }); 28 | 29 | -------------------------------------------------------------------------------- /.github/workflows/changeset.yaml: -------------------------------------------------------------------------------- 1 | name: Changeset 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | publish: 9 | name: Publish packages 10 | runs-on: ubuntu-latest 11 | timeout-minutes: 20 12 | 13 | steps: 14 | - name: Check out repository 15 | uses: actions/checkout@v3 16 | 17 | - name: Install pnpm 18 | uses: pnpm/action-setup@v2 19 | with: 20 | version: 7.14.2 21 | 22 | - name: Set up node 23 | uses: actions/setup-node@v3 24 | with: 25 | cache: pnpm 26 | node-version: 18 27 | 28 | - name: Install dependencies 29 | run: pnpm install 30 | 31 | - name: Create release pull request or publish 32 | uses: changesets/action@v1 33 | with: 34 | version: pnpm changeset:version 35 | publish: pnpm changeset:publish 36 | commit: 'chore: publish packages' 37 | title: 'chore: publish packages' 38 | env: 39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 41 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/harness/cases/bool_zod.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-validate-zod v0.0.2-dev with parameter "target=ts" 2 | // @generated from file tests/harness/cases/bool.proto (package tests.harness.cases, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import {z} from "zod"; 7 | 8 | /** 9 | * @generated from message tests.harness.cases.BoolNone 10 | */ 11 | export const BoolNoneSchema = z.object({ 12 | /** 13 | * @generated from field: bool val = 1; 14 | */ 15 | val: z.boolean(), 16 | }); 17 | 18 | /** 19 | * @generated from message tests.harness.cases.BoolConstTrue 20 | */ 21 | export const BoolConstTrueSchema = z.object({ 22 | /** 23 | * @generated from field: bool val = 1; 24 | * @validate {"bool":{"const":true}} 25 | */ 26 | val: z.boolean().and(z.literal(true)), 27 | }); 28 | 29 | /** 30 | * @generated from message tests.harness.cases.BoolConstFalse 31 | */ 32 | export const BoolConstFalseSchema = z.object({ 33 | /** 34 | * @generated from field: bool val = 1; 35 | * @validate {"bool":{"const":false}} 36 | */ 37 | val: z.boolean().and(z.literal(false)), 38 | }); 39 | 40 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": ["node_modules", "dist"], 3 | "compilerOptions": { 4 | // Type Checking 5 | "strict": true, 6 | "exactOptionalPropertyTypes": true, 7 | "noFallthroughCasesInSwitch": true, 8 | "noImplicitOverride": true, 9 | "noImplicitReturns": true, 10 | "noUnusedLocals": true, 11 | "noUnusedParameters": true, 12 | "noUncheckedIndexedAccess": true, 13 | 14 | // Modules 15 | "module": "ESNext", 16 | "moduleResolution": "Node", 17 | "resolveJsonModule": true, 18 | "types": ["node"], 19 | 20 | // Emit 21 | "declaration": true, 22 | "declarationMap": true, 23 | "stripInternal": true, 24 | 25 | // JavaScript Support 26 | "allowJs": false, 27 | "checkJs": false, 28 | 29 | // Interop Constraints 30 | "esModuleInterop": true, 31 | "forceConsistentCasingInFileNames": true, 32 | "isolatedModules": true, 33 | 34 | // Language and Environment 35 | "lib": ["ES2022"], 36 | "target": "ES2022", 37 | "useDefineForClassFields": true, 38 | 39 | // Completeness 40 | "skipLibCheck": true // skip all type checks for .d.ts files 41 | }, 42 | "ts-node": { 43 | "transpileOnly": true 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod-test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "protoc-gen-validate-zod-test", 3 | "version": "0.0.2", 4 | "author": "Sebastian Lorenz ", 5 | "license": "Apache-2.0", 6 | "type": "module", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/fubhy/protobuf-zod.git", 10 | "directory": "packages/protoc-gen-validate-zod-test" 11 | }, 12 | "publishConfig": { 13 | "access": "public", 14 | "registry": "https://registry.npmjs.org" 15 | }, 16 | "files": [ 17 | "dist/**", 18 | "bin/protoc-gen-validate-zod-test.js" 19 | ], 20 | "bin": { 21 | "protoc-gen-validate-zod-test": "bin/protoc-gen-validate-zod-test.js" 22 | }, 23 | "engineStrict": true, 24 | "engines": { 25 | "node": ">=16" 26 | }, 27 | "scripts": { 28 | "build": "tsc -p tsconfig.build.json", 29 | "lint": "eslint \"{src,tests,bin}/**/*.ts\" --cache", 30 | "format": "pnpm lint --fix", 31 | "typecheck": "tsc --noEmit" 32 | }, 33 | "dependencies": { 34 | "@bufbuild/protobuf": "^0.2.1", 35 | "@bufbuild/protoplugin": "^0.2.1" 36 | }, 37 | "devDependencies": { 38 | "@bufbuild/protobuf": "^0.2.1", 39 | "protobuf-zod": "workspace:*", 40 | "zod": "^3.19.1" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/protobuf-zod/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "protobuf-zod", 3 | "version": "0.0.2", 4 | "author": "Sebastian Lorenz ", 5 | "license": "Apache-2.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/fubhy/protobuf-zod.git", 9 | "directory": "packages/protobuf-zod" 10 | }, 11 | "publishConfig": { 12 | "access": "public", 13 | "registry": "https://registry.npmjs.org" 14 | }, 15 | "files": [ 16 | "dist/**" 17 | ], 18 | "sideEffects": false, 19 | "type": "module", 20 | "main": "./dist/index.cjs", 21 | "module": "./dist/index.js", 22 | "typings": "./dist/index.d.ts", 23 | "exports": { 24 | "import": "./dist/index.js", 25 | "require": "./dist/index.cjs", 26 | "types": "./dist/index.d.ts" 27 | }, 28 | "scripts": { 29 | "build": "tsup", 30 | "lint": "eslint \"{src,tests}/**/*.ts\" --cache", 31 | "format": "pnpm lint --fix", 32 | "typecheck": "tsc --noEmit" 33 | }, 34 | "devDependencies": { 35 | "@bufbuild/protobuf": "^0.2.1", 36 | "@types/validator": "^13.7.10", 37 | "validator": "^13.7.0", 38 | "zod": "^3.19.1" 39 | }, 40 | "peerDependencies": { 41 | "@bufbuild/protobuf": "^0.2.1", 42 | "validator": "^13.7.0", 43 | "zod": "^3.19.1" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/src/enum.ts: -------------------------------------------------------------------------------- 1 | import { DescEnum } from "@bufbuild/protobuf"; 2 | import { makeJsDoc, Printable } from "@bufbuild/protoplugin/ecmascript"; 3 | import { hasRulesFor } from "./rules.js"; 4 | import { GeneratedFile } from "./utils.js"; 5 | import { FieldRules } from "./generated/validate_pb.js"; 6 | 7 | export function generateEnumSchema(f: GeneratedFile, enumeration: DescEnum) { 8 | f.print(makeJsDoc(enumeration)); 9 | f.print("export const ", f.reference(enumeration), " = ", f.zod, ".nativeEnum(", f.import(enumeration), ");\n"); 10 | } 11 | 12 | export function getEnumSchema(f: GeneratedFile, enumeration: DescEnum, rules?: FieldRules): Printable { 13 | const typing: Printable[] = [f.lazy(f.reference(enumeration))]; 14 | 15 | if (hasRulesFor("enum", rules)) { 16 | if (rules.type.value.definedOnly !== undefined) { 17 | // TODO: Implement 18 | } 19 | 20 | if (rules.type.value.in.length) { 21 | typing.push(f.validate.isIn(rules.type.value.in)); 22 | } 23 | 24 | if (rules.type.value.notIn.length) { 25 | typing.push(f.validate.isNotIn(rules.type.value.notIn)); 26 | } 27 | 28 | if (rules.type.value.const !== undefined) { 29 | typing.push(f.validate.isConst(rules.type.value.const)); 30 | } 31 | } 32 | 33 | return typing; 34 | } 35 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "protoc-gen-validate-zod", 3 | "version": "0.0.2", 4 | "author": "Sebastian Lorenz ", 5 | "license": "Apache-2.0", 6 | "type": "module", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/fubhy/protobuf-zod.git", 10 | "directory": "packages/protoc-gen-validate-zod" 11 | }, 12 | "publishConfig": { 13 | "access": "public", 14 | "registry": "https://registry.npmjs.org" 15 | }, 16 | "files": [ 17 | "dist/**", 18 | "bin/protoc-gen-validate-zod.js" 19 | ], 20 | "bin": { 21 | "protoc-gen-validate-zod": "bin/protoc-gen-validate-zod.js" 22 | }, 23 | "engineStrict": true, 24 | "engines": { 25 | "node": ">=16" 26 | }, 27 | "scripts": { 28 | "build": "tsc -p tsconfig.build.json", 29 | "lint": "eslint \"{src,tests,bin}/**/*.ts\" --cache", 30 | "format": "pnpm lint --fix", 31 | "typecheck": "tsc --noEmit" 32 | }, 33 | "dependencies": { 34 | "@bufbuild/protobuf": "^0.2.1", 35 | "@bufbuild/protoplugin": "^0.2.1" 36 | }, 37 | "devDependencies": { 38 | "@bufbuild/protobuf": "^0.2.1", 39 | "@bufbuild/protoc-gen-es": "^0.2.1", 40 | "protobuf-zod": "workspace:*", 41 | "protobuf-zod-vitest": "workspace:*", 42 | "zod": "^3.19.1" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/harness/cases/bool.test.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-validate-zod-test v0.0.2-dev with parameter "target=ts,cases=packages/protoc-gen-validate-zod/tests/cases.txt,descriptor=vendor/cases.bin" 2 | // @generated from file tests/harness/cases/bool.proto (package tests.harness.cases, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import {expect, it} from "vitest"; 7 | import {BoolConstFalse, BoolConstTrue, BoolNone} from "./bool_pb.js"; 8 | import {BoolConstFalseSchema, BoolConstTrueSchema, BoolNoneSchema} from "./bool_zod.js"; 9 | 10 | it("bool - none - valid", () => { 11 | const message = BoolNone.fromJson({"val":true}); 12 | expect(message).toBeValid(BoolNoneSchema); 13 | }); 14 | 15 | it("bool - const (true) - valid", () => { 16 | const message = BoolConstTrue.fromJson({"val":true}); 17 | expect(message).toBeValid(BoolConstTrueSchema); 18 | }); 19 | 20 | it("bool - const (true) - invalid", () => { 21 | const message = BoolConstTrue.fromJson({}); 22 | expect(message).toBeInvalid(BoolConstTrueSchema, 1); 23 | }); 24 | 25 | it("bool - const (false) - valid", () => { 26 | const message = BoolConstFalse.fromJson({}); 27 | expect(message).toBeValid(BoolConstFalseSchema); 28 | }); 29 | 30 | it("bool - const (false) - invalid", () => { 31 | const message = BoolConstFalse.fromJson({"val":true}); 32 | expect(message).toBeInvalid(BoolConstFalseSchema, 1); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/harness/cases/other_package/embed_zod.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-validate-zod v0.0.2-dev with parameter "target=ts" 2 | // @generated from file tests/harness/cases/other_package/embed.proto (package tests.harness.cases.other_package, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import {z} from "zod"; 7 | import {Embed_DoubleEmbed_DoubleEnumerated, Embed_Enumerated} from "./embed_pb.js"; 8 | import {int64, numberGt} from "protobuf-zod"; 9 | import {protoInt64} from "@bufbuild/protobuf"; 10 | 11 | /** 12 | * @generated from enum tests.harness.cases.other_package.Embed.Enumerated 13 | */ 14 | export const Embed_EnumeratedSchema = z.nativeEnum(Embed_Enumerated); 15 | 16 | /** 17 | * @generated from enum tests.harness.cases.other_package.Embed.DoubleEmbed.DoubleEnumerated 18 | */ 19 | export const Embed_DoubleEmbed_DoubleEnumeratedSchema = z.nativeEnum(Embed_DoubleEmbed_DoubleEnumerated); 20 | 21 | /** 22 | * @generated from message tests.harness.cases.other_package.Embed.DoubleEmbed 23 | */ 24 | export const Embed_DoubleEmbedSchema = z.object({ 25 | }); 26 | 27 | /** 28 | * Validate message embedding across packages. 29 | * 30 | * @generated from message tests.harness.cases.other_package.Embed 31 | */ 32 | export const EmbedSchema = z.object({ 33 | /** 34 | * @generated from field: int64 val = 1; 35 | * @validate {"int64":{"gt":"0"}} 36 | */ 37 | val: int64.refine(numberGt(protoInt64.zero)), 38 | }); 39 | 40 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/harness/cases/wkt_nested_zod.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-validate-zod v0.0.2-dev with parameter "target=ts" 2 | // @generated from file tests/harness/cases/wkt_nested.proto (package tests.harness.cases, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import {z} from "zod"; 7 | import {stringIsUuid} from "protobuf-zod"; 8 | 9 | /** 10 | * @generated from message tests.harness.cases.WktLevelOne.WktLevelTwo.WktLevelThree 11 | */ 12 | export const WktLevelOne_WktLevelTwo_WktLevelThreeSchema = z.object({ 13 | /** 14 | * @generated from field: string uuid = 1; 15 | * @validate {"string":{"uuid":true}} 16 | */ 17 | uuid: z.string().refine(stringIsUuid()), 18 | }); 19 | 20 | /** 21 | * @generated from message tests.harness.cases.WktLevelOne.WktLevelTwo 22 | */ 23 | export const WktLevelOne_WktLevelTwoSchema = z.object({ 24 | /** 25 | * @generated from field: tests.harness.cases.WktLevelOne.WktLevelTwo.WktLevelThree three = 1; 26 | * @validate {"message":{"required":true}} 27 | */ 28 | three: z.lazy(() => WktLevelOne_WktLevelTwo_WktLevelThreeSchema), 29 | }); 30 | 31 | /** 32 | * @generated from message tests.harness.cases.WktLevelOne 33 | */ 34 | export const WktLevelOneSchema = z.object({ 35 | /** 36 | * @generated from field: tests.harness.cases.WktLevelOne.WktLevelTwo two = 1; 37 | * @validate {"message":{"required":true}} 38 | */ 39 | two: z.lazy(() => WktLevelOne_WktLevelTwoSchema), 40 | }); 41 | 42 | -------------------------------------------------------------------------------- /packages/protobuf-zod-vitest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "protobuf-zod-vitest", 3 | "version": "0.0.1", 4 | "author": "Sebastian Lorenz ", 5 | "license": "Apache-2.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/fubhy/protobuf-zod.git", 9 | "directory": "packages/protobuf-zod" 10 | }, 11 | "publishConfig": { 12 | "access": "public", 13 | "registry": "https://registry.npmjs.org" 14 | }, 15 | "files": [ 16 | "dist/**" 17 | ], 18 | "sideEffects": false, 19 | "type": "module", 20 | "main": "./dist/index.cjs", 21 | "module": "./dist/index.js", 22 | "typings": "./dist/index.d.ts", 23 | "exports": { 24 | ".": { 25 | "import": "./dist/index.js", 26 | "require": "./dist/index.cjs", 27 | "types": "./dist/index.d.ts" 28 | }, 29 | "./types": { 30 | "import": "./dist/types.js", 31 | "require": "./dist/types.cjs", 32 | "types": "./dist/types.d.ts" 33 | } 34 | }, 35 | "typesVersions": { 36 | "*": { 37 | "types": [ 38 | "./dist/types.d.ts" 39 | ] 40 | } 41 | }, 42 | "scripts": { 43 | "build": "tsup", 44 | "lint": "eslint \"{src,tests}/**/*.ts\" --cache", 45 | "format": "pnpm lint --fix", 46 | "typecheck": "tsc --noEmit" 47 | }, 48 | "devDependencies": { 49 | "@bufbuild/protobuf": "^0.2.1", 50 | "zod": "^3.19.1" 51 | }, 52 | "peerDependencies": { 53 | "@bufbuild/protobuf": "^0.2.1", 54 | "zod": "^3.19.1" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/harness/cases/wkt_any_zod.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-validate-zod v0.0.2-dev with parameter "target=ts" 2 | // @generated from file tests/harness/cases/wkt_any.proto (package tests.harness.cases, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import {z} from "zod"; 7 | 8 | /** 9 | * @generated from message tests.harness.cases.AnyNone 10 | */ 11 | export const AnyNoneSchema = z.object({ 12 | /** 13 | * @generated from field: google.protobuf.Any val = 1; 14 | */ 15 | val: z.any().nullish(), 16 | }); 17 | 18 | /** 19 | * @generated from message tests.harness.cases.AnyRequired 20 | */ 21 | export const AnyRequiredSchema = z.object({ 22 | /** 23 | * @generated from field: google.protobuf.Any val = 1; 24 | * @validate {"any":{"required":true}} 25 | */ 26 | val: z.any().nullish(), 27 | }); 28 | 29 | /** 30 | * @generated from message tests.harness.cases.AnyIn 31 | */ 32 | export const AnyInSchema = z.object({ 33 | /** 34 | * @generated from field: google.protobuf.Any val = 1; 35 | * @validate {"any":{"in":["type.googleapis.com/google.protobuf.Duration"]}} 36 | */ 37 | val: z.any().nullish(), 38 | }); 39 | 40 | /** 41 | * @generated from message tests.harness.cases.AnyNotIn 42 | */ 43 | export const AnyNotInSchema = z.object({ 44 | /** 45 | * @generated from field: google.protobuf.Any val = 1; 46 | * @validate {"any":{"notIn":["type.googleapis.com/google.protobuf.Timestamp"]}} 47 | */ 48 | val: z.any().nullish(), 49 | }); 50 | 51 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | types: 7 | - opened 8 | - reopened 9 | - synchronize 10 | - ready_for_review 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.ref }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | verify: 18 | name: Verify 19 | runs-on: ubuntu-latest 20 | if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }} 21 | timeout-minutes: 20 22 | 23 | steps: 24 | - name: Check out repository 25 | uses: actions/checkout@v3 26 | 27 | - name: Install pnpm 28 | uses: pnpm/action-setup@v2 29 | with: 30 | version: 7.14.2 31 | 32 | - name: Set up node 33 | uses: actions/setup-node@v3 34 | with: 35 | cache: pnpm 36 | node-version: 18 37 | 38 | - name: Install dependencies 39 | run: pnpm install 40 | 41 | - uses: actions/cache@v3 42 | name: Set up turborepo cache 43 | with: 44 | path: .turbocache 45 | key: ${{ runner.os }}-turbocache-verify-${{ github.sha }} 46 | restore-keys: | 47 | ${{ runner.os }}-turbocache-verify- 48 | ${{ runner.os }}-turbocache- 49 | 50 | - name: Lint 51 | run: pnpm prettier && pnpm turbo run lint --cache-dir .turbocache 52 | 53 | - name: Typecheck 54 | run: pnpm turbo run typecheck --cache-dir .turbocache 55 | 56 | - name: Test 57 | run: pnpm test 58 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/src/map.ts: -------------------------------------------------------------------------------- 1 | import { DescField } from "@bufbuild/protobuf"; 2 | import { Printable } from "@bufbuild/protoplugin/ecmascript"; 3 | import { getEnumSchema } from "./enum.js"; 4 | import { getMessageSchema } from "./message.js"; 5 | import { hasRulesFor } from "./rules.js"; 6 | import { getScalarSchema } from "./scalar.js"; 7 | import { GeneratedFile } from "./utils.js"; 8 | import { FieldRules } from "./generated/validate_pb.js"; 9 | 10 | type DescFieldMap = DescField & { 11 | fieldKind: "map"; 12 | }; 13 | 14 | type DescFieldMapKey = DescFieldMap["mapKey"]; 15 | type DescFieldMapValue = DescFieldMap["mapValue"]; 16 | 17 | export function getMapSchema( 18 | f: GeneratedFile, 19 | key: DescFieldMapKey, 20 | value: DescFieldMapValue, 21 | rules?: FieldRules 22 | ): Printable[] { 23 | const validated = hasRulesFor("map", rules); 24 | 25 | const keyRules = validated ? rules.type.value.keys : undefined; 26 | const valueRules = validated ? rules.type.value.values : undefined; 27 | 28 | const keySchema = getScalarSchema(f, key, keyRules); 29 | const valueSchema = getMapValueSchema(f, value, valueRules); 30 | 31 | const typing: Printable[] = [f.runtime("map"), "(", keySchema, ", ", valueSchema, ")"]; 32 | 33 | return typing; 34 | } 35 | 36 | function getMapValueSchema(f: GeneratedFile, value: DescFieldMapValue, rules?: FieldRules): Printable { 37 | switch (value.kind) { 38 | case "enum": 39 | return getEnumSchema(f, value.enum, rules); 40 | 41 | case "scalar": 42 | return getScalarSchema(f, value.scalar, rules); 43 | 44 | case "message": 45 | return getMessageSchema(f, value.message, rules); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/src/wkt.ts: -------------------------------------------------------------------------------- 1 | import { codegenInfo } from "@bufbuild/protobuf"; 2 | import { getMapSchema } from "./map.js"; 3 | import { getScalarSchema } from "./scalar.js"; 4 | import { GeneratedFile } from "./utils.js"; 5 | import { FieldRules } from "./generated/validate_pb.js"; 6 | 7 | // TODO: This should probably be exported from @bufbuild/protobuf 8 | export type DescWkt = NonNullable>; 9 | 10 | export function getWktSchema(f: GeneratedFile, wkt: DescWkt, rules?: FieldRules) { 11 | switch (wkt.typeName) { 12 | case "google.protobuf.Any": 13 | return [f.zod, ".any()"]; 14 | case "google.protobuf.Timestamp": 15 | // TODO: Implement 16 | return [f.zod, ".any()"]; 17 | case "google.protobuf.Duration": 18 | // TODO: Implement 19 | return [f.zod, ".any()"]; 20 | case "google.protobuf.Struct": 21 | return getMapSchema(f, wkt.fields.mapKey, wkt.fields.mapValue, rules); 22 | case "google.protobuf.Value": 23 | // TODO: Implement 24 | return [f.zod, ".any()"]; 25 | case "google.protobuf.ListValue": 26 | // TODO: Implement 27 | return [f.zod, ".any()"]; 28 | case "google.protobuf.FieldMask": 29 | // TODO: Implement 30 | return [f.zod, ".any()"]; 31 | case "google.protobuf.DoubleValue": 32 | case "google.protobuf.FloatValue": 33 | case "google.protobuf.Int64Value": 34 | case "google.protobuf.UInt64Value": 35 | case "google.protobuf.Int32Value": 36 | case "google.protobuf.UInt32Value": 37 | case "google.protobuf.BoolValue": 38 | case "google.protobuf.StringValue": 39 | case "google.protobuf.BytesValue": { 40 | return getScalarSchema(f, wkt.value.scalar, rules); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/protobuf-zod-vitest/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "@bufbuild/protobuf"; 2 | import { expect } from "vitest"; 3 | import type { z } from "zod"; 4 | 5 | expect.extend({ 6 | toBeValid(message: Message, schema: z.ZodTypeAny) { 7 | const result = schema.safeParse(message); 8 | 9 | return { 10 | pass: result.success, 11 | message: () => { 12 | const received = this.utils.stringify(message); 13 | const type = message.getType().typeName; 14 | 15 | let output = this.utils.matcherHint("toBeValid", received, type, this); 16 | 17 | if (!result.success) { 18 | const issues = this.utils.stringify( 19 | result.error.issues.map((issue) => `[${issue.path.join(".")}]: ${issue.message}`) 20 | ); 21 | 22 | output += `\n\nIssues: ${issues}`; 23 | } 24 | 25 | return output; 26 | }, 27 | }; 28 | }, 29 | toBeInvalid(message: Message, schema: z.ZodTypeAny, failureCount: number) { 30 | const result = schema.safeParse(message); 31 | 32 | return { 33 | pass: !result.success && result.error.issues.length === failureCount, 34 | message: () => { 35 | const received = this.utils.stringify(message); 36 | const type = message.getType().typeName; 37 | 38 | let output = this.utils.matcherHint(`toBeInvalid`, received, type, this); 39 | output += `\n\nExpected: ${failureCount} issues`; 40 | 41 | if (!result.success) { 42 | const issues = this.utils.stringify( 43 | result.error.issues.map((issue) => `[${issue.path.join(".")}]: ${issue.message}`) 44 | ); 45 | 46 | output += `\nIssues: ${issues}`; 47 | } else { 48 | output += `\nIssues: none`; 49 | } 50 | 51 | return output; 52 | }, 53 | }; 54 | }, 55 | }); 56 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/src/rules.ts: -------------------------------------------------------------------------------- 1 | import { ScalarType } from "@bufbuild/protobuf"; 2 | import { FieldRules, MessageRules } from "./generated/validate_pb.js"; 3 | 4 | const scalarToRule = { 5 | [ScalarType.STRING]: "string", 6 | [ScalarType.BOOL]: "bool", 7 | [ScalarType.BYTES]: "bytes", 8 | [ScalarType.UINT64]: "uint64", 9 | [ScalarType.INT64]: "int64", 10 | [ScalarType.UINT32]: "uint32", 11 | [ScalarType.INT32]: "int32", 12 | [ScalarType.FLOAT]: "float", 13 | [ScalarType.DOUBLE]: "double", 14 | [ScalarType.SINT32]: "sint32", 15 | [ScalarType.SFIXED32]: "sfixed32", 16 | [ScalarType.FIXED32]: "fixed32", 17 | [ScalarType.SINT64]: "sint64", 18 | [ScalarType.SFIXED64]: "sfixed64", 19 | [ScalarType.FIXED64]: "fixed64", 20 | } as const; 21 | 22 | export function hasRulesForScalar( 23 | scalar: TScalarType, 24 | rules?: FieldRules 25 | ): rules is FieldRules & { type: FieldRules["type"] & { case: typeof scalarToRule[TScalarType] } } { 26 | return hasRulesFor(scalarToRule[scalar], rules); 27 | } 28 | 29 | // eslint-disable-next-line no-unused-vars 30 | export function hasRulesFor(type: "message", rules?: FieldRules): rules is FieldRules & { message: MessageRules }; 31 | export function hasRulesFor>( 32 | // eslint-disable-next-line no-unused-vars 33 | type: TFieldRulesType, 34 | rules?: FieldRules 35 | ): rules is FieldRules & { type: FieldRules["type"] & { case: TFieldRulesType } }; 36 | export function hasRulesFor(type: string, rules?: FieldRules): boolean { 37 | if (rules === undefined) { 38 | return false; 39 | } 40 | 41 | if (type === "message") { 42 | return rules.message !== undefined; 43 | } 44 | 45 | if (rules.type.case !== undefined && rules.type.case !== type) { 46 | throw new Error(`Expected ${type} validation rules but got ${rules.type.case} validation rules`); 47 | } 48 | 49 | return true; 50 | } 51 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/harness/cases/kitchen_sink.test.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-validate-zod-test v0.0.2-dev with parameter "target=ts,cases=packages/protoc-gen-validate-zod/tests/cases.txt,descriptor=vendor/cases.bin" 2 | // @generated from file tests/harness/cases/kitchen_sink.proto (package tests.harness.cases, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import {expect, it} from "vitest"; 7 | import {createRegistry, Duration} from "@bufbuild/protobuf"; 8 | import {KitchenSinkMessage} from "./kitchen_sink_pb.js"; 9 | import {KitchenSinkMessageSchema} from "./kitchen_sink_zod.js"; 10 | 11 | it("kitchensink - field - valid", () => { 12 | const registry = createRegistry(Duration); 13 | const message = KitchenSinkMessage.fromJson({"val":{"const":"abcd","intConst":5,"floatVal":1,"durVal":"3s","tsVal":"1970-01-01T00:00:17Z","floatConst":7,"doubleIn":123,"enumConst":"ComplexTWO","anyVal":{"value":"0s","@type":"type.googleapis.com/google.protobuf.Duration"},"repTsVal":["1970-01-01T00:00:03Z"],"mapVal":{"-2":"b","-1":"a"},"bytesVal":"AJk=","x":"foobar"}}, { typeRegistry: registry }); 14 | expect(message).toBeValid(KitchenSinkMessageSchema); 15 | }); 16 | 17 | it("kitchensink - valid (unset)", () => { 18 | const message = KitchenSinkMessage.fromJson({}); 19 | expect(message).toBeValid(KitchenSinkMessageSchema); 20 | }); 21 | 22 | it("kitchensink - field - invalid", () => { 23 | const message = KitchenSinkMessage.fromJson({"val":{}}); 24 | expect(message).toBeInvalid(KitchenSinkMessageSchema, 7); 25 | }); 26 | 27 | it("kitchensink - field - embedded - invalid", () => { 28 | const message = KitchenSinkMessage.fromJson({"val":{"another":{}}}); 29 | expect(message).toBeInvalid(KitchenSinkMessageSchema, 14); 30 | }); 31 | 32 | it("kitchensink - field - invalid (transitive)", () => { 33 | const message = KitchenSinkMessage.fromJson({"val":{"const":"abcd","nested":{},"boolConst":true}}); 34 | expect(message).toBeInvalid(KitchenSinkMessageSchema, 14); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod-test/src/generated/harness_pb.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-es v0.2.1 with parameter "target=ts" 2 | // @generated from file harness.proto (package tests.harness, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import type {BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage} from "@bufbuild/protobuf"; 7 | import {Any, Message, proto3} from "@bufbuild/protobuf"; 8 | 9 | /** 10 | * @generated from message tests.harness.TestCase 11 | */ 12 | export class TestCase extends Message { 13 | /** 14 | * @generated from field: string name = 1; 15 | */ 16 | name = ""; 17 | 18 | /** 19 | * @generated from field: uint32 failures = 2; 20 | */ 21 | failures = 0; 22 | 23 | /** 24 | * @generated from field: google.protobuf.Any message = 3; 25 | */ 26 | message?: Any; 27 | 28 | constructor(data?: PartialMessage) { 29 | super(); 30 | proto3.util.initPartial(data, this); 31 | } 32 | 33 | static readonly runtime = proto3; 34 | static readonly typeName = "tests.harness.TestCase"; 35 | static readonly fields: FieldList = proto3.util.newFieldList(() => [ 36 | { no: 1, name: "name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, 37 | { no: 2, name: "failures", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, 38 | { no: 3, name: "message", kind: "message", T: Any }, 39 | ]); 40 | 41 | static fromBinary(bytes: Uint8Array, options?: Partial): TestCase { 42 | return new TestCase().fromBinary(bytes, options); 43 | } 44 | 45 | static fromJson(jsonValue: JsonValue, options?: Partial): TestCase { 46 | return new TestCase().fromJson(jsonValue, options); 47 | } 48 | 49 | static fromJsonString(jsonString: string, options?: Partial): TestCase { 50 | return new TestCase().fromJsonString(jsonString, options); 51 | } 52 | 53 | static equals(a: TestCase | PlainMessage | undefined, b: TestCase | PlainMessage | undefined): boolean { 54 | return proto3.util.equals(TestCase, a, b); 55 | } 56 | } 57 | 58 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "type": "module", 4 | "scripts": { 5 | "changeset:version": "changeset version && pnpm install --lockfile-only", 6 | "changeset:publish": "turbo run build && changeset publish", 7 | "codegen:vendor": "buf build https://github.com/bufbuild/protoc-gen-validate.git#subdir=validate --output vendor/validate.bin && buf build https://github.com/bufbuild/protoc-gen-validate.git --path=tests/harness/cases --output vendor/cases.bin", 8 | "codegen:base": "buf generate vendor/validate.bin --output packages/protoc-gen-validate-zod && buf generate packages/protoc-gen-validate-zod-test/proto --output packages/protoc-gen-validate-zod-test", 9 | "codegen:cases": "buf generate vendor/cases.bin --template buf.gen.cases.yaml --output packages/protoc-gen-validate-zod", 10 | "codegen": "pnpm run codegen:vendor && pnpm run codegen:base && pnpm run codegen:cases", 11 | "build": "turbo run build", 12 | "prettier": "prettier --check \"**/*.{ts,tsx,json,md,yml,yaml}\" --cache", 13 | "format": "pnpm prettier --write && turbo run format", 14 | "lint": "pnpm prettier && turbo run lint", 15 | "typecheck": "turbo run typecheck", 16 | "watch": "nodemon --watch packages/protoc-gen-validate-zod/src --ext ts --exec \"pnpm cases\"", 17 | "test": "vitest --run", 18 | "dev": "vitest --ui" 19 | }, 20 | "devDependencies": { 21 | "@bufbuild/protobuf": "^0.2.1", 22 | "@bufbuild/protoc-gen-es": "^0.2.1", 23 | "@changesets/cli": "^2.25.2", 24 | "@types/node": "^18.11.8", 25 | "@typescript-eslint/eslint-plugin": "^5.42.0", 26 | "@typescript-eslint/parser": "^5.42.0", 27 | "@vitest/ui": "^0.24.5", 28 | "concurrently": "^7.5.0", 29 | "eslint": "^8.26.0", 30 | "nodemon": "^2.0.20", 31 | "prettier": "^2.7.1", 32 | "protobuf-zod-vitest": "workspace:^0.0.1", 33 | "ts-node": "^10.9.1", 34 | "tsup": "^6.2.3", 35 | "turbo": "^1.6.2", 36 | "typescript": "^4.7.4", 37 | "vite": "^3.2.2", 38 | "vite-tsconfig-paths": "^3.5.2", 39 | "vitest": "^0.24.4", 40 | "zod": "^3.19.1" 41 | }, 42 | "prettier": { 43 | "printWidth": 120 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/src/message.ts: -------------------------------------------------------------------------------- 1 | import { DescMessage, ScalarType, codegenInfo } from "@bufbuild/protobuf"; 2 | import { findCustomScalarOption, makeJsDoc, Printable } from "@bufbuild/protoplugin/ecmascript"; 3 | import { generateEnumSchema } from "./enum.js"; 4 | import { generateFieldSchema, generateOneOfSchema } from "./field.js"; 5 | import { hasRulesFor } from "./rules.js"; 6 | import { GeneratedFile } from "./utils.js"; 7 | import { FieldRules } from "./generated/validate_pb.js"; 8 | import { getWktSchema } from "./wkt.js"; 9 | 10 | export function generateMessageSchema(f: GeneratedFile, message: DescMessage) { 11 | const disabled = findCustomScalarOption(message, 1071, ScalarType.BOOL) ?? false; 12 | const ignored = findCustomScalarOption(message, 1072, ScalarType.BOOL) ?? false; 13 | 14 | for (const nestedEnum of message.nestedEnums) { 15 | generateEnumSchema(f, nestedEnum); 16 | } 17 | 18 | for (const nestedMessage of message.nestedMessages) { 19 | generateMessageSchema(f, nestedMessage); 20 | } 21 | 22 | f.print(makeJsDoc(message)); 23 | f.print("export const ", f.reference(message), " = ", f.zod, ".object({"); 24 | 25 | for (const member of message.members) { 26 | switch (member.kind) { 27 | case "oneof": 28 | generateOneOfSchema(f, member, ignored || disabled); 29 | break; 30 | case "field": 31 | generateFieldSchema(f, member, ignored || disabled); 32 | break; 33 | } 34 | } 35 | 36 | f.print("});\n"); 37 | } 38 | 39 | export function getMessageSchema(f: GeneratedFile, message: DescMessage, rules?: FieldRules): Printable { 40 | const validated = hasRulesFor("message", rules); 41 | if (validated && rules.message.skip) { 42 | // TODO: Can we not use "any" here? 43 | return [f.zod, ".any().nullish()"]; 44 | } 45 | 46 | const typing: Printable[] = []; 47 | const wkt = codegenInfo.reifyWkt(message); 48 | if (wkt !== undefined) { 49 | typing.push(getWktSchema(f, wkt, rules)); 50 | } else { 51 | typing.push(f.lazy(f.reference(message))); 52 | } 53 | 54 | if (!validated || !rules.message.required) { 55 | typing.push(".nullish()"); 56 | } 57 | 58 | return typing; 59 | } 60 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/harness/cases/yet_another_package/embed_pb.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-es v0.2.1 with parameter "target=ts" 2 | // @generated from file tests/harness/cases/yet_another_package/embed.proto (package tests.harness.cases.yet_another_package, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import type {BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage} from "@bufbuild/protobuf"; 7 | import {Message, proto3, protoInt64} from "@bufbuild/protobuf"; 8 | 9 | /** 10 | * Validate message embedding across packages. 11 | * 12 | * @generated from message tests.harness.cases.yet_another_package.Embed 13 | */ 14 | export class Embed extends Message { 15 | /** 16 | * @generated from field: int64 val = 1; 17 | */ 18 | val = protoInt64.zero; 19 | 20 | constructor(data?: PartialMessage) { 21 | super(); 22 | proto3.util.initPartial(data, this); 23 | } 24 | 25 | static readonly runtime = proto3; 26 | static readonly typeName = "tests.harness.cases.yet_another_package.Embed"; 27 | static readonly fields: FieldList = proto3.util.newFieldList(() => [ 28 | { no: 1, name: "val", kind: "scalar", T: 3 /* ScalarType.INT64 */ }, 29 | ]); 30 | 31 | static fromBinary(bytes: Uint8Array, options?: Partial): Embed { 32 | return new Embed().fromBinary(bytes, options); 33 | } 34 | 35 | static fromJson(jsonValue: JsonValue, options?: Partial): Embed { 36 | return new Embed().fromJson(jsonValue, options); 37 | } 38 | 39 | static fromJsonString(jsonString: string, options?: Partial): Embed { 40 | return new Embed().fromJsonString(jsonString, options); 41 | } 42 | 43 | static equals(a: Embed | PlainMessage | undefined, b: Embed | PlainMessage | undefined): boolean { 44 | return proto3.util.equals(Embed, a, b); 45 | } 46 | } 47 | 48 | /** 49 | * @generated from enum tests.harness.cases.yet_another_package.Embed.Enumerated 50 | */ 51 | export enum Embed_Enumerated { 52 | /** 53 | * @generated from enum value: VALUE = 0; 54 | */ 55 | VALUE = 0, 56 | } 57 | // Retrieve enum metadata with: proto3.getEnumType(Embed_Enumerated) 58 | proto3.util.setEnumType(Embed_Enumerated, "tests.harness.cases.yet_another_package.Embed.Enumerated", [ 59 | { no: 0, name: "VALUE" }, 60 | ]); 61 | 62 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod-test/src/cases.ts: -------------------------------------------------------------------------------- 1 | import { Any, IMessageTypeRegistry, JsonValue } from "@bufbuild/protobuf"; 2 | import { TestCase } from "./generated/harness_pb.js"; 3 | import fs from "node:fs"; 4 | 5 | export interface TestCaseJson { 6 | test: TestCase; 7 | type: string; 8 | value: string; 9 | registry: Set; 10 | } 11 | 12 | export function readTestCases(files: string[], types: IMessageTypeRegistry): TestCaseJson[] { 13 | const cases: TestCaseJson[] = []; 14 | 15 | for (const input of files) { 16 | const lines = fs.readFileSync(input, "utf8").split("\n"); 17 | 18 | for (const line of lines) { 19 | if (!line) { 20 | continue; 21 | } 22 | 23 | const test = TestCase.fromJson(JSON.parse(line), { typeRegistry: types }); 24 | if (test.message?.value === undefined) { 25 | throw new Error("Missing message value"); 26 | } 27 | 28 | const message = unpackAny(test.message, types); 29 | const json = message.toJson({ typeRegistry: types }); 30 | const type = message.getType().typeName; 31 | const registry = new Set(collectAnyTypes(json)); 32 | 33 | cases.push({ 34 | type, 35 | test, 36 | registry, 37 | value: JSON.stringify(json), 38 | }); 39 | } 40 | } 41 | 42 | return cases; 43 | } 44 | 45 | // NOTE: Limits the list of types to only those present in the message payload. 46 | function collectAnyTypes(json: JsonValue): string[] { 47 | const types: string[] = []; 48 | 49 | if (json !== null && Array.isArray(json)) { 50 | for (const item of json) { 51 | types.push(...collectAnyTypes(item)); 52 | } 53 | } else if (json !== null && typeof json === "object") { 54 | for (const [key, value] of Object.entries(json)) { 55 | if (key === "@type" && typeof value === "string") { 56 | types.push(value.replace("type.googleapis.com/", "")); 57 | } else if (typeof value === "object" && value !== null) { 58 | types.push(...collectAnyTypes(value)); 59 | } 60 | } 61 | } 62 | 63 | return types; 64 | } 65 | 66 | // TODO: This would be useful in @bufbuild/protobuf. 67 | function unpackAny(any: Any, registry: IMessageTypeRegistry) { 68 | const type = any.typeUrl.replace("type.googleapis.com/", ""); 69 | const message = registry.findMessage(type); 70 | const instance = new message!(); 71 | any.unpackTo(instance); 72 | return instance; 73 | } 74 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/harness/cases/wkt_any.test.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-validate-zod-test v0.0.2-dev with parameter "target=ts,cases=packages/protoc-gen-validate-zod/tests/cases.txt,descriptor=vendor/cases.bin" 2 | // @generated from file tests/harness/cases/wkt_any.proto (package tests.harness.cases, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import {expect, it} from "vitest"; 7 | import {AnyIn, AnyNone, AnyNotIn, AnyRequired} from "./wkt_any_pb.js"; 8 | import {AnyInSchema, AnyNoneSchema, AnyNotInSchema, AnyRequiredSchema} from "./wkt_any_zod.js"; 9 | import {createRegistry, Duration, Timestamp} from "@bufbuild/protobuf"; 10 | 11 | it("any - none - valid", () => { 12 | const message = AnyNone.fromJson({"val":{}}); 13 | expect(message).toBeValid(AnyNoneSchema); 14 | }); 15 | 16 | it("any - required - valid", () => { 17 | const message = AnyRequired.fromJson({"val":{}}); 18 | expect(message).toBeValid(AnyRequiredSchema); 19 | }); 20 | 21 | it("any - required - invalid", () => { 22 | const message = AnyRequired.fromJson({}); 23 | expect(message).toBeInvalid(AnyRequiredSchema, 1); 24 | }); 25 | 26 | it("any - in - valid", () => { 27 | const registry = createRegistry(Duration); 28 | const message = AnyIn.fromJson({"val":{"value":"0s","@type":"type.googleapis.com/google.protobuf.Duration"}}, { typeRegistry: registry }); 29 | expect(message).toBeValid(AnyInSchema); 30 | }); 31 | 32 | it("any - in - valid (empty)", () => { 33 | const message = AnyIn.fromJson({}); 34 | expect(message).toBeValid(AnyInSchema); 35 | }); 36 | 37 | it("any - in - invalid", () => { 38 | const registry = createRegistry(Timestamp); 39 | const message = AnyIn.fromJson({"val":{"value":"1970-01-01T00:00:00Z","@type":"type.googleapis.com/google.protobuf.Timestamp"}}, { typeRegistry: registry }); 40 | expect(message).toBeInvalid(AnyInSchema, 1); 41 | }); 42 | 43 | it("any - not in - valid", () => { 44 | const registry = createRegistry(Duration); 45 | const message = AnyNotIn.fromJson({"val":{"value":"0s","@type":"type.googleapis.com/google.protobuf.Duration"}}, { typeRegistry: registry }); 46 | expect(message).toBeValid(AnyNotInSchema); 47 | }); 48 | 49 | it("any - not in - valid (empty)", () => { 50 | const message = AnyNotIn.fromJson({}); 51 | expect(message).toBeValid(AnyNotInSchema); 52 | }); 53 | 54 | it("any - not in - invalid", () => { 55 | const registry = createRegistry(Timestamp); 56 | const message = AnyNotIn.fromJson({"val":{"value":"1970-01-01T00:00:00Z","@type":"type.googleapis.com/google.protobuf.Timestamp"}}, { typeRegistry: registry }); 57 | expect(message).toBeInvalid(AnyNotInSchema, 1); 58 | }); 59 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/harness/cases/oneofs.test.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-validate-zod-test v0.0.2-dev with parameter "target=ts,cases=packages/protoc-gen-validate-zod/tests/cases.txt,descriptor=vendor/cases.bin" 2 | // @generated from file tests/harness/cases/oneofs.proto (package tests.harness.cases, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import {expect, it} from "vitest"; 7 | import {OneOf, OneOfIgnoreEmpty, OneOfNone, OneOfRequired} from "./oneofs_pb.js"; 8 | import {OneOfIgnoreEmptySchema, OneOfNoneSchema, OneOfRequiredSchema, OneOfSchema} from "./oneofs_zod.js"; 9 | 10 | it("oneof - none - valid", () => { 11 | const message = OneOfNone.fromJson({"x":"foo"}); 12 | expect(message).toBeValid(OneOfNoneSchema); 13 | }); 14 | 15 | it("oneof - none - valid (empty)", () => { 16 | const message = OneOfNone.fromJson({}); 17 | expect(message).toBeValid(OneOfNoneSchema); 18 | }); 19 | 20 | it("oneof - field - valid (X)", () => { 21 | const message = OneOf.fromJson({"x":"foobar"}); 22 | expect(message).toBeValid(OneOfSchema); 23 | }); 24 | 25 | it("oneof - field - valid (Y)", () => { 26 | const message = OneOf.fromJson({"y":123}); 27 | expect(message).toBeValid(OneOfSchema); 28 | }); 29 | 30 | it("oneof - field - valid (Z)", () => { 31 | const message = OneOf.fromJson({"z":{"val":true}}); 32 | expect(message).toBeValid(OneOfSchema); 33 | }); 34 | 35 | it("oneof - field - valid (empty)", () => { 36 | const message = OneOf.fromJson({}); 37 | expect(message).toBeValid(OneOfSchema); 38 | }); 39 | 40 | it("oneof - field - invalid (X)", () => { 41 | const message = OneOf.fromJson({"x":"fizzbuzz"}); 42 | expect(message).toBeInvalid(OneOfSchema, 1); 43 | }); 44 | 45 | it("oneof - field - invalid (Y)", () => { 46 | const message = OneOf.fromJson({"y":-1}); 47 | expect(message).toBeInvalid(OneOfSchema, 1); 48 | }); 49 | 50 | it("oneof - filed - invalid (Z)", () => { 51 | const message = OneOf.fromJson({"z":{}}); 52 | expect(message).toBeInvalid(OneOfSchema, 1); 53 | }); 54 | 55 | it("oneof - required - valid", () => { 56 | const message = OneOfRequired.fromJson({"x":""}); 57 | expect(message).toBeValid(OneOfRequiredSchema); 58 | }); 59 | 60 | it("oneof - require - invalid", () => { 61 | const message = OneOfRequired.fromJson({}); 62 | expect(message).toBeInvalid(OneOfRequiredSchema, 1); 63 | }); 64 | 65 | it("oneof - ignore_empty - valid (X)", () => { 66 | const message = OneOfIgnoreEmpty.fromJson({"x":""}); 67 | expect(message).toBeValid(OneOfIgnoreEmptySchema); 68 | }); 69 | 70 | it("oneof - ignore_empty - valid (Y)", () => { 71 | const message = OneOfIgnoreEmpty.fromJson({"y":""}); 72 | expect(message).toBeValid(OneOfIgnoreEmptySchema); 73 | }); 74 | 75 | it("oneof - ignore_empty - valid (Z)", () => { 76 | const message = OneOfIgnoreEmpty.fromJson({"z":0}); 77 | expect(message).toBeValid(OneOfIgnoreEmptySchema); 78 | }); 79 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod-test/src/generate.ts: -------------------------------------------------------------------------------- 1 | import { DescMessage, createRegistryFromDescriptors, createDescriptorSet, DescFile } from "@bufbuild/protobuf"; 2 | import { localName, literalString } from "@bufbuild/protoplugin/ecmascript"; 3 | import { Schema } from "@bufbuild/protoplugin"; 4 | import { readTestCases, TestCaseJson } from "./cases.js"; 5 | import { readFileSync } from "fs"; 6 | 7 | function splitParameter(parameter: string | undefined): { key: string; value: string }[] { 8 | if (parameter == undefined) { 9 | return []; 10 | } 11 | 12 | return parameter.split(",").map((pair) => { 13 | const i = pair.indexOf("="); 14 | return { 15 | key: i === -1 ? pair : pair.substring(0, i), 16 | value: i === -1 ? "" : pair.substring(i + 1), 17 | }; 18 | }); 19 | } 20 | 21 | function getParameterValues(parameter: string | undefined, key: string): undefined | string[] { 22 | const matches = splitParameter(parameter).filter((pair) => pair.key === key); 23 | if (!matches.length) { 24 | return undefined; 25 | } 26 | 27 | return matches.map((match) => match.value); 28 | } 29 | 30 | export function generateTs(schema: Schema) { 31 | const input = getParameterValues(schema.proto.parameter, "cases"); 32 | if (!input) { 33 | throw new Error("Missing cases option"); 34 | } 35 | 36 | const [descriptor] = getParameterValues(schema.proto.parameter, "descriptor") ?? []; 37 | if (!descriptor) { 38 | throw new Error("Missing descriptors option"); 39 | } 40 | 41 | const set = createDescriptorSet(readFileSync(descriptor)); 42 | const registry = createRegistryFromDescriptors(set); 43 | const cases = readTestCases(input, registry); 44 | 45 | for (const file of schema.files) { 46 | const f = schema.generateFile(`${file.name}.test.ts`); 47 | f.preamble(file); 48 | 49 | const tests = getTestCasesForFile(file, cases); 50 | for (const current of tests) { 51 | if (tests.indexOf(current) !== 0) { 52 | f.print(); 53 | } 54 | 55 | const it = f.import("it", "vitest"); 56 | const expect = f.import("expect", "vitest"); 57 | const schema = f.import(`${localName(current.message)}Schema`, `./${file.name}_zod.js`); 58 | f.print(it, "(", literalString(current.test.name), ", () => {"); 59 | 60 | if (current.registry.size) { 61 | const reg = f.import("createRegistry", "@bufbuild/protobuf"); 62 | const types = Array.from(current.registry.values()).map((type) => { 63 | const message = set.messages.get(type); 64 | 65 | if (message === undefined) { 66 | throw new Error(`Missing message ${type}`); 67 | } 68 | 69 | return f.import(message); 70 | }); 71 | 72 | const args = types.flatMap((message) => [message, ", "].slice(0, -1)); 73 | f.print(" ", "const registry = ", reg, "(", ...args, ");"); 74 | } 75 | 76 | const options = current.registry.size ? ", { typeRegistry: registry }" : ""; 77 | f.print(" ", "const message = ", current.message, ".fromJson(", current.value, options, ");"); 78 | 79 | if (current.test.failures === 0) { 80 | f.print(" ", expect, "(message).toBeValid(", schema, ");"); 81 | } else { 82 | f.print(" ", expect, "(message).toBeInvalid(", schema, ", ", current.test.failures, ");"); 83 | } 84 | 85 | f.print("});"); 86 | } 87 | } 88 | } 89 | 90 | function getMessages(messages: DescMessage[]): Map { 91 | let output = new Map(messages.map((message) => [message.typeName, message])); 92 | 93 | for (const message of messages) { 94 | if (message.nestedMessages.length) { 95 | output = new Map([...output.entries(), ...getMessages(message.nestedMessages)]); 96 | } 97 | } 98 | 99 | return output; 100 | } 101 | 102 | function getTestCasesForFile(file: DescFile, cases: TestCaseJson[]) { 103 | const messages = getMessages(file.messages); 104 | const tests = cases 105 | .filter((test) => messages.has(test.type)) 106 | .map((test) => ({ ...test, message: messages.get(test.type)! })); 107 | 108 | return tests; 109 | } 110 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/harness/cases/other_package/embed_pb.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-es v0.2.1 with parameter "target=ts" 2 | // @generated from file tests/harness/cases/other_package/embed.proto (package tests.harness.cases.other_package, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import type {BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage} from "@bufbuild/protobuf"; 7 | import {Message, proto3, protoInt64} from "@bufbuild/protobuf"; 8 | 9 | /** 10 | * Validate message embedding across packages. 11 | * 12 | * @generated from message tests.harness.cases.other_package.Embed 13 | */ 14 | export class Embed extends Message { 15 | /** 16 | * @generated from field: int64 val = 1; 17 | */ 18 | val = protoInt64.zero; 19 | 20 | constructor(data?: PartialMessage) { 21 | super(); 22 | proto3.util.initPartial(data, this); 23 | } 24 | 25 | static readonly runtime = proto3; 26 | static readonly typeName = "tests.harness.cases.other_package.Embed"; 27 | static readonly fields: FieldList = proto3.util.newFieldList(() => [ 28 | { no: 1, name: "val", kind: "scalar", T: 3 /* ScalarType.INT64 */ }, 29 | ]); 30 | 31 | static fromBinary(bytes: Uint8Array, options?: Partial): Embed { 32 | return new Embed().fromBinary(bytes, options); 33 | } 34 | 35 | static fromJson(jsonValue: JsonValue, options?: Partial): Embed { 36 | return new Embed().fromJson(jsonValue, options); 37 | } 38 | 39 | static fromJsonString(jsonString: string, options?: Partial): Embed { 40 | return new Embed().fromJsonString(jsonString, options); 41 | } 42 | 43 | static equals(a: Embed | PlainMessage | undefined, b: Embed | PlainMessage | undefined): boolean { 44 | return proto3.util.equals(Embed, a, b); 45 | } 46 | } 47 | 48 | /** 49 | * @generated from enum tests.harness.cases.other_package.Embed.Enumerated 50 | */ 51 | export enum Embed_Enumerated { 52 | /** 53 | * @generated from enum value: VALUE = 0; 54 | */ 55 | VALUE = 0, 56 | } 57 | // Retrieve enum metadata with: proto3.getEnumType(Embed_Enumerated) 58 | proto3.util.setEnumType(Embed_Enumerated, "tests.harness.cases.other_package.Embed.Enumerated", [ 59 | { no: 0, name: "VALUE" }, 60 | ]); 61 | 62 | /** 63 | * @generated from message tests.harness.cases.other_package.Embed.DoubleEmbed 64 | */ 65 | export class Embed_DoubleEmbed extends Message { 66 | constructor(data?: PartialMessage) { 67 | super(); 68 | proto3.util.initPartial(data, this); 69 | } 70 | 71 | static readonly runtime = proto3; 72 | static readonly typeName = "tests.harness.cases.other_package.Embed.DoubleEmbed"; 73 | static readonly fields: FieldList = proto3.util.newFieldList(() => [ 74 | ]); 75 | 76 | static fromBinary(bytes: Uint8Array, options?: Partial): Embed_DoubleEmbed { 77 | return new Embed_DoubleEmbed().fromBinary(bytes, options); 78 | } 79 | 80 | static fromJson(jsonValue: JsonValue, options?: Partial): Embed_DoubleEmbed { 81 | return new Embed_DoubleEmbed().fromJson(jsonValue, options); 82 | } 83 | 84 | static fromJsonString(jsonString: string, options?: Partial): Embed_DoubleEmbed { 85 | return new Embed_DoubleEmbed().fromJsonString(jsonString, options); 86 | } 87 | 88 | static equals(a: Embed_DoubleEmbed | PlainMessage | undefined, b: Embed_DoubleEmbed | PlainMessage | undefined): boolean { 89 | return proto3.util.equals(Embed_DoubleEmbed, a, b); 90 | } 91 | } 92 | 93 | /** 94 | * @generated from enum tests.harness.cases.other_package.Embed.DoubleEmbed.DoubleEnumerated 95 | */ 96 | export enum Embed_DoubleEmbed_DoubleEnumerated { 97 | /** 98 | * @generated from enum value: VALUE = 0; 99 | */ 100 | VALUE = 0, 101 | } 102 | // Retrieve enum metadata with: proto3.getEnumType(Embed_DoubleEmbed_DoubleEnumerated) 103 | proto3.util.setEnumType(Embed_DoubleEmbed_DoubleEnumerated, "tests.harness.cases.other_package.Embed.DoubleEmbed.DoubleEnumerated", [ 104 | { no: 0, name: "VALUE" }, 105 | ]); 106 | 107 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/harness/cases/messages_zod.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-validate-zod v0.0.2-dev with parameter "target=ts" 2 | // @generated from file tests/harness/cases/messages.proto (package tests.harness.cases, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import {z} from "zod"; 7 | import {uint64} from "protobuf-zod"; 8 | import {EmbedSchema} from "./other_package/embed_zod.js"; 9 | 10 | /** 11 | * @generated from message tests.harness.cases.TestMsg 12 | */ 13 | export const TestMsgSchema = z.object({ 14 | /** 15 | * @generated from field: string const = 1; 16 | * @validate {"string":{"const":"foo"}} 17 | */ 18 | const: z.string().and(z.literal("foo")), 19 | /** 20 | * @generated from field: tests.harness.cases.TestMsg nested = 2; 21 | */ 22 | nested: z.lazy(() => TestMsgSchema).nullish(), 23 | }); 24 | 25 | /** 26 | * @generated from message tests.harness.cases.MessageNone.NoneMsg 27 | */ 28 | export const MessageNone_NoneMsgSchema = z.object({ 29 | }); 30 | 31 | /** 32 | * @generated from message tests.harness.cases.MessageNone 33 | */ 34 | export const MessageNoneSchema = z.object({ 35 | /** 36 | * @generated from field: tests.harness.cases.MessageNone.NoneMsg val = 1; 37 | */ 38 | val: z.lazy(() => MessageNone_NoneMsgSchema).nullish(), 39 | }); 40 | 41 | /** 42 | * @generated from message tests.harness.cases.MessageDisabled 43 | */ 44 | export const MessageDisabledSchema = z.object({ 45 | /** 46 | * @generated from field: uint64 val = 1; 47 | * @validate (disabled) {"uint64":{"gt":"123"}} 48 | */ 49 | val: uint64, 50 | }); 51 | 52 | /** 53 | * @generated from message tests.harness.cases.MessageIgnored 54 | */ 55 | export const MessageIgnoredSchema = z.object({ 56 | /** 57 | * @generated from field: uint64 val = 1; 58 | * @validate (disabled) {"uint64":{"gt":"123"}} 59 | */ 60 | val: uint64, 61 | }); 62 | 63 | /** 64 | * @generated from message tests.harness.cases.Message 65 | */ 66 | export const MessageSchema = z.object({ 67 | /** 68 | * @generated from field: tests.harness.cases.TestMsg val = 1; 69 | */ 70 | val: z.lazy(() => TestMsgSchema).nullish(), 71 | }); 72 | 73 | /** 74 | * @generated from message tests.harness.cases.MessageCrossPackage 75 | */ 76 | export const MessageCrossPackageSchema = z.object({ 77 | /** 78 | * @generated from field: tests.harness.cases.other_package.Embed val = 1; 79 | */ 80 | val: z.lazy(() => EmbedSchema).nullish(), 81 | }); 82 | 83 | /** 84 | * @generated from message tests.harness.cases.MessageSkip 85 | */ 86 | export const MessageSkipSchema = z.object({ 87 | /** 88 | * @generated from field: tests.harness.cases.TestMsg val = 1; 89 | * @validate {"message":{"skip":true}} 90 | */ 91 | val: z.any().nullish(), 92 | }); 93 | 94 | /** 95 | * @generated from message tests.harness.cases.MessageRequired 96 | */ 97 | export const MessageRequiredSchema = z.object({ 98 | /** 99 | * @generated from field: tests.harness.cases.TestMsg val = 1; 100 | * @validate {"message":{"required":true}} 101 | */ 102 | val: z.lazy(() => TestMsgSchema), 103 | }); 104 | 105 | /** 106 | * @generated from message tests.harness.cases.MessageRequiredButOptional 107 | */ 108 | export const MessageRequiredButOptionalSchema = z.object({ 109 | /** 110 | * @generated from field: optional tests.harness.cases.TestMsg val = 1; 111 | * @validate {"message":{"required":true}} 112 | */ 113 | val: z.lazy(() => TestMsgSchema).nullish(), 114 | }); 115 | 116 | /** 117 | * @generated from message tests.harness.cases.MessageRequiredOneof 118 | */ 119 | export const MessageRequiredOneofSchema = z.object({ 120 | /** 121 | * @generated from oneof tests.harness.cases.MessageRequiredOneof.one 122 | */ 123 | one: z.object({ 124 | /** 125 | * @generated from field: tests.harness.cases.TestMsg val = 1; 126 | * @validate {"message":{"required":true}} 127 | */ 128 | case: z.literal("val"), 129 | value: z.lazy(() => TestMsgSchema), 130 | }), 131 | }); 132 | 133 | /** 134 | * @generated from message tests.harness.cases.MessageWith3dInside 135 | */ 136 | export const MessageWith3dInsideSchema = z.object({ 137 | }); 138 | 139 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/harness/cases/kitchen_sink_zod.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-validate-zod v0.0.2-dev with parameter "target=ts" 2 | // @generated from file tests/harness/cases/kitchen_sink.proto (package tests.harness.cases, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import {z} from "zod"; 7 | import {ComplexTestEnum} from "./kitchen_sink_pb.js"; 8 | import {bytes, bytesEquals, double, float, int32, isIn, map, numberGt, numberLt, oneof, sint32} from "protobuf-zod"; 9 | 10 | /** 11 | * @generated from enum tests.harness.cases.ComplexTestEnum 12 | */ 13 | export const ComplexTestEnumSchema = z.nativeEnum(ComplexTestEnum); 14 | 15 | /** 16 | * @generated from message tests.harness.cases.ComplexTestMsg 17 | */ 18 | export const ComplexTestMsgSchema = z.object({ 19 | /** 20 | * @generated from field: string const = 1; 21 | * @validate {"string":{"const":"abcd"}} 22 | */ 23 | const: z.string().and(z.literal("abcd")), 24 | /** 25 | * @generated from field: tests.harness.cases.ComplexTestMsg nested = 2; 26 | */ 27 | nested: z.lazy(() => ComplexTestMsgSchema).nullish(), 28 | /** 29 | * @generated from field: int32 int_const = 3; 30 | * @validate {"int32":{"const":5}} 31 | */ 32 | intConst: int32.and(z.literal(5)), 33 | /** 34 | * @generated from field: bool bool_const = 4; 35 | * @validate {"bool":{"const":false}} 36 | */ 37 | boolConst: z.boolean().and(z.literal(false)), 38 | /** 39 | * @generated from field: google.protobuf.FloatValue float_val = 5; 40 | * @validate {"float":{"gt":0}} 41 | */ 42 | floatVal: float.refine(numberGt(0)).nullish(), 43 | /** 44 | * @generated from field: google.protobuf.Duration dur_val = 6; 45 | * @validate {"duration":{"required":true,"lt":"17s"}} 46 | */ 47 | durVal: z.any().nullish(), 48 | /** 49 | * @generated from field: google.protobuf.Timestamp ts_val = 7; 50 | * @validate {"timestamp":{"gt":"1970-01-01T00:00:07Z"}} 51 | */ 52 | tsVal: z.any().nullish(), 53 | /** 54 | * @generated from field: tests.harness.cases.ComplexTestMsg another = 8; 55 | */ 56 | another: z.lazy(() => ComplexTestMsgSchema).nullish(), 57 | /** 58 | * @generated from field: float float_const = 9; 59 | * @validate {"float":{"lt":8}} 60 | */ 61 | floatConst: float.refine(numberLt(8)), 62 | /** 63 | * @generated from field: double double_in = 10; 64 | * @validate {"double":{"in":[456.789,123]}} 65 | */ 66 | doubleIn: double.refine(isIn([ 67 | 456.789, 68 | 123, 69 | ])), 70 | /** 71 | * @generated from field: tests.harness.cases.ComplexTestEnum enum_const = 11; 72 | * @validate {"enum":{"const":2}} 73 | */ 74 | enumConst: z.lazy(() => ComplexTestEnumSchema).and(z.literal(2)), 75 | /** 76 | * @generated from field: google.protobuf.Any any_val = 12; 77 | * @validate {"any":{"in":["type.googleapis.com/google.protobuf.Duration"]}} 78 | */ 79 | anyVal: z.any().nullish(), 80 | /** 81 | * @generated from field: repeated google.protobuf.Timestamp rep_ts_val = 13; 82 | * @validate {"repeated":{"items":{"timestamp":{"gte":"1970-01-01T00:00:00.001Z"}}}} 83 | */ 84 | repTsVal: z.any().nullish().array(), 85 | /** 86 | * @generated from field: map map_val = 14; 87 | * @validate {"map":{"keys":{"sint32":{"lt":0}}}} 88 | */ 89 | mapVal: map(sint32.refine(numberLt(0)), z.string()), 90 | /** 91 | * @generated from field: bytes bytes_val = 15; 92 | * @validate {"bytes":{"const":"AJk="}} 93 | */ 94 | bytesVal: bytes.refine(bytesEquals(new Uint8Array([0x00, 0x99]))), 95 | /** 96 | * @generated from oneof tests.harness.cases.ComplexTestMsg.o 97 | */ 98 | o: oneof([ 99 | z.object({ 100 | /** 101 | * @generated from field: string x = 16; 102 | */ 103 | case: z.literal("x"), 104 | value: z.string(), 105 | }), 106 | z.object({ 107 | /** 108 | * @generated from field: int32 y = 17; 109 | */ 110 | case: z.literal("y"), 111 | value: int32, 112 | }), 113 | ]), 114 | }); 115 | 116 | /** 117 | * @generated from message tests.harness.cases.KitchenSinkMessage 118 | */ 119 | export const KitchenSinkMessageSchema = z.object({ 120 | /** 121 | * @generated from field: tests.harness.cases.ComplexTestMsg val = 1; 122 | */ 123 | val: z.lazy(() => ComplexTestMsgSchema).nullish(), 124 | }); 125 | 126 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/harness/cases/bool_pb.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-es v0.2.1 with parameter "target=ts" 2 | // @generated from file tests/harness/cases/bool.proto (package tests.harness.cases, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import type {BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage} from "@bufbuild/protobuf"; 7 | import {Message, proto3} from "@bufbuild/protobuf"; 8 | 9 | /** 10 | * @generated from message tests.harness.cases.BoolNone 11 | */ 12 | export class BoolNone extends Message { 13 | /** 14 | * @generated from field: bool val = 1; 15 | */ 16 | val = false; 17 | 18 | constructor(data?: PartialMessage) { 19 | super(); 20 | proto3.util.initPartial(data, this); 21 | } 22 | 23 | static readonly runtime = proto3; 24 | static readonly typeName = "tests.harness.cases.BoolNone"; 25 | static readonly fields: FieldList = proto3.util.newFieldList(() => [ 26 | { no: 1, name: "val", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, 27 | ]); 28 | 29 | static fromBinary(bytes: Uint8Array, options?: Partial): BoolNone { 30 | return new BoolNone().fromBinary(bytes, options); 31 | } 32 | 33 | static fromJson(jsonValue: JsonValue, options?: Partial): BoolNone { 34 | return new BoolNone().fromJson(jsonValue, options); 35 | } 36 | 37 | static fromJsonString(jsonString: string, options?: Partial): BoolNone { 38 | return new BoolNone().fromJsonString(jsonString, options); 39 | } 40 | 41 | static equals(a: BoolNone | PlainMessage | undefined, b: BoolNone | PlainMessage | undefined): boolean { 42 | return proto3.util.equals(BoolNone, a, b); 43 | } 44 | } 45 | 46 | /** 47 | * @generated from message tests.harness.cases.BoolConstTrue 48 | */ 49 | export class BoolConstTrue extends Message { 50 | /** 51 | * @generated from field: bool val = 1; 52 | */ 53 | val = false; 54 | 55 | constructor(data?: PartialMessage) { 56 | super(); 57 | proto3.util.initPartial(data, this); 58 | } 59 | 60 | static readonly runtime = proto3; 61 | static readonly typeName = "tests.harness.cases.BoolConstTrue"; 62 | static readonly fields: FieldList = proto3.util.newFieldList(() => [ 63 | { no: 1, name: "val", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, 64 | ]); 65 | 66 | static fromBinary(bytes: Uint8Array, options?: Partial): BoolConstTrue { 67 | return new BoolConstTrue().fromBinary(bytes, options); 68 | } 69 | 70 | static fromJson(jsonValue: JsonValue, options?: Partial): BoolConstTrue { 71 | return new BoolConstTrue().fromJson(jsonValue, options); 72 | } 73 | 74 | static fromJsonString(jsonString: string, options?: Partial): BoolConstTrue { 75 | return new BoolConstTrue().fromJsonString(jsonString, options); 76 | } 77 | 78 | static equals(a: BoolConstTrue | PlainMessage | undefined, b: BoolConstTrue | PlainMessage | undefined): boolean { 79 | return proto3.util.equals(BoolConstTrue, a, b); 80 | } 81 | } 82 | 83 | /** 84 | * @generated from message tests.harness.cases.BoolConstFalse 85 | */ 86 | export class BoolConstFalse extends Message { 87 | /** 88 | * @generated from field: bool val = 1; 89 | */ 90 | val = false; 91 | 92 | constructor(data?: PartialMessage) { 93 | super(); 94 | proto3.util.initPartial(data, this); 95 | } 96 | 97 | static readonly runtime = proto3; 98 | static readonly typeName = "tests.harness.cases.BoolConstFalse"; 99 | static readonly fields: FieldList = proto3.util.newFieldList(() => [ 100 | { no: 1, name: "val", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, 101 | ]); 102 | 103 | static fromBinary(bytes: Uint8Array, options?: Partial): BoolConstFalse { 104 | return new BoolConstFalse().fromBinary(bytes, options); 105 | } 106 | 107 | static fromJson(jsonValue: JsonValue, options?: Partial): BoolConstFalse { 108 | return new BoolConstFalse().fromJson(jsonValue, options); 109 | } 110 | 111 | static fromJsonString(jsonString: string, options?: Partial): BoolConstFalse { 112 | return new BoolConstFalse().fromJsonString(jsonString, options); 113 | } 114 | 115 | static equals(a: BoolConstFalse | PlainMessage | undefined, b: BoolConstFalse | PlainMessage | undefined): boolean { 116 | return proto3.util.equals(BoolConstFalse, a, b); 117 | } 118 | } 119 | 120 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/src/field.ts: -------------------------------------------------------------------------------- 1 | import { DescOneof, ScalarType, DescField } from "@bufbuild/protobuf"; 2 | import { 3 | findCustomScalarOption, 4 | Printable, 5 | findCustomMessageOption, 6 | localName, 7 | makeJsDoc, 8 | } from "@bufbuild/protoplugin/ecmascript"; 9 | import { getEnumSchema } from "./enum.js"; 10 | import { getMapSchema } from "./map.js"; 11 | import { getMessageSchema } from "./message.js"; 12 | import { hasRulesFor } from "./rules.js"; 13 | import { getScalarSchema } from "./scalar.js"; 14 | import { GeneratedFile, makeJsDocWithRules } from "./utils.js"; 15 | import { FieldRules } from "./generated/validate_pb.js"; 16 | 17 | export function generateOneOfSchema(f: GeneratedFile, oneof: DescOneof, disabled: boolean) { 18 | try { 19 | const required = !disabled && (findCustomScalarOption(oneof, 1071, ScalarType.BOOL) ?? false); 20 | const multiple = oneof.fields.length + (required ? 0 : 1) > 1; 21 | const indentation = multiple ? " " : " "; 22 | const cases: Printable[] = []; 23 | 24 | if (!required) { 25 | const empty = [ 26 | [f.zod, ".object({\n"], 27 | [indentation, " case: ", f.literal(undefined), ",\n"], 28 | [indentation, " value: ", f.literal(undefined), ".nullish()", ",\n"], 29 | [indentation, "})"], 30 | ]; 31 | 32 | cases.push(empty); 33 | } 34 | 35 | for (const field of oneof.fields) { 36 | const option: Printable[] = []; 37 | 38 | const rules = findCustomMessageOption(field, 1071, FieldRules); 39 | const typing = getFieldSchema(f, field, disabled ? undefined : rules); 40 | const discriminator = f.literal(localName(field)); 41 | 42 | option.push(f.zod, ".object({\n"); 43 | option.push(makeJsDocWithRules(field, rules, disabled, indentation + " "), "\n"); 44 | option.push(indentation, " case: ", discriminator, ",\n"); 45 | option.push(indentation, " value: ", typing, ",\n"); 46 | option.push(indentation, "})"); 47 | 48 | cases.push(option); 49 | } 50 | 51 | const output: Printable[] = multiple ? [f.oneof(cases)] : cases; 52 | 53 | if (!required) { 54 | output.push(".nullish()"); 55 | } 56 | 57 | f.print(makeJsDoc(oneof, " ")); 58 | f.print(" ", localName(oneof), ": ", output, ","); 59 | } catch (e) { 60 | throw new Error(`Failed to generate schema for oneof ${oneof.parent.typeName}.${oneof.name}: ${e}`); 61 | } 62 | } 63 | 64 | export function generateFieldSchema(f: GeneratedFile, field: DescField, disabled: boolean) { 65 | try { 66 | const rules = findCustomMessageOption(field, 1071, FieldRules); 67 | const typing = getFieldSchema(f, field, disabled ? undefined : rules); 68 | 69 | f.print(makeJsDocWithRules(field, rules, disabled, " ")); 70 | f.print(" ", localName(field), ": ", typing, ","); 71 | } catch (e) { 72 | throw new Error(`Failed to generate schema for field ${field.parent.typeName}.${field.name}: ${e}`); 73 | } 74 | } 75 | 76 | function getFieldSchema(f: GeneratedFile, field: DescField, rules?: FieldRules): Printable[] { 77 | const typing: Printable[] = []; 78 | 79 | if (field.repeated && hasRulesFor("repeated", rules)) { 80 | typing.push(getFieldItemSchema(f, field, rules.type.value.items), ".array()"); 81 | 82 | if (rules.type.value.maxItems !== undefined) { 83 | typing.push(".max(", Number(rules.type.value.maxItems), ")"); 84 | } 85 | 86 | if (rules.type.value.minItems !== undefined) { 87 | typing.push(".min(", Number(rules.type.value.minItems), ")"); 88 | } 89 | 90 | if (rules.type.value.unique) { 91 | // TODO: How do we handle uniqueness of complex types? Messages, etc.? 92 | typing.push(f.validate.isUniqueList()); 93 | } 94 | 95 | if (rules.type.value.ignoreEmpty) { 96 | // TODO: Implement 97 | } 98 | } else if (field.repeated) { 99 | typing.push(getFieldItemSchema(f, field), ".array()"); 100 | } else { 101 | typing.push(getFieldItemSchema(f, field, rules)); 102 | } 103 | 104 | if (field.optional) { 105 | typing.push(".nullish()"); 106 | } 107 | 108 | return typing; 109 | } 110 | 111 | function getFieldItemSchema(f: GeneratedFile, field: DescField, rules?: FieldRules): Printable { 112 | switch (field.fieldKind) { 113 | case "scalar": 114 | return getScalarSchema(f, field.scalar, rules); 115 | 116 | case "enum": 117 | return getEnumSchema(f, field.enum, rules); 118 | 119 | case "message": 120 | return getMessageSchema(f, field.message, rules); 121 | 122 | case "map": 123 | return getMapSchema(f, field.mapKey, field.mapValue, rules); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/harness/cases/messages.test.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-validate-zod-test v0.0.2-dev with parameter "target=ts,cases=packages/protoc-gen-validate-zod/tests/cases.txt,descriptor=vendor/cases.bin" 2 | // @generated from file tests/harness/cases/messages.proto (package tests.harness.cases, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import {expect, it} from "vitest"; 7 | import {Message, MessageCrossPackage, MessageDisabled, MessageIgnored, MessageNone, MessageRequired, MessageRequiredButOptional, MessageRequiredOneof, MessageSkip} from "./messages_pb.js"; 8 | import {MessageCrossPackageSchema, MessageDisabledSchema, MessageIgnoredSchema, MessageNoneSchema, MessageRequiredButOptionalSchema, MessageRequiredOneofSchema, MessageRequiredSchema, MessageSchema, MessageSkipSchema} from "./messages_zod.js"; 9 | 10 | it("message - none - valid", () => { 11 | const message = MessageNone.fromJson({"val":{}}); 12 | expect(message).toBeValid(MessageNoneSchema); 13 | }); 14 | 15 | it("message - none - valid (unset)", () => { 16 | const message = MessageNone.fromJson({}); 17 | expect(message).toBeValid(MessageNoneSchema); 18 | }); 19 | 20 | it("message - disabled - valid", () => { 21 | const message = MessageDisabled.fromJson({"val":"456"}); 22 | expect(message).toBeValid(MessageDisabledSchema); 23 | }); 24 | 25 | it("message - disabled - valid (invalid field)", () => { 26 | const message = MessageDisabled.fromJson({}); 27 | expect(message).toBeValid(MessageDisabledSchema); 28 | }); 29 | 30 | it("message - ignored - valid", () => { 31 | const message = MessageIgnored.fromJson({"val":"456"}); 32 | expect(message).toBeValid(MessageIgnoredSchema); 33 | }); 34 | 35 | it("message - ignored - valid (invalid field)", () => { 36 | const message = MessageIgnored.fromJson({}); 37 | expect(message).toBeValid(MessageIgnoredSchema); 38 | }); 39 | 40 | it("message - field - valid", () => { 41 | const message = Message.fromJson({"val":{"const":"foo"}}); 42 | expect(message).toBeValid(MessageSchema); 43 | }); 44 | 45 | it("message - field - valid (unset)", () => { 46 | const message = Message.fromJson({}); 47 | expect(message).toBeValid(MessageSchema); 48 | }); 49 | 50 | it("message - field - invalid", () => { 51 | const message = Message.fromJson({"val":{}}); 52 | expect(message).toBeInvalid(MessageSchema, 1); 53 | }); 54 | 55 | it("message - field - invalid (transitive)", () => { 56 | const message = Message.fromJson({"val":{"const":"foo","nested":{}}}); 57 | expect(message).toBeInvalid(MessageSchema, 1); 58 | }); 59 | 60 | it("message - skip - valid", () => { 61 | const message = MessageSkip.fromJson({"val":{}}); 62 | expect(message).toBeValid(MessageSkipSchema); 63 | }); 64 | 65 | it("message - required - valid", () => { 66 | const message = MessageRequired.fromJson({"val":{"const":"foo"}}); 67 | expect(message).toBeValid(MessageRequiredSchema); 68 | }); 69 | 70 | it("message - required - valid (oneof)", () => { 71 | const message = MessageRequiredOneof.fromJson({"val":{"const":"foo"}}); 72 | expect(message).toBeValid(MessageRequiredOneofSchema); 73 | }); 74 | 75 | it("message - required - invalid", () => { 76 | const message = MessageRequired.fromJson({}); 77 | expect(message).toBeInvalid(MessageRequiredSchema, 1); 78 | }); 79 | 80 | it("message - required - invalid (oneof)", () => { 81 | const message = MessageRequiredOneof.fromJson({}); 82 | expect(message).toBeInvalid(MessageRequiredOneofSchema, 1); 83 | }); 84 | 85 | it("message - cross-package embed none - valid", () => { 86 | const message = MessageCrossPackage.fromJson({"val":{"val":"1"}}); 87 | expect(message).toBeValid(MessageCrossPackageSchema); 88 | }); 89 | 90 | it("message - cross-package embed none - valid (nil)", () => { 91 | const message = MessageCrossPackage.fromJson({}); 92 | expect(message).toBeValid(MessageCrossPackageSchema); 93 | }); 94 | 95 | it("message - cross-package embed none - valid (empty)", () => { 96 | const message = MessageCrossPackage.fromJson({"val":{}}); 97 | expect(message).toBeInvalid(MessageCrossPackageSchema, 1); 98 | }); 99 | 100 | it("message - cross-package embed none - invalid", () => { 101 | const message = MessageCrossPackage.fromJson({"val":{"val":"-1"}}); 102 | expect(message).toBeInvalid(MessageCrossPackageSchema, 1); 103 | }); 104 | 105 | it("message - required - valid", () => { 106 | const message = MessageRequiredButOptional.fromJson({"val":{"const":"foo"}}); 107 | expect(message).toBeValid(MessageRequiredButOptionalSchema); 108 | }); 109 | 110 | it("message - required - valid (unset)", () => { 111 | const message = MessageRequiredButOptional.fromJson({}); 112 | expect(message).toBeValid(MessageRequiredButOptionalSchema); 113 | }); 114 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/harness/cases/oneofs_zod.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-validate-zod v0.0.2-dev with parameter "target=ts" 2 | // @generated from file tests/harness/cases/oneofs.proto (package tests.harness.cases, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import {z} from "zod"; 7 | import {bytes, bytesLength, bytesMaxLength, bytesMinLength, int32, numberGt, numberOutsideGteLte, oneof} from "protobuf-zod"; 8 | 9 | /** 10 | * @generated from message tests.harness.cases.TestOneOfMsg 11 | */ 12 | export const TestOneOfMsgSchema = z.object({ 13 | /** 14 | * @generated from field: bool val = 1; 15 | * @validate {"bool":{"const":true}} 16 | */ 17 | val: z.boolean().and(z.literal(true)), 18 | }); 19 | 20 | /** 21 | * @generated from message tests.harness.cases.OneOfNone 22 | */ 23 | export const OneOfNoneSchema = z.object({ 24 | /** 25 | * @generated from oneof tests.harness.cases.OneOfNone.o 26 | */ 27 | o: oneof([ 28 | z.object({ 29 | case: z.literal(undefined), 30 | value: z.literal(undefined).nullish(), 31 | }), 32 | z.object({ 33 | /** 34 | * @generated from field: string x = 1; 35 | */ 36 | case: z.literal("x"), 37 | value: z.string(), 38 | }), 39 | z.object({ 40 | /** 41 | * @generated from field: int32 y = 2; 42 | */ 43 | case: z.literal("y"), 44 | value: int32, 45 | }), 46 | ]).nullish(), 47 | }); 48 | 49 | /** 50 | * @generated from message tests.harness.cases.OneOf 51 | */ 52 | export const OneOfSchema = z.object({ 53 | /** 54 | * @generated from oneof tests.harness.cases.OneOf.o 55 | */ 56 | o: oneof([ 57 | z.object({ 58 | case: z.literal(undefined), 59 | value: z.literal(undefined).nullish(), 60 | }), 61 | z.object({ 62 | /** 63 | * @generated from field: string x = 1; 64 | * @validate {"string":{"prefix":"foo"}} 65 | */ 66 | case: z.literal("x"), 67 | value: z.string().startsWith("foo"), 68 | }), 69 | z.object({ 70 | /** 71 | * @generated from field: int32 y = 2; 72 | * @validate {"int32":{"gt":0}} 73 | */ 74 | case: z.literal("y"), 75 | value: int32.refine(numberGt(0)), 76 | }), 77 | z.object({ 78 | /** 79 | * @generated from field: tests.harness.cases.TestOneOfMsg z = 3; 80 | */ 81 | case: z.literal("z"), 82 | value: z.lazy(() => TestOneOfMsgSchema).nullish(), 83 | }), 84 | ]).nullish(), 85 | }); 86 | 87 | /** 88 | * @generated from message tests.harness.cases.OneOfRequired 89 | */ 90 | export const OneOfRequiredSchema = z.object({ 91 | /** 92 | * @generated from oneof tests.harness.cases.OneOfRequired.o 93 | */ 94 | o: oneof([ 95 | z.object({ 96 | /** 97 | * @generated from field: string x = 1; 98 | */ 99 | case: z.literal("x"), 100 | value: z.string(), 101 | }), 102 | z.object({ 103 | /** 104 | * @generated from field: int32 y = 2; 105 | */ 106 | case: z.literal("y"), 107 | value: int32, 108 | }), 109 | z.object({ 110 | /** 111 | * @generated from field: int32 name_with_underscores = 3; 112 | */ 113 | case: z.literal("nameWithUnderscores"), 114 | value: int32, 115 | }), 116 | z.object({ 117 | /** 118 | * @generated from field: int32 under_and_1_number = 4; 119 | */ 120 | case: z.literal("underAnd1Number"), 121 | value: int32, 122 | }), 123 | ]), 124 | }); 125 | 126 | /** 127 | * @generated from message tests.harness.cases.OneOfIgnoreEmpty 128 | */ 129 | export const OneOfIgnoreEmptySchema = z.object({ 130 | /** 131 | * @generated from oneof tests.harness.cases.OneOfIgnoreEmpty.o 132 | */ 133 | o: oneof([ 134 | z.object({ 135 | case: z.literal(undefined), 136 | value: z.literal(undefined).nullish(), 137 | }), 138 | z.object({ 139 | /** 140 | * @generated from field: string x = 1; 141 | * @validate {"string":{"minLen":"3","maxLen":"5","ignoreEmpty":true}} 142 | */ 143 | case: z.literal("x"), 144 | value: z.literal("").or(z.string().min(3).max(5)), 145 | }), 146 | z.object({ 147 | /** 148 | * @generated from field: bytes y = 2; 149 | * @validate {"bytes":{"minLen":"3","maxLen":"5","ignoreEmpty":true}} 150 | */ 151 | case: z.literal("y"), 152 | value: bytes.refine(bytesLength(0)).or(bytes.refine(bytesMaxLength(5)).refine(bytesMinLength(3))), 153 | }), 154 | z.object({ 155 | /** 156 | * @generated from field: int32 z = 3; 157 | * @validate {"int32":{"lte":128,"gte":256,"ignoreEmpty":true}} 158 | */ 159 | case: z.literal("z"), 160 | value: z.literal(0).or(int32.refine(numberOutsideGteLte(256, 128))), 161 | }), 162 | ]).nullish(), 163 | }); 164 | 165 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/harness/cases/wkt_duration_zod.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-validate-zod v0.0.2-dev with parameter "target=ts" 2 | // @generated from file tests/harness/cases/wkt_duration.proto (package tests.harness.cases, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import {z} from "zod"; 7 | import {int32, numberGt} from "protobuf-zod"; 8 | 9 | /** 10 | * @generated from message tests.harness.cases.DurationNone 11 | */ 12 | export const DurationNoneSchema = z.object({ 13 | /** 14 | * @generated from field: google.protobuf.Duration val = 1; 15 | */ 16 | val: z.any().nullish(), 17 | }); 18 | 19 | /** 20 | * @generated from message tests.harness.cases.DurationRequired 21 | */ 22 | export const DurationRequiredSchema = z.object({ 23 | /** 24 | * @generated from field: google.protobuf.Duration val = 1; 25 | * @validate {"duration":{"required":true}} 26 | */ 27 | val: z.any().nullish(), 28 | }); 29 | 30 | /** 31 | * TODO(htuch): Add a very large duration, e.g. {seconds: 315576000000}, once 32 | * #34 is resolved. 33 | * 34 | * @generated from message tests.harness.cases.DurationConst 35 | */ 36 | export const DurationConstSchema = z.object({ 37 | /** 38 | * @generated from field: google.protobuf.Duration val = 1; 39 | * @validate {"duration":{"const":"3s"}} 40 | */ 41 | val: z.any().nullish(), 42 | }); 43 | 44 | /** 45 | * @generated from message tests.harness.cases.DurationIn 46 | */ 47 | export const DurationInSchema = z.object({ 48 | /** 49 | * @generated from field: google.protobuf.Duration val = 1; 50 | * @validate {"duration":{"in":["1s","0.000001s"]}} 51 | */ 52 | val: z.any().nullish(), 53 | }); 54 | 55 | /** 56 | * @generated from message tests.harness.cases.DurationNotIn 57 | */ 58 | export const DurationNotInSchema = z.object({ 59 | /** 60 | * @generated from field: google.protobuf.Duration val = 1; 61 | * @validate {"duration":{"notIn":["0s"]}} 62 | */ 63 | val: z.any().nullish(), 64 | }); 65 | 66 | /** 67 | * @generated from message tests.harness.cases.DurationLT 68 | */ 69 | export const DurationLTSchema = z.object({ 70 | /** 71 | * @generated from field: google.protobuf.Duration val = 1; 72 | * @validate {"duration":{"lt":"0s"}} 73 | */ 74 | val: z.any().nullish(), 75 | }); 76 | 77 | /** 78 | * @generated from message tests.harness.cases.DurationLTE 79 | */ 80 | export const DurationLTESchema = z.object({ 81 | /** 82 | * @generated from field: google.protobuf.Duration val = 1; 83 | * @validate {"duration":{"lte":"1s"}} 84 | */ 85 | val: z.any().nullish(), 86 | }); 87 | 88 | /** 89 | * @generated from message tests.harness.cases.DurationGT 90 | */ 91 | export const DurationGTSchema = z.object({ 92 | /** 93 | * @generated from field: google.protobuf.Duration val = 1; 94 | * @validate {"duration":{"gt":"0.000001s"}} 95 | */ 96 | val: z.any().nullish(), 97 | }); 98 | 99 | /** 100 | * @generated from message tests.harness.cases.DurationGTE 101 | */ 102 | export const DurationGTESchema = z.object({ 103 | /** 104 | * @generated from field: google.protobuf.Duration val = 1; 105 | * @validate {"duration":{"gte":"0.001s"}} 106 | */ 107 | val: z.any().nullish(), 108 | }); 109 | 110 | /** 111 | * @generated from message tests.harness.cases.DurationGTLT 112 | */ 113 | export const DurationGTLTSchema = z.object({ 114 | /** 115 | * @generated from field: google.protobuf.Duration val = 1; 116 | * @validate {"duration":{"lt":"1s","gt":"0s"}} 117 | */ 118 | val: z.any().nullish(), 119 | }); 120 | 121 | /** 122 | * @generated from message tests.harness.cases.DurationExLTGT 123 | */ 124 | export const DurationExLTGTSchema = z.object({ 125 | /** 126 | * @generated from field: google.protobuf.Duration val = 1; 127 | * @validate {"duration":{"lt":"0s","gt":"1s"}} 128 | */ 129 | val: z.any().nullish(), 130 | }); 131 | 132 | /** 133 | * @generated from message tests.harness.cases.DurationGTELTE 134 | */ 135 | export const DurationGTELTESchema = z.object({ 136 | /** 137 | * @generated from field: google.protobuf.Duration val = 1; 138 | * @validate {"duration":{"lte":"3600s","gte":"60s"}} 139 | */ 140 | val: z.any().nullish(), 141 | }); 142 | 143 | /** 144 | * @generated from message tests.harness.cases.DurationExGTELTE 145 | */ 146 | export const DurationExGTELTESchema = z.object({ 147 | /** 148 | * @generated from field: google.protobuf.Duration val = 1; 149 | * @validate {"duration":{"lte":"60s","gte":"3600s"}} 150 | */ 151 | val: z.any().nullish(), 152 | }); 153 | 154 | /** 155 | * Regression for earlier bug where missing Duration field would short circuit 156 | * evaluation in C++. 157 | * 158 | * @generated from message tests.harness.cases.DurationFieldWithOtherFields 159 | */ 160 | export const DurationFieldWithOtherFieldsSchema = z.object({ 161 | /** 162 | * @generated from field: google.protobuf.Duration duration_val = 1; 163 | * @validate {"duration":{"lte":"1s"}} 164 | */ 165 | durationVal: z.any().nullish(), 166 | /** 167 | * @generated from field: int32 int_val = 2; 168 | * @validate {"int32":{"gt":16}} 169 | */ 170 | intVal: int32.refine(numberGt(16)), 171 | }); 172 | 173 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/harness/cases/wkt_wrappers_zod.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-validate-zod v0.0.2-dev with parameter "target=ts" 2 | // @generated from file tests/harness/cases/wkt_wrappers.proto (package tests.harness.cases, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import {z} from "zod"; 7 | import {bytes, bytesMinLength, double, float, int32, int64, numberGt, stringIsUuid, uint32, uint64} from "protobuf-zod"; 8 | import {protoInt64} from "@bufbuild/protobuf"; 9 | 10 | /** 11 | * @generated from message tests.harness.cases.WrapperNone 12 | */ 13 | export const WrapperNoneSchema = z.object({ 14 | /** 15 | * @generated from field: google.protobuf.Int32Value val = 1; 16 | */ 17 | val: int32.nullish(), 18 | }); 19 | 20 | /** 21 | * @generated from message tests.harness.cases.WrapperFloat 22 | */ 23 | export const WrapperFloatSchema = z.object({ 24 | /** 25 | * @generated from field: google.protobuf.FloatValue val = 1; 26 | * @validate {"float":{"gt":0}} 27 | */ 28 | val: float.refine(numberGt(0)).nullish(), 29 | }); 30 | 31 | /** 32 | * @generated from message tests.harness.cases.WrapperDouble 33 | */ 34 | export const WrapperDoubleSchema = z.object({ 35 | /** 36 | * @generated from field: google.protobuf.DoubleValue val = 1; 37 | * @validate {"double":{"gt":0}} 38 | */ 39 | val: double.refine(numberGt(0)).nullish(), 40 | }); 41 | 42 | /** 43 | * @generated from message tests.harness.cases.WrapperInt64 44 | */ 45 | export const WrapperInt64Schema = z.object({ 46 | /** 47 | * @generated from field: google.protobuf.Int64Value val = 1; 48 | * @validate {"int64":{"gt":"0"}} 49 | */ 50 | val: int64.refine(numberGt(protoInt64.zero)).nullish(), 51 | }); 52 | 53 | /** 54 | * @generated from message tests.harness.cases.WrapperInt32 55 | */ 56 | export const WrapperInt32Schema = z.object({ 57 | /** 58 | * @generated from field: google.protobuf.Int32Value val = 1; 59 | * @validate {"int32":{"gt":0}} 60 | */ 61 | val: int32.refine(numberGt(0)).nullish(), 62 | }); 63 | 64 | /** 65 | * @generated from message tests.harness.cases.WrapperUInt64 66 | */ 67 | export const WrapperUInt64Schema = z.object({ 68 | /** 69 | * @generated from field: google.protobuf.UInt64Value val = 1; 70 | * @validate {"uint64":{"gt":"0"}} 71 | */ 72 | val: uint64.refine(numberGt(protoInt64.zero)).nullish(), 73 | }); 74 | 75 | /** 76 | * @generated from message tests.harness.cases.WrapperUInt32 77 | */ 78 | export const WrapperUInt32Schema = z.object({ 79 | /** 80 | * @generated from field: google.protobuf.UInt32Value val = 1; 81 | * @validate {"uint32":{"gt":0}} 82 | */ 83 | val: uint32.refine(numberGt(0)).nullish(), 84 | }); 85 | 86 | /** 87 | * @generated from message tests.harness.cases.WrapperBool 88 | */ 89 | export const WrapperBoolSchema = z.object({ 90 | /** 91 | * @generated from field: google.protobuf.BoolValue val = 1; 92 | * @validate {"bool":{"const":true}} 93 | */ 94 | val: z.boolean().and(z.literal(true)).nullish(), 95 | }); 96 | 97 | /** 98 | * @generated from message tests.harness.cases.WrapperString 99 | */ 100 | export const WrapperStringSchema = z.object({ 101 | /** 102 | * @generated from field: google.protobuf.StringValue val = 1; 103 | * @validate {"string":{"suffix":"bar"}} 104 | */ 105 | val: z.string().endsWith("bar").nullish(), 106 | }); 107 | 108 | /** 109 | * @generated from message tests.harness.cases.WrapperBytes 110 | */ 111 | export const WrapperBytesSchema = z.object({ 112 | /** 113 | * @generated from field: google.protobuf.BytesValue val = 1; 114 | * @validate {"bytes":{"minLen":"3"}} 115 | */ 116 | val: bytes.refine(bytesMinLength(3)).nullish(), 117 | }); 118 | 119 | /** 120 | * @generated from message tests.harness.cases.WrapperRequiredString 121 | */ 122 | export const WrapperRequiredStringSchema = z.object({ 123 | /** 124 | * @generated from field: google.protobuf.StringValue val = 1; 125 | * @validate {"message":{"required":true},"string":{"const":"bar"}} 126 | */ 127 | val: z.string().and(z.literal("bar")), 128 | }); 129 | 130 | /** 131 | * @generated from message tests.harness.cases.WrapperRequiredEmptyString 132 | */ 133 | export const WrapperRequiredEmptyStringSchema = z.object({ 134 | /** 135 | * @generated from field: google.protobuf.StringValue val = 1; 136 | * @validate {"message":{"required":true},"string":{"const":""}} 137 | */ 138 | val: z.string().and(z.literal("")), 139 | }); 140 | 141 | /** 142 | * @generated from message tests.harness.cases.WrapperOptionalUuidString 143 | */ 144 | export const WrapperOptionalUuidStringSchema = z.object({ 145 | /** 146 | * @generated from field: google.protobuf.StringValue val = 1; 147 | * @validate {"message":{"required":false},"string":{"uuid":true}} 148 | */ 149 | val: z.string().refine(stringIsUuid()).nullish(), 150 | }); 151 | 152 | /** 153 | * @generated from message tests.harness.cases.WrapperRequiredFloat 154 | */ 155 | export const WrapperRequiredFloatSchema = z.object({ 156 | /** 157 | * @generated from field: google.protobuf.FloatValue val = 1; 158 | * @validate {"message":{"required":true},"float":{"gt":0}} 159 | */ 160 | val: float.refine(numberGt(0)), 161 | }); 162 | 163 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/harness/cases/wkt_nested_pb.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-es v0.2.1 with parameter "target=ts" 2 | // @generated from file tests/harness/cases/wkt_nested.proto (package tests.harness.cases, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import type {BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage} from "@bufbuild/protobuf"; 7 | import {Message, proto3} from "@bufbuild/protobuf"; 8 | 9 | /** 10 | * @generated from message tests.harness.cases.WktLevelOne 11 | */ 12 | export class WktLevelOne extends Message { 13 | /** 14 | * @generated from field: tests.harness.cases.WktLevelOne.WktLevelTwo two = 1; 15 | */ 16 | two?: WktLevelOne_WktLevelTwo; 17 | 18 | constructor(data?: PartialMessage) { 19 | super(); 20 | proto3.util.initPartial(data, this); 21 | } 22 | 23 | static readonly runtime = proto3; 24 | static readonly typeName = "tests.harness.cases.WktLevelOne"; 25 | static readonly fields: FieldList = proto3.util.newFieldList(() => [ 26 | { no: 1, name: "two", kind: "message", T: WktLevelOne_WktLevelTwo }, 27 | ]); 28 | 29 | static fromBinary(bytes: Uint8Array, options?: Partial): WktLevelOne { 30 | return new WktLevelOne().fromBinary(bytes, options); 31 | } 32 | 33 | static fromJson(jsonValue: JsonValue, options?: Partial): WktLevelOne { 34 | return new WktLevelOne().fromJson(jsonValue, options); 35 | } 36 | 37 | static fromJsonString(jsonString: string, options?: Partial): WktLevelOne { 38 | return new WktLevelOne().fromJsonString(jsonString, options); 39 | } 40 | 41 | static equals(a: WktLevelOne | PlainMessage | undefined, b: WktLevelOne | PlainMessage | undefined): boolean { 42 | return proto3.util.equals(WktLevelOne, a, b); 43 | } 44 | } 45 | 46 | /** 47 | * @generated from message tests.harness.cases.WktLevelOne.WktLevelTwo 48 | */ 49 | export class WktLevelOne_WktLevelTwo extends Message { 50 | /** 51 | * @generated from field: tests.harness.cases.WktLevelOne.WktLevelTwo.WktLevelThree three = 1; 52 | */ 53 | three?: WktLevelOne_WktLevelTwo_WktLevelThree; 54 | 55 | constructor(data?: PartialMessage) { 56 | super(); 57 | proto3.util.initPartial(data, this); 58 | } 59 | 60 | static readonly runtime = proto3; 61 | static readonly typeName = "tests.harness.cases.WktLevelOne.WktLevelTwo"; 62 | static readonly fields: FieldList = proto3.util.newFieldList(() => [ 63 | { no: 1, name: "three", kind: "message", T: WktLevelOne_WktLevelTwo_WktLevelThree }, 64 | ]); 65 | 66 | static fromBinary(bytes: Uint8Array, options?: Partial): WktLevelOne_WktLevelTwo { 67 | return new WktLevelOne_WktLevelTwo().fromBinary(bytes, options); 68 | } 69 | 70 | static fromJson(jsonValue: JsonValue, options?: Partial): WktLevelOne_WktLevelTwo { 71 | return new WktLevelOne_WktLevelTwo().fromJson(jsonValue, options); 72 | } 73 | 74 | static fromJsonString(jsonString: string, options?: Partial): WktLevelOne_WktLevelTwo { 75 | return new WktLevelOne_WktLevelTwo().fromJsonString(jsonString, options); 76 | } 77 | 78 | static equals(a: WktLevelOne_WktLevelTwo | PlainMessage | undefined, b: WktLevelOne_WktLevelTwo | PlainMessage | undefined): boolean { 79 | return proto3.util.equals(WktLevelOne_WktLevelTwo, a, b); 80 | } 81 | } 82 | 83 | /** 84 | * @generated from message tests.harness.cases.WktLevelOne.WktLevelTwo.WktLevelThree 85 | */ 86 | export class WktLevelOne_WktLevelTwo_WktLevelThree extends Message { 87 | /** 88 | * @generated from field: string uuid = 1; 89 | */ 90 | uuid = ""; 91 | 92 | constructor(data?: PartialMessage) { 93 | super(); 94 | proto3.util.initPartial(data, this); 95 | } 96 | 97 | static readonly runtime = proto3; 98 | static readonly typeName = "tests.harness.cases.WktLevelOne.WktLevelTwo.WktLevelThree"; 99 | static readonly fields: FieldList = proto3.util.newFieldList(() => [ 100 | { no: 1, name: "uuid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, 101 | ]); 102 | 103 | static fromBinary(bytes: Uint8Array, options?: Partial): WktLevelOne_WktLevelTwo_WktLevelThree { 104 | return new WktLevelOne_WktLevelTwo_WktLevelThree().fromBinary(bytes, options); 105 | } 106 | 107 | static fromJson(jsonValue: JsonValue, options?: Partial): WktLevelOne_WktLevelTwo_WktLevelThree { 108 | return new WktLevelOne_WktLevelTwo_WktLevelThree().fromJson(jsonValue, options); 109 | } 110 | 111 | static fromJsonString(jsonString: string, options?: Partial): WktLevelOne_WktLevelTwo_WktLevelThree { 112 | return new WktLevelOne_WktLevelTwo_WktLevelThree().fromJsonString(jsonString, options); 113 | } 114 | 115 | static equals(a: WktLevelOne_WktLevelTwo_WktLevelThree | PlainMessage | undefined, b: WktLevelOne_WktLevelTwo_WktLevelThree | PlainMessage | undefined): boolean { 116 | return proto3.util.equals(WktLevelOne_WktLevelTwo_WktLevelThree, a, b); 117 | } 118 | } 119 | 120 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/harness/cases/maps_zod.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-validate-zod v0.0.2-dev with parameter "target=ts" 2 | // @generated from file tests/harness/cases/maps.proto (package tests.harness.cases, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import {z} from "zod"; 7 | import {double, float, int32, int64, map, numberGt, numberLt, sint64, uint32, uint64} from "protobuf-zod"; 8 | import {protoInt64} from "@bufbuild/protobuf"; 9 | 10 | /** 11 | * @generated from message tests.harness.cases.MapNone 12 | */ 13 | export const MapNoneSchema = z.object({ 14 | /** 15 | * @generated from field: map val = 1; 16 | */ 17 | val: map(uint32, z.boolean()), 18 | }); 19 | 20 | /** 21 | * @generated from message tests.harness.cases.MapMin 22 | */ 23 | export const MapMinSchema = z.object({ 24 | /** 25 | * @generated from field: map val = 1; 26 | * @validate {"map":{"minPairs":"2"}} 27 | */ 28 | val: map(int32, float), 29 | }); 30 | 31 | /** 32 | * @generated from message tests.harness.cases.MapMax 33 | */ 34 | export const MapMaxSchema = z.object({ 35 | /** 36 | * @generated from field: map val = 1; 37 | * @validate {"map":{"maxPairs":"3"}} 38 | */ 39 | val: map(int64, double), 40 | }); 41 | 42 | /** 43 | * @generated from message tests.harness.cases.MapMinMax 44 | */ 45 | export const MapMinMaxSchema = z.object({ 46 | /** 47 | * @generated from field: map val = 1; 48 | * @validate {"map":{"minPairs":"2","maxPairs":"4"}} 49 | */ 50 | val: map(z.string(), z.boolean()), 51 | }); 52 | 53 | /** 54 | * @generated from message tests.harness.cases.MapExact 55 | */ 56 | export const MapExactSchema = z.object({ 57 | /** 58 | * @generated from field: map val = 1; 59 | * @validate {"map":{"minPairs":"3","maxPairs":"3"}} 60 | */ 61 | val: map(uint64, z.string()), 62 | }); 63 | 64 | /** 65 | * @generated from message tests.harness.cases.MapNoSparse.Msg 66 | */ 67 | export const MapNoSparse_MsgSchema = z.object({ 68 | }); 69 | 70 | /** 71 | * @generated from message tests.harness.cases.MapNoSparse 72 | */ 73 | export const MapNoSparseSchema = z.object({ 74 | /** 75 | * @generated from field: map val = 1; 76 | * @validate {"map":{"noSparse":true}} 77 | */ 78 | val: map(uint32, z.lazy(() => MapNoSparse_MsgSchema).nullish()), 79 | }); 80 | 81 | /** 82 | * @generated from message tests.harness.cases.MapKeys 83 | */ 84 | export const MapKeysSchema = z.object({ 85 | /** 86 | * @generated from field: map val = 1; 87 | * @validate {"map":{"keys":{"sint64":{"lt":"0"}}}} 88 | */ 89 | val: map(sint64.refine(numberLt(protoInt64.zero)), z.string()), 90 | }); 91 | 92 | /** 93 | * @generated from message tests.harness.cases.MapValues 94 | */ 95 | export const MapValuesSchema = z.object({ 96 | /** 97 | * @generated from field: map val = 1; 98 | * @validate {"map":{"values":{"string":{"minLen":"3"}}}} 99 | */ 100 | val: map(z.string(), z.string().min(3)), 101 | }); 102 | 103 | /** 104 | * @generated from message tests.harness.cases.MapKeysPattern 105 | */ 106 | export const MapKeysPatternSchema = z.object({ 107 | /** 108 | * @generated from field: map val = 1; 109 | * @validate {"map":{"keys":{"string":{"pattern":"(?i)^[a-z0-9]+$"}}}} 110 | */ 111 | val: map(z.string().regex(new RegExp("invalid regular expression^")), z.string()), 112 | }); 113 | 114 | /** 115 | * @generated from message tests.harness.cases.MapValuesPattern 116 | */ 117 | export const MapValuesPatternSchema = z.object({ 118 | /** 119 | * @generated from field: map val = 1; 120 | * @validate {"map":{"values":{"string":{"pattern":"(?i)^[a-z0-9]+$"}}}} 121 | */ 122 | val: map(z.string(), z.string().regex(new RegExp("invalid regular expression^"))), 123 | }); 124 | 125 | /** 126 | * @generated from message tests.harness.cases.MapRecursive.Msg 127 | */ 128 | export const MapRecursive_MsgSchema = z.object({ 129 | /** 130 | * @generated from field: string val = 1; 131 | * @validate {"string":{"minLen":"3"}} 132 | */ 133 | val: z.string().min(3), 134 | }); 135 | 136 | /** 137 | * @generated from message tests.harness.cases.MapRecursive 138 | */ 139 | export const MapRecursiveSchema = z.object({ 140 | /** 141 | * @generated from field: map val = 1; 142 | */ 143 | val: map(uint32, z.lazy(() => MapRecursive_MsgSchema).nullish()), 144 | }); 145 | 146 | /** 147 | * @generated from message tests.harness.cases.MapExactIgnore 148 | */ 149 | export const MapExactIgnoreSchema = z.object({ 150 | /** 151 | * @generated from field: map val = 1; 152 | * @validate {"map":{"minPairs":"3","maxPairs":"3","ignoreEmpty":true}} 153 | */ 154 | val: map(uint64, z.string()), 155 | }); 156 | 157 | /** 158 | * @generated from message tests.harness.cases.MultipleMaps 159 | */ 160 | export const MultipleMapsSchema = z.object({ 161 | /** 162 | * @generated from field: map first = 1; 163 | * @validate {"map":{"keys":{"uint32":{"gt":0}}}} 164 | */ 165 | first: map(uint32.refine(numberGt(0)), z.string()), 166 | /** 167 | * @generated from field: map second = 2; 168 | * @validate {"map":{"keys":{"int32":{"lt":0}}}} 169 | */ 170 | second: map(int32.refine(numberLt(0)), z.boolean()), 171 | /** 172 | * @generated from field: map third = 3; 173 | * @validate {"map":{"keys":{"int32":{"gt":0}}}} 174 | */ 175 | third: map(int32.refine(numberGt(0)), z.boolean()), 176 | }); 177 | 178 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/harness/cases/wkt_timestamp_zod.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-validate-zod v0.0.2-dev with parameter "target=ts" 2 | // @generated from file tests/harness/cases/wkt_timestamp.proto (package tests.harness.cases, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import {z} from "zod"; 7 | 8 | /** 9 | * @generated from message tests.harness.cases.TimestampNone 10 | */ 11 | export const TimestampNoneSchema = z.object({ 12 | /** 13 | * @generated from field: google.protobuf.Timestamp val = 1; 14 | */ 15 | val: z.any().nullish(), 16 | }); 17 | 18 | /** 19 | * @generated from message tests.harness.cases.TimestampRequired 20 | */ 21 | export const TimestampRequiredSchema = z.object({ 22 | /** 23 | * @generated from field: google.protobuf.Timestamp val = 1; 24 | * @validate {"timestamp":{"required":true}} 25 | */ 26 | val: z.any().nullish(), 27 | }); 28 | 29 | /** 30 | * @generated from message tests.harness.cases.TimestampConst 31 | */ 32 | export const TimestampConstSchema = z.object({ 33 | /** 34 | * @generated from field: google.protobuf.Timestamp val = 1; 35 | * @validate {"timestamp":{"const":"1970-01-01T00:00:03Z"}} 36 | */ 37 | val: z.any().nullish(), 38 | }); 39 | 40 | /** 41 | * @generated from message tests.harness.cases.TimestampLT 42 | */ 43 | export const TimestampLTSchema = z.object({ 44 | /** 45 | * @generated from field: google.protobuf.Timestamp val = 1; 46 | * @validate {"timestamp":{"lt":"1970-01-01T00:00:00Z"}} 47 | */ 48 | val: z.any().nullish(), 49 | }); 50 | 51 | /** 52 | * @generated from message tests.harness.cases.TimestampLTE 53 | */ 54 | export const TimestampLTESchema = z.object({ 55 | /** 56 | * @generated from field: google.protobuf.Timestamp val = 1; 57 | * @validate {"timestamp":{"lte":"1970-01-01T00:00:01Z"}} 58 | */ 59 | val: z.any().nullish(), 60 | }); 61 | 62 | /** 63 | * @generated from message tests.harness.cases.TimestampGT 64 | */ 65 | export const TimestampGTSchema = z.object({ 66 | /** 67 | * @generated from field: google.protobuf.Timestamp val = 1; 68 | * @validate {"timestamp":{"gt":"1970-01-01T00:00:00.000001Z"}} 69 | */ 70 | val: z.any().nullish(), 71 | }); 72 | 73 | /** 74 | * @generated from message tests.harness.cases.TimestampGTE 75 | */ 76 | export const TimestampGTESchema = z.object({ 77 | /** 78 | * @generated from field: google.protobuf.Timestamp val = 1; 79 | * @validate {"timestamp":{"gte":"1970-01-01T00:00:00.001Z"}} 80 | */ 81 | val: z.any().nullish(), 82 | }); 83 | 84 | /** 85 | * @generated from message tests.harness.cases.TimestampGTLT 86 | */ 87 | export const TimestampGTLTSchema = z.object({ 88 | /** 89 | * @generated from field: google.protobuf.Timestamp val = 1; 90 | * @validate {"timestamp":{"lt":"1970-01-01T00:00:01Z","gt":"1970-01-01T00:00:00Z"}} 91 | */ 92 | val: z.any().nullish(), 93 | }); 94 | 95 | /** 96 | * @generated from message tests.harness.cases.TimestampExLTGT 97 | */ 98 | export const TimestampExLTGTSchema = z.object({ 99 | /** 100 | * @generated from field: google.protobuf.Timestamp val = 1; 101 | * @validate {"timestamp":{"lt":"1970-01-01T00:00:00Z","gt":"1970-01-01T00:00:01Z"}} 102 | */ 103 | val: z.any().nullish(), 104 | }); 105 | 106 | /** 107 | * @generated from message tests.harness.cases.TimestampGTELTE 108 | */ 109 | export const TimestampGTELTESchema = z.object({ 110 | /** 111 | * @generated from field: google.protobuf.Timestamp val = 1; 112 | * @validate {"timestamp":{"lte":"1970-01-01T01:00:00Z","gte":"1970-01-01T00:01:00Z"}} 113 | */ 114 | val: z.any().nullish(), 115 | }); 116 | 117 | /** 118 | * @generated from message tests.harness.cases.TimestampExGTELTE 119 | */ 120 | export const TimestampExGTELTESchema = z.object({ 121 | /** 122 | * @generated from field: google.protobuf.Timestamp val = 1; 123 | * @validate {"timestamp":{"lte":"1970-01-01T00:01:00Z","gte":"1970-01-01T01:00:00Z"}} 124 | */ 125 | val: z.any().nullish(), 126 | }); 127 | 128 | /** 129 | * @generated from message tests.harness.cases.TimestampLTNow 130 | */ 131 | export const TimestampLTNowSchema = z.object({ 132 | /** 133 | * @generated from field: google.protobuf.Timestamp val = 1; 134 | * @validate {"timestamp":{"ltNow":true}} 135 | */ 136 | val: z.any().nullish(), 137 | }); 138 | 139 | /** 140 | * @generated from message tests.harness.cases.TimestampGTNow 141 | */ 142 | export const TimestampGTNowSchema = z.object({ 143 | /** 144 | * @generated from field: google.protobuf.Timestamp val = 1; 145 | * @validate {"timestamp":{"gtNow":true}} 146 | */ 147 | val: z.any().nullish(), 148 | }); 149 | 150 | /** 151 | * @generated from message tests.harness.cases.TimestampWithin 152 | */ 153 | export const TimestampWithinSchema = z.object({ 154 | /** 155 | * @generated from field: google.protobuf.Timestamp val = 1; 156 | * @validate {"timestamp":{"within":"3600s"}} 157 | */ 158 | val: z.any().nullish(), 159 | }); 160 | 161 | /** 162 | * @generated from message tests.harness.cases.TimestampLTNowWithin 163 | */ 164 | export const TimestampLTNowWithinSchema = z.object({ 165 | /** 166 | * @generated from field: google.protobuf.Timestamp val = 1; 167 | * @validate {"timestamp":{"ltNow":true,"within":"3600s"}} 168 | */ 169 | val: z.any().nullish(), 170 | }); 171 | 172 | /** 173 | * @generated from message tests.harness.cases.TimestampGTNowWithin 174 | */ 175 | export const TimestampGTNowWithinSchema = z.object({ 176 | /** 177 | * @generated from field: google.protobuf.Timestamp val = 1; 178 | * @validate {"timestamp":{"gtNow":true,"within":"3600s"}} 179 | */ 180 | val: z.any().nullish(), 181 | }); 182 | 183 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/harness/cases/wkt_any_pb.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-es v0.2.1 with parameter "target=ts" 2 | // @generated from file tests/harness/cases/wkt_any.proto (package tests.harness.cases, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import type {BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage} from "@bufbuild/protobuf"; 7 | import {Any, Message, proto3} from "@bufbuild/protobuf"; 8 | 9 | /** 10 | * @generated from message tests.harness.cases.AnyNone 11 | */ 12 | export class AnyNone extends Message { 13 | /** 14 | * @generated from field: google.protobuf.Any val = 1; 15 | */ 16 | val?: Any; 17 | 18 | constructor(data?: PartialMessage) { 19 | super(); 20 | proto3.util.initPartial(data, this); 21 | } 22 | 23 | static readonly runtime = proto3; 24 | static readonly typeName = "tests.harness.cases.AnyNone"; 25 | static readonly fields: FieldList = proto3.util.newFieldList(() => [ 26 | { no: 1, name: "val", kind: "message", T: Any }, 27 | ]); 28 | 29 | static fromBinary(bytes: Uint8Array, options?: Partial): AnyNone { 30 | return new AnyNone().fromBinary(bytes, options); 31 | } 32 | 33 | static fromJson(jsonValue: JsonValue, options?: Partial): AnyNone { 34 | return new AnyNone().fromJson(jsonValue, options); 35 | } 36 | 37 | static fromJsonString(jsonString: string, options?: Partial): AnyNone { 38 | return new AnyNone().fromJsonString(jsonString, options); 39 | } 40 | 41 | static equals(a: AnyNone | PlainMessage | undefined, b: AnyNone | PlainMessage | undefined): boolean { 42 | return proto3.util.equals(AnyNone, a, b); 43 | } 44 | } 45 | 46 | /** 47 | * @generated from message tests.harness.cases.AnyRequired 48 | */ 49 | export class AnyRequired extends Message { 50 | /** 51 | * @generated from field: google.protobuf.Any val = 1; 52 | */ 53 | val?: Any; 54 | 55 | constructor(data?: PartialMessage) { 56 | super(); 57 | proto3.util.initPartial(data, this); 58 | } 59 | 60 | static readonly runtime = proto3; 61 | static readonly typeName = "tests.harness.cases.AnyRequired"; 62 | static readonly fields: FieldList = proto3.util.newFieldList(() => [ 63 | { no: 1, name: "val", kind: "message", T: Any }, 64 | ]); 65 | 66 | static fromBinary(bytes: Uint8Array, options?: Partial): AnyRequired { 67 | return new AnyRequired().fromBinary(bytes, options); 68 | } 69 | 70 | static fromJson(jsonValue: JsonValue, options?: Partial): AnyRequired { 71 | return new AnyRequired().fromJson(jsonValue, options); 72 | } 73 | 74 | static fromJsonString(jsonString: string, options?: Partial): AnyRequired { 75 | return new AnyRequired().fromJsonString(jsonString, options); 76 | } 77 | 78 | static equals(a: AnyRequired | PlainMessage | undefined, b: AnyRequired | PlainMessage | undefined): boolean { 79 | return proto3.util.equals(AnyRequired, a, b); 80 | } 81 | } 82 | 83 | /** 84 | * @generated from message tests.harness.cases.AnyIn 85 | */ 86 | export class AnyIn extends Message { 87 | /** 88 | * @generated from field: google.protobuf.Any val = 1; 89 | */ 90 | val?: Any; 91 | 92 | constructor(data?: PartialMessage) { 93 | super(); 94 | proto3.util.initPartial(data, this); 95 | } 96 | 97 | static readonly runtime = proto3; 98 | static readonly typeName = "tests.harness.cases.AnyIn"; 99 | static readonly fields: FieldList = proto3.util.newFieldList(() => [ 100 | { no: 1, name: "val", kind: "message", T: Any }, 101 | ]); 102 | 103 | static fromBinary(bytes: Uint8Array, options?: Partial): AnyIn { 104 | return new AnyIn().fromBinary(bytes, options); 105 | } 106 | 107 | static fromJson(jsonValue: JsonValue, options?: Partial): AnyIn { 108 | return new AnyIn().fromJson(jsonValue, options); 109 | } 110 | 111 | static fromJsonString(jsonString: string, options?: Partial): AnyIn { 112 | return new AnyIn().fromJsonString(jsonString, options); 113 | } 114 | 115 | static equals(a: AnyIn | PlainMessage | undefined, b: AnyIn | PlainMessage | undefined): boolean { 116 | return proto3.util.equals(AnyIn, a, b); 117 | } 118 | } 119 | 120 | /** 121 | * @generated from message tests.harness.cases.AnyNotIn 122 | */ 123 | export class AnyNotIn extends Message { 124 | /** 125 | * @generated from field: google.protobuf.Any val = 1; 126 | */ 127 | val?: Any; 128 | 129 | constructor(data?: PartialMessage) { 130 | super(); 131 | proto3.util.initPartial(data, this); 132 | } 133 | 134 | static readonly runtime = proto3; 135 | static readonly typeName = "tests.harness.cases.AnyNotIn"; 136 | static readonly fields: FieldList = proto3.util.newFieldList(() => [ 137 | { no: 1, name: "val", kind: "message", T: Any }, 138 | ]); 139 | 140 | static fromBinary(bytes: Uint8Array, options?: Partial): AnyNotIn { 141 | return new AnyNotIn().fromBinary(bytes, options); 142 | } 143 | 144 | static fromJson(jsonValue: JsonValue, options?: Partial): AnyNotIn { 145 | return new AnyNotIn().fromJson(jsonValue, options); 146 | } 147 | 148 | static fromJsonString(jsonString: string, options?: Partial): AnyNotIn { 149 | return new AnyNotIn().fromJsonString(jsonString, options); 150 | } 151 | 152 | static equals(a: AnyNotIn | PlainMessage | undefined, b: AnyNotIn | PlainMessage | undefined): boolean { 153 | return proto3.util.equals(AnyNotIn, a, b); 154 | } 155 | } 156 | 157 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/harness/cases/bytes_zod.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-validate-zod v0.0.2-dev with parameter "target=ts" 2 | // @generated from file tests/harness/cases/bytes.proto (package tests.harness.cases, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import {z} from "zod"; 7 | import {bytes, bytesContains, bytesEndsWith, bytesEquals, bytesIsIn, bytesIsIp, bytesIsNotIn, bytesLength, bytesMatches, bytesMaxLength, bytesMinLength, bytesStartsWith} from "protobuf-zod"; 8 | 9 | /** 10 | * @generated from message tests.harness.cases.BytesNone 11 | */ 12 | export const BytesNoneSchema = z.object({ 13 | /** 14 | * @generated from field: bytes val = 1; 15 | */ 16 | val: bytes, 17 | }); 18 | 19 | /** 20 | * @generated from message tests.harness.cases.BytesConst 21 | */ 22 | export const BytesConstSchema = z.object({ 23 | /** 24 | * @generated from field: bytes val = 1; 25 | * @validate {"bytes":{"const":"Zm9v"}} 26 | */ 27 | val: bytes.refine(bytesEquals(new Uint8Array([0x66, 0x6F, 0x6F]))), 28 | }); 29 | 30 | /** 31 | * @generated from message tests.harness.cases.BytesIn 32 | */ 33 | export const BytesInSchema = z.object({ 34 | /** 35 | * @generated from field: bytes val = 1; 36 | * @validate {"bytes":{"in":["YmFy","YmF6"]}} 37 | */ 38 | val: bytes.refine(bytesIsIn([ 39 | new Uint8Array([0x62, 0x61, 0x72]), 40 | new Uint8Array([0x62, 0x61, 0x7A]), 41 | ])), 42 | }); 43 | 44 | /** 45 | * @generated from message tests.harness.cases.BytesNotIn 46 | */ 47 | export const BytesNotInSchema = z.object({ 48 | /** 49 | * @generated from field: bytes val = 1; 50 | * @validate {"bytes":{"notIn":["Zml6eg==","YnV6eg=="]}} 51 | */ 52 | val: bytes.refine(bytesIsNotIn([ 53 | new Uint8Array([0x66, 0x69, 0x7A, 0x7A]), 54 | new Uint8Array([0x62, 0x75, 0x7A, 0x7A]), 55 | ])), 56 | }); 57 | 58 | /** 59 | * @generated from message tests.harness.cases.BytesLen 60 | */ 61 | export const BytesLenSchema = z.object({ 62 | /** 63 | * @generated from field: bytes val = 1; 64 | * @validate {"bytes":{"len":"3"}} 65 | */ 66 | val: bytes.refine(bytesLength(3)), 67 | }); 68 | 69 | /** 70 | * @generated from message tests.harness.cases.BytesMinLen 71 | */ 72 | export const BytesMinLenSchema = z.object({ 73 | /** 74 | * @generated from field: bytes val = 1; 75 | * @validate {"bytes":{"minLen":"3"}} 76 | */ 77 | val: bytes.refine(bytesMinLength(3)), 78 | }); 79 | 80 | /** 81 | * @generated from message tests.harness.cases.BytesMaxLen 82 | */ 83 | export const BytesMaxLenSchema = z.object({ 84 | /** 85 | * @generated from field: bytes val = 1; 86 | * @validate {"bytes":{"maxLen":"5"}} 87 | */ 88 | val: bytes.refine(bytesMaxLength(5)), 89 | }); 90 | 91 | /** 92 | * @generated from message tests.harness.cases.BytesMinMaxLen 93 | */ 94 | export const BytesMinMaxLenSchema = z.object({ 95 | /** 96 | * @generated from field: bytes val = 1; 97 | * @validate {"bytes":{"minLen":"3","maxLen":"5"}} 98 | */ 99 | val: bytes.refine(bytesMaxLength(5)).refine(bytesMinLength(3)), 100 | }); 101 | 102 | /** 103 | * @generated from message tests.harness.cases.BytesEqualMinMaxLen 104 | */ 105 | export const BytesEqualMinMaxLenSchema = z.object({ 106 | /** 107 | * @generated from field: bytes val = 1; 108 | * @validate {"bytes":{"minLen":"5","maxLen":"5"}} 109 | */ 110 | val: bytes.refine(bytesMaxLength(5)).refine(bytesMinLength(5)), 111 | }); 112 | 113 | /** 114 | * @generated from message tests.harness.cases.BytesPattern 115 | */ 116 | export const BytesPatternSchema = z.object({ 117 | /** 118 | * @generated from field: bytes val = 1; 119 | * @validate {"bytes":{"pattern":"^[\u0000-]+$"}} 120 | */ 121 | val: bytes.refine(bytesMatches(new RegExp("^[-]+$"))), 122 | }); 123 | 124 | /** 125 | * @generated from message tests.harness.cases.BytesPrefix 126 | */ 127 | export const BytesPrefixSchema = z.object({ 128 | /** 129 | * @generated from field: bytes val = 1; 130 | * @validate {"bytes":{"prefix":"mQ=="}} 131 | */ 132 | val: bytes.refine(bytesStartsWith(new Uint8Array([0x99]))), 133 | }); 134 | 135 | /** 136 | * @generated from message tests.harness.cases.BytesContains 137 | */ 138 | export const BytesContainsSchema = z.object({ 139 | /** 140 | * @generated from field: bytes val = 1; 141 | * @validate {"bytes":{"contains":"YmFy"}} 142 | */ 143 | val: bytes.refine(bytesContains(new Uint8Array([0x62, 0x61, 0x72]))), 144 | }); 145 | 146 | /** 147 | * @generated from message tests.harness.cases.BytesSuffix 148 | */ 149 | export const BytesSuffixSchema = z.object({ 150 | /** 151 | * @generated from field: bytes val = 1; 152 | * @validate {"bytes":{"suffix":"YnV6eg=="}} 153 | */ 154 | val: bytes.refine(bytesEndsWith(new Uint8Array([0x62, 0x75, 0x7A, 0x7A]))), 155 | }); 156 | 157 | /** 158 | * @generated from message tests.harness.cases.BytesIP 159 | */ 160 | export const BytesIPSchema = z.object({ 161 | /** 162 | * @generated from field: bytes val = 1; 163 | * @validate {"bytes":{"ip":true}} 164 | */ 165 | val: bytes.refine(bytesIsIp()), 166 | }); 167 | 168 | /** 169 | * @generated from message tests.harness.cases.BytesIPv4 170 | */ 171 | export const BytesIPv4Schema = z.object({ 172 | /** 173 | * @generated from field: bytes val = 1; 174 | * @validate {"bytes":{"ipv4":true}} 175 | */ 176 | val: bytes.refine(bytesIsIp(4)), 177 | }); 178 | 179 | /** 180 | * @generated from message tests.harness.cases.BytesIPv6 181 | */ 182 | export const BytesIPv6Schema = z.object({ 183 | /** 184 | * @generated from field: bytes val = 1; 185 | * @validate {"bytes":{"ipv6":true}} 186 | */ 187 | val: bytes.refine(bytesIsIp(6)), 188 | }); 189 | 190 | /** 191 | * @generated from message tests.harness.cases.BytesIPv6Ignore 192 | */ 193 | export const BytesIPv6IgnoreSchema = z.object({ 194 | /** 195 | * @generated from field: bytes val = 1; 196 | * @validate {"bytes":{"ipv6":true,"ignoreEmpty":true}} 197 | */ 198 | val: bytes.refine(bytesLength(0)).or(bytes.refine(bytesIsIp(6))), 199 | }); 200 | 201 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/harness/cases/maps.test.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-validate-zod-test v0.0.2-dev with parameter "target=ts,cases=packages/protoc-gen-validate-zod/tests/cases.txt,descriptor=vendor/cases.bin" 2 | // @generated from file tests/harness/cases/maps.proto (package tests.harness.cases, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import {expect, it} from "vitest"; 7 | import {MapExact, MapExactIgnore, MapKeys, MapKeysPattern, MapMax, MapMin, MapMinMax, MapNone, MapNoSparse, MapRecursive, MapValues, MapValuesPattern, MultipleMaps} from "./maps_pb.js"; 8 | import {MapExactIgnoreSchema, MapExactSchema, MapKeysPatternSchema, MapKeysSchema, MapMaxSchema, MapMinMaxSchema, MapMinSchema, MapNoneSchema, MapNoSparseSchema, MapRecursiveSchema, MapValuesPatternSchema, MapValuesSchema, MultipleMapsSchema} from "./maps_zod.js"; 9 | 10 | it("map - none - valid", () => { 11 | const message = MapNone.fromJson({"val":{"123":true,"456":false}}); 12 | expect(message).toBeValid(MapNoneSchema); 13 | }); 14 | 15 | it("map - min pairs - valid", () => { 16 | const message = MapMin.fromJson({"val":{"1":2,"3":4,"5":6}}); 17 | expect(message).toBeValid(MapMinSchema); 18 | }); 19 | 20 | it("map - min pairs - valid (equal)", () => { 21 | const message = MapMin.fromJson({"val":{"1":2,"3":4}}); 22 | expect(message).toBeValid(MapMinSchema); 23 | }); 24 | 25 | it("map - min pairs - invalid", () => { 26 | const message = MapMin.fromJson({"val":{"1":2}}); 27 | expect(message).toBeInvalid(MapMinSchema, 1); 28 | }); 29 | 30 | it("map - max pairs - valid", () => { 31 | const message = MapMax.fromJson({"val":{"1":2,"3":4}}); 32 | expect(message).toBeValid(MapMaxSchema); 33 | }); 34 | 35 | it("map - max pairs - valid (equal)", () => { 36 | const message = MapMax.fromJson({"val":{"1":2,"3":4,"5":6}}); 37 | expect(message).toBeValid(MapMaxSchema); 38 | }); 39 | 40 | it("map - max pairs - invalid", () => { 41 | const message = MapMax.fromJson({"val":{"1":2,"3":4,"5":6,"7":8}}); 42 | expect(message).toBeInvalid(MapMaxSchema, 1); 43 | }); 44 | 45 | it("map - min/max - valid", () => { 46 | const message = MapMinMax.fromJson({"val":{"a":true,"b":false,"c":true}}); 47 | expect(message).toBeValid(MapMinMaxSchema); 48 | }); 49 | 50 | it("map - min/max - valid (min)", () => { 51 | const message = MapMinMax.fromJson({"val":{"a":true,"b":false}}); 52 | expect(message).toBeValid(MapMinMaxSchema); 53 | }); 54 | 55 | it("map - min/max - valid (max)", () => { 56 | const message = MapMinMax.fromJson({"val":{"a":true,"b":false,"c":true,"d":false}}); 57 | expect(message).toBeValid(MapMinMaxSchema); 58 | }); 59 | 60 | it("map - min/max - invalid (below)", () => { 61 | const message = MapMinMax.fromJson({}); 62 | expect(message).toBeInvalid(MapMinMaxSchema, 1); 63 | }); 64 | 65 | it("map - min/max - invalid (above)", () => { 66 | const message = MapMinMax.fromJson({"val":{"a":true,"b":false,"c":true,"d":false,"e":true}}); 67 | expect(message).toBeInvalid(MapMinMaxSchema, 1); 68 | }); 69 | 70 | it("map - exact - valid", () => { 71 | const message = MapExact.fromJson({"val":{"1":"a","2":"b","3":"c"}}); 72 | expect(message).toBeValid(MapExactSchema); 73 | }); 74 | 75 | it("map - exact - invalid (below)", () => { 76 | const message = MapExact.fromJson({"val":{"1":"a","2":"b"}}); 77 | expect(message).toBeInvalid(MapExactSchema, 1); 78 | }); 79 | 80 | it("map - exact - invalid (above)", () => { 81 | const message = MapExact.fromJson({"val":{"1":"a","2":"b","3":"c","4":"d"}}); 82 | expect(message).toBeInvalid(MapExactSchema, 1); 83 | }); 84 | 85 | it("map - no sparse - valid", () => { 86 | const message = MapNoSparse.fromJson({"val":{"1":{},"2":{}}}); 87 | expect(message).toBeValid(MapNoSparseSchema); 88 | }); 89 | 90 | it("map - no sparse - valid (empty)", () => { 91 | const message = MapNoSparse.fromJson({}); 92 | expect(message).toBeValid(MapNoSparseSchema); 93 | }); 94 | 95 | it("map - keys - valid", () => { 96 | const message = MapKeys.fromJson({"val":{"-2":"b","-1":"a"}}); 97 | expect(message).toBeValid(MapKeysSchema); 98 | }); 99 | 100 | it("map - keys - valid (empty)", () => { 101 | const message = MapKeys.fromJson({}); 102 | expect(message).toBeValid(MapKeysSchema); 103 | }); 104 | 105 | it("map - keys - valid (pattern)", () => { 106 | const message = MapKeysPattern.fromJson({"val":{"A":"a"}}); 107 | expect(message).toBeValid(MapKeysPatternSchema); 108 | }); 109 | 110 | it("map - keys - invalid", () => { 111 | const message = MapKeys.fromJson({"val":{"1":"a"}}); 112 | expect(message).toBeInvalid(MapKeysSchema, 1); 113 | }); 114 | 115 | it("map - keys - invalid (pattern)", () => { 116 | const message = MapKeysPattern.fromJson({"val":{"!@#$%^&*()":"b","A":"a"}}); 117 | expect(message).toBeInvalid(MapKeysPatternSchema, 1); 118 | }); 119 | 120 | it("map - values - valid", () => { 121 | const message = MapValues.fromJson({"val":{"a":"Alpha","b":"Beta"}}); 122 | expect(message).toBeValid(MapValuesSchema); 123 | }); 124 | 125 | it("map - values - valid (empty)", () => { 126 | const message = MapValues.fromJson({}); 127 | expect(message).toBeValid(MapValuesSchema); 128 | }); 129 | 130 | it("map - values - valid (pattern)", () => { 131 | const message = MapValuesPattern.fromJson({"val":{"a":"A"}}); 132 | expect(message).toBeValid(MapValuesPatternSchema); 133 | }); 134 | 135 | it("map - values - invalid", () => { 136 | const message = MapValues.fromJson({"val":{"a":"A","b":"B"}}); 137 | expect(message).toBeInvalid(MapValuesSchema, 2); 138 | }); 139 | 140 | it("map - values - invalid (pattern)", () => { 141 | const message = MapValuesPattern.fromJson({"val":{"a":"A","b":"!@#$%^&*()"}}); 142 | expect(message).toBeInvalid(MapValuesPatternSchema, 1); 143 | }); 144 | 145 | it("map - recursive - valid", () => { 146 | const message = MapRecursive.fromJson({"val":{"1":{"val":"abc"}}}); 147 | expect(message).toBeValid(MapRecursiveSchema); 148 | }); 149 | 150 | it("map - recursive - invalid", () => { 151 | const message = MapRecursive.fromJson({"val":{"1":{}}}); 152 | expect(message).toBeInvalid(MapRecursiveSchema, 1); 153 | }); 154 | 155 | it("map - exact - valid (ignore_empty)", () => { 156 | const message = MapExactIgnore.fromJson({}); 157 | expect(message).toBeValid(MapExactIgnoreSchema); 158 | }); 159 | 160 | it("map - multiple - valid", () => { 161 | const message = MultipleMaps.fromJson({"first":{"1":"a","2":"b"},"second":{"-2":false,"-1":true}}); 162 | expect(message).toBeValid(MultipleMapsSchema); 163 | }); 164 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/harness/cases/enums.test.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-validate-zod-test v0.0.2-dev with parameter "target=ts,cases=packages/protoc-gen-validate-zod/tests/cases.txt,descriptor=vendor/cases.bin" 2 | // @generated from file tests/harness/cases/enums.proto (package tests.harness.cases, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import {expect, it} from "vitest"; 7 | import {EnumAliasConst, EnumAliasDefined, EnumAliasIn, EnumAliasNotIn, EnumConst, EnumDefined, EnumExternal, EnumIn, EnumNone, EnumNotIn, MapEnumDefined, MapExternalEnumDefined, RepeatedEnumDefined, RepeatedExternalEnumDefined, RepeatedYetAnotherExternalEnumDefined} from "./enums_pb.js"; 8 | import {EnumAliasConstSchema, EnumAliasDefinedSchema, EnumAliasInSchema, EnumAliasNotInSchema, EnumConstSchema, EnumDefinedSchema, EnumExternalSchema, EnumInSchema, EnumNoneSchema, EnumNotInSchema, MapEnumDefinedSchema, MapExternalEnumDefinedSchema, RepeatedEnumDefinedSchema, RepeatedExternalEnumDefinedSchema, RepeatedYetAnotherExternalEnumDefinedSchema} from "./enums_zod.js"; 9 | 10 | it("enum - none - valid", () => { 11 | const message = EnumNone.fromJson({"val":"ONE"}); 12 | expect(message).toBeValid(EnumNoneSchema); 13 | }); 14 | 15 | it("enum - const - valid", () => { 16 | const message = EnumConst.fromJson({"val":"TWO"}); 17 | expect(message).toBeValid(EnumConstSchema); 18 | }); 19 | 20 | it("enum - const - invalid", () => { 21 | const message = EnumConst.fromJson({"val":"ONE"}); 22 | expect(message).toBeInvalid(EnumConstSchema, 1); 23 | }); 24 | 25 | it("enum alias - const - valid", () => { 26 | const message = EnumAliasConst.fromJson({"val":"GAMMA"}); 27 | expect(message).toBeValid(EnumAliasConstSchema); 28 | }); 29 | 30 | it("enum alias - const - valid (alias)", () => { 31 | const message = EnumAliasConst.fromJson({"val":"GAMMA"}); 32 | expect(message).toBeValid(EnumAliasConstSchema); 33 | }); 34 | 35 | it("enum alias - const - invalid", () => { 36 | const message = EnumAliasConst.fromJson({}); 37 | expect(message).toBeInvalid(EnumAliasConstSchema, 1); 38 | }); 39 | 40 | it("enum - defined_only - valid", () => { 41 | const message = EnumDefined.fromJson({}); 42 | expect(message).toBeValid(EnumDefinedSchema); 43 | }); 44 | 45 | it("enum - defined_only - invalid", () => { 46 | const message = EnumDefined.fromJson({"val":2147483647}); 47 | expect(message).toBeInvalid(EnumDefinedSchema, 1); 48 | }); 49 | 50 | it("enum alias - defined_only - valid", () => { 51 | const message = EnumAliasDefined.fromJson({"val":"BETA"}); 52 | expect(message).toBeValid(EnumAliasDefinedSchema); 53 | }); 54 | 55 | it("enum alias - defined_only - invalid", () => { 56 | const message = EnumAliasDefined.fromJson({"val":2147483647}); 57 | expect(message).toBeInvalid(EnumAliasDefinedSchema, 1); 58 | }); 59 | 60 | it("enum - in - valid", () => { 61 | const message = EnumIn.fromJson({"val":"TWO"}); 62 | expect(message).toBeValid(EnumInSchema); 63 | }); 64 | 65 | it("enum - in - invalid", () => { 66 | const message = EnumIn.fromJson({"val":"ONE"}); 67 | expect(message).toBeInvalid(EnumInSchema, 1); 68 | }); 69 | 70 | it("enum alias - in - valid", () => { 71 | const message = EnumAliasIn.fromJson({}); 72 | expect(message).toBeValid(EnumAliasInSchema); 73 | }); 74 | 75 | it("enum alias - in - valid (alias)", () => { 76 | const message = EnumAliasIn.fromJson({}); 77 | expect(message).toBeValid(EnumAliasInSchema); 78 | }); 79 | 80 | it("enum alias - in - invalid", () => { 81 | const message = EnumAliasIn.fromJson({"val":"BETA"}); 82 | expect(message).toBeInvalid(EnumAliasInSchema, 1); 83 | }); 84 | 85 | it("enum - not in - valid", () => { 86 | const message = EnumNotIn.fromJson({}); 87 | expect(message).toBeValid(EnumNotInSchema); 88 | }); 89 | 90 | it("enum - not in - valid (undefined)", () => { 91 | const message = EnumNotIn.fromJson({"val":2147483647}); 92 | expect(message).toBeValid(EnumNotInSchema); 93 | }); 94 | 95 | it("enum - not in - invalid", () => { 96 | const message = EnumNotIn.fromJson({"val":"ONE"}); 97 | expect(message).toBeInvalid(EnumNotInSchema, 1); 98 | }); 99 | 100 | it("enum alias - not in - valid", () => { 101 | const message = EnumAliasNotIn.fromJson({}); 102 | expect(message).toBeValid(EnumAliasNotInSchema); 103 | }); 104 | 105 | it("enum alias - not in - invalid", () => { 106 | const message = EnumAliasNotIn.fromJson({"val":"BETA"}); 107 | expect(message).toBeInvalid(EnumAliasNotInSchema, 1); 108 | }); 109 | 110 | it("enum alias - not in - invalid (alias)", () => { 111 | const message = EnumAliasNotIn.fromJson({"val":"BETA"}); 112 | expect(message).toBeInvalid(EnumAliasNotInSchema, 1); 113 | }); 114 | 115 | it("enum external - defined_only - valid", () => { 116 | const message = EnumExternal.fromJson({}); 117 | expect(message).toBeValid(EnumExternalSchema); 118 | }); 119 | 120 | it("enum external - defined_only - invalid", () => { 121 | const message = EnumExternal.fromJson({"val":2147483647}); 122 | expect(message).toBeInvalid(EnumExternalSchema, 1); 123 | }); 124 | 125 | it("enum repeated - defined_only - valid", () => { 126 | const message = RepeatedEnumDefined.fromJson({"val":["ONE","TWO"]}); 127 | expect(message).toBeValid(RepeatedEnumDefinedSchema); 128 | }); 129 | 130 | it("enum repeated - defined_only - invalid", () => { 131 | const message = RepeatedEnumDefined.fromJson({"val":["ONE",2147483647]}); 132 | expect(message).toBeInvalid(RepeatedEnumDefinedSchema, 1); 133 | }); 134 | 135 | it("enum repeated (external) - defined_only - valid", () => { 136 | const message = RepeatedExternalEnumDefined.fromJson({"val":["VALUE"]}); 137 | expect(message).toBeValid(RepeatedExternalEnumDefinedSchema); 138 | }); 139 | 140 | it("enum repeated (external) - defined_only - invalid", () => { 141 | const message = RepeatedExternalEnumDefined.fromJson({"val":[2147483647]}); 142 | expect(message).toBeInvalid(RepeatedExternalEnumDefinedSchema, 1); 143 | }); 144 | 145 | it("enum repeated (another external) - defined_only - valid", () => { 146 | const message = RepeatedYetAnotherExternalEnumDefined.fromJson({"val":["VALUE"]}); 147 | expect(message).toBeValid(RepeatedYetAnotherExternalEnumDefinedSchema); 148 | }); 149 | 150 | it("enum map - defined_only - valid", () => { 151 | const message = MapEnumDefined.fromJson({"val":{"foo":"TWO"}}); 152 | expect(message).toBeValid(MapEnumDefinedSchema); 153 | }); 154 | 155 | it("enum map - defined_only - invalid", () => { 156 | const message = MapEnumDefined.fromJson({"val":{"foo":2147483647}}); 157 | expect(message).toBeInvalid(MapEnumDefinedSchema, 1); 158 | }); 159 | 160 | it("enum map (external) - defined_only - valid", () => { 161 | const message = MapExternalEnumDefined.fromJson({"val":{"foo":"VALUE"}}); 162 | expect(message).toBeValid(MapExternalEnumDefinedSchema); 163 | }); 164 | 165 | it("enum map (external) - defined_only - invalid", () => { 166 | const message = MapExternalEnumDefined.fromJson({"val":{"foo":2147483647}}); 167 | expect(message).toBeInvalid(MapExternalEnumDefinedSchema, 1); 168 | }); 169 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/harness/cases/kitchen_sink_pb.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-es v0.2.1 with parameter "target=ts" 2 | // @generated from file tests/harness/cases/kitchen_sink.proto (package tests.harness.cases, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import type {BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage} from "@bufbuild/protobuf"; 7 | import {Any, Duration, FloatValue, Message, proto3, Timestamp} from "@bufbuild/protobuf"; 8 | 9 | /** 10 | * @generated from enum tests.harness.cases.ComplexTestEnum 11 | */ 12 | export enum ComplexTestEnum { 13 | /** 14 | * @generated from enum value: ComplexZero = 0; 15 | */ 16 | ComplexZero = 0, 17 | 18 | /** 19 | * @generated from enum value: ComplexONE = 1; 20 | */ 21 | ComplexONE = 1, 22 | 23 | /** 24 | * @generated from enum value: ComplexTWO = 2; 25 | */ 26 | ComplexTWO = 2, 27 | } 28 | // Retrieve enum metadata with: proto3.getEnumType(ComplexTestEnum) 29 | proto3.util.setEnumType(ComplexTestEnum, "tests.harness.cases.ComplexTestEnum", [ 30 | { no: 0, name: "ComplexZero" }, 31 | { no: 1, name: "ComplexONE" }, 32 | { no: 2, name: "ComplexTWO" }, 33 | ]); 34 | 35 | /** 36 | * @generated from message tests.harness.cases.ComplexTestMsg 37 | */ 38 | export class ComplexTestMsg extends Message { 39 | /** 40 | * @generated from field: string const = 1; 41 | */ 42 | const = ""; 43 | 44 | /** 45 | * @generated from field: tests.harness.cases.ComplexTestMsg nested = 2; 46 | */ 47 | nested?: ComplexTestMsg; 48 | 49 | /** 50 | * @generated from field: int32 int_const = 3; 51 | */ 52 | intConst = 0; 53 | 54 | /** 55 | * @generated from field: bool bool_const = 4; 56 | */ 57 | boolConst = false; 58 | 59 | /** 60 | * @generated from field: google.protobuf.FloatValue float_val = 5; 61 | */ 62 | floatVal?: number; 63 | 64 | /** 65 | * @generated from field: google.protobuf.Duration dur_val = 6; 66 | */ 67 | durVal?: Duration; 68 | 69 | /** 70 | * @generated from field: google.protobuf.Timestamp ts_val = 7; 71 | */ 72 | tsVal?: Timestamp; 73 | 74 | /** 75 | * @generated from field: tests.harness.cases.ComplexTestMsg another = 8; 76 | */ 77 | another?: ComplexTestMsg; 78 | 79 | /** 80 | * @generated from field: float float_const = 9; 81 | */ 82 | floatConst = 0; 83 | 84 | /** 85 | * @generated from field: double double_in = 10; 86 | */ 87 | doubleIn = 0; 88 | 89 | /** 90 | * @generated from field: tests.harness.cases.ComplexTestEnum enum_const = 11; 91 | */ 92 | enumConst = ComplexTestEnum.ComplexZero; 93 | 94 | /** 95 | * @generated from field: google.protobuf.Any any_val = 12; 96 | */ 97 | anyVal?: Any; 98 | 99 | /** 100 | * @generated from field: repeated google.protobuf.Timestamp rep_ts_val = 13; 101 | */ 102 | repTsVal: Timestamp[] = []; 103 | 104 | /** 105 | * @generated from field: map map_val = 14; 106 | */ 107 | mapVal: { [key: number]: string } = {}; 108 | 109 | /** 110 | * @generated from field: bytes bytes_val = 15; 111 | */ 112 | bytesVal = new Uint8Array(0); 113 | 114 | /** 115 | * @generated from oneof tests.harness.cases.ComplexTestMsg.o 116 | */ 117 | o: { 118 | /** 119 | * @generated from field: string x = 16; 120 | */ 121 | value: string; 122 | case: "x"; 123 | } | { 124 | /** 125 | * @generated from field: int32 y = 17; 126 | */ 127 | value: number; 128 | case: "y"; 129 | } | { case: undefined; value?: undefined } = { case: undefined }; 130 | 131 | constructor(data?: PartialMessage) { 132 | super(); 133 | proto3.util.initPartial(data, this); 134 | } 135 | 136 | static readonly runtime = proto3; 137 | static readonly typeName = "tests.harness.cases.ComplexTestMsg"; 138 | static readonly fields: FieldList = proto3.util.newFieldList(() => [ 139 | { no: 1, name: "const", kind: "scalar", T: 9 /* ScalarType.STRING */ }, 140 | { no: 2, name: "nested", kind: "message", T: ComplexTestMsg }, 141 | { no: 3, name: "int_const", kind: "scalar", T: 5 /* ScalarType.INT32 */ }, 142 | { no: 4, name: "bool_const", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, 143 | { no: 5, name: "float_val", kind: "message", T: FloatValue }, 144 | { no: 6, name: "dur_val", kind: "message", T: Duration }, 145 | { no: 7, name: "ts_val", kind: "message", T: Timestamp }, 146 | { no: 8, name: "another", kind: "message", T: ComplexTestMsg }, 147 | { no: 9, name: "float_const", kind: "scalar", T: 2 /* ScalarType.FLOAT */ }, 148 | { no: 10, name: "double_in", kind: "scalar", T: 1 /* ScalarType.DOUBLE */ }, 149 | { no: 11, name: "enum_const", kind: "enum", T: proto3.getEnumType(ComplexTestEnum) }, 150 | { no: 12, name: "any_val", kind: "message", T: Any }, 151 | { no: 13, name: "rep_ts_val", kind: "message", T: Timestamp, repeated: true }, 152 | { no: 14, name: "map_val", kind: "map", K: 17 /* ScalarType.SINT32 */, V: {kind: "scalar", T: 9 /* ScalarType.STRING */} }, 153 | { no: 15, name: "bytes_val", kind: "scalar", T: 12 /* ScalarType.BYTES */ }, 154 | { no: 16, name: "x", kind: "scalar", T: 9 /* ScalarType.STRING */, oneof: "o" }, 155 | { no: 17, name: "y", kind: "scalar", T: 5 /* ScalarType.INT32 */, oneof: "o" }, 156 | ]); 157 | 158 | static fromBinary(bytes: Uint8Array, options?: Partial): ComplexTestMsg { 159 | return new ComplexTestMsg().fromBinary(bytes, options); 160 | } 161 | 162 | static fromJson(jsonValue: JsonValue, options?: Partial): ComplexTestMsg { 163 | return new ComplexTestMsg().fromJson(jsonValue, options); 164 | } 165 | 166 | static fromJsonString(jsonString: string, options?: Partial): ComplexTestMsg { 167 | return new ComplexTestMsg().fromJsonString(jsonString, options); 168 | } 169 | 170 | static equals(a: ComplexTestMsg | PlainMessage | undefined, b: ComplexTestMsg | PlainMessage | undefined): boolean { 171 | return proto3.util.equals(ComplexTestMsg, a, b); 172 | } 173 | } 174 | 175 | /** 176 | * @generated from message tests.harness.cases.KitchenSinkMessage 177 | */ 178 | export class KitchenSinkMessage extends Message { 179 | /** 180 | * @generated from field: tests.harness.cases.ComplexTestMsg val = 1; 181 | */ 182 | val?: ComplexTestMsg; 183 | 184 | constructor(data?: PartialMessage) { 185 | super(); 186 | proto3.util.initPartial(data, this); 187 | } 188 | 189 | static readonly runtime = proto3; 190 | static readonly typeName = "tests.harness.cases.KitchenSinkMessage"; 191 | static readonly fields: FieldList = proto3.util.newFieldList(() => [ 192 | { no: 1, name: "val", kind: "message", T: ComplexTestMsg }, 193 | ]); 194 | 195 | static fromBinary(bytes: Uint8Array, options?: Partial): KitchenSinkMessage { 196 | return new KitchenSinkMessage().fromBinary(bytes, options); 197 | } 198 | 199 | static fromJson(jsonValue: JsonValue, options?: Partial): KitchenSinkMessage { 200 | return new KitchenSinkMessage().fromJson(jsonValue, options); 201 | } 202 | 203 | static fromJsonString(jsonString: string, options?: Partial): KitchenSinkMessage { 204 | return new KitchenSinkMessage().fromJsonString(jsonString, options); 205 | } 206 | 207 | static equals(a: KitchenSinkMessage | PlainMessage | undefined, b: KitchenSinkMessage | PlainMessage | undefined): boolean { 208 | return proto3.util.equals(KitchenSinkMessage, a, b); 209 | } 210 | } 211 | 212 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/harness/cases/enums_zod.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-validate-zod v0.0.2-dev with parameter "target=ts" 2 | // @generated from file tests/harness/cases/enums.proto (package tests.harness.cases, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import {z} from "zod"; 7 | import {TestEnum, TestEnumAlias} from "./enums_pb.js"; 8 | import {isIn, isNotIn, map, oneof} from "protobuf-zod"; 9 | import {Embed_DoubleEmbed_DoubleEnumeratedSchema, Embed_EnumeratedSchema} from "./other_package/embed_zod.js"; 10 | import {Embed_EnumeratedSchema as Embed_EnumeratedSchema$1} from "./yet_another_package/embed_zod.js"; 11 | 12 | /** 13 | * @generated from enum tests.harness.cases.TestEnum 14 | */ 15 | export const TestEnumSchema = z.nativeEnum(TestEnum); 16 | 17 | /** 18 | * @generated from enum tests.harness.cases.TestEnumAlias 19 | */ 20 | export const TestEnumAliasSchema = z.nativeEnum(TestEnumAlias); 21 | 22 | /** 23 | * @generated from message tests.harness.cases.EnumNone 24 | */ 25 | export const EnumNoneSchema = z.object({ 26 | /** 27 | * @generated from field: tests.harness.cases.TestEnum val = 1; 28 | */ 29 | val: z.lazy(() => TestEnumSchema), 30 | }); 31 | 32 | /** 33 | * @generated from message tests.harness.cases.EnumConst 34 | */ 35 | export const EnumConstSchema = z.object({ 36 | /** 37 | * @generated from field: tests.harness.cases.TestEnum val = 1; 38 | * @validate {"enum":{"const":2}} 39 | */ 40 | val: z.lazy(() => TestEnumSchema).and(z.literal(2)), 41 | }); 42 | 43 | /** 44 | * @generated from message tests.harness.cases.EnumAliasConst 45 | */ 46 | export const EnumAliasConstSchema = z.object({ 47 | /** 48 | * @generated from field: tests.harness.cases.TestEnumAlias val = 1; 49 | * @validate {"enum":{"const":2}} 50 | */ 51 | val: z.lazy(() => TestEnumAliasSchema).and(z.literal(2)), 52 | }); 53 | 54 | /** 55 | * @generated from message tests.harness.cases.EnumDefined 56 | */ 57 | export const EnumDefinedSchema = z.object({ 58 | /** 59 | * @generated from field: tests.harness.cases.TestEnum val = 1; 60 | * @validate {"enum":{"definedOnly":true}} 61 | */ 62 | val: z.lazy(() => TestEnumSchema), 63 | }); 64 | 65 | /** 66 | * @generated from message tests.harness.cases.EnumAliasDefined 67 | */ 68 | export const EnumAliasDefinedSchema = z.object({ 69 | /** 70 | * @generated from field: tests.harness.cases.TestEnumAlias val = 1; 71 | * @validate {"enum":{"definedOnly":true}} 72 | */ 73 | val: z.lazy(() => TestEnumAliasSchema), 74 | }); 75 | 76 | /** 77 | * @generated from message tests.harness.cases.EnumIn 78 | */ 79 | export const EnumInSchema = z.object({ 80 | /** 81 | * @generated from field: tests.harness.cases.TestEnum val = 1; 82 | * @validate {"enum":{"in":[0,2]}} 83 | */ 84 | val: z.lazy(() => TestEnumSchema).refine(isIn([ 85 | 0, 86 | 2, 87 | ])), 88 | }); 89 | 90 | /** 91 | * @generated from message tests.harness.cases.EnumAliasIn 92 | */ 93 | export const EnumAliasInSchema = z.object({ 94 | /** 95 | * @generated from field: tests.harness.cases.TestEnumAlias val = 1; 96 | * @validate {"enum":{"in":[0,2]}} 97 | */ 98 | val: z.lazy(() => TestEnumAliasSchema).refine(isIn([ 99 | 0, 100 | 2, 101 | ])), 102 | }); 103 | 104 | /** 105 | * @generated from message tests.harness.cases.EnumNotIn 106 | */ 107 | export const EnumNotInSchema = z.object({ 108 | /** 109 | * @generated from field: tests.harness.cases.TestEnum val = 1; 110 | * @validate {"enum":{"notIn":[1]}} 111 | */ 112 | val: z.lazy(() => TestEnumSchema).refine(isNotIn([ 113 | 1, 114 | ])), 115 | }); 116 | 117 | /** 118 | * @generated from message tests.harness.cases.EnumAliasNotIn 119 | */ 120 | export const EnumAliasNotInSchema = z.object({ 121 | /** 122 | * @generated from field: tests.harness.cases.TestEnumAlias val = 1; 123 | * @validate {"enum":{"notIn":[1]}} 124 | */ 125 | val: z.lazy(() => TestEnumAliasSchema).refine(isNotIn([ 126 | 1, 127 | ])), 128 | }); 129 | 130 | /** 131 | * @generated from message tests.harness.cases.EnumExternal 132 | */ 133 | export const EnumExternalSchema = z.object({ 134 | /** 135 | * @generated from field: tests.harness.cases.other_package.Embed.Enumerated val = 1; 136 | * @validate {"enum":{"definedOnly":true}} 137 | */ 138 | val: z.lazy(() => Embed_EnumeratedSchema), 139 | }); 140 | 141 | /** 142 | * @generated from message tests.harness.cases.EnumExternal2 143 | */ 144 | export const EnumExternal2Schema = z.object({ 145 | /** 146 | * @generated from field: tests.harness.cases.other_package.Embed.DoubleEmbed.DoubleEnumerated val = 1; 147 | * @validate {"enum":{"definedOnly":true}} 148 | */ 149 | val: z.lazy(() => Embed_DoubleEmbed_DoubleEnumeratedSchema), 150 | }); 151 | 152 | /** 153 | * @generated from message tests.harness.cases.RepeatedEnumDefined 154 | */ 155 | export const RepeatedEnumDefinedSchema = z.object({ 156 | /** 157 | * @generated from field: repeated tests.harness.cases.TestEnum val = 1; 158 | * @validate {"repeated":{"items":{"enum":{"definedOnly":true}}}} 159 | */ 160 | val: z.lazy(() => TestEnumSchema).array(), 161 | }); 162 | 163 | /** 164 | * @generated from message tests.harness.cases.RepeatedExternalEnumDefined 165 | */ 166 | export const RepeatedExternalEnumDefinedSchema = z.object({ 167 | /** 168 | * @generated from field: repeated tests.harness.cases.other_package.Embed.Enumerated val = 1; 169 | * @validate {"repeated":{"items":{"enum":{"definedOnly":true}}}} 170 | */ 171 | val: z.lazy(() => Embed_EnumeratedSchema).array(), 172 | }); 173 | 174 | /** 175 | * @generated from message tests.harness.cases.RepeatedYetAnotherExternalEnumDefined 176 | */ 177 | export const RepeatedYetAnotherExternalEnumDefinedSchema = z.object({ 178 | /** 179 | * @generated from field: repeated tests.harness.cases.yet_another_package.Embed.Enumerated val = 1; 180 | * @validate {"repeated":{"items":{"enum":{"definedOnly":true}}}} 181 | */ 182 | val: z.lazy(() => Embed_EnumeratedSchema$1).array(), 183 | }); 184 | 185 | /** 186 | * @generated from message tests.harness.cases.MapEnumDefined 187 | */ 188 | export const MapEnumDefinedSchema = z.object({ 189 | /** 190 | * @generated from field: map val = 1; 191 | * @validate {"map":{"values":{"enum":{"definedOnly":true}}}} 192 | */ 193 | val: map(z.string(), z.lazy(() => TestEnumSchema)), 194 | }); 195 | 196 | /** 197 | * @generated from message tests.harness.cases.MapExternalEnumDefined 198 | */ 199 | export const MapExternalEnumDefinedSchema = z.object({ 200 | /** 201 | * @generated from field: map val = 1; 202 | * @validate {"map":{"values":{"enum":{"definedOnly":true}}}} 203 | */ 204 | val: map(z.string(), z.lazy(() => Embed_EnumeratedSchema)), 205 | }); 206 | 207 | /** 208 | * @generated from message tests.harness.cases.EnumInsideOneOf 209 | */ 210 | export const EnumInsideOneOfSchema = z.object({ 211 | /** 212 | * @generated from oneof tests.harness.cases.EnumInsideOneOf.foo 213 | */ 214 | foo: oneof([ 215 | z.object({ 216 | case: z.literal(undefined), 217 | value: z.literal(undefined).nullish(), 218 | }), 219 | z.object({ 220 | /** 221 | * @generated from field: tests.harness.cases.TestEnum val = 1; 222 | * @validate {"enum":{"definedOnly":true}} 223 | */ 224 | case: z.literal("val"), 225 | value: z.lazy(() => TestEnumSchema), 226 | }), 227 | ]).nullish(), 228 | /** 229 | * @generated from oneof tests.harness.cases.EnumInsideOneOf.bar 230 | */ 231 | bar: oneof([ 232 | z.object({ 233 | case: z.literal(undefined), 234 | value: z.literal(undefined).nullish(), 235 | }), 236 | z.object({ 237 | /** 238 | * @generated from field: tests.harness.cases.TestEnum val2 = 2; 239 | * @validate {"enum":{"definedOnly":true,"notIn":[0]}} 240 | */ 241 | case: z.literal("val2"), 242 | value: z.lazy(() => TestEnumSchema).refine(isNotIn([ 243 | 0, 244 | ])), 245 | }), 246 | ]).nullish(), 247 | }); 248 | 249 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/harness/cases/wkt_wrappers.test.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-validate-zod-test v0.0.2-dev with parameter "target=ts,cases=packages/protoc-gen-validate-zod/tests/cases.txt,descriptor=vendor/cases.bin" 2 | // @generated from file tests/harness/cases/wkt_wrappers.proto (package tests.harness.cases, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import {expect, it} from "vitest"; 7 | import {WrapperBool, WrapperBytes, WrapperDouble, WrapperFloat, WrapperInt32, WrapperInt64, WrapperNone, WrapperOptionalUuidString, WrapperRequiredEmptyString, WrapperRequiredFloat, WrapperRequiredString, WrapperString, WrapperUInt32, WrapperUInt64} from "./wkt_wrappers_pb.js"; 8 | import {WrapperBoolSchema, WrapperBytesSchema, WrapperDoubleSchema, WrapperFloatSchema, WrapperInt32Schema, WrapperInt64Schema, WrapperNoneSchema, WrapperOptionalUuidStringSchema, WrapperRequiredEmptyStringSchema, WrapperRequiredFloatSchema, WrapperRequiredStringSchema, WrapperStringSchema, WrapperUInt32Schema, WrapperUInt64Schema} from "./wkt_wrappers_zod.js"; 9 | 10 | it("wrapper - none - valid", () => { 11 | const message = WrapperNone.fromJson({"val":123}); 12 | expect(message).toBeValid(WrapperNoneSchema); 13 | }); 14 | 15 | it("wrapper - none - valid (empty)", () => { 16 | const message = WrapperNone.fromJson({}); 17 | expect(message).toBeValid(WrapperNoneSchema); 18 | }); 19 | 20 | it("wrapper - float - valid", () => { 21 | const message = WrapperFloat.fromJson({"val":1}); 22 | expect(message).toBeValid(WrapperFloatSchema); 23 | }); 24 | 25 | it("wrapper - float - valid (empty)", () => { 26 | const message = WrapperFloat.fromJson({}); 27 | expect(message).toBeValid(WrapperFloatSchema); 28 | }); 29 | 30 | it("wrapper - float - invalid", () => { 31 | const message = WrapperFloat.fromJson({"val":0}); 32 | expect(message).toBeInvalid(WrapperFloatSchema, 1); 33 | }); 34 | 35 | it("wrapper - double - valid", () => { 36 | const message = WrapperDouble.fromJson({"val":1}); 37 | expect(message).toBeValid(WrapperDoubleSchema); 38 | }); 39 | 40 | it("wrapper - double - valid (empty)", () => { 41 | const message = WrapperDouble.fromJson({}); 42 | expect(message).toBeValid(WrapperDoubleSchema); 43 | }); 44 | 45 | it("wrapper - double - invalid", () => { 46 | const message = WrapperDouble.fromJson({"val":0}); 47 | expect(message).toBeInvalid(WrapperDoubleSchema, 1); 48 | }); 49 | 50 | it("wrapper - int64 - valid", () => { 51 | const message = WrapperInt64.fromJson({"val":"1"}); 52 | expect(message).toBeValid(WrapperInt64Schema); 53 | }); 54 | 55 | it("wrapper - int64 - valid (empty)", () => { 56 | const message = WrapperInt64.fromJson({}); 57 | expect(message).toBeValid(WrapperInt64Schema); 58 | }); 59 | 60 | it("wrapper - int64 - invalid", () => { 61 | const message = WrapperInt64.fromJson({"val":"0"}); 62 | expect(message).toBeInvalid(WrapperInt64Schema, 1); 63 | }); 64 | 65 | it("wrapper - int32 - valid", () => { 66 | const message = WrapperInt32.fromJson({"val":1}); 67 | expect(message).toBeValid(WrapperInt32Schema); 68 | }); 69 | 70 | it("wrapper - int32 - valid (empty)", () => { 71 | const message = WrapperInt32.fromJson({}); 72 | expect(message).toBeValid(WrapperInt32Schema); 73 | }); 74 | 75 | it("wrapper - int32 - invalid", () => { 76 | const message = WrapperInt32.fromJson({"val":0}); 77 | expect(message).toBeInvalid(WrapperInt32Schema, 1); 78 | }); 79 | 80 | it("wrapper - uint64 - valid", () => { 81 | const message = WrapperUInt64.fromJson({"val":"1"}); 82 | expect(message).toBeValid(WrapperUInt64Schema); 83 | }); 84 | 85 | it("wrapper - uint64 - valid (empty)", () => { 86 | const message = WrapperUInt64.fromJson({}); 87 | expect(message).toBeValid(WrapperUInt64Schema); 88 | }); 89 | 90 | it("wrapper - uint64 - invalid", () => { 91 | const message = WrapperUInt64.fromJson({"val":"0"}); 92 | expect(message).toBeInvalid(WrapperUInt64Schema, 1); 93 | }); 94 | 95 | it("wrapper - uint32 - valid", () => { 96 | const message = WrapperUInt32.fromJson({"val":1}); 97 | expect(message).toBeValid(WrapperUInt32Schema); 98 | }); 99 | 100 | it("wrapper - uint32 - valid (empty)", () => { 101 | const message = WrapperUInt32.fromJson({}); 102 | expect(message).toBeValid(WrapperUInt32Schema); 103 | }); 104 | 105 | it("wrapper - uint32 - invalid", () => { 106 | const message = WrapperUInt32.fromJson({"val":0}); 107 | expect(message).toBeInvalid(WrapperUInt32Schema, 1); 108 | }); 109 | 110 | it("wrapper - bool - valid", () => { 111 | const message = WrapperBool.fromJson({"val":true}); 112 | expect(message).toBeValid(WrapperBoolSchema); 113 | }); 114 | 115 | it("wrapper - bool - valid (empty)", () => { 116 | const message = WrapperBool.fromJson({}); 117 | expect(message).toBeValid(WrapperBoolSchema); 118 | }); 119 | 120 | it("wrapper - bool - invalid", () => { 121 | const message = WrapperBool.fromJson({"val":false}); 122 | expect(message).toBeInvalid(WrapperBoolSchema, 1); 123 | }); 124 | 125 | it("wrapper - string - valid", () => { 126 | const message = WrapperString.fromJson({"val":"foobar"}); 127 | expect(message).toBeValid(WrapperStringSchema); 128 | }); 129 | 130 | it("wrapper - string - valid (empty)", () => { 131 | const message = WrapperString.fromJson({}); 132 | expect(message).toBeValid(WrapperStringSchema); 133 | }); 134 | 135 | it("wrapper - string - invalid", () => { 136 | const message = WrapperString.fromJson({"val":"fizzbuzz"}); 137 | expect(message).toBeInvalid(WrapperStringSchema, 1); 138 | }); 139 | 140 | it("wrapper - bytes - valid", () => { 141 | const message = WrapperBytes.fromJson({"val":"Zm9v"}); 142 | expect(message).toBeValid(WrapperBytesSchema); 143 | }); 144 | 145 | it("wrapper - bytes - valid (empty)", () => { 146 | const message = WrapperBytes.fromJson({}); 147 | expect(message).toBeValid(WrapperBytesSchema); 148 | }); 149 | 150 | it("wrapper - bytes - invalid", () => { 151 | const message = WrapperBytes.fromJson({"val":"eA=="}); 152 | expect(message).toBeInvalid(WrapperBytesSchema, 1); 153 | }); 154 | 155 | it("wrapper - required - string - valid", () => { 156 | const message = WrapperRequiredString.fromJson({"val":"bar"}); 157 | expect(message).toBeValid(WrapperRequiredStringSchema); 158 | }); 159 | 160 | it("wrapper - required - string - invalid", () => { 161 | const message = WrapperRequiredString.fromJson({"val":"foo"}); 162 | expect(message).toBeInvalid(WrapperRequiredStringSchema, 1); 163 | }); 164 | 165 | it("wrapper - required - string - invalid (empty)", () => { 166 | const message = WrapperRequiredString.fromJson({}); 167 | expect(message).toBeInvalid(WrapperRequiredStringSchema, 1); 168 | }); 169 | 170 | it("wrapper - required - string (empty) - valid", () => { 171 | const message = WrapperRequiredEmptyString.fromJson({"val":""}); 172 | expect(message).toBeValid(WrapperRequiredEmptyStringSchema); 173 | }); 174 | 175 | it("wrapper - required - string (empty) - invalid", () => { 176 | const message = WrapperRequiredEmptyString.fromJson({"val":"foo"}); 177 | expect(message).toBeInvalid(WrapperRequiredEmptyStringSchema, 1); 178 | }); 179 | 180 | it("wrapper - required - string (empty) - invalid (empty)", () => { 181 | const message = WrapperRequiredEmptyString.fromJson({}); 182 | expect(message).toBeInvalid(WrapperRequiredEmptyStringSchema, 1); 183 | }); 184 | 185 | it("wrapper - optional - string (uuid) - valid", () => { 186 | const message = WrapperOptionalUuidString.fromJson({"val":"8b72987b-024a-43b3-b4cf-647a1f925c5d"}); 187 | expect(message).toBeValid(WrapperOptionalUuidStringSchema); 188 | }); 189 | 190 | it("wrapper - optional - string (uuid) - valid (empty)", () => { 191 | const message = WrapperOptionalUuidString.fromJson({}); 192 | expect(message).toBeValid(WrapperOptionalUuidStringSchema); 193 | }); 194 | 195 | it("wrapper - optional - string (uuid) - invalid", () => { 196 | const message = WrapperOptionalUuidString.fromJson({"val":"foo"}); 197 | expect(message).toBeInvalid(WrapperOptionalUuidStringSchema, 1); 198 | }); 199 | 200 | it("wrapper - required - float - valid", () => { 201 | const message = WrapperRequiredFloat.fromJson({"val":1}); 202 | expect(message).toBeValid(WrapperRequiredFloatSchema); 203 | }); 204 | 205 | it("wrapper - required - float - invalid", () => { 206 | const message = WrapperRequiredFloat.fromJson({"val":-5}); 207 | expect(message).toBeInvalid(WrapperRequiredFloatSchema, 1); 208 | }); 209 | 210 | it("wrapper - required - float - invalid (empty)", () => { 211 | const message = WrapperRequiredFloat.fromJson({}); 212 | expect(message).toBeInvalid(WrapperRequiredFloatSchema, 1); 213 | }); 214 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/src/utils.ts: -------------------------------------------------------------------------------- 1 | import type { DescEnum, DescField, DescFile, DescMessage } from "@bufbuild/protobuf"; 2 | import { ScalarType } from "@bufbuild/protobuf"; 3 | import { 4 | GeneratedFile as GeneratedFileBase, 5 | ImportSymbol, 6 | literalString, 7 | Printable, 8 | Schema, 9 | } from "@bufbuild/protoplugin/ecmascript"; 10 | import { localName, createJsDocBlock, makeJsDoc } from "@bufbuild/protoplugin/ecmascript"; 11 | import { FieldRules } from "./generated/validate_pb.js"; 12 | 13 | type NumberOrBigint = number | bigint; 14 | 15 | type RuntimeExports = typeof import("protobuf-zod"); 16 | type RuntimeExportsKeys = keyof RuntimeExports; 17 | type RuntimeRefinementExports = { 18 | // eslint-disable-next-line no-unused-vars 19 | [TKey in RuntimeExportsKeys]: RuntimeExports[TKey] extends (...args: any) => (...args: any) => boolean ? TKey : never; 20 | }[RuntimeExportsKeys]; 21 | 22 | export function makeJsDocWithRules(desc: DescField, rules?: FieldRules, disabled = false, indentation = ""): Printable { 23 | const docs = makeJsDoc(desc, indentation) as Printable[]; 24 | if (!rules) { 25 | return docs; 26 | } 27 | 28 | const annotation = `@validate ${disabled ? "(disabled) " : ""} ${rules.toJsonString()}`; 29 | const suffix = createJsDocBlock(annotation, indentation) as Printable[]; 30 | return [...docs.slice(0, -1), ...suffix.slice(1)]; 31 | } 32 | 33 | export function createFile(schema: Schema, file: DescFile): GeneratedFile { 34 | const f = schema.generateFile(`${file.name}_zod.ts`) as GeneratedFile; 35 | Object.assign(f, createGeneratorUtils(f)); 36 | 37 | return f; 38 | } 39 | 40 | export type GeneratedFile = GeneratedFileBase & ReturnType; 41 | 42 | export function createGeneratorUtils(f: GeneratedFileBase) { 43 | const zod = f.import("z", "zod"); 44 | 45 | const args = (values: Printable[]): Printable => values.flatMap((value) => [value, ", "]).slice(0, -1); 46 | const union = (values: Printable[]): Printable => [zod, ".union(", array(values), ")"]; 47 | const lazy = (symbol: ImportSymbol): Printable => [zod, ".lazy(() => ", symbol, ")"]; 48 | const and = (values: Printable): Printable => [".and(", values, ")"]; 49 | const or = (values: Printable): Printable => [".or(", values, ")"]; 50 | 51 | const literal = (value: Printable | undefined | null): Printable => { 52 | const thing = 53 | typeof value === "string" ? literalString(value) : value === undefined || value === null ? `${value}` : value; 54 | 55 | return [zod, ".literal(", thing, ")"]; 56 | }; 57 | 58 | const array = (value: Printable, indentation = " "): Printable => { 59 | const array = Array.isArray(value) ? value : [value]; 60 | const items = array.flatMap((value) => [indentation, " ", value, ",\n"]); 61 | return ["[\n", items, indentation, "]"]; 62 | }; 63 | 64 | // TODO: We might just want to throw an error if we encounter a regexp that is not valid in JavaScript. 65 | const regexp = (pattern: string): Printable => { 66 | try { 67 | new RegExp(pattern); 68 | 69 | return ["new RegExp(", literalString(pattern), ")"]; 70 | } catch { 71 | return ["new RegExp(", literalString("invalid regular expression^"), ")"]; 72 | } 73 | }; 74 | 75 | const runtime = (name: RuntimeExportsKeys) => f.import(name, "protobuf-zod"); 76 | const refine = ( 77 | fn: TExport, 78 | args?: Printable, 79 | message?: string 80 | ): Printable => { 81 | const refinement: Printable[] = [runtime(fn), "(", ...(args === undefined ? [] : [args]), ")"]; 82 | 83 | if (message !== undefined) { 84 | refinement.push([", ", literalString(message)]); 85 | } 86 | 87 | return [".refine(", refinement, ")"]; 88 | }; 89 | 90 | return { 91 | zod, 92 | lazy, 93 | and, 94 | or, 95 | literal, 96 | union, 97 | array, 98 | runtime, 99 | oneof: (cases: Printable[]) => [runtime("oneof"), "(", array(cases), ")"], 100 | transform: (value: Printable) => [runtime("transform"), "(", value, ")"], 101 | reference: (desc: DescEnum | DescMessage) => { 102 | // NOTE: This should ideally use a registry based on `f.export`, etc. but that seems to not work reliably accross package boundaries. 103 | const name = `${localName(desc)}Schema`; 104 | return f.import(name, `./${desc.file.name}_zod.js`); 105 | }, 106 | scalars: { 107 | [ScalarType.STRING]: [zod, ".string()"], 108 | [ScalarType.BOOL]: [zod, ".boolean()"], 109 | [ScalarType.BYTES]: runtime("bytes"), 110 | [ScalarType.UINT64]: runtime("uint64"), 111 | [ScalarType.INT64]: runtime("int64"), 112 | [ScalarType.UINT32]: runtime("uint32"), 113 | [ScalarType.INT32]: runtime("int32"), 114 | [ScalarType.FLOAT]: runtime("float"), 115 | [ScalarType.DOUBLE]: runtime("double"), 116 | [ScalarType.SINT32]: runtime("sint32"), 117 | [ScalarType.SFIXED32]: runtime("sfixed32"), 118 | [ScalarType.FIXED32]: runtime("fixed32"), 119 | [ScalarType.SINT64]: runtime("sint64"), 120 | [ScalarType.SFIXED64]: runtime("sfixed64"), 121 | [ScalarType.FIXED64]: runtime("fixed64"), 122 | }, 123 | validate: { 124 | bytesMatches: (pattern: string) => refine("bytesMatches", regexp(pattern)), 125 | bytesEquals: (value: Uint8Array) => refine("bytesEquals", value), 126 | bytesContains: (value: Uint8Array) => refine("bytesContains", value), 127 | bytesStartsWith: (value: Uint8Array) => refine("bytesStartsWith", value), 128 | bytesEndsWith: (value: Uint8Array) => refine("bytesEndsWith", value), 129 | bytesLength: (value: NumberOrBigint) => refine("bytesLength", Number(value)), 130 | bytesMaxLength: (value: NumberOrBigint) => refine("bytesMaxLength", Number(value)), 131 | bytesMinLength: (value: NumberOrBigint) => refine("bytesMinLength", Number(value)), 132 | bytesIsIn: (values: Uint8Array[]) => refine("bytesIsIn", array(values)), 133 | bytesIsNotIn: (values: Uint8Array[]) => refine("bytesIsNotIn", array(values)), 134 | bytesIsIp: (version?: 4 | 6) => refine("bytesIsIp", version), 135 | isConst: (value: Printable) => and(literal(value)), 136 | isUniqueList: () => refine("isUniqueList"), 137 | isIn: (values: Printable[]) => refine("isIn", array(values)), 138 | isNotIn: (values: Printable[]) => refine("isNotIn", array(values)), 139 | stringContains: (value: string) => refine("stringContains", literalString(value)), 140 | stringNotContains: (value: string) => refine("stringNotContains", literalString(value)), 141 | stringIsEmail: () => refine("stringIsEmail"), 142 | stringIsHostname: () => refine("stringIsHostname"), 143 | stringIsAddress: () => refine("stringIsAddress"), 144 | stringIsUrl: (absolute = true) => refine("stringIsUrl", absolute), 145 | stringIsUuid: () => refine("stringIsUuid"), 146 | stringIsIp: (version?: 4 | 6) => refine("stringIsIp", version), 147 | stringIsHttpHeaderName: (strict = true) => refine("stringIsHttpHeaderName", strict), 148 | stringIsHttpHeaderValue: (strict = true) => refine("stringIsHttpHeaderValue", strict), 149 | stringMatches: (pattern: string) => [".regex(", regexp(pattern), ")"], 150 | numberGt: (value: NumberOrBigint) => refine("numberGt", value), 151 | numberGte: (value: NumberOrBigint) => refine("numberGte", value), 152 | numberLt: (value: NumberOrBigint) => refine("numberLt", value), 153 | numberLte: (value: NumberOrBigint) => refine("numberLte", value), 154 | numberInsideGtLt: (gt: NumberOrBigint, lt: NumberOrBigint) => refine("numberInsideGtLt", args([gt, lt])), 155 | numberOutsideGtLt: (gt: NumberOrBigint, lt: NumberOrBigint) => refine("numberOutsideGtLt", args([gt, lt])), 156 | numberInsideGteLt: (gte: NumberOrBigint, lt: NumberOrBigint) => refine("numberInsideGteLt", args([gte, lt])), 157 | numberOutsideGteLt: (gte: NumberOrBigint, lt: NumberOrBigint) => refine("numberOutsideGteLt", args([gte, lt])), 158 | numberInsideGtLte: (gt: NumberOrBigint, lte: NumberOrBigint) => refine("numberInsideGtLte", args([gt, lte])), 159 | numberOutsideGtLte: (gt: NumberOrBigint, lte: NumberOrBigint) => refine("numberOutsideGtLte", args([gt, lte])), 160 | numberInsideGteLte: (gte: NumberOrBigint, lte: NumberOrBigint) => refine("numberInsideGteLte", args([gte, lte])), 161 | numberOutsideGteLte: (gte: NumberOrBigint, lte: NumberOrBigint) => 162 | refine("numberOutsideGteLte", args([gte, lte])), 163 | floatEquals: (value: number) => refine("floatEquals", value), 164 | floatIsIn: (list: number[]) => refine("floatIsIn", array(list)), 165 | floatIsNotIn: (list: number[]) => refine("floatIsNotIn", array(list)), 166 | }, 167 | }; 168 | } 169 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/harness/cases/bytes.test.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-validate-zod-test v0.0.2-dev with parameter "target=ts,cases=packages/protoc-gen-validate-zod/tests/cases.txt,descriptor=vendor/cases.bin" 2 | // @generated from file tests/harness/cases/bytes.proto (package tests.harness.cases, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import {expect, it} from "vitest"; 7 | import {BytesConst, BytesContains, BytesEqualMinMaxLen, BytesIn, BytesIP, BytesIPv4, BytesIPv6, BytesIPv6Ignore, BytesLen, BytesMaxLen, BytesMinLen, BytesMinMaxLen, BytesNone, BytesNotIn, BytesPattern, BytesPrefix, BytesSuffix} from "./bytes_pb.js"; 8 | import {BytesConstSchema, BytesContainsSchema, BytesEqualMinMaxLenSchema, BytesInSchema, BytesIPSchema, BytesIPv4Schema, BytesIPv6IgnoreSchema, BytesIPv6Schema, BytesLenSchema, BytesMaxLenSchema, BytesMinLenSchema, BytesMinMaxLenSchema, BytesNoneSchema, BytesNotInSchema, BytesPatternSchema, BytesPrefixSchema, BytesSuffixSchema} from "./bytes_zod.js"; 9 | 10 | it("bytes - none - valid", () => { 11 | const message = BytesNone.fromJson({"val":"cXV1eA=="}); 12 | expect(message).toBeValid(BytesNoneSchema); 13 | }); 14 | 15 | it("bytes - const - valid", () => { 16 | const message = BytesConst.fromJson({"val":"Zm9v"}); 17 | expect(message).toBeValid(BytesConstSchema); 18 | }); 19 | 20 | it("bytes - const - invalid", () => { 21 | const message = BytesConst.fromJson({"val":"YmFy"}); 22 | expect(message).toBeInvalid(BytesConstSchema, 1); 23 | }); 24 | 25 | it("bytes - in - valid", () => { 26 | const message = BytesIn.fromJson({"val":"YmFy"}); 27 | expect(message).toBeValid(BytesInSchema); 28 | }); 29 | 30 | it("bytes - in - invalid", () => { 31 | const message = BytesIn.fromJson({"val":"cXV1eA=="}); 32 | expect(message).toBeInvalid(BytesInSchema, 1); 33 | }); 34 | 35 | it("bytes - not in - valid", () => { 36 | const message = BytesNotIn.fromJson({"val":"cXV1eA=="}); 37 | expect(message).toBeValid(BytesNotInSchema); 38 | }); 39 | 40 | it("bytes - not in - invalid", () => { 41 | const message = BytesNotIn.fromJson({"val":"Zml6eg=="}); 42 | expect(message).toBeInvalid(BytesNotInSchema, 1); 43 | }); 44 | 45 | it("bytes - len - valid", () => { 46 | const message = BytesLen.fromJson({"val":"YmF6"}); 47 | expect(message).toBeValid(BytesLenSchema); 48 | }); 49 | 50 | it("bytes - len - invalid (lt)", () => { 51 | const message = BytesLen.fromJson({"val":"Z28="}); 52 | expect(message).toBeInvalid(BytesLenSchema, 1); 53 | }); 54 | 55 | it("bytes - len - invalid (gt)", () => { 56 | const message = BytesLen.fromJson({"val":"Zml6eg=="}); 57 | expect(message).toBeInvalid(BytesLenSchema, 1); 58 | }); 59 | 60 | it("bytes - min len - valid", () => { 61 | const message = BytesMinLen.fromJson({"val":"Zml6eg=="}); 62 | expect(message).toBeValid(BytesMinLenSchema); 63 | }); 64 | 65 | it("bytes - min len - valid (min)", () => { 66 | const message = BytesMinLen.fromJson({"val":"YmF6"}); 67 | expect(message).toBeValid(BytesMinLenSchema); 68 | }); 69 | 70 | it("bytes - min len - invalid", () => { 71 | const message = BytesMinLen.fromJson({"val":"Z28="}); 72 | expect(message).toBeInvalid(BytesMinLenSchema, 1); 73 | }); 74 | 75 | it("bytes - max len - valid", () => { 76 | const message = BytesMaxLen.fromJson({"val":"Zm9v"}); 77 | expect(message).toBeValid(BytesMaxLenSchema); 78 | }); 79 | 80 | it("bytes - max len - valid (max)", () => { 81 | const message = BytesMaxLen.fromJson({"val":"cHJvdG8="}); 82 | expect(message).toBeValid(BytesMaxLenSchema); 83 | }); 84 | 85 | it("bytes - max len - invalid", () => { 86 | const message = BytesMaxLen.fromJson({"val":"MTIzNDU2Nzg5MA=="}); 87 | expect(message).toBeInvalid(BytesMaxLenSchema, 1); 88 | }); 89 | 90 | it("bytes - min/max len - valid", () => { 91 | const message = BytesMinMaxLen.fromJson({"val":"cXV1eA=="}); 92 | expect(message).toBeValid(BytesMinMaxLenSchema); 93 | }); 94 | 95 | it("bytes - min/max len - valid (min)", () => { 96 | const message = BytesMinMaxLen.fromJson({"val":"Zm9v"}); 97 | expect(message).toBeValid(BytesMinMaxLenSchema); 98 | }); 99 | 100 | it("bytes - min/max len - valid (max)", () => { 101 | const message = BytesMinMaxLen.fromJson({"val":"cHJvdG8="}); 102 | expect(message).toBeValid(BytesMinMaxLenSchema); 103 | }); 104 | 105 | it("bytes - min/max len - invalid (below)", () => { 106 | const message = BytesMinMaxLen.fromJson({"val":"Z28="}); 107 | expect(message).toBeInvalid(BytesMinMaxLenSchema, 1); 108 | }); 109 | 110 | it("bytes - min/max len - invalid (above)", () => { 111 | const message = BytesMinMaxLen.fromJson({"val":"dmFsaWRhdGU="}); 112 | expect(message).toBeInvalid(BytesMinMaxLenSchema, 1); 113 | }); 114 | 115 | it("bytes - equal min/max len - valid", () => { 116 | const message = BytesEqualMinMaxLen.fromJson({"val":"cHJvdG8="}); 117 | expect(message).toBeValid(BytesEqualMinMaxLenSchema); 118 | }); 119 | 120 | it("bytes - equal min/max len - invalid", () => { 121 | const message = BytesEqualMinMaxLen.fromJson({"val":"dmFsaWRhdGU="}); 122 | expect(message).toBeInvalid(BytesEqualMinMaxLenSchema, 1); 123 | }); 124 | 125 | it("bytes - pattern - valid", () => { 126 | const message = BytesPattern.fromJson({"val":"Rm9vMTIz"}); 127 | expect(message).toBeValid(BytesPatternSchema); 128 | }); 129 | 130 | it("bytes - pattern - invalid", () => { 131 | const message = BytesPattern.fromJson({"val":"5L2g5aW95L2g5aW9"}); 132 | expect(message).toBeInvalid(BytesPatternSchema, 1); 133 | }); 134 | 135 | it("bytes - pattern - invalid (empty)", () => { 136 | const message = BytesPattern.fromJson({}); 137 | expect(message).toBeInvalid(BytesPatternSchema, 1); 138 | }); 139 | 140 | it("bytes - prefix - valid", () => { 141 | const message = BytesPrefix.fromJson({"val":"mZ8I"}); 142 | expect(message).toBeValid(BytesPrefixSchema); 143 | }); 144 | 145 | it("bytes - prefix - valid (only)", () => { 146 | const message = BytesPrefix.fromJson({"val":"mQ=="}); 147 | expect(message).toBeValid(BytesPrefixSchema); 148 | }); 149 | 150 | it("bytes - prefix - invalid", () => { 151 | const message = BytesPrefix.fromJson({"val":"YmFy"}); 152 | expect(message).toBeInvalid(BytesPrefixSchema, 1); 153 | }); 154 | 155 | it("bytes - contains - valid", () => { 156 | const message = BytesContains.fromJson({"val":"Y2FuZHkgYmFycw=="}); 157 | expect(message).toBeValid(BytesContainsSchema); 158 | }); 159 | 160 | it("bytes - contains - valid (only)", () => { 161 | const message = BytesContains.fromJson({"val":"YmFy"}); 162 | expect(message).toBeValid(BytesContainsSchema); 163 | }); 164 | 165 | it("bytes - contains - invalid", () => { 166 | const message = BytesContains.fromJson({"val":"Y2FuZHkgYmF6cw=="}); 167 | expect(message).toBeInvalid(BytesContainsSchema, 1); 168 | }); 169 | 170 | it("bytes - suffix - valid", () => { 171 | const message = BytesSuffix.fromJson({"val":"YnV6eg=="}); 172 | expect(message).toBeValid(BytesSuffixSchema); 173 | }); 174 | 175 | it("bytes - suffix - valid (only)", () => { 176 | const message = BytesSuffix.fromJson({"val":"YnV6eg=="}); 177 | expect(message).toBeValid(BytesSuffixSchema); 178 | }); 179 | 180 | it("bytes - suffix - invalid", () => { 181 | const message = BytesSuffix.fromJson({"val":"Zm9vYmFy"}); 182 | expect(message).toBeInvalid(BytesSuffixSchema, 1); 183 | }); 184 | 185 | it("bytes - suffix - invalid (case-sensitive)", () => { 186 | const message = BytesSuffix.fromJson({"val":"Rm9vQmF6"}); 187 | expect(message).toBeInvalid(BytesSuffixSchema, 1); 188 | }); 189 | 190 | it("bytes - IP - valid (v4)", () => { 191 | const message = BytesIP.fromJson({"val":"wKgAAQ=="}); 192 | expect(message).toBeValid(BytesIPSchema); 193 | }); 194 | 195 | it("bytes - IP - valid (v6)", () => { 196 | const message = BytesIP.fromJson({"val":"IAENuIWjAAAAAIouA3BzNA=="}); 197 | expect(message).toBeValid(BytesIPSchema); 198 | }); 199 | 200 | it("bytes - IP - invalid", () => { 201 | const message = BytesIP.fromJson({"val":"Zm9vYmFy"}); 202 | expect(message).toBeInvalid(BytesIPSchema, 1); 203 | }); 204 | 205 | it("bytes - IPv4 - valid", () => { 206 | const message = BytesIPv4.fromJson({"val":"wKgAAQ=="}); 207 | expect(message).toBeValid(BytesIPv4Schema); 208 | }); 209 | 210 | it("bytes - IPv4 - invalid", () => { 211 | const message = BytesIPv4.fromJson({"val":"Zm9vYmFy"}); 212 | expect(message).toBeInvalid(BytesIPv4Schema, 1); 213 | }); 214 | 215 | it("bytes - IPv4 - invalid (v6)", () => { 216 | const message = BytesIPv4.fromJson({"val":"IAENuIWjAAAAAIouA3BzNA=="}); 217 | expect(message).toBeInvalid(BytesIPv4Schema, 1); 218 | }); 219 | 220 | it("bytes - IPv6 - valid", () => { 221 | const message = BytesIPv6.fromJson({"val":"IAENuIWjAAAAAIouA3BzNA=="}); 222 | expect(message).toBeValid(BytesIPv6Schema); 223 | }); 224 | 225 | it("bytes - IPv6 - invalid", () => { 226 | const message = BytesIPv6.fromJson({"val":"Zm9vYXI="}); 227 | expect(message).toBeInvalid(BytesIPv6Schema, 1); 228 | }); 229 | 230 | it("bytes - IPv6 - invalid (v4)", () => { 231 | const message = BytesIPv6.fromJson({"val":"wKgAAQ=="}); 232 | expect(message).toBeInvalid(BytesIPv6Schema, 1); 233 | }); 234 | 235 | it("bytes - IPv6 - valid (ignore_empty)", () => { 236 | const message = BytesIPv6Ignore.fromJson({}); 237 | expect(message).toBeValid(BytesIPv6IgnoreSchema); 238 | }); 239 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/harness/cases/repeated_zod.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-validate-zod v0.0.2-dev with parameter "target=ts" 2 | // @generated from file tests/harness/cases/repeated.proto (package tests.harness.cases, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import {z} from "zod"; 7 | import {AnEnum, RepeatedEmbeddedEnumIn_AnotherInEnum, RepeatedEmbeddedEnumNotIn_AnotherNotInEnum} from "./repeated_pb.js"; 8 | import {double, float, int64, isIn, isNotIn, isUniqueList, numberGt, sfixed32, uint32} from "protobuf-zod"; 9 | import {protoInt64} from "@bufbuild/protobuf"; 10 | import {EmbedSchema as EmbedSchema$1} from "./other_package/embed_zod.js"; 11 | 12 | /** 13 | * @generated from enum tests.harness.cases.AnEnum 14 | */ 15 | export const AnEnumSchema = z.nativeEnum(AnEnum); 16 | 17 | /** 18 | * @generated from message tests.harness.cases.Embed 19 | */ 20 | export const EmbedSchema = z.object({ 21 | /** 22 | * @generated from field: int64 val = 1; 23 | * @validate {"int64":{"gt":"0"}} 24 | */ 25 | val: int64.refine(numberGt(protoInt64.zero)), 26 | }); 27 | 28 | /** 29 | * @generated from message tests.harness.cases.RepeatedNone 30 | */ 31 | export const RepeatedNoneSchema = z.object({ 32 | /** 33 | * @generated from field: repeated int64 val = 1; 34 | */ 35 | val: int64.array(), 36 | }); 37 | 38 | /** 39 | * @generated from message tests.harness.cases.RepeatedEmbedNone 40 | */ 41 | export const RepeatedEmbedNoneSchema = z.object({ 42 | /** 43 | * @generated from field: repeated tests.harness.cases.Embed val = 1; 44 | */ 45 | val: z.lazy(() => EmbedSchema).nullish().array(), 46 | }); 47 | 48 | /** 49 | * @generated from message tests.harness.cases.RepeatedEmbedCrossPackageNone 50 | */ 51 | export const RepeatedEmbedCrossPackageNoneSchema = z.object({ 52 | /** 53 | * @generated from field: repeated tests.harness.cases.other_package.Embed val = 1; 54 | */ 55 | val: z.lazy(() => EmbedSchema$1).nullish().array(), 56 | }); 57 | 58 | /** 59 | * @generated from message tests.harness.cases.RepeatedMin 60 | */ 61 | export const RepeatedMinSchema = z.object({ 62 | /** 63 | * @generated from field: repeated tests.harness.cases.Embed val = 1; 64 | * @validate {"repeated":{"minItems":"2"}} 65 | */ 66 | val: z.lazy(() => EmbedSchema).nullish().array().min(2), 67 | }); 68 | 69 | /** 70 | * @generated from message tests.harness.cases.RepeatedMax 71 | */ 72 | export const RepeatedMaxSchema = z.object({ 73 | /** 74 | * @generated from field: repeated double val = 1; 75 | * @validate {"repeated":{"maxItems":"3"}} 76 | */ 77 | val: double.array().max(3), 78 | }); 79 | 80 | /** 81 | * @generated from message tests.harness.cases.RepeatedMinMax 82 | */ 83 | export const RepeatedMinMaxSchema = z.object({ 84 | /** 85 | * @generated from field: repeated sfixed32 val = 1; 86 | * @validate {"repeated":{"minItems":"2","maxItems":"4"}} 87 | */ 88 | val: sfixed32.array().max(4).min(2), 89 | }); 90 | 91 | /** 92 | * @generated from message tests.harness.cases.RepeatedExact 93 | */ 94 | export const RepeatedExactSchema = z.object({ 95 | /** 96 | * @generated from field: repeated uint32 val = 1; 97 | * @validate {"repeated":{"minItems":"3","maxItems":"3"}} 98 | */ 99 | val: uint32.array().max(3).min(3), 100 | }); 101 | 102 | /** 103 | * @generated from message tests.harness.cases.RepeatedUnique 104 | */ 105 | export const RepeatedUniqueSchema = z.object({ 106 | /** 107 | * @generated from field: repeated string val = 1; 108 | * @validate {"repeated":{"unique":true}} 109 | */ 110 | val: z.string().array().refine(isUniqueList()), 111 | }); 112 | 113 | /** 114 | * @generated from message tests.harness.cases.RepeatedItemRule 115 | */ 116 | export const RepeatedItemRuleSchema = z.object({ 117 | /** 118 | * @generated from field: repeated float val = 1; 119 | * @validate {"repeated":{"items":{"float":{"gt":0}}}} 120 | */ 121 | val: float.refine(numberGt(0)).array(), 122 | }); 123 | 124 | /** 125 | * @generated from message tests.harness.cases.RepeatedItemPattern 126 | */ 127 | export const RepeatedItemPatternSchema = z.object({ 128 | /** 129 | * @generated from field: repeated string val = 1; 130 | * @validate {"repeated":{"items":{"string":{"pattern":"(?i)^[a-z0-9]+$"}}}} 131 | */ 132 | val: z.string().regex(new RegExp("invalid regular expression^")).array(), 133 | }); 134 | 135 | /** 136 | * @generated from message tests.harness.cases.RepeatedEmbedSkip 137 | */ 138 | export const RepeatedEmbedSkipSchema = z.object({ 139 | /** 140 | * @generated from field: repeated tests.harness.cases.Embed val = 1; 141 | * @validate {"repeated":{"items":{"message":{"skip":true}}}} 142 | */ 143 | val: z.any().nullish().array(), 144 | }); 145 | 146 | /** 147 | * @generated from message tests.harness.cases.RepeatedItemIn 148 | */ 149 | export const RepeatedItemInSchema = z.object({ 150 | /** 151 | * @generated from field: repeated string val = 1; 152 | * @validate {"repeated":{"items":{"string":{"in":["foo","bar"]}}}} 153 | */ 154 | val: z.string().refine(isIn([ 155 | "foo", 156 | "bar", 157 | ])).array(), 158 | }); 159 | 160 | /** 161 | * @generated from message tests.harness.cases.RepeatedItemNotIn 162 | */ 163 | export const RepeatedItemNotInSchema = z.object({ 164 | /** 165 | * @generated from field: repeated string val = 1; 166 | * @validate {"repeated":{"items":{"string":{"notIn":["foo","bar"]}}}} 167 | */ 168 | val: z.string().refine(isNotIn([ 169 | "foo", 170 | "bar", 171 | ])).array(), 172 | }); 173 | 174 | /** 175 | * @generated from message tests.harness.cases.RepeatedEnumIn 176 | */ 177 | export const RepeatedEnumInSchema = z.object({ 178 | /** 179 | * @generated from field: repeated tests.harness.cases.AnEnum val = 1; 180 | * @validate {"repeated":{"items":{"enum":{"in":[0]}}}} 181 | */ 182 | val: z.lazy(() => AnEnumSchema).refine(isIn([ 183 | 0, 184 | ])).array(), 185 | }); 186 | 187 | /** 188 | * @generated from message tests.harness.cases.RepeatedEnumNotIn 189 | */ 190 | export const RepeatedEnumNotInSchema = z.object({ 191 | /** 192 | * @generated from field: repeated tests.harness.cases.AnEnum val = 1; 193 | * @validate {"repeated":{"items":{"enum":{"notIn":[0]}}}} 194 | */ 195 | val: z.lazy(() => AnEnumSchema).refine(isNotIn([ 196 | 0, 197 | ])).array(), 198 | }); 199 | 200 | /** 201 | * @generated from enum tests.harness.cases.RepeatedEmbeddedEnumIn.AnotherInEnum 202 | */ 203 | export const RepeatedEmbeddedEnumIn_AnotherInEnumSchema = z.nativeEnum(RepeatedEmbeddedEnumIn_AnotherInEnum); 204 | 205 | /** 206 | * @generated from message tests.harness.cases.RepeatedEmbeddedEnumIn 207 | */ 208 | export const RepeatedEmbeddedEnumInSchema = z.object({ 209 | /** 210 | * @generated from field: repeated tests.harness.cases.RepeatedEmbeddedEnumIn.AnotherInEnum val = 1; 211 | * @validate {"repeated":{"items":{"enum":{"in":[0]}}}} 212 | */ 213 | val: z.lazy(() => RepeatedEmbeddedEnumIn_AnotherInEnumSchema).refine(isIn([ 214 | 0, 215 | ])).array(), 216 | }); 217 | 218 | /** 219 | * @generated from enum tests.harness.cases.RepeatedEmbeddedEnumNotIn.AnotherNotInEnum 220 | */ 221 | export const RepeatedEmbeddedEnumNotIn_AnotherNotInEnumSchema = z.nativeEnum(RepeatedEmbeddedEnumNotIn_AnotherNotInEnum); 222 | 223 | /** 224 | * @generated from message tests.harness.cases.RepeatedEmbeddedEnumNotIn 225 | */ 226 | export const RepeatedEmbeddedEnumNotInSchema = z.object({ 227 | /** 228 | * @generated from field: repeated tests.harness.cases.RepeatedEmbeddedEnumNotIn.AnotherNotInEnum val = 1; 229 | * @validate {"repeated":{"items":{"enum":{"notIn":[0]}}}} 230 | */ 231 | val: z.lazy(() => RepeatedEmbeddedEnumNotIn_AnotherNotInEnumSchema).refine(isNotIn([ 232 | 0, 233 | ])).array(), 234 | }); 235 | 236 | /** 237 | * @generated from message tests.harness.cases.RepeatedAnyIn 238 | */ 239 | export const RepeatedAnyInSchema = z.object({ 240 | /** 241 | * @generated from field: repeated google.protobuf.Any val = 1; 242 | * @validate {"repeated":{"items":{"any":{"in":["type.googleapis.com/google.protobuf.Duration"]}}}} 243 | */ 244 | val: z.any().nullish().array(), 245 | }); 246 | 247 | /** 248 | * @generated from message tests.harness.cases.RepeatedAnyNotIn 249 | */ 250 | export const RepeatedAnyNotInSchema = z.object({ 251 | /** 252 | * @generated from field: repeated google.protobuf.Any val = 1; 253 | * @validate {"repeated":{"items":{"any":{"notIn":["type.googleapis.com/google.protobuf.Timestamp"]}}}} 254 | */ 255 | val: z.any().nullish().array(), 256 | }); 257 | 258 | /** 259 | * @generated from message tests.harness.cases.RepeatedMinAndItemLen 260 | */ 261 | export const RepeatedMinAndItemLenSchema = z.object({ 262 | /** 263 | * @generated from field: repeated string val = 1; 264 | * @validate {"repeated":{"minItems":"1","items":{"string":{"len":"3"}}}} 265 | */ 266 | val: z.string().length(3).array().min(1), 267 | }); 268 | 269 | /** 270 | * @generated from message tests.harness.cases.RepeatedMinAndMaxItemLen 271 | */ 272 | export const RepeatedMinAndMaxItemLenSchema = z.object({ 273 | /** 274 | * @generated from field: repeated string val = 1; 275 | * @validate {"repeated":{"minItems":"1","maxItems":"3"}} 276 | */ 277 | val: z.string().array().max(3).min(1), 278 | }); 279 | 280 | /** 281 | * @generated from message tests.harness.cases.RepeatedDuration 282 | */ 283 | export const RepeatedDurationSchema = z.object({ 284 | /** 285 | * @generated from field: repeated google.protobuf.Duration val = 1; 286 | * @validate {"repeated":{"items":{"duration":{"gte":"0.001s"}}}} 287 | */ 288 | val: z.any().nullish().array(), 289 | }); 290 | 291 | /** 292 | * @generated from message tests.harness.cases.RepeatedExactIgnore 293 | */ 294 | export const RepeatedExactIgnoreSchema = z.object({ 295 | /** 296 | * @generated from field: repeated uint32 val = 1; 297 | * @validate {"repeated":{"minItems":"3","maxItems":"3","ignoreEmpty":true}} 298 | */ 299 | val: uint32.array().max(3).min(3), 300 | }); 301 | 302 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/harness/cases/oneofs_pb.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-es v0.2.1 with parameter "target=ts" 2 | // @generated from file tests/harness/cases/oneofs.proto (package tests.harness.cases, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import type {BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage} from "@bufbuild/protobuf"; 7 | import {Message, proto3} from "@bufbuild/protobuf"; 8 | 9 | /** 10 | * @generated from message tests.harness.cases.TestOneOfMsg 11 | */ 12 | export class TestOneOfMsg extends Message { 13 | /** 14 | * @generated from field: bool val = 1; 15 | */ 16 | val = false; 17 | 18 | constructor(data?: PartialMessage) { 19 | super(); 20 | proto3.util.initPartial(data, this); 21 | } 22 | 23 | static readonly runtime = proto3; 24 | static readonly typeName = "tests.harness.cases.TestOneOfMsg"; 25 | static readonly fields: FieldList = proto3.util.newFieldList(() => [ 26 | { no: 1, name: "val", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, 27 | ]); 28 | 29 | static fromBinary(bytes: Uint8Array, options?: Partial): TestOneOfMsg { 30 | return new TestOneOfMsg().fromBinary(bytes, options); 31 | } 32 | 33 | static fromJson(jsonValue: JsonValue, options?: Partial): TestOneOfMsg { 34 | return new TestOneOfMsg().fromJson(jsonValue, options); 35 | } 36 | 37 | static fromJsonString(jsonString: string, options?: Partial): TestOneOfMsg { 38 | return new TestOneOfMsg().fromJsonString(jsonString, options); 39 | } 40 | 41 | static equals(a: TestOneOfMsg | PlainMessage | undefined, b: TestOneOfMsg | PlainMessage | undefined): boolean { 42 | return proto3.util.equals(TestOneOfMsg, a, b); 43 | } 44 | } 45 | 46 | /** 47 | * @generated from message tests.harness.cases.OneOfNone 48 | */ 49 | export class OneOfNone extends Message { 50 | /** 51 | * @generated from oneof tests.harness.cases.OneOfNone.o 52 | */ 53 | o: { 54 | /** 55 | * @generated from field: string x = 1; 56 | */ 57 | value: string; 58 | case: "x"; 59 | } | { 60 | /** 61 | * @generated from field: int32 y = 2; 62 | */ 63 | value: number; 64 | case: "y"; 65 | } | { case: undefined; value?: undefined } = { case: undefined }; 66 | 67 | constructor(data?: PartialMessage) { 68 | super(); 69 | proto3.util.initPartial(data, this); 70 | } 71 | 72 | static readonly runtime = proto3; 73 | static readonly typeName = "tests.harness.cases.OneOfNone"; 74 | static readonly fields: FieldList = proto3.util.newFieldList(() => [ 75 | { no: 1, name: "x", kind: "scalar", T: 9 /* ScalarType.STRING */, oneof: "o" }, 76 | { no: 2, name: "y", kind: "scalar", T: 5 /* ScalarType.INT32 */, oneof: "o" }, 77 | ]); 78 | 79 | static fromBinary(bytes: Uint8Array, options?: Partial): OneOfNone { 80 | return new OneOfNone().fromBinary(bytes, options); 81 | } 82 | 83 | static fromJson(jsonValue: JsonValue, options?: Partial): OneOfNone { 84 | return new OneOfNone().fromJson(jsonValue, options); 85 | } 86 | 87 | static fromJsonString(jsonString: string, options?: Partial): OneOfNone { 88 | return new OneOfNone().fromJsonString(jsonString, options); 89 | } 90 | 91 | static equals(a: OneOfNone | PlainMessage | undefined, b: OneOfNone | PlainMessage | undefined): boolean { 92 | return proto3.util.equals(OneOfNone, a, b); 93 | } 94 | } 95 | 96 | /** 97 | * @generated from message tests.harness.cases.OneOf 98 | */ 99 | export class OneOf extends Message { 100 | /** 101 | * @generated from oneof tests.harness.cases.OneOf.o 102 | */ 103 | o: { 104 | /** 105 | * @generated from field: string x = 1; 106 | */ 107 | value: string; 108 | case: "x"; 109 | } | { 110 | /** 111 | * @generated from field: int32 y = 2; 112 | */ 113 | value: number; 114 | case: "y"; 115 | } | { 116 | /** 117 | * @generated from field: tests.harness.cases.TestOneOfMsg z = 3; 118 | */ 119 | value: TestOneOfMsg; 120 | case: "z"; 121 | } | { case: undefined; value?: undefined } = { case: undefined }; 122 | 123 | constructor(data?: PartialMessage) { 124 | super(); 125 | proto3.util.initPartial(data, this); 126 | } 127 | 128 | static readonly runtime = proto3; 129 | static readonly typeName = "tests.harness.cases.OneOf"; 130 | static readonly fields: FieldList = proto3.util.newFieldList(() => [ 131 | { no: 1, name: "x", kind: "scalar", T: 9 /* ScalarType.STRING */, oneof: "o" }, 132 | { no: 2, name: "y", kind: "scalar", T: 5 /* ScalarType.INT32 */, oneof: "o" }, 133 | { no: 3, name: "z", kind: "message", T: TestOneOfMsg, oneof: "o" }, 134 | ]); 135 | 136 | static fromBinary(bytes: Uint8Array, options?: Partial): OneOf { 137 | return new OneOf().fromBinary(bytes, options); 138 | } 139 | 140 | static fromJson(jsonValue: JsonValue, options?: Partial): OneOf { 141 | return new OneOf().fromJson(jsonValue, options); 142 | } 143 | 144 | static fromJsonString(jsonString: string, options?: Partial): OneOf { 145 | return new OneOf().fromJsonString(jsonString, options); 146 | } 147 | 148 | static equals(a: OneOf | PlainMessage | undefined, b: OneOf | PlainMessage | undefined): boolean { 149 | return proto3.util.equals(OneOf, a, b); 150 | } 151 | } 152 | 153 | /** 154 | * @generated from message tests.harness.cases.OneOfRequired 155 | */ 156 | export class OneOfRequired extends Message { 157 | /** 158 | * @generated from oneof tests.harness.cases.OneOfRequired.o 159 | */ 160 | o: { 161 | /** 162 | * @generated from field: string x = 1; 163 | */ 164 | value: string; 165 | case: "x"; 166 | } | { 167 | /** 168 | * @generated from field: int32 y = 2; 169 | */ 170 | value: number; 171 | case: "y"; 172 | } | { 173 | /** 174 | * @generated from field: int32 name_with_underscores = 3; 175 | */ 176 | value: number; 177 | case: "nameWithUnderscores"; 178 | } | { 179 | /** 180 | * @generated from field: int32 under_and_1_number = 4; 181 | */ 182 | value: number; 183 | case: "underAnd1Number"; 184 | } | { case: undefined; value?: undefined } = { case: undefined }; 185 | 186 | constructor(data?: PartialMessage) { 187 | super(); 188 | proto3.util.initPartial(data, this); 189 | } 190 | 191 | static readonly runtime = proto3; 192 | static readonly typeName = "tests.harness.cases.OneOfRequired"; 193 | static readonly fields: FieldList = proto3.util.newFieldList(() => [ 194 | { no: 1, name: "x", kind: "scalar", T: 9 /* ScalarType.STRING */, oneof: "o" }, 195 | { no: 2, name: "y", kind: "scalar", T: 5 /* ScalarType.INT32 */, oneof: "o" }, 196 | { no: 3, name: "name_with_underscores", kind: "scalar", T: 5 /* ScalarType.INT32 */, oneof: "o" }, 197 | { no: 4, name: "under_and_1_number", kind: "scalar", T: 5 /* ScalarType.INT32 */, oneof: "o" }, 198 | ]); 199 | 200 | static fromBinary(bytes: Uint8Array, options?: Partial): OneOfRequired { 201 | return new OneOfRequired().fromBinary(bytes, options); 202 | } 203 | 204 | static fromJson(jsonValue: JsonValue, options?: Partial): OneOfRequired { 205 | return new OneOfRequired().fromJson(jsonValue, options); 206 | } 207 | 208 | static fromJsonString(jsonString: string, options?: Partial): OneOfRequired { 209 | return new OneOfRequired().fromJsonString(jsonString, options); 210 | } 211 | 212 | static equals(a: OneOfRequired | PlainMessage | undefined, b: OneOfRequired | PlainMessage | undefined): boolean { 213 | return proto3.util.equals(OneOfRequired, a, b); 214 | } 215 | } 216 | 217 | /** 218 | * @generated from message tests.harness.cases.OneOfIgnoreEmpty 219 | */ 220 | export class OneOfIgnoreEmpty extends Message { 221 | /** 222 | * @generated from oneof tests.harness.cases.OneOfIgnoreEmpty.o 223 | */ 224 | o: { 225 | /** 226 | * @generated from field: string x = 1; 227 | */ 228 | value: string; 229 | case: "x"; 230 | } | { 231 | /** 232 | * @generated from field: bytes y = 2; 233 | */ 234 | value: Uint8Array; 235 | case: "y"; 236 | } | { 237 | /** 238 | * @generated from field: int32 z = 3; 239 | */ 240 | value: number; 241 | case: "z"; 242 | } | { case: undefined; value?: undefined } = { case: undefined }; 243 | 244 | constructor(data?: PartialMessage) { 245 | super(); 246 | proto3.util.initPartial(data, this); 247 | } 248 | 249 | static readonly runtime = proto3; 250 | static readonly typeName = "tests.harness.cases.OneOfIgnoreEmpty"; 251 | static readonly fields: FieldList = proto3.util.newFieldList(() => [ 252 | { no: 1, name: "x", kind: "scalar", T: 9 /* ScalarType.STRING */, oneof: "o" }, 253 | { no: 2, name: "y", kind: "scalar", T: 12 /* ScalarType.BYTES */, oneof: "o" }, 254 | { no: 3, name: "z", kind: "scalar", T: 5 /* ScalarType.INT32 */, oneof: "o" }, 255 | ]); 256 | 257 | static fromBinary(bytes: Uint8Array, options?: Partial): OneOfIgnoreEmpty { 258 | return new OneOfIgnoreEmpty().fromBinary(bytes, options); 259 | } 260 | 261 | static fromJson(jsonValue: JsonValue, options?: Partial): OneOfIgnoreEmpty { 262 | return new OneOfIgnoreEmpty().fromJson(jsonValue, options); 263 | } 264 | 265 | static fromJsonString(jsonString: string, options?: Partial): OneOfIgnoreEmpty { 266 | return new OneOfIgnoreEmpty().fromJsonString(jsonString, options); 267 | } 268 | 269 | static equals(a: OneOfIgnoreEmpty | PlainMessage | undefined, b: OneOfIgnoreEmpty | PlainMessage | undefined): boolean { 270 | return proto3.util.equals(OneOfIgnoreEmpty, a, b); 271 | } 272 | } 273 | 274 | -------------------------------------------------------------------------------- /packages/protoc-gen-validate-zod/tests/harness/cases/wkt_duration.test.ts: -------------------------------------------------------------------------------- 1 | // @generated by protoc-gen-validate-zod-test v0.0.2-dev with parameter "target=ts,cases=packages/protoc-gen-validate-zod/tests/cases.txt,descriptor=vendor/cases.bin" 2 | // @generated from file tests/harness/cases/wkt_duration.proto (package tests.harness.cases, syntax proto3) 3 | /* eslint-disable */ 4 | /* @ts-nocheck */ 5 | 6 | import {expect, it} from "vitest"; 7 | import {DurationConst, DurationExGTELTE, DurationExLTGT, DurationFieldWithOtherFields, DurationGT, DurationGTE, DurationGTELTE, DurationGTLT, DurationIn, DurationLT, DurationLTE, DurationNone, DurationNotIn, DurationRequired} from "./wkt_duration_pb.js"; 8 | import {DurationConstSchema, DurationExGTELTESchema, DurationExLTGTSchema, DurationFieldWithOtherFieldsSchema, DurationGTELTESchema, DurationGTESchema, DurationGTLTSchema, DurationGTSchema, DurationInSchema, DurationLTESchema, DurationLTSchema, DurationNoneSchema, DurationNotInSchema, DurationRequiredSchema} from "./wkt_duration_zod.js"; 9 | 10 | it("duration - none - valid", () => { 11 | const message = DurationNone.fromJson({"val":"123s"}); 12 | expect(message).toBeValid(DurationNoneSchema); 13 | }); 14 | 15 | it("duration - required - valid", () => { 16 | const message = DurationRequired.fromJson({"val":"0s"}); 17 | expect(message).toBeValid(DurationRequiredSchema); 18 | }); 19 | 20 | it("duration - required - invalid", () => { 21 | const message = DurationRequired.fromJson({}); 22 | expect(message).toBeInvalid(DurationRequiredSchema, 1); 23 | }); 24 | 25 | it("duration - const - valid", () => { 26 | const message = DurationConst.fromJson({"val":"3s"}); 27 | expect(message).toBeValid(DurationConstSchema); 28 | }); 29 | 30 | it("duration - const - valid (empty)", () => { 31 | const message = DurationConst.fromJson({}); 32 | expect(message).toBeValid(DurationConstSchema); 33 | }); 34 | 35 | it("duration - const - invalid", () => { 36 | const message = DurationConst.fromJson({"val":"0.000000003s"}); 37 | expect(message).toBeInvalid(DurationConstSchema, 1); 38 | }); 39 | 40 | it("duration - in - valid", () => { 41 | const message = DurationIn.fromJson({"val":"1s"}); 42 | expect(message).toBeValid(DurationInSchema); 43 | }); 44 | 45 | it("duration - in - valid (empty)", () => { 46 | const message = DurationIn.fromJson({}); 47 | expect(message).toBeValid(DurationInSchema); 48 | }); 49 | 50 | it("duration - in - invalid", () => { 51 | const message = DurationIn.fromJson({"val":"0s"}); 52 | expect(message).toBeInvalid(DurationInSchema, 1); 53 | }); 54 | 55 | it("duration - not in - valid", () => { 56 | const message = DurationNotIn.fromJson({"val":"0.000000001s"}); 57 | expect(message).toBeValid(DurationNotInSchema); 58 | }); 59 | 60 | it("duration - not in - valid (empty)", () => { 61 | const message = DurationNotIn.fromJson({}); 62 | expect(message).toBeValid(DurationNotInSchema); 63 | }); 64 | 65 | it("duration - not in - invalid", () => { 66 | const message = DurationNotIn.fromJson({"val":"0s"}); 67 | expect(message).toBeInvalid(DurationNotInSchema, 1); 68 | }); 69 | 70 | it("duration - lt - valid", () => { 71 | const message = DurationLT.fromJson({"val":"0.000000001s"}); 72 | expect(message).toBeValid(DurationLTSchema); 73 | }); 74 | 75 | it("duration - lt - valid (empty)", () => { 76 | const message = DurationLT.fromJson({}); 77 | expect(message).toBeValid(DurationLTSchema); 78 | }); 79 | 80 | it("duration - lt - invalid (equal)", () => { 81 | const message = DurationLT.fromJson({"val":"0s"}); 82 | expect(message).toBeInvalid(DurationLTSchema, 1); 83 | }); 84 | 85 | it("duration - lt - invalid", () => { 86 | const message = DurationLT.fromJson({"val":"1s"}); 87 | expect(message).toBeInvalid(DurationLTSchema, 1); 88 | }); 89 | 90 | it("duration - lte - valid", () => { 91 | const message = DurationLTE.fromJson({"val":"0s"}); 92 | expect(message).toBeValid(DurationLTESchema); 93 | }); 94 | 95 | it("duration - lte - valid (empty)", () => { 96 | const message = DurationLTE.fromJson({}); 97 | expect(message).toBeValid(DurationLTESchema); 98 | }); 99 | 100 | it("duration - lte - valid (equal)", () => { 101 | const message = DurationLTE.fromJson({"val":"1s"}); 102 | expect(message).toBeValid(DurationLTESchema); 103 | }); 104 | 105 | it("duration - lte - invalid", () => { 106 | const message = DurationLTE.fromJson({"val":"1.000000001s"}); 107 | expect(message).toBeInvalid(DurationLTESchema, 1); 108 | }); 109 | 110 | it("duration - gt - valid", () => { 111 | const message = DurationGT.fromJson({"val":"1s"}); 112 | expect(message).toBeValid(DurationGTSchema); 113 | }); 114 | 115 | it("duration - gt - valid (empty)", () => { 116 | const message = DurationGT.fromJson({}); 117 | expect(message).toBeValid(DurationGTSchema); 118 | }); 119 | 120 | it("duration - gt - invalid (equal)", () => { 121 | const message = DurationGT.fromJson({"val":"0.000001s"}); 122 | expect(message).toBeInvalid(DurationGTSchema, 1); 123 | }); 124 | 125 | it("duration - gt - invalid", () => { 126 | const message = DurationGT.fromJson({"val":"0s"}); 127 | expect(message).toBeInvalid(DurationGTSchema, 1); 128 | }); 129 | 130 | it("duration - gte - valid", () => { 131 | const message = DurationGTE.fromJson({"val":"3s"}); 132 | expect(message).toBeValid(DurationGTESchema); 133 | }); 134 | 135 | it("duration - gte - valid (empty)", () => { 136 | const message = DurationGTE.fromJson({}); 137 | expect(message).toBeValid(DurationGTESchema); 138 | }); 139 | 140 | it("duration - gte - valid (equal)", () => { 141 | const message = DurationGTE.fromJson({"val":"0.001s"}); 142 | expect(message).toBeValid(DurationGTESchema); 143 | }); 144 | 145 | it("duration - gte - invalid", () => { 146 | const message = DurationGTE.fromJson({"val":"-1s"}); 147 | expect(message).toBeInvalid(DurationGTESchema, 1); 148 | }); 149 | 150 | it("duration - gt & lt - valid", () => { 151 | const message = DurationGTLT.fromJson({"val":"0.000001s"}); 152 | expect(message).toBeValid(DurationGTLTSchema); 153 | }); 154 | 155 | it("duration - gt & lt - valid (empty)", () => { 156 | const message = DurationGTLT.fromJson({}); 157 | expect(message).toBeValid(DurationGTLTSchema); 158 | }); 159 | 160 | it("duration - gt & lt - invalid (above)", () => { 161 | const message = DurationGTLT.fromJson({"val":"1000s"}); 162 | expect(message).toBeInvalid(DurationGTLTSchema, 1); 163 | }); 164 | 165 | it("duration - gt & lt - invalid (below)", () => { 166 | const message = DurationGTLT.fromJson({"val":"0.000001s"}); 167 | expect(message).toBeInvalid(DurationGTLTSchema, 1); 168 | }); 169 | 170 | it("duration - gt & lt - invalid (max)", () => { 171 | const message = DurationGTLT.fromJson({"val":"1s"}); 172 | expect(message).toBeInvalid(DurationGTLTSchema, 1); 173 | }); 174 | 175 | it("duration - gt & lt - invalid (min)", () => { 176 | const message = DurationGTLT.fromJson({"val":"0s"}); 177 | expect(message).toBeInvalid(DurationGTLTSchema, 1); 178 | }); 179 | 180 | it("duration - exclusive gt & lt - valid (empty)", () => { 181 | const message = DurationExLTGT.fromJson({}); 182 | expect(message).toBeValid(DurationExLTGTSchema); 183 | }); 184 | 185 | it("duration - exclusive gt & lt - valid (above)", () => { 186 | const message = DurationExLTGT.fromJson({"val":"2s"}); 187 | expect(message).toBeValid(DurationExLTGTSchema); 188 | }); 189 | 190 | it("duration - exclusive gt & lt - valid (below)", () => { 191 | const message = DurationExLTGT.fromJson({"val":"0.000000001s"}); 192 | expect(message).toBeValid(DurationExLTGTSchema); 193 | }); 194 | 195 | it("duration - exclusive gt & lt - invalid", () => { 196 | const message = DurationExLTGT.fromJson({"val":"0.000001s"}); 197 | expect(message).toBeInvalid(DurationExLTGTSchema, 1); 198 | }); 199 | 200 | it("duration - exclusive gt & lt - invalid (max)", () => { 201 | const message = DurationExLTGT.fromJson({"val":"1s"}); 202 | expect(message).toBeInvalid(DurationExLTGTSchema, 1); 203 | }); 204 | 205 | it("duration - exclusive gt & lt - invalid (min)", () => { 206 | const message = DurationExLTGT.fromJson({"val":"0s"}); 207 | expect(message).toBeInvalid(DurationExLTGTSchema, 1); 208 | }); 209 | 210 | it("duration - gte & lte - valid", () => { 211 | const message = DurationGTELTE.fromJson({"val":"60.000000001s"}); 212 | expect(message).toBeValid(DurationGTELTESchema); 213 | }); 214 | 215 | it("duration - gte & lte - valid (empty)", () => { 216 | const message = DurationGTELTE.fromJson({}); 217 | expect(message).toBeValid(DurationGTELTESchema); 218 | }); 219 | 220 | it("duration - gte & lte - valid (max)", () => { 221 | const message = DurationGTELTE.fromJson({"val":"3600s"}); 222 | expect(message).toBeValid(DurationGTELTESchema); 223 | }); 224 | 225 | it("duration - gte & lte - valid (min)", () => { 226 | const message = DurationGTELTE.fromJson({"val":"60s"}); 227 | expect(message).toBeValid(DurationGTELTESchema); 228 | }); 229 | 230 | it("duration - gte & lte - invalid (above)", () => { 231 | const message = DurationGTELTE.fromJson({"val":"3600.000000001s"}); 232 | expect(message).toBeInvalid(DurationGTELTESchema, 1); 233 | }); 234 | 235 | it("duration - gte & lte - invalid (below)", () => { 236 | const message = DurationGTELTE.fromJson({"val":"59s"}); 237 | expect(message).toBeInvalid(DurationGTELTESchema, 1); 238 | }); 239 | 240 | it("duration - gte & lte - valid (empty)", () => { 241 | const message = DurationExGTELTE.fromJson({}); 242 | expect(message).toBeValid(DurationExGTELTESchema); 243 | }); 244 | 245 | it("duration - exclusive gte & lte - valid (above)", () => { 246 | const message = DurationExGTELTE.fromJson({"val":"3601s"}); 247 | expect(message).toBeValid(DurationExGTELTESchema); 248 | }); 249 | 250 | it("duration - exclusive gte & lte - valid (below)", () => { 251 | const message = DurationExGTELTE.fromJson({"val":"0s"}); 252 | expect(message).toBeValid(DurationExGTELTESchema); 253 | }); 254 | 255 | it("duration - exclusive gte & lte - valid (max)", () => { 256 | const message = DurationExGTELTE.fromJson({"val":"3600s"}); 257 | expect(message).toBeValid(DurationExGTELTESchema); 258 | }); 259 | 260 | it("duration - exclusive gte & lte - valid (min)", () => { 261 | const message = DurationExGTELTE.fromJson({"val":"60s"}); 262 | expect(message).toBeValid(DurationExGTELTESchema); 263 | }); 264 | 265 | it("duration - exclusive gte & lte - invalid", () => { 266 | const message = DurationExGTELTE.fromJson({"val":"61s"}); 267 | expect(message).toBeInvalid(DurationExGTELTESchema, 1); 268 | }); 269 | 270 | it("duration - fields with other fields - invalid other field", () => { 271 | const message = DurationFieldWithOtherFields.fromJson({"intVal":12}); 272 | expect(message).toBeInvalid(DurationFieldWithOtherFieldsSchema, 1); 273 | }); 274 | --------------------------------------------------------------------------------