22 * Represents an argument condition used during verification.
33 */
44export class Arg {
5- private _condition : ( value : any , args : ReadonlyArray < any > , index : number ) => { valid : boolean , next ?: number } ;
5+ private _validate : ( value : any ) => boolean ;
66 private _message : string ;
7+ private _rest : boolean ;
78
8- private constructor ( condition : ( value : any , args : ReadonlyArray < any > , index : number ) => { valid : boolean , next ?: number } , message : string ) {
9- this . _condition = condition ;
9+ private constructor ( condition : ( value : any ) => boolean , message : string , rest = false ) {
10+ this . _validate = condition ;
1011 this . _message = message ;
12+ this . _rest = rest ;
1113 }
1214
1315 /**
1416 * Allows any value.
1517 */
1618 public static any < T = any > ( ) : T & Arg {
17- return < any > new Arg ( ( ) => ( { valid : true } ) , `any` ) ;
19+ return < any > new Arg ( ( ) => true , `any` ) ;
1820 }
1921
2022 /**
2123 * Allows a value that matches the specified condition.
2224 * @param match The condition used to match the value.
2325 */
2426 public static is < T = any > ( match : ( value : T ) => boolean ) : T & Arg {
25- return < any > new Arg ( value => ( { valid : match ( value ) } ) , `is` ) ;
27+ return < any > new Arg ( match , `is` ) ;
2628 }
2729
2830 /**
2931 * Allows only a null value.
3032 */
3133 public static null < T = any > ( ) : T & Arg {
32- return < any > new Arg ( value => ( { valid : value === null } ) , `null` ) ;
34+ return < any > new Arg ( value => value === null , `null` ) ;
3335 }
3436
3537 /**
@@ -43,7 +45,7 @@ export class Arg {
4345 * Allows only an undefined value.
4446 */
4547 public static undefined < T = any > ( ) : T & Arg {
46- return < any > new Arg ( value => ( { valid : value === undefined } ) , `undefined` ) ;
48+ return < any > new Arg ( value => value === undefined , `undefined` ) ;
4749 }
4850
4951 /**
@@ -67,20 +69,24 @@ export class Arg {
6769 return Arg . not ( Arg . nullOrUndefined ( ) ) ;
6870 }
6971
72+ public static optional < T = any > ( condition : T | T & Arg ) : T & Arg {
73+ return Arg . or ( condition , Arg . undefined ( ) ) ;
74+ }
75+
7076 /**
7177 * Allows any value within the provided range.
7278 * @param min The minimum value.
7379 * @param max The maximum value.
7480 */
7581 public static between < T = any > ( min : T , max : T ) : T & Arg {
76- return < any > new Arg ( value => ( { valid : min <= value && value <= max } ) , `between ${ min } and ${ max } ` ) ;
82+ return < any > new Arg ( value => min <= value && value <= max , `between ${ min } and ${ max } ` ) ;
7783 }
7884
7985 /**
8086 * Allows any value in the provided array.
8187 */
8288 public static in < T = any > ( values : T [ ] ) : T & Arg {
83- return < any > new Arg ( value => ( { valid : values . indexOf ( value ) > - 1 } ) , `in ${ values . join ( ", " ) } ` ) ;
89+ return < any > new Arg ( value => values . indexOf ( value ) > - 1 , `in ${ values . join ( ", " ) } ` ) ;
8490 }
8591
8692 /**
@@ -94,19 +100,26 @@ export class Arg {
94100 * Allows any value that matches the provided pattern.
95101 */
96102 public static match < T = any > ( pattern : RegExp ) : T & Arg {
97- return < any > new Arg ( value => ( { valid : pattern . test ( value ) } ) , `matches ${ pattern } ` ) ;
103+ return < any > new Arg ( value => pattern . test ( value ) , `matches ${ pattern } ` ) ;
98104 }
99105
100106 public static startsWith ( text : string ) : string & Arg {
101- return < any > new Arg ( value => ( { valid : String ( value ) . startsWith ( text ) } ) , `starts with ${ text } ` ) ;
107+ return < any > new Arg ( value => typeof value === "string" && value . startsWith ( text ) , `starts with ${ text } ` ) ;
102108 }
103109
104110 public static endsWith ( text : string ) : string & Arg {
105- return < any > new Arg ( value => ( { valid : String ( value ) . endsWith ( text ) } ) , `ends with ${ text } ` ) ;
111+ return < any > new Arg ( value => typeof value === "string" && value . endsWith ( text ) , `ends with ${ text } ` ) ;
112+ }
113+
114+ public static includes ( value : string ) : string & string [ ] & Arg ;
115+ public static includes < T > ( value : T ) : T [ ] & Arg ;
116+ public static includes < T > ( value : T ) : Arg {
117+ return new Arg ( value_ => Array . isArray ( value_ ) ? value_ . includes ( value ) : typeof value_ === "string" && value_ . includes ( "" + value ) , `contains ${ value } ` ) ;
106118 }
107119
108- public static includes ( text : string ) : string & Arg {
109- return < any > new Arg ( value => ( { valid : String ( value ) . includes ( text ) } ) , `contains ${ text } ` ) ;
120+ public static array < T > ( values : ( T | T & Arg ) [ ] ) : T [ ] & Arg {
121+ const conditions = values . map ( Arg . from ) ;
122+ return < any > new Arg ( value => value . length === conditions . length && Arg . validateAll ( conditions , value ) , `array [${ conditions . join ( ", " ) } ]` ) ;
110123 }
111124
112125 /**
@@ -142,7 +155,7 @@ export class Arg {
142155 */
143156 public static typeof < T = any > ( tag : string ) : T & Arg ;
144157 public static typeof ( tag : string ) : any {
145- return < any > new Arg ( value => ( { valid : typeof value === tag } ) , `typeof ${ tag } ` ) ;
158+ return < any > new Arg ( value => typeof value === tag , `typeof ${ tag } ` ) ;
146159 }
147160
148161 public static string ( ) { return this . typeof ( "string" ) ; }
@@ -157,21 +170,21 @@ export class Arg {
157170 * @param type The expected constructor.
158171 */
159172 public static instanceof < TClass extends { new ( ...args : any [ ] ) : object ; prototype : object ; } > ( type : TClass ) : TClass [ "prototype" ] & Arg {
160- return < any > new Arg ( value => ( { valid : value instanceof type } ) , `instanceof ${ type . name } ` ) ;
173+ return < any > new Arg ( value => value instanceof type , `instanceof ${ type . name } ` ) ;
161174 }
162175
163176 /**
164177 * Allows any value that has the provided property names in its prototype chain.
165178 */
166179 public static has < T > ( ...names : string [ ] ) : T & Arg {
167- return < any > new Arg ( value => ( { valid : names . filter ( name => name in value ) . length === names . length } ) , `has ${ names . join ( ", " ) } ` ) ;
180+ return < any > new Arg ( value => names . filter ( name => name in value ) . length === names . length , `has ${ names . join ( ", " ) } ` ) ;
168181 }
169182
170183 /**
171184 * Allows any value that has the provided property names on itself but not its prototype chain.
172185 */
173186 public static hasOwn < T > ( ...names : string [ ] ) : T & Arg {
174- return < any > new Arg ( value => ( { valid : names . filter ( name => Object . prototype . hasOwnProperty . call ( value , name ) ) . length === names . length } ) , `hasOwn ${ names . join ( ", " ) } ` ) ;
187+ return < any > new Arg ( value => names . filter ( name => Object . prototype . hasOwnProperty . call ( value , name ) ) . length === names . length , `hasOwn ${ names . join ( ", " ) } ` ) ;
175188 }
176189
177190 /**
@@ -180,74 +193,51 @@ export class Arg {
180193 */
181194 public static rest < T > ( condition ?: T | ( T & Arg ) ) : T & Arg {
182195 if ( condition === undefined ) {
183- return < any > new Arg ( ( _ , args ) => ( { valid : true , next : args . length } ) , `rest` ) ;
196+ return < any > new Arg ( ( ) => true , `rest` , /*rest*/ true ) ;
184197 }
185198
186199 const arg = Arg . from ( condition ) ;
187- return < any > new Arg (
188- ( _ , args , index ) => {
189- while ( index < args . length ) {
190- const { valid, next } = Arg . validate ( arg , args , index ) ;
191- if ( ! valid ) return { valid : false } ;
192- index = typeof next === "undefined" ? index + 1 : next ;
193- }
194- return { valid : true , next : index } ;
195- } ,
196- `rest ${ arg . _message } `
197- ) ;
200+ return < any > new Arg ( value => arg . _validate ( value ) , `rest ${ arg . _message } ` , /*rest*/ true ) ;
198201 }
199202
200203 /**
201204 * Negates a condition.
202205 */
203206 public static not < T = any > ( value : T | ( T & Arg ) ) : T & Arg {
204207 const arg = Arg . from ( value ) ;
205- return < any > new Arg ( ( value , args , index ) => {
206- const result = arg . _condition ( value , args , index ) ;
207- return { valid : ! result . valid , next : result . next } ;
208- } , `not ${ arg . _message } ` ) ;
208+ return < any > new Arg ( value => ! arg . _validate ( value ) , `not ${ arg . _message } ` ) ;
209209 }
210210
211211 /**
212212 * Combines conditions, where all conditions must be `true`.
213213 */
214214 public static and < T = any > ( ...args : ( ( T & Arg ) | T ) [ ] ) : T & Arg {
215215 const conditions = args . map ( Arg . from ) ;
216- return < any > new Arg ( ( value , args , index ) => {
217- for ( const condition of conditions ) {
218- const result = condition . _condition ( value , args , index ) ;
219- if ( ! result . valid ) return { valid : false } ;
220- }
221- return { valid : true } ;
222- } , conditions . map ( condition => condition . _message ) . join ( " and " ) ) ;
216+ return < any > new Arg ( value => conditions . every ( condition => condition . _validate ( value ) ) , conditions . map ( condition => condition . _message ) . join ( " and " ) ) ;
223217 }
224218
225219 /**
226220 * Combines conditions, where no condition may be `true`.
227221 */
228222 public static nand < T = any > ( ...args : ( ( T & Arg ) | T ) [ ] ) : T & Arg {
229- return this . not ( this . and ( ...args ) ) ;
223+ const conditions = args . map ( Arg . from ) ;
224+ return < any > new Arg ( value => ! conditions . every ( condition => condition . _validate ( value ) ) , "not " + conditions . map ( condition => condition . _message ) . join ( " and " ) ) ;
230225 }
231226
232227 /**
233228 * Combines conditions, where any conditions may be `true`.
234229 */
235230 public static or < T = any > ( ...args : ( ( T & Arg ) | T ) [ ] ) : T & Arg {
236231 const conditions = args . map ( Arg . from ) ;
237- return < any > new Arg ( ( value , args , index ) => {
238- for ( const condition of conditions ) {
239- const result = condition . _condition ( value , args , index ) ;
240- if ( result . valid ) return { valid : true } ;
241- }
242- return { valid : false } ;
243- } , conditions . map ( condition => condition . _message ) . join ( " or " ) ) ;
232+ return < any > new Arg ( value => conditions . some ( condition => condition . _validate ( value ) ) , conditions . map ( condition => condition . _message ) . join ( " or " ) ) ;
244233 }
245234
246235 /**
247236 * Combines conditions, where all conditions must be `true`.
248237 */
249238 public static nor < T = any > ( ...args : ( ( T & Arg ) | T ) [ ] ) : T & Arg {
250- return this . not ( this . or ( ...args ) ) ;
239+ const conditions = args . map ( Arg . from ) ;
240+ return < any > new Arg ( value => ! conditions . some ( condition => condition . _validate ( value ) ) , "neither " + conditions . map ( condition => condition . _message ) . join ( " nor " ) ) ;
251241 }
252242
253243 /**
@@ -256,25 +246,40 @@ export class Arg {
256246 * @returns The condition
257247 */
258248 public static from < T > ( value : T ) : T & Arg {
259- if ( value instanceof Arg ) {
260- return value ;
261- }
249+ return value instanceof Arg ? value :
250+ value === undefined ? Arg . undefined ( ) :
251+ value === null ? Arg . null ( ) :
252+ < any > new Arg ( v => is ( v , value ) , JSON . stringify ( value ) ) ;
253+ }
262254
263- return < any > new Arg ( v => ( { valid : is ( v , value ) } ) , JSON . stringify ( value ) ) ;
255+ /**
256+ * Validates an argument against a condition
257+ * @param condition The condition to validate.
258+ * @param arg The argument to validate against the condition.
259+ */
260+ public static validate ( condition : Arg , arg : any ) : boolean {
261+ return condition . _validate ( arg ) ;
264262 }
265263
266264 /**
267265 * Validates the arguments against the condition.
268- * @param args The arguments for the execution
269- * @param index The current index into the `args` array
270- * @returns An object that specifies whether the condition is `valid` and what the `next` index should be.
271- */
272- public static validate ( arg : Arg , args : ReadonlyArray < any > , index : number ) : { valid : boolean , next ?: number } {
273- const value = index >= 0 && index < args . length ? args [ index ] : undefined ;
274- const { valid, next } = arg . _condition ( value , args , index ) ;
275- return valid
276- ? { valid : true , next : next === undefined ? index + 1 : next }
277- : { valid : false } ;
266+ * @param conditions The conditions to validate.
267+ * @param args The arguments for the execution.
268+ */
269+ public static validateAll ( conditions : ReadonlyArray < Arg > , args : ReadonlyArray < any > ) : boolean {
270+ const length = Math . max ( conditions . length , args . length ) ;
271+ let conditionIndex = 0 ;
272+ let argIndex = 0 ;
273+ while ( argIndex < length ) {
274+ const condition = conditionIndex < conditions . length ? conditions [ conditionIndex ] : undefined ;
275+ const arg = argIndex < args . length ? args [ argIndex ] : undefined ;
276+ if ( ! condition ) return false ;
277+ if ( argIndex >= args . length && condition . _rest ) return true ;
278+ if ( ! condition . _validate ( arg ) ) return false ;
279+ if ( ! condition . _rest ) conditionIndex ++ ;
280+ argIndex ++ ;
281+ }
282+ return true ;
278283 }
279284
280285 /**
0 commit comments