11import datetime
22import re
33import uuid
4+ from typing import Optional
45
56from common .validator .constants .enums import MESSAGES , ExceptionLevels , MessageLabel
67from common .validator .error_report .record_error import ErrorReport , RecordError
78from common .validator .lookup_expressions .key_data import KeyData
89from common .validator .lookup_expressions .lookup_data import LookUpData
10+ from common .validator .validation_utils import check_if_future_date
911
1012
1113class ExpressionChecker :
@@ -28,8 +30,10 @@ def validate_expression(
2830 return self ._validate_datetime (expression_rule , field_name , field_value , row )
2931 case "STRING" :
3032 return self ._validate_for_string_values (expression_rule , field_name , field_value , row )
33+ case "LIST" :
34+ return self ._validate_for_list_values (expression_rule , field_name , field_value , row )
3135 case "DATE" :
32- return self ._validate_datetime (expression_rule , field_name , field_value , row )
36+ return self .validate_for_date (expression_rule , field_name , field_value , row )
3337 case "UUID" :
3438 return self ._validate_uuid (expression_rule , field_name , field_value , row )
3539 case "INT" :
@@ -97,6 +101,23 @@ def _validate_datetime(self, _expression_rule, field_name, field_value, row) ->
97101 message = MESSAGES [ExceptionLevels .UNEXPECTED_EXCEPTION ] % (e .__class__ .__name__ , e )
98102 return ErrorReport (ExceptionLevels .UNEXPECTED_EXCEPTION , message , row , field_name , "" , self .summarise )
99103
104+ def validate_for_date (self , _expression_rule , field_name , field_value , row , future_date_allowed : bool = False ):
105+ """
106+ Apply pre-validation to a date field to ensure that it is a string (JSON dates must be
107+ written as strings) containing a valid date in the format "YYYY-MM-DD"
108+ """
109+ if not isinstance (field_value , str ):
110+ raise TypeError (f"{ field_name } must be a string" )
111+
112+ try :
113+ parsed_date = datetime .strptime (field_value , "%Y-%m-%d" ).date ()
114+ except ValueError as value_error :
115+ raise ValueError (f'{ field_name } must be a valid date string in the format "YYYY-MM-DD"' ) from value_error
116+
117+ # Enforce future date rule using central checker after successful parse
118+ if not future_date_allowed and check_if_future_date (parsed_date ):
119+ raise ValueError (f"{ field_name } must not be in the future" )
120+
100121 # UUID validate
101122 def _validate_uuid (self , _expression_rule : str , field_name : str , field_value : str , row : dict ) -> ErrorReport :
102123 try :
@@ -206,6 +227,42 @@ def _validate_equal(self, expression_rule: str, field_name: str, field_value: st
206227 message = MESSAGES [ExceptionLevels .UNEXPECTED_EXCEPTION ] % (e .__class__ .__name__ , e )
207228 return ErrorReport (ExceptionLevels .UNEXPECTED_EXCEPTION , message , row , field_name , "" , self .summarise )
208229
230+ def for_list (self , expression_rule : str , field_name : str , field_value : list , row : dict ):
231+ """
232+ Apply validation to a list field to ensure it is a non-empty list which meets the length requirements and
233+ requirements, if applicable, for each list element to be a non-empty string or non-empty dictionary
234+ """
235+ defined_length : Optional [int ] = (None ,)
236+ max_length : Optional [int ] = (None ,)
237+ elements_are_strings : bool = (False ,)
238+ string_element_max_length : Optional [int ] = (None ,)
239+ elements_are_dicts : bool = (False ,)
240+ if not isinstance (field_value , list ):
241+ raise TypeError (f"{ field_name } must be an array" )
242+
243+ if defined_length :
244+ if len (field_value ) != defined_length :
245+ raise ValueError (f"{ field_name } must be an array of length { defined_length } " )
246+ else :
247+ if len (field_value ) == 0 :
248+ raise ValueError (f"{ field_name } must be a non-empty array" )
249+
250+ if max_length is not None and len (field_value ) > max_length :
251+ raise ValueError (f"{ field_name } must be an array of maximum length { max_length } " )
252+
253+ if elements_are_strings :
254+ for idx , element in enumerate (field_value ):
255+ self ._validate_for_string_values .for_string (
256+ element , f"{ field_name } [{ idx } ]" , max_length = string_element_max_length
257+ )
258+
259+ if elements_are_dicts :
260+ for element in field_value :
261+ if not isinstance (element , dict ):
262+ raise TypeError (f"{ field_name } must be an array of objects" )
263+ if len (element ) == 0 :
264+ raise ValueError (f"{ field_name } must be an array of non-empty objects" )
265+
209266 # Not Equal Validate
210267 def _validate_not_equal (self , expression_rule : str , field_name : str , field_value : str , row : dict ) -> ErrorReport :
211268 try :
0 commit comments