@@ -8,6 +8,7 @@ import type {
88 MemberExpression ,
99 UnaryExpression ,
1010} from '@zenstackhq/sdk/schema' ;
11+ import { Decimal } from 'decimal.js' ;
1112import { match , P } from 'ts-pattern' ;
1213import { z } from 'zod' ;
1314import { ExpressionUtils } from '../../../schema' ;
@@ -25,95 +26,180 @@ export function addStringValidation(schema: z.ZodString, attributes: AttributeAp
2526 return schema ;
2627 }
2728
29+ let result = schema ;
2830 for ( const attr of attributes ) {
2931 match ( attr . name )
3032 . with ( '@length' , ( ) => {
3133 const min = getArgValue < number > ( attr . args ?. [ 0 ] ?. value ) ;
3234 if ( min !== undefined ) {
33- schema = schema . min ( min ) ;
35+ result = result . min ( min ) ;
3436 }
3537 const max = getArgValue < number > ( attr . args ?. [ 1 ] ?. value ) ;
3638 if ( max !== undefined ) {
37- schema = schema . max ( max ) ;
39+ result = result . max ( max ) ;
3840 }
3941 } )
4042 . with ( '@startsWith' , ( ) => {
4143 const value = getArgValue < string > ( attr . args ?. [ 0 ] ?. value ) ;
4244 if ( value !== undefined ) {
43- schema = schema . startsWith ( value ) ;
45+ result = result . startsWith ( value ) ;
4446 }
4547 } )
4648 . with ( '@endsWith' , ( ) => {
4749 const value = getArgValue < string > ( attr . args ?. [ 0 ] ?. value ) ;
4850 if ( value !== undefined ) {
49- schema = schema . endsWith ( value ) ;
51+ result = result . endsWith ( value ) ;
5052 }
5153 } )
5254 . with ( '@contains' , ( ) => {
5355 const value = getArgValue < string > ( attr . args ?. [ 0 ] ?. value ) ;
5456 if ( value !== undefined ) {
55- schema = schema . includes ( value ) ;
57+ result = result . includes ( value ) ;
5658 }
5759 } )
5860 . with ( '@regex' , ( ) => {
5961 const pattern = getArgValue < string > ( attr . args ?. [ 0 ] ?. value ) ;
6062 if ( pattern !== undefined ) {
61- schema = schema . regex ( new RegExp ( pattern ) ) ;
63+ result = result . regex ( new RegExp ( pattern ) ) ;
6264 }
6365 } )
6466 . with ( '@email' , ( ) => {
65- schema = schema . email ( ) ;
67+ result = result . email ( ) ;
6668 } )
6769 . with ( '@datetime' , ( ) => {
68- schema = schema . datetime ( ) ;
70+ result = result . datetime ( ) ;
6971 } )
7072 . with ( '@url' , ( ) => {
71- schema = schema . url ( ) ;
73+ result = result . url ( ) ;
7274 } )
7375 . with ( '@trim' , ( ) => {
74- schema = schema . trim ( ) ;
76+ result = result . trim ( ) ;
7577 } )
7678 . with ( '@lower' , ( ) => {
77- schema = schema . toLowerCase ( ) ;
79+ result = result . toLowerCase ( ) ;
7880 } )
7981 . with ( '@upper' , ( ) => {
80- schema = schema . toUpperCase ( ) ;
82+ result = result . toUpperCase ( ) ;
8183 } ) ;
8284 }
83- return schema ;
85+ return result ;
8486}
8587
8688export function addNumberValidation ( schema : z . ZodNumber , attributes : AttributeApplication [ ] | undefined ) : z . ZodSchema {
8789 if ( ! attributes || attributes . length === 0 ) {
8890 return schema ;
8991 }
9092
93+ let result = schema ;
9194 for ( const attr of attributes ) {
9295 const val = getArgValue < number > ( attr . args ?. [ 0 ] ?. value ) ;
9396 if ( val === undefined ) {
9497 continue ;
9598 }
9699 match ( attr . name )
97100 . with ( '@gt' , ( ) => {
98- schema = schema . gt ( val ) ;
101+ result = result . gt ( val ) ;
99102 } )
100103 . with ( '@gte' , ( ) => {
101- schema = schema . gte ( val ) ;
104+ result = result . gte ( val ) ;
102105 } )
103106 . with ( '@lt' , ( ) => {
104- schema = schema . lt ( val ) ;
107+ result = result . lt ( val ) ;
105108 } )
106109 . with ( '@lte' , ( ) => {
107- schema = schema . lte ( val ) ;
110+ result = result . lte ( val ) ;
111+ } ) ;
112+ }
113+ return result ;
114+ }
115+
116+ export function addBigIntValidation ( schema : z . ZodBigInt , attributes : AttributeApplication [ ] | undefined ) : z . ZodSchema {
117+ if ( ! attributes || attributes . length === 0 ) {
118+ return schema ;
119+ }
120+
121+ let result = schema ;
122+ for ( const attr of attributes ) {
123+ const val = getArgValue < number > ( attr . args ?. [ 0 ] ?. value ) ;
124+ if ( val === undefined ) {
125+ continue ;
126+ }
127+ const bigIntVal = BigInt ( val ) ;
128+ match ( attr . name )
129+ . with ( '@gt' , ( ) => {
130+ result = result . gt ( bigIntVal ) ;
131+ } )
132+ . with ( '@gte' , ( ) => {
133+ result = result . gte ( bigIntVal ) ;
108134 } )
109135 . with ( '@lt' , ( ) => {
110- schema = schema . lt ( val ) ;
136+ result = result . lt ( bigIntVal ) ;
111137 } )
112138 . with ( '@lte' , ( ) => {
113- schema = schema . lte ( val ) ;
139+ result = result . lte ( bigIntVal ) ;
114140 } ) ;
115141 }
116- return schema ;
142+ return result ;
143+ }
144+
145+ export function addDecimalValidation (
146+ schema : z . ZodType < Decimal > | z . ZodString ,
147+ attributes : AttributeApplication [ ] | undefined ,
148+ ) : z . ZodSchema {
149+ let result : z . ZodSchema = schema ;
150+
151+ // parse string to Decimal
152+ if ( schema instanceof z . ZodString ) {
153+ result = schema
154+ . superRefine ( ( v , ctx ) => {
155+ try {
156+ new Decimal ( v ) ;
157+ } catch ( err ) {
158+ ctx . addIssue ( {
159+ code : z . ZodIssueCode . custom ,
160+ message : `Invalid decimal: ${ err } ` ,
161+ } ) ;
162+ }
163+ } )
164+ . transform ( ( val ) => new Decimal ( val ) ) ;
165+ }
166+
167+ // add validations
168+
169+ function refine ( schema : z . ZodSchema , op : 'gt' | 'gte' | 'lt' | 'lte' , value : number ) {
170+ return schema . superRefine ( ( v , ctx ) => {
171+ const base = z . number ( ) ;
172+ const { error } = base [ op ] ( value ) . safeParse ( ( v as Decimal ) . toNumber ( ) ) ;
173+ error ?. errors . forEach ( ( e ) => {
174+ ctx . addIssue ( e ) ;
175+ } ) ;
176+ } ) ;
177+ }
178+
179+ if ( attributes ) {
180+ for ( const attr of attributes ) {
181+ const val = getArgValue < number > ( attr . args ?. [ 0 ] ?. value ) ;
182+ if ( val === undefined ) {
183+ continue ;
184+ }
185+
186+ match ( attr . name )
187+ . with ( '@gt' , ( ) => {
188+ result = refine ( result , 'gt' , val ) ;
189+ } )
190+ . with ( '@gte' , ( ) => {
191+ result = refine ( result , 'gte' , val ) ;
192+ } )
193+ . with ( '@lt' , ( ) => {
194+ result = refine ( result , 'lt' , val ) ;
195+ } )
196+ . with ( '@lte' , ( ) => {
197+ result = refine ( result , 'lte' , val ) ;
198+ } ) ;
199+ }
200+ }
201+
202+ return result ;
117203}
118204
119205export function addCustomValidation ( schema : z . ZodSchema , attributes : AttributeApplication [ ] | undefined ) : z . ZodSchema {
@@ -122,6 +208,7 @@ export function addCustomValidation(schema: z.ZodSchema, attributes: AttributeAp
122208 return schema ;
123209 }
124210
211+ let result = schema ;
125212 for ( const attr of attrs ) {
126213 const expr = attr . args ?. [ 0 ] ?. value ;
127214 if ( ! expr ) {
@@ -133,9 +220,9 @@ export function addCustomValidation(schema: z.ZodSchema, attributes: AttributeAp
133220 if ( pathExpr && ExpressionUtils . isArray ( pathExpr ) ) {
134221 path = pathExpr . items . map ( ( e ) => ExpressionUtils . getLiteralValue ( e ) as string ) ;
135222 }
136- schema = applyValidation ( schema , expr , message , path ) ;
223+ result = applyValidation ( result , expr , message , path ) ;
137224 }
138- return schema ;
225+ return result ;
139226}
140227
141228function applyValidation (
@@ -245,10 +332,10 @@ function evalCall(data: any, expr: CallExpression) {
245332
246333 const min = getArgValue < number > ( expr . args ?. [ 1 ] ) ;
247334 const max = getArgValue < number > ( expr . args ?. [ 2 ] ) ;
248- if ( min && fieldArg . length < min ) {
335+ if ( min !== undefined && fieldArg . length < min ) {
249336 return false ;
250337 }
251- if ( max && fieldArg . length > max ) {
338+ if ( max !== undefined && fieldArg . length > max ) {
252339 return false ;
253340 }
254341 return true ;
0 commit comments