Skip to content

Commit

Permalink
Merge pull request #375 from Code-Hex/fix/373
Browse files Browse the repository at this point in the history
fix 373
  • Loading branch information
Code-Hex authored May 27, 2023
2 parents 4b80770 + 0684828 commit 8d2b58a
Show file tree
Hide file tree
Showing 10 changed files with 683 additions and 555 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,16 @@ generates:
# You can put the config for typescript plugin here
# see: https://www.graphql-code-generator.com/plugins/typescript
strictScalars: true
# Overrides built-in ID scalar to both input and output types as string.
# see: https://the-guild.dev/graphql/codegen/plugins/typescript/typescript#scalars
scalars:
ID: string
# You can also write the config for this plugin together
schema: yup # or zod
```
It is recommended to write `scalars` config for built-in type `ID`, as in the yaml example shown above. For more information: [#375](https://github.com/Code-Hex/graphql-codegen-typescript-validation-schema/pull/375)

You can check [example](https://github.com/Code-Hex/graphql-codegen-typescript-validation-schema/tree/main/example) directory if you want to see more complex config example or how is generated some files.

The Q&A for each schema is written in the README in the respective example directory.
Expand Down
9 changes: 9 additions & 0 deletions codegen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ generates:
example/types.ts:
plugins:
- typescript
config:
scalars:
ID: string
example/yup/schemas.ts:
plugins:
- ./dist/main/index.js:
Expand Down Expand Up @@ -38,6 +41,8 @@ generates:
max: ['max', '$1 + 1']
exclusiveMin: min
exclusiveMax: max
scalars:
ID: string
example/zod/schemas.ts:
plugins:
- ./dist/main/index.js:
Expand All @@ -59,6 +64,8 @@ generates:
startsWith: ['regex', '/^$1/', 'message']
format:
email: email
scalars:
ID: string
example/myzod/schemas.ts:
plugins:
- ./dist/main/index.js:
Expand All @@ -72,3 +79,5 @@ generates:
startsWith: ['pattern', '/^$1/']
format:
email: email
scalars:
ID: string
60 changes: 31 additions & 29 deletions example/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,27 @@ export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
export type MakeEmpty<T extends { [key: string]: unknown }, K extends keyof T> = { [_ in K]?: never };
export type Incremental<T> = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
ID: string;
String: string;
Boolean: boolean;
Int: number;
Float: number;
Date: any;
URL: any;
ID: { input: string; output: string; }
String: { input: string; output: string; }
Boolean: { input: boolean; output: boolean; }
Int: { input: number; output: number; }
Float: { input: number; output: number; }
Date: { input: any; output: any; }
URL: { input: any; output: any; }
};

export type Admin = {
__typename?: 'Admin';
lastModifiedAt?: Maybe<Scalars['Date']>;
lastModifiedAt?: Maybe<Scalars['Date']['output']>;
};

export type AttributeInput = {
key?: InputMaybe<Scalars['String']>;
val?: InputMaybe<Scalars['String']>;
key?: InputMaybe<Scalars['String']['input']>;
val?: InputMaybe<Scalars['String']['input']>;
};

export enum ButtonComponentType {
Expand All @@ -33,7 +35,7 @@ export type ComponentInput = {
child?: InputMaybe<ComponentInput>;
childrens?: InputMaybe<Array<InputMaybe<ComponentInput>>>;
event?: InputMaybe<EventInput>;
name: Scalars['String'];
name: Scalars['String']['input'];
type: ButtonComponentType;
};

Expand All @@ -43,8 +45,8 @@ export type DropDownComponentInput = {
};

export type EventArgumentInput = {
name: Scalars['String'];
value: Scalars['String'];
name: Scalars['String']['input'];
value: Scalars['String']['input'];
};

export type EventInput = {
Expand All @@ -59,12 +61,12 @@ export enum EventOptionType {

export type Guest = {
__typename?: 'Guest';
lastLoggedIn?: Maybe<Scalars['Date']>;
lastLoggedIn?: Maybe<Scalars['Date']['output']>;
};

export type HttpInput = {
method?: InputMaybe<HttpMethod>;
url: Scalars['URL'];
url: Scalars['URL']['input'];
};

export enum HttpMethod {
Expand All @@ -78,16 +80,16 @@ export type LayoutInput = {

export type PageInput = {
attributes?: InputMaybe<Array<AttributeInput>>;
date?: InputMaybe<Scalars['Date']>;
height: Scalars['Float'];
id: Scalars['ID'];
date?: InputMaybe<Scalars['Date']['input']>;
height: Scalars['Float']['input'];
id: Scalars['ID']['input'];
layout: LayoutInput;
pageType: PageType;
postIDs?: InputMaybe<Array<Scalars['ID']>>;
show: Scalars['Boolean'];
tags?: InputMaybe<Array<InputMaybe<Scalars['String']>>>;
title: Scalars['String'];
width: Scalars['Int'];
postIDs?: InputMaybe<Array<Scalars['ID']['input']>>;
show: Scalars['Boolean']['input'];
tags?: InputMaybe<Array<InputMaybe<Scalars['String']['input']>>>;
title: Scalars['String']['input'];
width: Scalars['Int']['input'];
};

export enum PageType {
Expand All @@ -99,13 +101,13 @@ export enum PageType {

export type User = {
__typename?: 'User';
createdAt?: Maybe<Scalars['Date']>;
email?: Maybe<Scalars['String']>;
id?: Maybe<Scalars['ID']>;
createdAt?: Maybe<Scalars['Date']['output']>;
email?: Maybe<Scalars['String']['output']>;
id?: Maybe<Scalars['ID']['output']>;
kind?: Maybe<UserKind>;
name?: Maybe<Scalars['String']>;
password?: Maybe<Scalars['String']>;
updatedAt?: Maybe<Scalars['Date']>;
name?: Maybe<Scalars['String']['output']>;
password?: Maybe<Scalars['String']['output']>;
updatedAt?: Maybe<Scalars['Date']['output']>;
};

export type UserKind = Admin | Guest;
80 changes: 38 additions & 42 deletions src/myzod/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,14 @@ import {
UnionTypeDefinitionNode,
} from 'graphql';
import { DeclarationBlock, indent } from '@graphql-codegen/visitor-plugin-common';
import { TsVisitor } from '@graphql-codegen/typescript';
import { Visitor } from '../visitor';
import { buildApi, formatDirectiveConfig } from '../directive';
import { SchemaVisitor } from '../types';

const importZod = `import * as myzod from 'myzod'`;
const anySchema = `definedNonNullAnySchema`;

export const MyZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchemaPluginConfig): SchemaVisitor => {
const tsVisitor = new TsVisitor(schema, config);

const importTypes: string[] = [];

return {
Expand All @@ -39,12 +37,11 @@ export const MyZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSche
].join('\n'),
InputObjectTypeDefinition: {
leave: (node: InputObjectTypeDefinitionNode) => {
const name = tsVisitor.convertName(node.name.value);
const visitor = new Visitor('input', schema, config);
const name = visitor.convertName(node.name.value);
importTypes.push(name);

const shape = node.fields
?.map(field => generateFieldMyZodSchema(config, tsVisitor, schema, field, 2))
.join(',\n');
const shape = node.fields?.map(field => generateFieldMyZodSchema(config, visitor, field, 2)).join(',\n');

return new DeclarationBlock({})
.export()
Expand All @@ -55,12 +52,11 @@ export const MyZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSche
},
ObjectTypeDefinition: {
leave: ObjectTypeDefinitionBuilder(config.withObjectType, (node: ObjectTypeDefinitionNode) => {
const name = tsVisitor.convertName(node.name.value);
const visitor = new Visitor('output', schema, config);
const name = visitor.convertName(node.name.value);
importTypes.push(name);

const shape = node.fields
?.map(field => generateFieldMyZodSchema(config, tsVisitor, schema, field, 2))
.join(',\n');
const shape = node.fields?.map(field => generateFieldMyZodSchema(config, visitor, field, 2)).join(',\n');

return new DeclarationBlock({})
.export()
Expand All @@ -78,7 +74,8 @@ export const MyZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSche
},
EnumTypeDefinition: {
leave: (node: EnumTypeDefinitionNode) => {
const enumname = tsVisitor.convertName(node.name.value);
const visitor = new Visitor('both', schema, config);
const enumname = visitor.convertName(node.name.value);
importTypes.push(enumname);
// z.enum are basically myzod.literals
if (config.enumsAsTypes) {
Expand All @@ -101,11 +98,13 @@ export const MyZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSche
leave: (node: UnionTypeDefinitionNode) => {
if (!node.types || !config.withObjectType) return;

const unionName = tsVisitor.convertName(node.name.value);
const visitor = new Visitor('output', schema, config);

const unionName = visitor.convertName(node.name.value);
const unionElements = node.types
?.map(t => {
const element = tsVisitor.convertName(t.name.value);
const typ = schema.getType(t.name.value);
const element = visitor.convertName(t.name.value);
const typ = visitor.getType(t.name.value);
if (typ?.astNode?.kind === 'EnumTypeDefinition') {
return `${element}Schema`;
}
Expand All @@ -126,25 +125,23 @@ export const MyZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSche

const generateFieldMyZodSchema = (
config: ValidationSchemaPluginConfig,
tsVisitor: TsVisitor,
schema: GraphQLSchema,
visitor: Visitor,
field: InputValueDefinitionNode | FieldDefinitionNode,
indentCount: number
): string => {
const gen = generateFieldTypeMyZodSchema(config, tsVisitor, schema, field, field.type);
const gen = generateFieldTypeMyZodSchema(config, visitor, field, field.type);
return indent(`${field.name.value}: ${maybeLazy(field.type, gen)}`, indentCount);
};

const generateFieldTypeMyZodSchema = (
config: ValidationSchemaPluginConfig,
tsVisitor: TsVisitor,
schema: GraphQLSchema,
visitor: Visitor,
field: InputValueDefinitionNode | FieldDefinitionNode,
type: TypeNode,
parentType?: TypeNode
): string => {
if (isListType(type)) {
const gen = generateFieldTypeMyZodSchema(config, tsVisitor, schema, field, type.type, type);
const gen = generateFieldTypeMyZodSchema(config, visitor, field, type.type, type);
if (!isNonNullType(parentType)) {
const arrayGen = `myzod.array(${maybeLazy(type.type, gen)})`;
const maybeLazyGen = applyDirectives(config, field, arrayGen);
Expand All @@ -153,18 +150,18 @@ const generateFieldTypeMyZodSchema = (
return `myzod.array(${maybeLazy(type.type, gen)})`;
}
if (isNonNullType(type)) {
const gen = generateFieldTypeMyZodSchema(config, tsVisitor, schema, field, type.type, type);
const gen = generateFieldTypeMyZodSchema(config, visitor, field, type.type, type);
return maybeLazy(type.type, gen);
}
if (isNamedType(type)) {
const gen = generateNameNodeMyZodSchema(config, tsVisitor, schema, type.name);
const gen = generateNameNodeMyZodSchema(config, visitor, type.name);
if (isListType(parentType)) {
return `${gen}.nullable()`;
}
const appliedDirectivesGen = applyDirectives(config, field, gen);
if (isNonNullType(parentType)) {
if (config.notAllowEmptyString === true) {
const tsType = tsVisitor.scalars[type.name.value];
const tsType = visitor.getScalarType(type.name.value);
if (tsType === 'string') return `${gen}.min(1)`;
}
return appliedDirectivesGen;
Expand Down Expand Up @@ -192,33 +189,32 @@ const applyDirectives = (

const generateNameNodeMyZodSchema = (
config: ValidationSchemaPluginConfig,
tsVisitor: TsVisitor,
schema: GraphQLSchema,
visitor: Visitor,
node: NameNode
): string => {
const typ = schema.getType(node.value);
const converter = visitor.getNameNodeConverter(node);

if (typ?.astNode?.kind === 'InputObjectTypeDefinition') {
const enumName = tsVisitor.convertName(typ.astNode.name.value);
return `${enumName}Schema()`;
if (converter?.targetKind === 'InputObjectTypeDefinition') {
const name = converter.convertName();
return `${name}Schema()`;
}

if (typ?.astNode?.kind === 'ObjectTypeDefinition') {
const enumName = tsVisitor.convertName(typ.astNode.name.value);
return `${enumName}Schema()`;
if (converter?.targetKind === 'ObjectTypeDefinition') {
const name = converter.convertName();
return `${name}Schema()`;
}

if (typ?.astNode?.kind === 'EnumTypeDefinition') {
const enumName = tsVisitor.convertName(typ.astNode.name.value);
return `${enumName}Schema`;
if (converter?.targetKind === 'EnumTypeDefinition') {
const name = converter.convertName();
return `${name}Schema`;
}

if (typ?.astNode?.kind === 'UnionTypeDefinition') {
const enumName = tsVisitor.convertName(typ.astNode.name.value);
return `${enumName}Schema()`;
if (converter?.targetKind === 'UnionTypeDefinition') {
const name = converter.convertName();
return `${name}Schema()`;
}

return myzod4Scalar(config, tsVisitor, node.value);
return myzod4Scalar(config, visitor, node.value);
};

const maybeLazy = (type: TypeNode, schema: string): string => {
Expand All @@ -228,11 +224,11 @@ const maybeLazy = (type: TypeNode, schema: string): string => {
return schema;
};

const myzod4Scalar = (config: ValidationSchemaPluginConfig, tsVisitor: TsVisitor, scalarName: string): string => {
const myzod4Scalar = (config: ValidationSchemaPluginConfig, visitor: Visitor, scalarName: string): string => {
if (config.scalarSchemas?.[scalarName]) {
return config.scalarSchemas[scalarName];
}
const tsType = tsVisitor.scalars[scalarName];
const tsType = visitor.getScalarType(scalarName);
switch (tsType) {
case 'string':
return `myzod.string()`;
Expand Down
36 changes: 36 additions & 0 deletions src/visitor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { ValidationSchemaPluginConfig } from './config';
import { TsVisitor } from '@graphql-codegen/typescript';
import { NameNode, GraphQLSchema } from 'graphql';

export class Visitor extends TsVisitor {
constructor(
private scalarDirection: 'input' | 'output' | 'both',
private schema: GraphQLSchema,
config: ValidationSchemaPluginConfig
) {
super(schema, config);
}

public getType(name: string) {
return this.schema.getType(name);
}

public getNameNodeConverter(node: NameNode) {
const typ = this.schema.getType(node.value);
const astNode = typ?.astNode;
if (astNode === undefined || astNode === null) {
return undefined;
}
return {
targetKind: astNode.kind,
convertName: () => this.convertName(astNode.name.value),
};
}

public getScalarType(scalarName: string): string | null {
if (this.scalarDirection === 'both') {
return null;
}
return this.scalars[scalarName][this.scalarDirection];
}
}
Loading

0 comments on commit 8d2b58a

Please sign in to comment.