@@ -376,34 +376,216 @@ export class Validator {
376376 }
377377
378378 /**
379- * Validate uniqueness (stub - requires database access) .
379+ * Validate uniqueness by checking database for existing values .
380380 */
381381 private async validateUniqueness (
382382 rule : UniquenessValidationRule ,
383383 context : ValidationContext
384384 ) : Promise < ValidationRuleResult > {
385- // TODO: Implement database query for uniqueness check
386- // This requires access to the data layer (driver/repository)
387- // Stub: Pass silently until implementation is complete
388- return {
389- rule : rule . name ,
390- valid : true ,
391- } ;
385+ // Check if API is available for database access
386+ if ( ! context . api ) {
387+ // If no API provided, we can't validate - pass by default
388+ return {
389+ rule : rule . name ,
390+ valid : true ,
391+ } ;
392+ }
393+
394+ // Get object name from context metadata
395+ if ( ! context . metadata ?. objectName ) {
396+ return {
397+ rule : rule . name ,
398+ valid : false ,
399+ message : 'Object name not provided in validation context' ,
400+ severity : rule . severity || 'error' ,
401+ } ;
402+ }
403+
404+ const objectName = context . metadata . objectName ;
405+
406+ // Determine fields to check for uniqueness
407+ const fieldsToCheck : string [ ] = rule . fields || ( rule . field ? [ rule . field ] : [ ] ) ;
408+
409+ if ( fieldsToCheck . length === 0 ) {
410+ return {
411+ rule : rule . name ,
412+ valid : false ,
413+ message : 'No fields specified for uniqueness validation' ,
414+ severity : rule . severity || 'error' ,
415+ } ;
416+ }
417+
418+ // Build query filter
419+ const filters : any = { } ;
420+
421+ // Add field conditions
422+ for ( const field of fieldsToCheck ) {
423+ const fieldValue = context . record [ field ] ;
424+
425+ // Skip validation if field value is null/undefined (no value to check uniqueness for)
426+ if ( fieldValue === null || fieldValue === undefined ) {
427+ return {
428+ rule : rule . name ,
429+ valid : true ,
430+ } ;
431+ }
432+
433+ // Handle case sensitivity for string values
434+ if ( typeof fieldValue === 'string' && rule . case_sensitive === false ) {
435+ // For case-insensitive, we would need regex or toLowerCase comparison
436+ // This is a simplified implementation - driver-specific logic may be needed
437+ filters [ field ] = fieldValue ;
438+ } else {
439+ filters [ field ] = fieldValue ;
440+ }
441+ }
442+
443+ // Apply scope conditions if specified
444+ if ( rule . scope ) {
445+ // Evaluate scope condition and add to filters
446+ const scopeFields = this . extractFieldsFromCondition ( rule . scope ) ;
447+ for ( const field of scopeFields ) {
448+ if ( context . record [ field ] !== undefined ) {
449+ filters [ field ] = context . record [ field ] ;
450+ }
451+ }
452+ }
453+
454+ // Exclude current record for update operations
455+ if ( context . operation === 'update' && context . previousRecord ?. _id ) {
456+ filters . _id = { $ne : context . previousRecord . _id } ;
457+ }
458+
459+ try {
460+ // Query database to count existing records with same field values
461+ const count = await context . api . count ( objectName , filters ) ;
462+
463+ const valid = count === 0 ;
464+
465+ return {
466+ rule : rule . name ,
467+ valid,
468+ message : valid ? undefined : this . formatMessage ( rule . message , context . record ) ,
469+ error_code : rule . error_code ,
470+ severity : rule . severity || 'error' ,
471+ fields : fieldsToCheck ,
472+ } ;
473+ } catch ( error ) {
474+ // If query fails, treat as validation error
475+ return {
476+ rule : rule . name ,
477+ valid : false ,
478+ message : `Uniqueness check failed: ${ error instanceof Error ? error . message : 'Unknown error' } ` ,
479+ severity : rule . severity || 'error' ,
480+ fields : fieldsToCheck ,
481+ } ;
482+ }
392483 }
393484
394485 /**
395- * Validate business rule (stub - requires complex logic).
486+ * Extract field names from a validation condition.
487+ */
488+ private extractFieldsFromCondition ( condition : ValidationCondition ) : string [ ] {
489+ const fields : string [ ] = [ ] ;
490+
491+ if ( condition . field ) {
492+ fields . push ( condition . field ) ;
493+ }
494+
495+ if ( condition . all_of ) {
496+ for ( const subcondition of condition . all_of ) {
497+ fields . push ( ...this . extractFieldsFromCondition ( subcondition ) ) ;
498+ }
499+ }
500+
501+ if ( condition . any_of ) {
502+ for ( const subcondition of condition . any_of ) {
503+ fields . push ( ...this . extractFieldsFromCondition ( subcondition ) ) ;
504+ }
505+ }
506+
507+ return fields ;
508+ }
509+
510+ /**
511+ * Validate business rule by evaluating constraint conditions.
396512 */
397513 private async validateBusinessRule (
398514 rule : BusinessRuleValidationRule ,
399515 context : ValidationContext
400516 ) : Promise < ValidationRuleResult > {
401- // TODO: Implement business rule evaluation
402- // This requires expression parsing and relationship resolution
403- // Stub: Pass silently until implementation is complete
517+ if ( ! rule . constraint ) {
518+ // No constraint specified, validation passes
519+ return {
520+ rule : rule . name ,
521+ valid : true ,
522+ } ;
523+ }
524+
525+ const constraint = rule . constraint ;
526+ let valid = true ;
527+
528+ // Evaluate all_of conditions (all must be true)
529+ if ( constraint . all_of && constraint . all_of . length > 0 ) {
530+ valid = constraint . all_of . every ( condition => this . evaluateCondition ( condition , context . record ) ) ;
531+
532+ if ( ! valid ) {
533+ return {
534+ rule : rule . name ,
535+ valid : false ,
536+ message : this . formatMessage ( rule . message , context . record ) ,
537+ error_code : rule . error_code ,
538+ severity : rule . severity || 'error' ,
539+ } ;
540+ }
541+ }
542+
543+ // Evaluate any_of conditions (at least one must be true)
544+ if ( constraint . any_of && constraint . any_of . length > 0 ) {
545+ valid = constraint . any_of . some ( condition => this . evaluateCondition ( condition , context . record ) ) ;
546+
547+ if ( ! valid ) {
548+ return {
549+ rule : rule . name ,
550+ valid : false ,
551+ message : this . formatMessage ( rule . message , context . record ) ,
552+ error_code : rule . error_code ,
553+ severity : rule . severity || 'error' ,
554+ } ;
555+ }
556+ }
557+
558+ // Evaluate expression if provided (basic implementation)
559+ if ( constraint . expression ) {
560+ // For now, we'll treat expression validation as a stub
561+ // Full implementation would require safe expression evaluation
562+ // This could be extended to use a safe expression evaluator in the future
563+ valid = true ;
564+ }
565+
566+ // Evaluate then_require conditions (conditional required fields)
567+ if ( constraint . then_require && constraint . then_require . length > 0 ) {
568+ for ( const condition of constraint . then_require ) {
569+ const conditionMet = this . evaluateCondition ( condition , context . record ) ;
570+
571+ if ( ! conditionMet ) {
572+ return {
573+ rule : rule . name ,
574+ valid : false ,
575+ message : this . formatMessage ( rule . message , context . record ) ,
576+ error_code : rule . error_code ,
577+ severity : rule . severity || 'error' ,
578+ } ;
579+ }
580+ }
581+ }
582+
404583 return {
405584 rule : rule . name ,
406- valid : true ,
585+ valid,
586+ message : valid ? undefined : this . formatMessage ( rule . message , context . record ) ,
587+ error_code : rule . error_code ,
588+ severity : rule . severity || 'error' ,
407589 } ;
408590 }
409591
0 commit comments