Skip to content

Commit

Permalink
Merge pull request #667 from MH4GF/support-valibot-2
Browse files Browse the repository at this point in the history
  • Loading branch information
Code-Hex authored Jun 6, 2024
2 parents 6108482 + b9cb35b commit 326f110
Show file tree
Hide file tree
Showing 10 changed files with 1,503 additions and 2 deletions.
23 changes: 23 additions & 0 deletions codegen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,26 @@ generates:
email: email
scalars:
ID: string
example/valibot/schemas.ts:
plugins:
- ./dist/main/index.js:
schema: valibot
importFrom: ../types
withObjectType: true
directives:
# Write directives like
#
# directive:
# arg1: schemaApi
# arg2: ["schemaApi2", "Hello $1"]
#
# See more examples in `./tests/directive.spec.ts`
# https://github.com/Code-Hex/graphql-codegen-typescript-validation-schema/blob/main/tests/directive.spec.ts
constraint:
minLength: minLength
# Replace $1 with specified `startsWith` argument value of the constraint directive
startsWith: [regex, /^$1/, message]
format:
email: email
scalars:
ID: string
130 changes: 130 additions & 0 deletions example/valibot/schemas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import * as v from 'valibot'
import { Admin, AttributeInput, ButtonComponentType, ComponentInput, DropDownComponentInput, EventArgumentInput, EventInput, EventOptionType, Guest, HttpInput, HttpMethod, LayoutInput, MyType, MyTypeFooArgs, Namer, PageInput, PageType, User } from '../types'

export const ButtonComponentTypeSchema = v.enum_(ButtonComponentType);

export const EventOptionTypeSchema = v.enum_(EventOptionType);

export const HttpMethodSchema = v.enum_(HttpMethod);

export const PageTypeSchema = v.enum_(PageType);

export function AdminSchema(): v.GenericSchema<Admin> {
return v.object({
__typename: v.optional(v.literal('Admin')),
lastModifiedAt: v.nullish(v.any())
})
}

export function AttributeInputSchema(): v.GenericSchema<AttributeInput> {
return v.object({
key: v.nullish(v.string()),
val: v.nullish(v.string())
})
}

export function ComponentInputSchema(): v.GenericSchema<ComponentInput> {
return v.object({
child: v.lazy(() => v.nullish(ComponentInputSchema())),
childrens: v.nullish(v.array(v.lazy(() => v.nullable(ComponentInputSchema())))),
event: v.lazy(() => v.nullish(EventInputSchema())),
name: v.string(),
type: ButtonComponentTypeSchema
})
}

export function DropDownComponentInputSchema(): v.GenericSchema<DropDownComponentInput> {
return v.object({
dropdownComponent: v.lazy(() => v.nullish(ComponentInputSchema())),
getEvent: v.lazy(() => EventInputSchema())
})
}

export function EventArgumentInputSchema(): v.GenericSchema<EventArgumentInput> {
return v.object({
name: v.pipe(v.string(), v.minLength(5)),
value: v.pipe(v.string(), v.regex(/^foo/, "message"))
})
}

export function EventInputSchema(): v.GenericSchema<EventInput> {
return v.object({
arguments: v.array(v.lazy(() => EventArgumentInputSchema())),
options: v.nullish(v.array(EventOptionTypeSchema))
})
}

export function GuestSchema(): v.GenericSchema<Guest> {
return v.object({
__typename: v.optional(v.literal('Guest')),
lastLoggedIn: v.nullish(v.any())
})
}

export function HttpInputSchema(): v.GenericSchema<HttpInput> {
return v.object({
method: v.nullish(HttpMethodSchema),
url: v.any()
})
}

export function LayoutInputSchema(): v.GenericSchema<LayoutInput> {
return v.object({
dropdown: v.lazy(() => v.nullish(DropDownComponentInputSchema()))
})
}

export function MyTypeSchema(): v.GenericSchema<MyType> {
return v.object({
__typename: v.optional(v.literal('MyType')),
foo: v.nullish(v.string())
})
}

export function MyTypeFooArgsSchema(): v.GenericSchema<MyTypeFooArgs> {
return v.object({
a: v.nullish(v.string()),
b: v.number(),
c: v.nullish(v.boolean()),
d: v.number()
})
}

export function NamerSchema(): v.GenericSchema<Namer> {
return v.object({
name: v.nullish(v.string())
})
}

export function PageInputSchema(): v.GenericSchema<PageInput> {
return v.object({
attributes: v.nullish(v.array(v.lazy(() => AttributeInputSchema()))),
date: v.nullish(v.any()),
height: v.number(),
id: v.string(),
layout: v.lazy(() => LayoutInputSchema()),
pageType: PageTypeSchema,
postIDs: v.nullish(v.array(v.string())),
show: v.boolean(),
tags: v.nullish(v.array(v.nullable(v.string()))),
title: v.string(),
width: v.number()
})
}

export function UserSchema(): v.GenericSchema<User> {
return v.object({
__typename: v.optional(v.literal('User')),
createdAt: v.nullish(v.any()),
email: v.nullish(v.string()),
id: v.nullish(v.string()),
kind: v.nullish(UserKindSchema()),
name: v.nullish(v.string()),
password: v.nullish(v.string()),
updatedAt: v.nullish(v.any())
})
}

export function UserKindSchema() {
return v.union([AdminSchema(), GuestSchema()])
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"type-check:yup": "tsc --strict --skipLibCheck --noEmit example/yup/schemas.ts",
"type-check:zod": "tsc --strict --skipLibCheck --noEmit example/zod/schemas.ts",
"type-check:myzod": "tsc --strict --skipLibCheck --noEmit example/myzod/schemas.ts",
"type-check:valibot": "tsc --strict --skipLibCheck --noEmit example/valibot/schemas.ts",
"test": "vitest run",
"build": "run-p build:*",
"build:main": "tsc -p tsconfig.main.json",
Expand Down Expand Up @@ -82,6 +83,7 @@
"ts-dedent": "^2.2.0",
"ts-jest": "29.1.4",
"typescript": "5.4.5",
"valibot": "0.31.0-rc.6",
"vitest": "^1.0.0",
"yup": "1.4.0",
"zod": "3.23.8"
Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { TypeScriptPluginConfig } from '@graphql-codegen/typescript';

export type ValidationSchema = 'yup' | 'zod' | 'myzod';
export type ValidationSchema = 'yup' | 'zod' | 'myzod' | 'valibot';
export type ValidationSchemaExportType = 'function' | 'const';

export interface DirectiveConfig {
Expand Down
38 changes: 37 additions & 1 deletion src/directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,39 @@ export function buildApi(config: FormattedDirectiveConfig, directives: ReadonlyA
.join('')
}

// This function generates `[v.minLength(100), v.email()]`
// NOTE: valibot's API is not a method chain, so it is prepared separately from buildApi.
//
// config
// {
// 'constraint': {
// 'minLength': ['minLength', '$1'],
// 'format': {
// 'uri': ['url', '$2'],
// 'email': ['email', '$2'],
// }
// }
// }
//
// GraphQL schema
// ```graphql
// input ExampleInput {
// email: String! @required(msg: "message") @constraint(minLength: 100, format: "email")
// }
// ```
//
// FIXME: v.required() is not supported yet. v.required() is classified as `Methods` and must wrap the schema. ex) `v.required(v.object({...}))`
export function buildApiForValibot(config: FormattedDirectiveConfig, directives: ReadonlyArray<ConstDirectiveNode>): string[] {
return directives
.filter(directive => config[directive.name.value] !== undefined)
.map((directive) => {
const directiveName = directive.name.value;
const argsConfig = config[directiveName];
const apis = _buildApiFromDirectiveArguments(argsConfig, directive.arguments ?? []);
return apis.map(api => `v${api}`);
}).flat()
}

function buildApiSchema(validationSchema: string[] | undefined, argValue: ConstValueNode): string {
if (!validationSchema)
return '';
Expand All @@ -133,6 +166,10 @@ function buildApiSchema(validationSchema: string[] | undefined, argValue: ConstV
}

function buildApiFromDirectiveArguments(config: FormattedDirectiveArguments, args: ReadonlyArray<ConstArgumentNode>): string {
return _buildApiFromDirectiveArguments(config, args).join('');
}

function _buildApiFromDirectiveArguments(config: FormattedDirectiveArguments, args: ReadonlyArray<ConstArgumentNode>): string[] {
return args
.map((arg) => {
const argName = arg.name.value;
Expand All @@ -142,7 +179,6 @@ function buildApiFromDirectiveArguments(config: FormattedDirectiveArguments, arg

return buildApiSchema(validationSchema, arg.value);
})
.join('');
}

function buildApiFromDirectiveObjectArguments(config: FormattedDirectiveObjectArguments, argValue: ConstValueNode): string {
Expand Down
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { MyZodSchemaVisitor } from './myzod/index';
import type { SchemaVisitor } from './types';
import { YupSchemaVisitor } from './yup/index';
import { ZodSchemaVisitor } from './zod/index';
import { ValibotSchemaVisitor } from './valibot';

export const plugin: PluginFunction<ValidationSchemaPluginConfig, Types.ComplexPluginOutput> = (
schema: GraphQLSchema,
Expand All @@ -33,6 +34,8 @@ function schemaVisitor(schema: GraphQLSchema, config: ValidationSchemaPluginConf
return new ZodSchemaVisitor(schema, config);
else if (config?.schema === 'myzod')
return new MyZodSchemaVisitor(schema, config);
else if (config?.schema === 'valibot')
return new ValibotSchemaVisitor(schema, config);

return new YupSchemaVisitor(schema, config);
}
Expand Down
Loading

0 comments on commit 326f110

Please sign in to comment.