@@ -2,6 +2,7 @@ import { assert, Equals } from "../index.js";
22import {
33 any ,
44 array ,
5+ arrayBuffer ,
56 bigint ,
67 boolean ,
78 brandedString ,
@@ -20,6 +21,7 @@ import {
2021 pretendRequired ,
2122 string ,
2223 typedV ,
24+ ValidationError ,
2325} from "../validators.js" ;
2426import { convexTest } from "convex-test" ;
2527import {
@@ -37,6 +39,7 @@ import { Infer, ObjectType } from "convex/values";
3739import { expect , test } from "vitest" ;
3840import { modules } from "./setup.test.js" ;
3941import { getOrThrow } from "convex-helpers/server/relationships" ;
42+ import { validate } from "../validators.js" ;
4043
4144export const testLiterals = internalQueryGeneric ( {
4245 args : {
@@ -268,3 +271,198 @@ test("validators disallow things when they're wrong", async () => {
268271 } as ExampleFields ) ;
269272 } ) . rejects . toThrowError ( "Validator error" ) ;
270273} ) ;
274+
275+ describe ( "validate" , ( ) => {
276+ test ( "validates primitive validators" , ( ) => {
277+ // String
278+ expect ( validate ( string , "hello" ) ) . toBe ( true ) ;
279+ expect ( validate ( string , 123 ) ) . toBe ( false ) ;
280+ expect ( validate ( string , null ) ) . toBe ( false ) ;
281+
282+ // Number
283+ expect ( validate ( number , 123 ) ) . toBe ( true ) ;
284+ expect ( validate ( number , "123" ) ) . toBe ( false ) ;
285+ expect ( validate ( number , null ) ) . toBe ( false ) ;
286+
287+ // Boolean
288+ expect ( validate ( boolean , true ) ) . toBe ( true ) ;
289+ expect ( validate ( boolean , false ) ) . toBe ( true ) ;
290+ expect ( validate ( boolean , "true" ) ) . toBe ( false ) ;
291+ expect ( validate ( boolean , 1 ) ) . toBe ( false ) ;
292+
293+ // Null
294+ expect ( validate ( null_ , null ) ) . toBe ( true ) ;
295+ expect ( validate ( null_ , undefined ) ) . toBe ( false ) ;
296+ expect ( validate ( null_ , false ) ) . toBe ( false ) ;
297+
298+ // BigInt/Int64
299+ expect ( validate ( bigint , 123n ) ) . toBe ( true ) ;
300+ expect ( validate ( bigint , 123 ) ) . toBe ( false ) ;
301+ expect ( validate ( bigint , "123" ) ) . toBe ( false ) ;
302+ } ) ;
303+
304+ test ( "validates array validator" , ( ) => {
305+ const arrayOfStrings = array ( string ) ;
306+ expect ( validate ( arrayOfStrings , [ "a" , "b" , "c" ] ) ) . toBe ( true ) ;
307+ expect ( validate ( arrayOfStrings , [ ] ) ) . toBe ( true ) ;
308+ expect ( validate ( arrayOfStrings , [ "a" , 1 , "c" ] ) ) . toBe ( false ) ;
309+ expect ( validate ( arrayOfStrings , null ) ) . toBe ( false ) ;
310+ expect ( validate ( arrayOfStrings , "not an array" ) ) . toBe ( false ) ;
311+ } ) ;
312+
313+ test ( "validates object validator" , ( ) => {
314+ const personValidator = object ( {
315+ name : string ,
316+ age : number ,
317+ optional : optional ( string ) ,
318+ } ) ;
319+
320+ expect ( validate ( personValidator , { name : "Alice" , age : 30 } ) ) . toBe ( true ) ;
321+ expect (
322+ validate ( personValidator , { name : "Bob" , age : 25 , optional : "test" } ) ,
323+ ) . toBe ( true ) ;
324+ expect ( validate ( personValidator , { name : "Charlie" , age : "30" } ) ) . toBe (
325+ false ,
326+ ) ;
327+ expect ( validate ( personValidator , { name : "Dave" } ) ) . toBe ( false ) ;
328+ expect ( validate ( personValidator , null ) ) . toBe ( false ) ;
329+ expect (
330+ validate ( personValidator , { name : "Eve" , age : 20 , extra : "field" } ) ,
331+ ) . toBe ( false ) ;
332+ } ) ;
333+
334+ test ( "validates union validator" , ( ) => {
335+ const unionValidator = or ( string , number , object ( { type : is ( "test" ) } ) ) ;
336+
337+ expect ( validate ( unionValidator , "string" ) ) . toBe ( true ) ;
338+ expect ( validate ( unionValidator , 123 ) ) . toBe ( true ) ;
339+ expect ( validate ( unionValidator , { type : "test" } ) ) . toBe ( true ) ;
340+ expect ( validate ( unionValidator , { type : "wrong" } ) ) . toBe ( false ) ;
341+ expect ( validate ( unionValidator , true ) ) . toBe ( false ) ;
342+ expect ( validate ( unionValidator , null ) ) . toBe ( false ) ;
343+ } ) ;
344+
345+ test ( "validates literal validator" , ( ) => {
346+ const literalValidator = is ( "specific" ) ;
347+ expect ( validate ( literalValidator , "specific" ) ) . toBe ( true ) ;
348+ expect ( validate ( literalValidator , "other" ) ) . toBe ( false ) ;
349+ expect ( validate ( literalValidator , null ) ) . toBe ( false ) ;
350+ } ) ;
351+
352+ test ( "validates optional values" , ( ) => {
353+ const optionalString = optional ( string ) ;
354+ expect ( validate ( optionalString , "value" ) ) . toBe ( true ) ;
355+ expect ( validate ( optionalString , undefined ) ) . toBe ( true ) ;
356+ expect ( validate ( optionalString , null ) ) . toBe ( false ) ;
357+ expect ( validate ( optionalString , 123 ) ) . toBe ( false ) ;
358+ } ) ;
359+
360+ test ( "throws validation errors when configured" , ( ) => {
361+ expect ( ( ) => validate ( string , 123 , { throw : true } ) ) . toThrow (
362+ "Validator error" ,
363+ ) ;
364+
365+ expect ( ( ) =>
366+ validate ( object ( { name : string } ) , { name : 123 } , { throw : true } ) ,
367+ ) . toThrow ( "Validator error" ) ;
368+
369+ expect ( ( ) =>
370+ validate (
371+ object ( { name : string } ) ,
372+ { name : "valid" , extra : true } ,
373+ { throw : true } ,
374+ ) ,
375+ ) . toThrow ( "Validator error" ) ;
376+ } ) ;
377+
378+ test ( "includes path in error messages" , ( ) => {
379+ const complexValidator = object ( {
380+ user : object ( {
381+ details : object ( {
382+ name : string ,
383+ } ) ,
384+ } ) ,
385+ } ) ;
386+
387+ try {
388+ validate (
389+ complexValidator ,
390+ {
391+ user : {
392+ details : {
393+ name : 123 ,
394+ } ,
395+ } ,
396+ } ,
397+ { throw : true } ,
398+ ) ;
399+ fail ( "Should have thrown" ) ;
400+ } catch ( e : any ) {
401+ expect ( e . message ) . toContain ( "user.details.name" ) ;
402+ }
403+ } ) ;
404+
405+ test ( "includes path for nested objects" , ( ) => {
406+ const complexValidator = object ( {
407+ user : object ( {
408+ details : object ( {
409+ name : string ,
410+ } ) ,
411+ } ) ,
412+ } ) ;
413+ expect (
414+ validate ( complexValidator , { user : { details : { name : "Alice" } } } ) ,
415+ ) . toBe ( true ) ;
416+ expect (
417+ validate ( complexValidator , { user : { details : { name : 123 } } } ) ,
418+ ) . toBe ( false ) ;
419+ try {
420+ validate (
421+ complexValidator ,
422+ { user : { details : { name : 123 } } } ,
423+ { throw : true } ,
424+ ) ;
425+ fail ( "Should have thrown" ) ;
426+ } catch ( e : any ) {
427+ expect ( e . message ) . toContain ( "user.details.name" ) ;
428+ }
429+ } ) ;
430+
431+ test ( "includes path for nested arrays" , ( ) => {
432+ const complexValidator = object ( {
433+ user : object ( {
434+ details : array ( string ) ,
435+ } ) ,
436+ } ) ;
437+ expect (
438+ validate ( complexValidator , { user : { details : [ "a" , "b" , "c" ] } } ) ,
439+ ) . toBe ( true ) ;
440+ expect ( validate ( complexValidator , { user : { details : [ 1 , 2 , 3 ] } } ) ) . toBe (
441+ false ,
442+ ) ;
443+ try {
444+ validate (
445+ complexValidator ,
446+ { user : { details : [ "a" , 3 ] } } ,
447+ { throw : true } ,
448+ ) ;
449+ fail ( "Should have thrown" ) ;
450+ } catch ( e : any ) {
451+ expect ( e . message ) . toContain ( "user.details[1]" ) ;
452+ }
453+ } ) ;
454+
455+ test ( "validates bytes/ArrayBuffer" , ( ) => {
456+ const buffer = new ArrayBuffer ( 8 ) ;
457+ expect ( validate ( arrayBuffer , buffer ) ) . toBe ( true ) ;
458+ expect ( validate ( arrayBuffer , new Uint8Array ( 8 ) ) ) . toBe ( false ) ;
459+ expect ( validate ( arrayBuffer , "binary" ) ) . toBe ( false ) ;
460+ } ) ;
461+
462+ test ( "validates any" , ( ) => {
463+ expect ( validate ( any , "anything" ) ) . toBe ( true ) ;
464+ expect ( validate ( any , 123 ) ) . toBe ( true ) ;
465+ expect ( validate ( any , null ) ) . toBe ( true ) ;
466+ expect ( validate ( any , { complex : "object" } ) ) . toBe ( true ) ;
467+ } ) ;
468+ } ) ;
0 commit comments