@@ -376,34 +376,217 @@ 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 : Record < string , 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+ // NOTE: Case-insensitive comparison requires driver-specific implementation
436+ // Some drivers support regex (MongoDB), others use LOWER() function (SQL)
437+ // For now, we use exact match - driver adapters should implement case-insensitive logic
438+ filters [ field ] = fieldValue ;
439+ } else {
440+ filters [ field ] = fieldValue ;
441+ }
442+ }
443+
444+ // Apply scope conditions if specified
445+ if ( rule . scope ) {
446+ // Evaluate scope condition and add to filters
447+ const scopeFields = this . extractFieldsFromCondition ( rule . scope ) ;
448+ for ( const field of scopeFields ) {
449+ if ( context . record [ field ] !== undefined ) {
450+ filters [ field ] = context . record [ field ] ;
451+ }
452+ }
453+ }
454+
455+ // Exclude current record for update operations
456+ if ( context . operation === 'update' && context . previousRecord ?. _id ) {
457+ filters . _id = { $ne : context . previousRecord . _id } ;
458+ }
459+
460+ try {
461+ // Query database to count existing records with same field values
462+ const count = await context . api . count ( objectName , filters ) ;
463+
464+ const valid = count === 0 ;
465+
466+ return {
467+ rule : rule . name ,
468+ valid,
469+ message : valid ? undefined : this . formatMessage ( rule . message , context . record ) ,
470+ error_code : rule . error_code ,
471+ severity : rule . severity || 'error' ,
472+ fields : fieldsToCheck ,
473+ } ;
474+ } catch ( error ) {
475+ // If query fails, treat as validation error
476+ return {
477+ rule : rule . name ,
478+ valid : false ,
479+ message : `Uniqueness check failed: ${ error instanceof Error ? error . message : 'Unknown error' } ` ,
480+ severity : rule . severity || 'error' ,
481+ fields : fieldsToCheck ,
482+ } ;
483+ }
392484 }
393485
394486 /**
395- * Validate business rule (stub - requires complex logic).
487+ * Extract field names from a validation condition.
488+ */
489+ private extractFieldsFromCondition ( condition : ValidationCondition ) : string [ ] {
490+ const fields : string [ ] = [ ] ;
491+
492+ if ( condition . field ) {
493+ fields . push ( condition . field ) ;
494+ }
495+
496+ if ( condition . all_of ) {
497+ for ( const subcondition of condition . all_of ) {
498+ fields . push ( ...this . extractFieldsFromCondition ( subcondition ) ) ;
499+ }
500+ }
501+
502+ if ( condition . any_of ) {
503+ for ( const subcondition of condition . any_of ) {
504+ fields . push ( ...this . extractFieldsFromCondition ( subcondition ) ) ;
505+ }
506+ }
507+
508+ return fields ;
509+ }
510+
511+ /**
512+ * Validate business rule by evaluating constraint conditions.
396513 */
397514 private async validateBusinessRule (
398515 rule : BusinessRuleValidationRule ,
399516 context : ValidationContext
400517 ) : Promise < ValidationRuleResult > {
401- // TODO: Implement business rule evaluation
402- // This requires expression parsing and relationship resolution
403- // Stub: Pass silently until implementation is complete
518+ if ( ! rule . constraint ) {
519+ // No constraint specified, validation passes
520+ return {
521+ rule : rule . name ,
522+ valid : true ,
523+ } ;
524+ }
525+
526+ const constraint = rule . constraint ;
527+ let valid = true ;
528+
529+ // Evaluate all_of conditions (all must be true)
530+ if ( constraint . all_of && constraint . all_of . length > 0 ) {
531+ valid = constraint . all_of . every ( condition => this . evaluateCondition ( condition , context . record ) ) ;
532+
533+ if ( ! valid ) {
534+ return {
535+ rule : rule . name ,
536+ valid : false ,
537+ message : this . formatMessage ( rule . message , context . record ) ,
538+ error_code : rule . error_code ,
539+ severity : rule . severity || 'error' ,
540+ } ;
541+ }
542+ }
543+
544+ // Evaluate any_of conditions (at least one must be true)
545+ if ( constraint . any_of && constraint . any_of . length > 0 ) {
546+ valid = constraint . any_of . some ( condition => this . evaluateCondition ( condition , context . record ) ) ;
547+
548+ if ( ! valid ) {
549+ return {
550+ rule : rule . name ,
551+ valid : false ,
552+ message : this . formatMessage ( rule . message , context . record ) ,
553+ error_code : rule . error_code ,
554+ severity : rule . severity || 'error' ,
555+ } ;
556+ }
557+ }
558+
559+ // Evaluate expression if provided (basic implementation)
560+ if ( constraint . expression ) {
561+ // For now, we'll treat expression validation as a stub
562+ // Full implementation would require safe expression evaluation
563+ // This could be extended to use a safe expression evaluator in the future
564+ valid = true ;
565+ }
566+
567+ // Evaluate then_require conditions (conditional required fields)
568+ if ( constraint . then_require && constraint . then_require . length > 0 ) {
569+ for ( const condition of constraint . then_require ) {
570+ const conditionMet = this . evaluateCondition ( condition , context . record ) ;
571+
572+ if ( ! conditionMet ) {
573+ return {
574+ rule : rule . name ,
575+ valid : false ,
576+ message : this . formatMessage ( rule . message , context . record ) ,
577+ error_code : rule . error_code ,
578+ severity : rule . severity || 'error' ,
579+ } ;
580+ }
581+ }
582+ }
583+
404584 return {
405585 rule : rule . name ,
406- valid : true ,
586+ valid,
587+ message : valid ? undefined : this . formatMessage ( rule . message , context . record ) ,
588+ error_code : rule . error_code ,
589+ severity : rule . severity || 'error' ,
407590 } ;
408591 }
409592
0 commit comments