11import type { ConstArgumentNode , ConstDirectiveNode , ConstValueNode } from 'graphql' ;
22import { Kind , valueFromASTUntyped } from 'graphql' ;
33
4- import type { DirectiveConfig , DirectiveObjectArguments } from './config.js' ;
4+ import type { DirectiveConfig , DirectiveObjectArguments , SingleDirectiveConfig } from './config.js' ;
55import { isConvertableRegexp } from './regexp.js' ;
66
7- export interface FormattedDirectiveConfig {
8- [ directive : string ] : FormattedDirectiveArguments
9- }
10-
11- export interface FormattedDirectiveArguments {
12- [ argument : string ] : string [ ] | FormattedDirectiveObjectArguments | undefined
13- }
14-
15- export interface FormattedDirectiveObjectArguments {
16- [ matched : string ] : string [ ] | undefined
17- }
18-
19- function isFormattedDirectiveObjectArguments ( arg : FormattedDirectiveArguments [ keyof FormattedDirectiveArguments ] ) : arg is FormattedDirectiveObjectArguments {
20- return arg !== undefined && ! Array . isArray ( arg )
21- }
22-
23- // ```yml
24- // directives:
25- // required:
26- // msg: required
27- // constraint:
28- // minLength: min
29- // format:
30- // uri: url
31- // email: email
32- // ```
33- //
34- // This function convterts to like below
35- // {
36- // 'required': {
37- // 'msg': ['required', '$1'],
38- // },
39- // 'constraint': {
40- // 'minLength': ['min', '$1'],
41- // 'format': {
42- // 'uri': ['url', '$2'],
43- // 'email': ['email', '$2'],
44- // }
45- // }
46- // }
47- export function formatDirectiveConfig ( config : DirectiveConfig ) : FormattedDirectiveConfig {
48- return Object . fromEntries (
49- Object . entries ( config ) . map ( ( [ directive , arg ] ) => {
50- const formatted = Object . fromEntries (
51- Object . entries ( arg ) . map ( ( [ arg , val ] ) => {
52- if ( Array . isArray ( val ) )
53- return [ arg , val ] ;
54-
55- if ( typeof val === 'string' )
56- return [ arg , [ val , '$1' ] ] ;
57-
58- return [ arg , formatDirectiveObjectArguments ( val ) ] ;
59- } ) ,
60- ) ;
61- return [ directive , formatted ] ;
62- } ) ,
63- ) ;
64- }
65-
66- // ```yml
67- // format:
68- // # For example, `@constraint(format: "uri")`. this case $1 will be "uri".
69- // # Therefore the generator generates yup schema `.url()` followed by `uri: 'url'`
70- // # If $1 does not match anywhere, the generator will ignore.
71- // uri: url
72- // email: ["email", "$2"]
73- // ```
74- //
75- // This function convterts to like below
76- // {
77- // 'uri': ['url', '$2'],
78- // 'email': ['email'],
79- // }
80- export function formatDirectiveObjectArguments ( args : DirectiveObjectArguments ) : FormattedDirectiveObjectArguments {
81- const formatted = Object . entries ( args ) . map ( ( [ arg , val ] ) => {
82- if ( Array . isArray ( val ) )
83- return [ arg , val ] ;
84-
85- return [ arg , [ val , '$2' ] ] ;
86- } ) ;
87- return Object . fromEntries ( formatted ) ;
88- }
89-
907// This function generates `.required("message").min(100).email()`
918//
929// config
@@ -109,13 +26,19 @@ export function formatDirectiveObjectArguments(args: DirectiveObjectArguments):
10926// email: String! @required(msg: "message") @constraint(minLength: 100, format: "email")
11027// }
11128// ```
112- export function buildApi ( config : FormattedDirectiveConfig , directives : ReadonlyArray < ConstDirectiveNode > ) : string {
29+ export function buildApi ( config : DirectiveConfig , directives : ReadonlyArray < ConstDirectiveNode > ) : string {
11330 return directives
11431 . filter ( directive => config [ directive . name . value ] !== undefined )
11532 . map ( ( directive ) => {
11633 const directiveName = directive . name . value ;
117- const argsConfig = config [ directiveName ] ;
118- return buildApiFromDirectiveArguments ( argsConfig , directive . arguments ?? [ ] ) ;
34+ const directiveConfig = config [ directiveName ] ;
35+ if ( typeof directiveConfig === 'string' ) {
36+ return `.${ directiveConfig } ()` ;
37+ }
38+ if ( typeof directiveConfig === 'function' ) {
39+ return directiveConfig ( directiveArgs ( directive ) ) ;
40+ }
41+ return buildApiFromDirectiveArguments ( directiveConfig , directive . arguments ?? [ ] ) ;
11942 } )
12043 . join ( '' )
12144}
@@ -142,20 +65,30 @@ export function buildApi(config: FormattedDirectiveConfig, directives: ReadonlyA
14265// ```
14366//
14467// FIXME: v.required() is not supported yet. v.required() is classified as `Methods` and must wrap the schema. ex) `v.required(v.object({...}))`
145- export function buildApiForValibot ( config : FormattedDirectiveConfig , directives : ReadonlyArray < ConstDirectiveNode > ) : string [ ] {
68+ export function buildApiForValibot ( config : DirectiveConfig , directives : ReadonlyArray < ConstDirectiveNode > ) : string [ ] {
14669 return directives
14770 . filter ( directive => config [ directive . name . value ] !== undefined )
14871 . map ( ( directive ) => {
14972 const directiveName = directive . name . value ;
150- const argsConfig = config [ directiveName ] ;
151- const apis = _buildApiFromDirectiveArguments ( argsConfig , directive . arguments ?? [ ] ) ;
73+ const directiveConfig = config [ directiveName ] ;
74+ if ( typeof directiveConfig === 'string' ) {
75+ return `.${ directiveConfig } ()` ;
76+ }
77+ if ( typeof directiveConfig === 'function' ) {
78+ return directiveConfig ( directiveArgs ( directive ) ) ;
79+ }
80+ const apis = _buildApiFromDirectiveArguments ( directiveConfig , directive . arguments ?? [ ] ) ;
15281 return apis . map ( api => `v${ api } ` ) ;
15382 } ) . flat ( )
15483}
15584
156- function buildApiSchema ( validationSchema : string [ ] | undefined , argValue : ConstValueNode ) : string {
157- if ( ! validationSchema )
85+ function buildApiSchema ( validationSchema : string | string [ ] | undefined , argValue : ConstValueNode ) : string {
86+ if ( ! validationSchema ) {
15887 return '' ;
88+ }
89+ if ( ! Array . isArray ( validationSchema ) ) {
90+ return `.${ validationSchema } ()`
91+ }
15992
16093 const schemaApi = validationSchema [ 0 ] ;
16194 const schemaApiArgs = validationSchema . slice ( 1 ) . map ( ( templateArg ) => {
@@ -165,27 +98,39 @@ function buildApiSchema(validationSchema: string[] | undefined, argValue: ConstV
16598 return `.${ schemaApi } (${ schemaApiArgs . join ( ', ' ) } )` ;
16699}
167100
168- function buildApiFromDirectiveArguments ( config : FormattedDirectiveArguments , args : ReadonlyArray < ConstArgumentNode > ) : string {
101+ function buildApiFromDirectiveArguments ( config : SingleDirectiveConfig , args : ReadonlyArray < ConstArgumentNode > ) : string {
169102 return _buildApiFromDirectiveArguments ( config , args ) . join ( '' ) ;
170103}
171104
172- function _buildApiFromDirectiveArguments ( config : FormattedDirectiveArguments , args : ReadonlyArray < ConstArgumentNode > ) : string [ ] {
105+ function _buildApiFromDirectiveArguments ( config : SingleDirectiveConfig , args : ReadonlyArray < ConstArgumentNode > ) : string [ ] {
173106 return args
174107 . map ( ( arg ) => {
175108 const argName = arg . name . value ;
176109 const validationSchema = config [ argName ] ;
177- if ( isFormattedDirectiveObjectArguments ( validationSchema ) )
178- return buildApiFromDirectiveObjectArguments ( validationSchema , arg . value ) ;
179-
180- return buildApiSchema ( validationSchema , arg . value ) ;
110+ if ( ! validationSchema ) {
111+ return ''
112+ }
113+ if ( typeof validationSchema === 'function' ) {
114+ return validationSchema ( valueFromASTUntyped ( arg . value ) ) ;
115+ }
116+ if ( typeof validationSchema === 'string' ) {
117+ return buildApiSchema ( [ validationSchema , '$1' ] , arg . value ) ;
118+ }
119+ if ( Array . isArray ( validationSchema ) ) {
120+ return buildApiSchema ( validationSchema , arg . value ) ;
121+ }
122+ return buildApiFromDirectiveObjectArguments ( validationSchema , arg . value ) ;
181123 } )
182124}
183125
184- function buildApiFromDirectiveObjectArguments ( config : FormattedDirectiveObjectArguments , argValue : ConstValueNode ) : string {
185- if ( argValue . kind !== Kind . STRING && argValue . kind !== Kind . ENUM )
126+ function buildApiFromDirectiveObjectArguments ( config : DirectiveObjectArguments , argValue : ConstValueNode ) : string {
127+ if ( argValue . kind !== Kind . STRING && argValue . kind !== Kind . ENUM ) {
186128 return '' ;
187-
129+ }
188130 const validationSchema = config [ argValue . value ] ;
131+ if ( typeof validationSchema === 'function' ) {
132+ return validationSchema ( ) ;
133+ }
189134 return buildApiSchema ( validationSchema , argValue ) ;
190135}
191136
@@ -240,6 +185,13 @@ function apiArgsFromConstValueNode(value: ConstValueNode): any[] {
240185 return [ val ] ;
241186}
242187
188+ function directiveArgs ( directive : ConstDirectiveNode ) : Record < string , any > {
189+ if ( ! directive . arguments ) {
190+ return { }
191+ }
192+ return Object . fromEntries ( directive . arguments . map ( arg => [ arg . name . value , valueFromASTUntyped ( arg . value ) ] ) )
193+ }
194+
243195function tryEval ( maybeValidJavaScript : string ) : any | undefined {
244196 try {
245197 // eslint-disable-next-line no-eval
0 commit comments