Skip to content

Commit fb5c8e0

Browse files
committed
feat: support non-null and defined
1 parent 28d0296 commit fb5c8e0

File tree

4 files changed

+147
-1
lines changed

4 files changed

+147
-1
lines changed

src/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { TypeScriptPluginConfig } from '@graphql-codegen/typescript';
22

3-
export type ValidationSchema = 'yup' | 'zod' | 'myzod';
3+
export type ValidationSchema = 'yup' | 'zod' | 'myzod' | 'valibot';
44
export type ValidationSchemaExportType = 'function' | 'const';
55

66
export interface DirectiveConfig {

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { MyZodSchemaVisitor } from './myzod/index';
99
import type { SchemaVisitor } from './types';
1010
import { YupSchemaVisitor } from './yup/index';
1111
import { ZodSchemaVisitor } from './zod/index';
12+
import { ValibotSchemaVisitor } from './valibot';
1213

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

3740
return new YupSchemaVisitor(schema, config);
3841
}

src/valibot/index.ts

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { DeclarationBlock, indent } from '@graphql-codegen/visitor-plugin-common';
2+
import type {
3+
FieldDefinitionNode,
4+
GraphQLSchema,
5+
InputObjectTypeDefinitionNode,
6+
InputValueDefinitionNode,
7+
NameNode,
8+
TypeNode,
9+
} from 'graphql';
10+
11+
import type { ValidationSchemaPluginConfig } from '../config';
12+
import { BaseSchemaVisitor } from '../schema_visitor';
13+
import type { Visitor } from '../visitor';
14+
import {
15+
isNamedType,
16+
isNonNullType,
17+
} from './../graphql';
18+
19+
export class ValibotSchemaVisitor extends BaseSchemaVisitor {
20+
constructor(schema: GraphQLSchema, config: ValidationSchemaPluginConfig) {
21+
super(schema, config);
22+
}
23+
24+
importValidationSchema(): string {
25+
return `import * as v from 'valibot'`;
26+
}
27+
28+
initialEmit(): string {
29+
return '';
30+
}
31+
32+
get InputObjectTypeDefinition() {
33+
return {
34+
leave: (node: InputObjectTypeDefinitionNode) => {
35+
const visitor = this.createVisitor('input');
36+
const name = visitor.convertName(node.name.value);
37+
this.importTypes.push(name);
38+
return this.buildInputFields(node.fields ?? [], visitor, name);
39+
},
40+
};
41+
}
42+
43+
protected buildInputFields(
44+
fields: readonly (FieldDefinitionNode | InputValueDefinitionNode)[],
45+
visitor: Visitor,
46+
name: string,
47+
) {
48+
const shape = fields.map(field => generateFieldValibotSchema(this.config, visitor, field, 2)).join(',\n');
49+
50+
switch (this.config.validationSchemaExportType) {
51+
default:
52+
return new DeclarationBlock({})
53+
.export()
54+
.asKind('function')
55+
.withName(`${name}Schema(): v.GenericSchema<${name}>`)
56+
.withBlock([indent(`return v.object({`), shape, indent('})')].join('\n')).string;
57+
}
58+
}
59+
}
60+
61+
function generateFieldValibotSchema(config: ValidationSchemaPluginConfig, visitor: Visitor, field: InputValueDefinitionNode | FieldDefinitionNode, indentCount: number): string {
62+
const gen = generateFieldTypeValibotSchema(config, visitor, field, field.type);
63+
return indent(`${field.name.value}: ${maybeLazy(field.type, gen)}`, indentCount);
64+
}
65+
66+
function generateFieldTypeValibotSchema(config: ValidationSchemaPluginConfig, visitor: Visitor, field: InputValueDefinitionNode | FieldDefinitionNode, type: TypeNode, parentType?: TypeNode): string {
67+
if (isNonNullType(type)) {
68+
const gen = generateFieldTypeValibotSchema(config, visitor, field, type.type, type);
69+
return maybeLazy(type.type, gen);
70+
}
71+
if (isNamedType(type)) {
72+
const gen = generateNameNodeValibotSchema(config, visitor, type.name);
73+
74+
if (isNonNullType(parentType))
75+
return gen;
76+
}
77+
console.warn('unhandled type:', type);
78+
return '';
79+
}
80+
81+
function generateNameNodeValibotSchema(config: ValidationSchemaPluginConfig, visitor: Visitor, node: NameNode): string {
82+
const converter = visitor.getNameNodeConverter(node);
83+
84+
switch (converter?.targetKind) {
85+
default:
86+
if (converter?.targetKind)
87+
console.warn('Unknown targetKind', converter?.targetKind);
88+
89+
return valibot4Scalar(config, visitor, node.value);
90+
}
91+
}
92+
93+
function maybeLazy(type: TypeNode, schema: string): string {
94+
return schema;
95+
}
96+
97+
function valibot4Scalar(config: ValidationSchemaPluginConfig, visitor: Visitor, scalarName: string): string {
98+
const tsType = visitor.getScalarType(scalarName);
99+
switch (tsType) {
100+
case 'string':
101+
return `v.string()`;
102+
case 'number':
103+
return `v.number()`;
104+
case 'boolean':
105+
return `v.boolean()`;
106+
}
107+
console.warn('unhandled scalar name:', scalarName);
108+
return 'v.any()';
109+
}

tests/valibot.spec.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { buildSchema } from 'graphql';
2+
3+
import { plugin } from '../src/index';
4+
5+
describe('valibot', () => {
6+
it('non-null and defined', async () => {
7+
const schema = buildSchema(/* GraphQL */ `
8+
input PrimitiveInput {
9+
a: ID!
10+
b: String!
11+
c: Boolean!
12+
d: Int!
13+
e: Float!
14+
}
15+
`);
16+
const scalars = {
17+
ID: 'string',
18+
}
19+
const result = await plugin(schema, [], { schema: 'valibot', scalars }, {});
20+
expect(result.content).toMatchInlineSnapshot(`
21+
"
22+
export function PrimitiveInputSchema(): v.GenericSchema<PrimitiveInput> {
23+
return v.object({
24+
a: v.string(),
25+
b: v.string(),
26+
c: v.boolean(),
27+
d: v.number(),
28+
e: v.number()
29+
})
30+
}
31+
"
32+
`);
33+
})
34+
})

0 commit comments

Comments
 (0)