@@ -472,6 +472,7 @@ export abstract class ZodType<
472472type ZodStringCheck =
473473 | { kind : "min" ; value : number ; message ?: string }
474474 | { kind : "max" ; value : number ; message ?: string }
475+ | { kind : "length" ; value : number ; message ?: string }
475476 | { kind : "email" ; message ?: string }
476477 | { kind : "url" ; message ?: string }
477478 | { kind : "uuid" ; message ?: string }
@@ -572,6 +573,7 @@ export class ZodString extends ZodType<string, ZodStringDef> {
572573 minimum : check . value ,
573574 type : "string" ,
574575 inclusive : true ,
576+ exact : false ,
575577 message : check . message ,
576578 } ) ;
577579 status . dirty ( ) ;
@@ -584,10 +586,37 @@ export class ZodString extends ZodType<string, ZodStringDef> {
584586 maximum : check . value ,
585587 type : "string" ,
586588 inclusive : true ,
589+ exact : false ,
587590 message : check . message ,
588591 } ) ;
589592 status . dirty ( ) ;
590593 }
594+ } else if ( check . kind === "length" ) {
595+ const tooBig = input . data . length > check . value ;
596+ const tooSmall = input . data . length < check . value ;
597+ if ( tooBig || tooSmall ) {
598+ ctx = this . _getOrReturnCtx ( input , ctx ) ;
599+ if ( tooBig ) {
600+ addIssueToContext ( ctx , {
601+ code : ZodIssueCode . too_big ,
602+ maximum : check . value ,
603+ type : "string" ,
604+ inclusive : true ,
605+ exact : true ,
606+ message : check . message ,
607+ } ) ;
608+ } else if ( tooSmall ) {
609+ addIssueToContext ( ctx , {
610+ code : ZodIssueCode . too_small ,
611+ minimum : check . value ,
612+ type : "string" ,
613+ inclusive : true ,
614+ exact : true ,
615+ message : check . message ,
616+ } ) ;
617+ }
618+ status . dirty ( ) ;
619+ }
591620 } else if ( check . kind === "email" ) {
592621 if ( ! emailRegex . test ( input . data ) ) {
593622 ctx = this . _getOrReturnCtx ( input , ctx ) ;
@@ -781,10 +810,11 @@ export class ZodString extends ZodType<string, ZodStringDef> {
781810 }
782811
783812 length ( len : number , message ?: errorUtil . ErrMessage ) {
784- const defaultMessage = `String must contain exactly ${ len } character(s)` ;
785- const actualMessage = message ?? defaultMessage ;
786-
787- return this . min ( len , actualMessage ) . max ( len , actualMessage ) ;
813+ return this . _addCheck ( {
814+ kind : "length" ,
815+ value : len ,
816+ ...errorUtil . errToObj ( message ) ,
817+ } ) ;
788818 }
789819
790820 /**
@@ -913,6 +943,7 @@ export class ZodNumber extends ZodType<number, ZodNumberDef> {
913943 minimum : check . value ,
914944 type : "number" ,
915945 inclusive : check . inclusive ,
946+ exact : false ,
916947 message : check . message ,
917948 } ) ;
918949 status . dirty ( ) ;
@@ -928,6 +959,7 @@ export class ZodNumber extends ZodType<number, ZodNumberDef> {
928959 maximum : check . value ,
929960 type : "number" ,
930961 inclusive : check . inclusive ,
962+ exact : false ,
931963 message : check . message ,
932964 } ) ;
933965 status . dirty ( ) ;
@@ -1214,6 +1246,7 @@ export class ZodDate extends ZodType<Date, ZodDateDef> {
12141246 code : ZodIssueCode . too_small ,
12151247 message : check . message ,
12161248 inclusive : true ,
1249+ exact : false ,
12171250 minimum : check . value ,
12181251 type : "date" ,
12191252 } ) ;
@@ -1226,6 +1259,7 @@ export class ZodDate extends ZodType<Date, ZodDateDef> {
12261259 code : ZodIssueCode . too_big ,
12271260 message : check . message ,
12281261 inclusive : true ,
1262+ exact : false ,
12291263 maximum : check . value ,
12301264 type : "date" ,
12311265 } ) ;
@@ -1524,6 +1558,7 @@ export interface ZodArrayDef<T extends ZodTypeAny = ZodTypeAny>
15241558 extends ZodTypeDef {
15251559 type : T ;
15261560 typeName : ZodFirstPartyTypeKind . ZodArray ;
1561+ exactLength : { value : number ; message ?: string } | null ;
15271562 minLength : { value : number ; message ?: string } | null ;
15281563 maxLength : { value : number ; message ?: string } | null ;
15291564}
@@ -1560,13 +1595,31 @@ export class ZodArray<
15601595 return INVALID ;
15611596 }
15621597
1598+ if ( def . exactLength !== null ) {
1599+ const tooBig = ctx . data . length > def . exactLength . value ;
1600+ const tooSmall = ctx . data . length < def . exactLength . value ;
1601+ if ( tooBig || tooSmall ) {
1602+ addIssueToContext ( ctx , {
1603+ code : tooBig ? ZodIssueCode . too_big : ZodIssueCode . too_small ,
1604+ minimum : ( tooSmall ? def . exactLength . value : undefined ) as number ,
1605+ maximum : ( tooBig ? def . exactLength . value : undefined ) as number ,
1606+ type : "array" ,
1607+ inclusive : true ,
1608+ exact : true ,
1609+ message : def . exactLength . message ,
1610+ } ) ;
1611+ status . dirty ( ) ;
1612+ }
1613+ }
1614+
15631615 if ( def . minLength !== null ) {
15641616 if ( ctx . data . length < def . minLength . value ) {
15651617 addIssueToContext ( ctx , {
15661618 code : ZodIssueCode . too_small ,
15671619 minimum : def . minLength . value ,
15681620 type : "array" ,
15691621 inclusive : true ,
1622+ exact : false ,
15701623 message : def . minLength . message ,
15711624 } ) ;
15721625 status . dirty ( ) ;
@@ -1580,6 +1633,7 @@ export class ZodArray<
15801633 maximum : def . maxLength . value ,
15811634 type : "array" ,
15821635 inclusive : true ,
1636+ exact : false ,
15831637 message : def . maxLength . message ,
15841638 } ) ;
15851639 status . dirty ( ) ;
@@ -1626,10 +1680,10 @@ export class ZodArray<
16261680 }
16271681
16281682 length ( len : number , message ?: errorUtil . ErrMessage ) : this {
1629- const defaultMessage = `Array must contain exactly ${ len } element(s)` ;
1630- const actualMessage = message ?? defaultMessage ;
1631-
1632- return this . min ( len , actualMessage ) . max ( len , actualMessage ) as any ;
1683+ return new ZodArray ( {
1684+ ... this . _def ,
1685+ exactLength : { value : len , message : errorUtil . toString ( message ) } ,
1686+ } ) as any ;
16331687 }
16341688
16351689 nonempty ( message ?: errorUtil . ErrMessage ) : ZodArray < T , "atleastone" > {
@@ -1644,6 +1698,7 @@ export class ZodArray<
16441698 type : schema ,
16451699 minLength : null ,
16461700 maxLength : null ,
1701+ exactLength : null ,
16471702 typeName : ZodFirstPartyTypeKind . ZodArray ,
16481703 ...processCreateParams ( params ) ,
16491704 } ) ;
@@ -2706,6 +2761,7 @@ export class ZodTuple<
27062761 code : ZodIssueCode . too_small ,
27072762 minimum : this . _def . items . length ,
27082763 inclusive : true ,
2764+ exact : false ,
27092765 type : "array" ,
27102766 } ) ;
27112767
@@ -2719,6 +2775,7 @@ export class ZodTuple<
27192775 code : ZodIssueCode . too_big ,
27202776 maximum : this . _def . items . length ,
27212777 inclusive : true ,
2778+ exact : false ,
27222779 type : "array" ,
27232780 } ) ;
27242781 status . dirty ( ) ;
@@ -3017,6 +3074,7 @@ export class ZodSet<Value extends ZodTypeAny = ZodTypeAny> extends ZodType<
30173074 minimum : def . minSize . value ,
30183075 type : "set" ,
30193076 inclusive : true ,
3077+ exact : false ,
30203078 message : def . minSize . message ,
30213079 } ) ;
30223080 status . dirty ( ) ;
@@ -3030,6 +3088,7 @@ export class ZodSet<Value extends ZodTypeAny = ZodTypeAny> extends ZodType<
30303088 maximum : def . maxSize . value ,
30313089 type : "set" ,
30323090 inclusive : true ,
3091+ exact : false ,
30333092 message : def . maxSize . message ,
30343093 } ) ;
30353094 status . dirty ( ) ;
0 commit comments