diff --git a/src/server/BreCal/api/ships.py b/src/server/BreCal/api/ships.py index 5935402..9f0c43e 100644 --- a/src/server/BreCal/api/ships.py +++ b/src/server/BreCal/api/ships.py @@ -38,7 +38,7 @@ def PostShip(): # as ParticipantType is an IntFlag, a user belonging to multiple groups is properly evaluated. is_bsmd = check_if_user_is_bsmd_type(user_data) if not is_bsmd: - raise ValidationError(f"current user does not belong to BSMD. Cannot post shipcalls. Found user data: {user_data}") + raise ValidationError({"participant_type":f"current user does not belong to BSMD. Cannot post shipcalls. Found user data: {user_data}"}) content = request.get_json(force=True) loadedModel = model.ShipSchema().load(data=content, many=False, partial=True) diff --git a/src/server/BreCal/schemas/model.py b/src/server/BreCal/schemas/model.py index 14bceda..c3eed38 100644 --- a/src/server/BreCal/schemas/model.py +++ b/src/server/BreCal/schemas/model.py @@ -177,7 +177,7 @@ class Participant(Schema): valid_type = 0 <= value < max_int if not valid_type: - raise ValidationError(f"the provided integer is not supported for default behaviour of the ParticipantType IntFlag. Your choice: {value}. Supported values are: 0 <= value {max_int}") + raise ValidationError({"type":f"the provided integer is not supported for default behaviour of the ParticipantType IntFlag. Your choice: {value}. Supported values are: 0 <= value {max_int}"}) @validates("flags") @@ -188,7 +188,7 @@ class Participant(Schema): valid_type = 0 <= value < max_int if not valid_type: - raise ValidationError(f"the provided integer is not supported for default behaviour of the ParticipantFlag IntFlag. Your choice: {value}. Supported values are: 0 <= value {max_int}") + raise ValidationError({"flags":f"the provided integer is not supported for default behaviour of the ParticipantFlag IntFlag. Your choice: {value}. Supported values are: 0 <= value {max_int}"}) class ParticipantList(Participant): @@ -253,7 +253,7 @@ class ShipcallSchema(Schema): valid_shipcall_type = int(value) in [item.value for item in ShipcallType] if not valid_shipcall_type: - raise ValidationError(f"the provided type is not a valid shipcall type.") + raise ValidationError({"type":f"the provided type is not a valid shipcall type."}) @dataclass @@ -393,7 +393,7 @@ class TimesSchema(Schema): value = ParticipantType(value) if ParticipantType.BSMD in value: - raise ValidationError(f"the participant_type must not be .BSMD") + raise ValidationError({"participant_type":f"the participant_type must not be .BSMD"}) @validates("eta_berth") def validate_eta_berth(self, value): @@ -471,12 +471,12 @@ class UserSchema(Schema): def validate_user_phone(self, value): valid_characters = list(map(str,range(0,10)))+["+", " "] if not all([v in valid_characters for v in value]): - raise ValidationError(f"one of the phone number values is not valid.") + raise ValidationError({"user_phone":f"one of the phone number values is not valid."}) @validates("user_email") def validate_user_email(self, value): if not "@" in value: - raise ValidationError(f"invalid email address") + raise ValidationError({"user_email":f"invalid email address"}) @dataclass @@ -565,12 +565,12 @@ class ShipSchema(Schema): def validate_name(self, value): character_length = len(str(value)) if character_length<1: - raise ValidationError(f"'name' argument should have at least one character") + raise ValidationError({"name":f"'name' argument should have at least one character"}) elif character_length>=64: - raise ValidationError(f"'name' argument should have at max. 63 characters") + raise ValidationError({"name":f"'name' argument should have at max. 63 characters"}) if check_if_string_has_special_characters(value): - raise ValidationError(f"'name' argument should not have special characters.") + raise ValidationError({"name":f"'name' argument should not have special characters."}) return @validates("imo") @@ -578,7 +578,7 @@ class ShipSchema(Schema): value = str(value).zfill(7) # 1 becomes '0000001' (7 characters). 12345678 becomes '12345678' (8 characters) imo_length = len(value) if imo_length != 7: - raise ValidationError(f"'imo' should be a 7-digit number") + raise ValidationError({"imo":f"'imo' should be a 7-digit number"}) return @validates("callsign") @@ -586,10 +586,10 @@ class ShipSchema(Schema): if value is not None: callsign_length = len(str(value)) if callsign_length>8: - raise ValidationError(f"'callsign' argument should not have more than 8 characters") + raise ValidationError({"callsign":f"'callsign' argument should not have more than 8 characters"}) if check_if_string_has_special_characters(value): - raise ValidationError(f"'callsign' argument should not have special characters.") + raise ValidationError({"callsign":f"'callsign' argument should not have special characters."}) return diff --git a/src/server/BreCal/validators/input_validation.py b/src/server/BreCal/validators/input_validation.py index 1fb9649..cc97ccb 100644 --- a/src/server/BreCal/validators/input_validation.py +++ b/src/server/BreCal/validators/input_validation.py @@ -35,24 +35,24 @@ def validate_posted_shipcall_data(user_data:dict, loadedModel:dict, content:dict # as ParticipantType is an IntFlag, a user belonging to multiple groups is properly evaluated. is_bsmd = check_if_user_is_bsmd_type(user_data) if not is_bsmd: - raise ValidationError(f"current user does not belong to BSMD. Cannot post shipcalls. Found user data: {user_data}") + raise ValidationError({"user_participant_type":f"current user does not belong to BSMD. Cannot post shipcalls. Found user data: {user_data}"}) ##### Section 2: check loadedModel ##### valid_ship_id = check_if_ship_id_is_valid(ship_id=loadedModel.get("ship_id", None)) if not valid_ship_id: - raise ValidationError(f"provided an invalid ship id, which is not found in the database: {loadedModel.get('ship_id', None)}") + raise ValidationError({"ship_id":f"provided an invalid ship id, which is not found in the database: {loadedModel.get('ship_id', None)}"}) valid_arrival_berth_id = check_if_berth_id_is_valid(berth_id=loadedModel.get("arrival_berth_id", None)) if not valid_arrival_berth_id: - raise ValidationError(f"provided an invalid arrival berth id, which is not found in the database: {loadedModel.get('arrival_berth_id', None)}") + raise ValidationError({"arrival_berth_id":f"provided an invalid arrival berth id, which is not found in the database: {loadedModel.get('arrival_berth_id', None)}"}) valid_departure_berth_id = check_if_berth_id_is_valid(berth_id=loadedModel.get("departure_berth_id", None)) if not valid_departure_berth_id: - raise ValidationError(f"provided an invalid departure berth id, which is not found in the database: {loadedModel.get('departure_berth_id', None)}") + raise ValidationError({"departure_berth_id":f"provided an invalid departure berth id, which is not found in the database: {loadedModel.get('departure_berth_id', None)}"}) valid_participant_ids = check_if_participant_ids_are_valid(participants=loadedModel.get("participants",[])) if not valid_participant_ids: - raise ValidationError(f"one of the provided participant ids is invalid. Could not find one of these in the database: {loadedModel.get('participants', None)}") + raise ValidationError({"participants":f"one of the provided participant ids is invalid. Could not find one of these in the database: {loadedModel.get('participants', None)}"}) ##### Section 3: check content ##### @@ -62,11 +62,11 @@ def validate_posted_shipcall_data(user_data:dict, loadedModel:dict, content:dict for forbidden_key in ["canceled", "evaluation", "evaluation_message"]: value = content.get(forbidden_key, None) if value is not None: - raise ValidationError(f"'{forbidden_key}' may not be set on POST. Found: {value}") + raise ValidationError({"forbidden_key":f"'{forbidden_key}' may not be set on POST. Found: {value}"}) voyage_str_is_invalid = check_if_string_has_special_characters(text=content.get("voyage","")) if voyage_str_is_invalid: - raise ValidationError(f"there are invalid characters in the 'voyage'-string. Please use only digits and ASCII letters. Allowed: {ascii_letters+digits}. Found: {content.get('voyage')}") + raise ValidationError({"voyage":f"there are invalid characters in the 'voyage'-string. Please use only digits and ASCII letters. Allowed: {ascii_letters+digits}. Found: {content.get('voyage')}"}) ##### Section 4: check loadedModel & content ##### @@ -77,43 +77,43 @@ def validate_posted_shipcall_data(user_data:dict, loadedModel:dict, content:dict time_now = datetime.datetime.now() type_ = loadedModel.get("type", int(ShipcallType.undefined)) if int(type_)==int(ShipcallType.undefined): - raise ValidationError(f"providing 'type' is mandatory. Missing key!") + raise ValidationError({"type":f"providing 'type' is mandatory. Missing key!"}) elif int(type_)==int(ShipcallType.arrival): eta = loadedModel.get("eta") if (content.get("eta", None) is None): - raise ValidationError(f"providing 'eta' is mandatory. Missing key!") + raise ValidationError({"eta":f"providing 'eta' is mandatory. Missing key!"}) if content.get("arrival_berth_id", None) is None: - raise ValidationError(f"providing 'arrival_berth_id' is mandatory. Missing key!") + raise ValidationError({"arrival_berth_id":f"providing 'arrival_berth_id' is mandatory. Missing key!"}) if not eta >= time_now: - raise ValidationError(f"'eta' must be in the future. Incorrect datetime provided.") + raise ValidationError({"eta":f"'eta' must be in the future. Incorrect datetime provided."}) elif int(type_)==int(ShipcallType.departure): etd = loadedModel.get("etd") if (content.get("etd", None) is None): - raise ValidationError(f"providing 'etd' is mandatory. Missing key!") + raise ValidationError({"etd":f"providing 'etd' is mandatory. Missing key!"}) if content.get("departure_berth_id", None) is None: - raise ValidationError(f"providing 'departure_berth_id' is mandatory. Missing key!") + raise ValidationError({"departure_berth_id":f"providing 'departure_berth_id' is mandatory. Missing key!"}) if not etd >= time_now: - raise ValidationError(f"'etd' must be in the future. Incorrect datetime provided.") + raise ValidationError({"etd":f"'etd' must be in the future. Incorrect datetime provided."}) elif int(type_)==int(ShipcallType.shifting): eta = loadedModel.get("eta") etd = loadedModel.get("etd") # * arrival_berth_id / departure_berth_id (depending on type, see above) if (content.get("eta", None) is None) or (content.get("etd", None) is None): - raise ValidationError(f"providing 'eta' and 'etd' is mandatory. Missing one of those keys!") + raise ValidationError({"eta_or_etd":f"providing 'eta' and 'etd' is mandatory. Missing one of those keys!"}) if (content.get("arrival_berth_id", None) is None) or (content.get("departure_berth_id", None) is None): - raise ValidationError(f"providing 'arrival_berth_id' & 'departure_berth_id' is mandatory. Missing key!") + raise ValidationError({"arrival_berth_id_or_departure_berth_id":f"providing 'arrival_berth_id' & 'departure_berth_id' is mandatory. Missing key!"}) if (not eta >= time_now) or (not etd >= time_now) or (not eta >= etd): - raise ValidationError(f"'eta' and 'etd' must be in the future. Incorrect datetime provided.") + raise ValidationError({"eta_or_etd":f"'eta' and 'etd' must be in the future. Incorrect datetime provided."}) tidal_window_from = loadedModel.get("tidal_window_from", None) tidal_window_to = loadedModel.get("tidal_window_to", None) if tidal_window_to is not None: if not tidal_window_to >= time_now: - raise ValidationError(f"'tidal_window_to' must be in the future. Incorrect datetime provided.") + raise ValidationError({"tidal_window_to":f"'tidal_window_to' must be in the future. Incorrect datetime provided."}) if tidal_window_from is not None: if not tidal_window_from >= time_now: - raise ValidationError(f"'tidal_window_from' must be in the future. Incorrect datetime provided.") + raise ValidationError({"tidal_window_from":f"'tidal_window_from' must be in the future. Incorrect datetime provided."}) # #TODO: assert tidal_window_from > tidal_window_to diff --git a/src/server/BreCal/validators/input_validation_ship.py b/src/server/BreCal/validators/input_validation_ship.py index 3a47b2c..4b9ad23 100644 --- a/src/server/BreCal/validators/input_validation_ship.py +++ b/src/server/BreCal/validators/input_validation_ship.py @@ -75,16 +75,16 @@ class InputValidationShip(): if bollard_pull is not None: if not is_tug: - raise ValidationError(f"'bollard_pull' is only allowed, when a ship is a tug ('is_tug').") + raise ValidationError({"bollard_pull":f"'bollard_pull' is only allowed, when a ship is a tug ('is_tug')."}) if (not (0 < bollard_pull < 500)) & (is_tug): - raise ValidationError(f"when a ship is a tug, the bollard pull must be 0 < value < 500. ") + raise ValidationError({"bollard_pull":f"when a ship is a tug, the bollard pull must be 0 < value < 500. "}) @staticmethod def check_user_is_bsmd_type(user_data:dict): is_bsmd = check_if_user_is_bsmd_type(user_data) if not is_bsmd: - raise ValidationError(f"current user does not belong to BSMD. Cannot post, put or delete ships. Found user data: {user_data}") + raise ValidationError({"participant_type":f"current user does not belong to BSMD. Cannot post, put or delete ships. Found user data: {user_data}"}) @staticmethod def check_ship_imo_already_exists(loadedModel:dict): @@ -98,7 +98,7 @@ class InputValidationShip(): # check, if the imo in the POST-request already exists in the list imo_already_exists = loadedModel.get("imo") in ship_imos if imo_already_exists: - raise ValidationError(f"the provided ship IMO {loadedModel.get('imo')} already exists. A ship may only be added, if there is no other ship with the same IMO number.") + raise ValidationError({"imo":f"the provided ship IMO {loadedModel.get('imo')} already exists. A ship may only be added, if there is no other ship with the same IMO number."}) return @staticmethod @@ -112,14 +112,14 @@ class InputValidationShip(): ship = execute_sql_query_standalone(SQLQuery.get_ship_by_id(), param={"id":content.get("id")}, command_type="single", model=Ship) if put_data_ship_imo != ship.imo: - raise ValidationError(f"The IMO number field may not be changed since it serves the purpose of a primary (matching) key.") + raise ValidationError({"imo":f"The IMO number field may not be changed since it serves the purpose of a primary (matching) key."}) return @staticmethod def content_contains_ship_id(content:dict): put_data_ship_id = content.get('id',None) if put_data_ship_id is None: - raise ValidationError(f"The id field is required.") + raise ValidationError({"id":f"The id field is required."}) return @staticmethod @@ -130,19 +130,19 @@ class InputValidationShip(): database entry may not have a deletion state already. """ if ship_id is None: - raise ValidationError(f"The ship_id must be provided.") + raise ValidationError({"id":f"The ship id must be provided."}) response, status_code, header = GetShips(token=None) ships = json.loads(response) existing_database_entries = [ship for ship in ships if ship.get("id")==ship_id] if len(existing_database_entries)==0: - raise ValidationError(f"Could not find a ship with the specified ID. Selected: {ship_id}") + raise ValidationError({"id":f"Could not find a ship with the specified ID. Selected: {ship_id}"}) existing_database_entry = existing_database_entries[0] deletion_state = existing_database_entry.get("deleted",None) if deletion_state: - raise ValidationError(f"The selected ship entry is already deleted.") + raise ValidationError({"deleted":f"The selected ship entry is already deleted."}) return diff --git a/src/server/BreCal/validators/input_validation_shipcall.py b/src/server/BreCal/validators/input_validation_shipcall.py index 59b8b35..aae7e11 100644 --- a/src/server/BreCal/validators/input_validation_shipcall.py +++ b/src/server/BreCal/validators/input_validation_shipcall.py @@ -113,12 +113,12 @@ class InputValidationShipcall(): # voyage shall not contain special characters voyage_str_is_invalid = check_if_string_has_special_characters(text=content.get("voyage","")) if voyage_str_is_invalid: - raise ValidationError(f"there are invalid characters in the 'voyage'-string. Please use only digits and ASCII letters. Allowed: {ascii_letters+digits}. Found: {content.get('voyage')}") + raise ValidationError({"voyage":f"there are invalid characters in the 'voyage'-string. Please use only digits and ASCII letters. Allowed: {ascii_letters+digits}. Found: {content.get('voyage')}"}) # the 'flags' integer must be valid flags_value = content.get("flags", 0) if check_if_int_is_valid_flag(flags_value, enum_object=ParticipantFlag): - raise ValidationError(f"incorrect value provided for 'flags'. Must be a valid combination of the flags.") + raise ValidationError({"flags":f"incorrect value provided for 'flags'. Must be a valid combination of the flags."}) # time values must use future-dates InputValidationShipcall.check_times_are_in_future(loadedModel, content) @@ -212,7 +212,7 @@ class InputValidationShipcall(): is_bsmd_or_agency = (is_bsmd) or (is_agency) if not is_bsmd_or_agency: - raise ValidationError(f"current user must be either of participant type BSMD or AGENCY. Cannot post or put shipcalls. Found user data: {user_data} and participant_type: {participant_type}") + raise ValidationError({"participant_type":f"current user must be either of participant type BSMD or AGENCY. Cannot post or put shipcalls. Found user data: {user_data} and participant_type: {participant_type}"}) return @staticmethod @@ -232,23 +232,23 @@ class InputValidationShipcall(): valid_ship_id = check_if_ship_id_is_valid(ship_id=ship_id) if not valid_ship_id: - raise ValidationError(f"provided an invalid ship id, which is not found in the database: {ship_id}") + raise ValidationError({"ship_id":f"provided an invalid ship id, which is not found in the database: {ship_id}"}) valid_arrival_berth_id = check_if_berth_id_is_valid(berth_id=arrival_berth_id) if not valid_arrival_berth_id: - raise ValidationError(f"provided an invalid arrival berth id, which is not found in the database: {arrival_berth_id}") + raise ValidationError({"arrival_berth_id":f"provided an invalid arrival berth id, which is not found in the database: {arrival_berth_id}"}) valid_departure_berth_id = check_if_berth_id_is_valid(berth_id=departure_berth_id) if not valid_departure_berth_id: - raise ValidationError(f"provided an invalid departure berth id, which is not found in the database: {departure_berth_id}") + raise ValidationError({"departure_berth_id":f"provided an invalid departure berth id, which is not found in the database: {departure_berth_id}"}) valid_participant_ids = check_if_participant_ids_are_valid(participants=participants) if not valid_participant_ids: - raise ValidationError(f"one of the provided participant ids is invalid. Could not find one of these in the database: {participants}") + raise ValidationError({"participants":f"one of the provided participant ids is invalid. Could not find one of these in the database: {participants}"}) valid_participant_types = check_if_participant_ids_and_types_are_valid(participants=participants) if not valid_participant_types: # #TODO: according to Daniel, there may eventually be multi-assignment of participants for the same role - raise ValidationError(f"every participant id and type should be listed only once. Found multiple entries for one of the participants.") + raise ValidationError({"participants":f"every participant id and type should be listed only once. Found multiple entries for one of the participants."}) @staticmethod def check_shipcall_type_is_unchanged(loadedModel:dict): @@ -257,7 +257,7 @@ class InputValidationShipcall(): shipcall = execute_sql_query_standalone(query=query, model=Shipcall, param={"id":loadedModel.get("id")}, command_type="single") if int(loadedModel["type"]) != int(shipcall.type): - raise ValidationError(f"The shipcall type may only be set in the initial POST-request. Afterwards, changing the shipcall type is not allowed.") # @pytest.raises + raise ValidationError({"type":f"The shipcall type may only be set in the initial POST-request. Afterwards, changing the shipcall type is not allowed."}) # @pytest.raises return @staticmethod @@ -271,7 +271,7 @@ class InputValidationShipcall(): for forbidden_key in forbidden_keys: value = content.get(forbidden_key, None) if value is not None: - raise ValidationError(f"'{forbidden_key}' may not be set on POST. Found: {value}") + raise ValidationError({"forbidden_key":f"'{forbidden_key}' may not be set on POST. Found: {value}"}) return @staticmethod @@ -287,36 +287,36 @@ class InputValidationShipcall(): departure_berth_id = content.get("departure_berth_id", None) if ship_id is None: - raise ValidationError(f"providing 'ship_id' is mandatory. Missing key!") + raise ValidationError({"ship_id":f"providing 'ship_id' is mandatory. Missing key!"}) if int(type_)==int(ShipcallType.undefined): - raise ValidationError(f"providing 'type' is mandatory. Missing key!") + raise ValidationError({"type":f"providing 'type' is mandatory. Missing key!"}) # arrival: arrival_berth_id & eta must exist elif int(type_)==int(ShipcallType.arrival): if eta is None: - raise ValidationError(f"providing 'eta' is mandatory. Missing key!") + raise ValidationError({"eta":f"providing 'eta' is mandatory. Missing key!"}) if arrival_berth_id is None: - raise ValidationError(f"providing 'arrival_berth_id' is mandatory. Missing key!") + raise ValidationError({"arrival_berth_id":f"providing 'arrival_berth_id' is mandatory. Missing key!"}) # departure: departive_berth_id and etd must exist elif int(type_)==int(ShipcallType.departure): if etd is None: - raise ValidationError(f"providing 'etd' is mandatory. Missing key!") + raise ValidationError({"etd":f"providing 'etd' is mandatory. Missing key!"}) if departure_berth_id is None: - raise ValidationError(f"providing 'departure_berth_id' is mandatory. Missing key!") + raise ValidationError({"departure_berth_id":f"providing 'departure_berth_id' is mandatory. Missing key!"}) # shifting: arrival_berth_id, departure_berth_id, eta and etd must exist elif int(type_)==int(ShipcallType.shifting): if (eta is None) or (etd is None): - raise ValidationError(f"providing 'eta' and 'etd' is mandatory. Missing one of those keys!") + raise ValidationError({"eta_or_etd":f"providing 'eta' and 'etd' is mandatory. Missing one of those keys!"}) if (arrival_berth_id is None) or (departure_berth_id is None): - raise ValidationError(f"providing 'arrival_berth_id' & 'departure_berth_id' is mandatory. Missing key!") + raise ValidationError({"arrival_berth_id_or_departure_berth_id":f"providing 'arrival_berth_id' & 'departure_berth_id' is mandatory. Missing key!"}) else: - raise ValidationError(f"incorrect 'type' provided!") + raise ValidationError({"type":f"incorrect 'type' provided!"}) return @staticmethod @@ -363,32 +363,32 @@ class InputValidationShipcall(): return if type_ is None: - raise ValidationError(f"when providing 'eta' or 'etd', one must provide the type of the shipcall, so the datetimes can be verified.") + raise ValidationError({"type":f"when providing 'eta' or 'etd', one must provide the type of the shipcall, so the datetimes can be verified."}) if not isinstance(type_, (int, ShipcallType)): type_ = ShipcallType[type_] # #TODO: properly handle what happens, when eta or etd (or both) are None if int(type_)==int(ShipcallType.undefined): - raise ValidationError(f"providing 'type' is mandatory. Missing key!") + raise ValidationError({"type":f"providing 'type' is mandatory. Missing key!"}) elif int(type_)==int(ShipcallType.arrival): if eta is None: # null values -> no violation return if not eta > time_now: - raise ValidationError(f"'eta' must be in the future. Incorrect datetime provided. Current Time: {time_now}. ETA: {eta}.") + raise ValidationError({"eta":f"'eta' must be in the future. Incorrect datetime provided. Current Time: {time_now}. ETA: {eta}."}) if etd is not None: - raise ValidationError(f"'etd' should not be set when the shipcall type is 'arrival'.") + raise ValidationError({"etd":f"'etd' should not be set when the shipcall type is 'arrival'."}) elif int(type_)==int(ShipcallType.departure): if etd is None: # null values -> no violation return if not etd > time_now: - raise ValidationError(f"'etd' must be in the future. Incorrect datetime provided. Current Time: {time_now}. ETD: {etd}.") + raise ValidationError({"etd":f"'etd' must be in the future. Incorrect datetime provided. Current Time: {time_now}. ETD: {etd}."}) if eta is not None: - raise ValidationError(f"'eta' should not be set when the shipcall type is 'departure'.") + raise ValidationError({"eta":f"'eta' should not be set when the shipcall type is 'departure'."}) elif int(type_)==int(ShipcallType.shifting): if (eta is None) and (etd is None): # null values -> no violation @@ -397,30 +397,30 @@ class InputValidationShipcall(): if not ((eta is not None) and (etd is not None)): # for PUT-requests, a user could try modifying only 'eta' or only 'etd'. To simplify the # rules, a user is only allowed to provide *both* values. - raise ValidationError(f"For shifting shipcalls one should always provide, both, eta and etd.") + raise ValidationError({"eta_or_etd":f"For shifting shipcalls one should always provide, both, eta and etd."}) if (not eta > time_now) or (not etd > time_now): - raise ValidationError(f"'eta' and 'etd' must be in the future. Incorrect datetime provided. Current Time: {time_now}. ETA: {eta}. ETD: {etd}") + raise ValidationError({"eta_or_etd":f"'eta' and 'etd' must be in the future. Incorrect datetime provided. Current Time: {time_now}. ETA: {eta}. ETD: {etd}"}) if (not etd < eta): - raise ValidationError(f"The estimated time of departure ('etd') must take place *before the estimated time of arrival ('eta'). The ship cannot arrive, before it has departed. Found: ETD: {etd}, ETA: {eta}") + raise ValidationError({"eta_or_etd":f"The estimated time of departure ('etd') must take place *before the estimated time of arrival ('eta'). The ship cannot arrive, before it has departed. Found: ETD: {etd}, ETA: {eta}"}) if (eta is not None and etd is None) or (eta is None and etd is not None): - raise ValidationError(f"'eta' and 'etd' must both be provided when the shipcall type is 'shifting'.") + raise ValidationError({"eta_or_etd":f"'eta' and 'etd' must both be provided when the shipcall type is 'shifting'."}) return @staticmethod def check_tidal_window_in_future(type_, time_now, tidal_window_from, tidal_window_to): if tidal_window_to is not None: if not tidal_window_to >= time_now: - raise ValidationError(f"'tidal_window_to' must be in the future. Incorrect datetime provided.") + raise ValidationError({"tidal_window_to":f"'tidal_window_to' must be in the future. Incorrect datetime provided."}) if tidal_window_from is not None: if not tidal_window_from >= time_now: - raise ValidationError(f"'tidal_window_from' must be in the future. Incorrect datetime provided.") + raise ValidationError({"tidal_window_from":f"'tidal_window_from' must be in the future. Incorrect datetime provided."}) if (tidal_window_to is not None) and (tidal_window_from is not None): if tidal_window_to < tidal_window_from: - raise ValidationError(f"'tidal_window_to' must take place after 'tidal_window_from'. Incorrect datetime provided. Found 'tidal_window_to': {tidal_window_to}, 'tidal_window_from': {tidal_window_to}.") + raise ValidationError({"tidal_window_to_or_tidal_window_from":f"'tidal_window_to' must take place after 'tidal_window_from'. Incorrect datetime provided. Found 'tidal_window_to': {tidal_window_to}, 'tidal_window_from': {tidal_window_to}."}) return @staticmethod @@ -432,7 +432,7 @@ class InputValidationShipcall(): is_agency_participant = [ParticipantType.AGENCY in ParticipantType(participant.get("type")) for participant in participants] if not any(is_agency_participant): - raise ValidationError(f"One of the assigned participants *must* be of type 'ParticipantType.AGENCY'. Found list of participants: {participants}") + raise ValidationError({"participants":f"One of the assigned participants *must* be of type 'ParticipantType.AGENCY'. Found list of participants: {participants}"}) return @staticmethod @@ -448,14 +448,14 @@ class InputValidationShipcall(): # if the *existing* shipcall in the database is canceled, it may not be changed if shipcall.get("canceled", False): - raise ValidationError(f"The shipcall with id 'shipcall_id' is canceled. A canceled shipcall may not be changed.") + raise ValidationError({"canceled":f"The shipcall with id 'shipcall_id' is canceled. A canceled shipcall may not be changed."}) return @staticmethod def check_required_fields_of_put_request(content:dict): shipcall_id = content.get("id", None) if shipcall_id is None: - raise ValidationError(f"A PUT request requires an 'id' to refer to.") + raise ValidationError({"id":f"A PUT request requires an 'id' to refer to."}) @staticmethod def check_shipcall_id_exists(loadedModel): @@ -464,7 +464,7 @@ class InputValidationShipcall(): query = 'SELECT * FROM shipcall where (id = ?shipcall_id?)' shipcalls = execute_sql_query_standalone(query=query, model=Shipcall, param={"shipcall_id" : shipcall_id}) if len(shipcalls)==0: - raise ValidationError(f"unknown shipcall_id. There are no shipcalls with the ID {shipcall_id}") + raise ValidationError({"id":f"unknown shipcall_id. There are no shipcalls with the ID {shipcall_id}"}) return @staticmethod @@ -497,7 +497,7 @@ class InputValidationShipcall(): an_agency_is_assigned = len(assigned_agency)==1 if len(assigned_agency)>1: - raise ValidationError(f"Internal error? Found more than one assigned agency for the shipcall with ID {shipcall_id}. Found: {assigned_agency}") + raise ValidationError({"internal_error":f"Internal error? Found more than one assigned agency for the shipcall with ID {shipcall_id}. Found: {assigned_agency}"}) if an_agency_is_assigned: # Agency assigned? User must belong to the assigned agency or be a BSMD user, in case the flag is set diff --git a/src/server/BreCal/validators/input_validation_times.py b/src/server/BreCal/validators/input_validation_times.py index 688c651..7e402fa 100644 --- a/src/server/BreCal/validators/input_validation_times.py +++ b/src/server/BreCal/validators/input_validation_times.py @@ -137,7 +137,7 @@ class InputValidationTimes(): pdata = execute_sql_query_standalone(query=query, param={"id":times_id}, pooledConnection=None) if len(pdata)==0: - raise ValidationError(f"The selected time entry is already deleted. ID: {times_id}") + raise ValidationError({"deleted":f"The selected time entry is already deleted. ID: {times_id}"}) return @staticmethod @@ -145,7 +145,7 @@ class InputValidationTimes(): """a new dataset may only be created by a user who is *not* belonging to participant group BSMD""" is_bsmd = check_if_user_is_bsmd_type(user_data) if is_bsmd: - raise ValidationError(f"current user belongs to BSMD. Cannot post 'times' datasets. Found user data: {user_data}") + raise ValidationError({"participant_type":f"current user belongs to BSMD. Cannot post 'times' datasets. Found user data: {user_data}"}) return @staticmethod @@ -166,18 +166,18 @@ class InputValidationTimes(): loadedModel["participant_type"] = ParticipantType(loadedModel["participant_type"]) if ParticipantType.BSMD in loadedModel["participant_type"]: - raise ValidationError(f"current user belongs to BSMD. Cannot post times datasets. Found user data: {user_data}") + raise ValidationError({"participant_type":f"current user belongs to BSMD. Cannot post times datasets. Found user data: {user_data}"}) if (loadedModel["etd_interval_end"] is not None) and (loadedModel["etd_berth"] is not None): time_end_after_time_start = loadedModel["etd_interval_end"] >= loadedModel["etd_berth"] if not time_end_after_time_start: - raise ValidationError(f"The provided time interval for the estimated departure time is invalid. The interval end takes place before the interval start. Found interval data: {loadedModel['etd_berth']} to {loadedModel['etd_interval_end']}") + raise ValidationError({"etd":f"The provided time interval for the estimated departure time is invalid. The interval end takes place before the interval start. Found interval data: {loadedModel['etd_berth']} to {loadedModel['etd_interval_end']}"}) if (loadedModel["eta_interval_end"] is not None) and (loadedModel["eta_berth"] is not None): time_end_after_time_start = loadedModel["eta_interval_end"] >= loadedModel["eta_berth"] if not time_end_after_time_start: - raise ValidationError(f"The provided time interval for the estimated arrival time is invalid. The interval begin takes place after the interval end. Found interval data: {loadedModel['eta_berth']} to {loadedModel['eta_interval_end']}") + raise ValidationError({"eta":f"The provided time interval for the estimated arrival time is invalid. The interval begin takes place after the interval end. Found interval data: {loadedModel['eta_berth']} to {loadedModel['eta_interval_end']}"}) return @staticmethod @@ -189,19 +189,19 @@ class InputValidationTimes(): Note: whenever an ID is 'None', there is no exception, because a different method is supposed to capture non-existant mandatory fields. """ # extract the IDs - berth_id, participant_id, shipcall_id = content.get("berth_id"), content.get("participant_id"), content.get("shipcall_id") + berth_id, shipcall_id, participant_id = content.get("berth_id"), content.get("shipcall_id"), content.get("participant_id") valid_berth_id_reference = check_if_berth_id_is_valid(berth_id) if not valid_berth_id_reference: - raise ValidationError(f"The referenced berth_id '{berth_id}' does not exist in the database.") + raise ValidationError({"berth_id":f"The referenced berth_id '{berth_id}' does not exist in the database."}) valid_shipcall_id_reference = check_if_shipcall_id_is_valid(shipcall_id) if not valid_shipcall_id_reference: - raise ValidationError(f"The referenced shipcall_id '{shipcall_id}' does not exist in the database.") + raise ValidationError({"shipcall_id":f"The referenced shipcall_id '{shipcall_id}' does not exist in the database."}) valid_participant_id_reference = check_if_participant_id_is_valid_standalone(participant_id, participant_type=None) if not valid_participant_id_reference: - raise ValidationError(f"The referenced participant_id '{participant_id}' does not exist in the database.") + raise ValidationError({"participant_id":f"The referenced participant_id '{participant_id}' does not exist in the database."}) return @@ -223,7 +223,7 @@ class InputValidationTimes(): shipcall_type = ShipcallType[shipcalls.get(shipcall_id,{}).get("type",ShipcallType.undefined.name)] if (participant_type is None) or (int(shipcall_type) == int(ShipcallType.undefined)): - raise ValidationError(f"At least one of the required fields is missing. Missing: 'participant_type' or 'shipcall_type'") + raise ValidationError({"required_fields":f"At least one of the required fields is missing. Missing: 'participant_type' or 'shipcall_type'"}) # build a list of required fields based on shipcall and participant type, as well as type-independent fields @@ -240,14 +240,14 @@ class InputValidationTimes(): if any(missing_required_fields): # create a tuple of (field_key, bool) to describe to a user, which one of the fields may be missing verbosity_tuple = [(field, missing) for field, missing in zip(required_fields, missing_required_fields) if missing] - raise ValidationError(f"At least one of the required fields is missing. Missing: {verbosity_tuple}") + raise ValidationError({"required_fields":f"At least one of the required fields is missing. Missing: {verbosity_tuple}"}) return @staticmethod def check_times_required_fields_put_data(content:dict): """in a PUT request, only the 'id' is a required field. All other fields are simply ignored, when they are not provided.""" if content.get("id") is None: - raise ValidationError(f"A PUT-request requires an 'id' reference, which was not found.") + raise ValidationError({"id":f"A PUT-request requires an 'id' reference, which was not found."}) return @staticmethod @@ -337,7 +337,7 @@ class InputValidationTimes(): ] if not len(matching_spm)>0: - raise ValidationError(f'The participant group with id {user_participant_id} is not assigned to the shipcall. Found ShipcallParticipantMap: {spm_shipcall_data}') # part of a pytest.raises + raise ValidationError({"participant_id":f'The participant group with id {user_participant_id} is not assigned to the shipcall. Found ShipcallParticipantMap: {spm_shipcall_data}'}) # part of a pytest.raises return @staticmethod @@ -356,7 +356,7 @@ class InputValidationTimes(): # check, if there is already a dataset for the participant type participant_type_exists_already = any([ParticipantType(time_.get("participant_type",0)) in participant_type for time_ in times]) if participant_type_exists_already: - raise ValidationError(f"A dataset for the participant type is already present. Participant Type: {participant_type}. Times Datasets: {times}") + raise ValidationError({"participant_type":f"A dataset for the participant type is already present. Participant Type: {participant_type}. Times Datasets: {times}"}) return @staticmethod @@ -399,7 +399,7 @@ class InputValidationTimes(): # extracts the participant_id from the first matching entry, if applicable if not len(pdata)>0: # this case is usually covered by the InputValidationTimes.check_if_entry_is_already_deleted method already - raise ValidationError(f"Unknown times_id. Could not find a matching entry for ID: {times_id}") + raise ValidationError({"times_id":f"Unknown times_id. Could not find a matching entry for ID: {times_id}"}) else: participant_type = pdata[0].get("participant_type") shipcall_id = pdata[0].get("shipcall_id") @@ -415,14 +415,14 @@ class InputValidationTimes(): if special_case__bsmd_may_edit_agency_dataset: return else: - raise ValidationError(f"The dataset may only be changed by a user belonging to the same participant group as the times dataset is referring to. User participant_id: {user_participant_id}; Dataset participant_id: {participant_id_of_times_dataset}") + raise ValidationError({"user_participant_type":f"The dataset may only be changed by a user belonging to the same participant group as the times dataset is referring to. User participant_id: {user_participant_id}; Dataset participant_id: {participant_id_of_times_dataset}"}) return @staticmethod def get_participant_id_from_shipcall_participant_map(shipcall_id:int, participant_type:int, spm_shipcall_data=None)->int: """use shipcall_id and participant_type to identify the matching participant_id""" if shipcall_id is None: - raise ValidationError(f"Could not find a referenced shipcall_id within the request.") + raise ValidationError({"shipcall_id":f"Could not find a referenced shipcall_id within the request."}) if spm_shipcall_data is None: spm_shipcall_data = execute_sql_query_standalone( @@ -432,7 +432,7 @@ class InputValidationTimes(): # raise an error when there are no matches if len(spm_shipcall_data)==0: - raise ValidationError(f"Could not find a matching time dataset for the provided participant_type: {participant_type} at shipcall with id {shipcall_id}.") + raise ValidationError({"participant_type":f"Could not find a matching time dataset for the provided participant_type: {participant_type} at shipcall with id {shipcall_id}."}) participant_id_of_times_dataset = spm_shipcall_data[0].get("participant_id") return participant_id_of_times_dataset diff --git a/src/server/BreCal/validators/input_validation_utils.py b/src/server/BreCal/validators/input_validation_utils.py index e68f18e..8c2c2ed 100644 --- a/src/server/BreCal/validators/input_validation_utils.py +++ b/src/server/BreCal/validators/input_validation_utils.py @@ -158,7 +158,7 @@ def check_if_participant_id_is_valid_standalone(participant_id:int, participant_ if participant_type is not None: if participant_id not in list(participants.keys()): - raise ValidationError(f"the provided participant_id {participant_id} does not exist in the database.") + raise ValidationError({"participant_id":f"the provided participant_id {participant_id} does not exist in the database."}) # IntFlag object participant_type_in_db = ParticipantType(int(participants.get(participant_id).get("type", ParticipantType.undefined))) diff --git a/src/server/BreCal/validators/time_logic.py b/src/server/BreCal/validators/time_logic.py index 8ebad56..27e80b8 100644 --- a/src/server/BreCal/validators/time_logic.py +++ b/src/server/BreCal/validators/time_logic.py @@ -36,10 +36,10 @@ def validate_time_is_in_not_too_distant_future(raise_validation_error:bool, valu if raise_validation_error: if not is_in_future: - raise ValidationError(f"The provided value must be in the future. Current Time: {datetime.datetime.now()}, Value: {value}") + raise ValidationError({"any_date":f"The provided value must be in the future. Current Time: {datetime.datetime.now()}, Value: {value}"}) if is_too_distant: - raise ValidationError(f"The provided value is in the too distant future and exceeds a threshold for 'reasonable' entries. Found: {value}") + raise ValidationError({"any_date":f"The provided value is in the too distant future and exceeds a threshold for 'reasonable' entries. Found: {value}"}) return is_in_future & (not is_too_distant) diff --git a/src/server/BreCal/validators/validation_error.py b/src/server/BreCal/validators/validation_error.py index 65ee985..9ea846a 100644 --- a/src/server/BreCal/validators/validation_error.py +++ b/src/server/BreCal/validators/validation_error.py @@ -11,8 +11,24 @@ def create_validation_error_response(ex:ValidationError, status_code:int=400)->t # generate an overview the errors #example: # {'lock_time': ['The provided value must be in the future. Current Time: 2024-09-02 08:23:32.600791, Value: 2024-09-01 08:20:41.853000']} + # when the model schema returns an error, 'messages' is by default a dictionary. + # e.g., loadedModel = model.TimesSchema().load(data=content, many=False, partial=True) + # returns: {'eta_berth': ['The provided value must be in the future} + + # when raising a custom ValidationError, it can return a string, list or dict. + # we would like to ensure, that the content of the .messages is a dictionary. This can be accomplished by calling + # raise ValidationError({"example_key_which_fails":"the respective error message"}) errors = ex.messages + # raise ValidationError("example error") + # creates a .messages object, which is an array. e.g., ex.messages = ["example error"] + # the following conversion snipped ensures a dictionary output + if isinstance(errors, (str,list)): + errors = {"undefined_schema":errors} + + # hence, errors always has the following type: dict[str, list[str]] + + # example: # "Valid Data": { # "id": 2894,