Repository: mathesar-foundation/mathesar

Issue:
Internal Server Error when searching with partial dates in Find a Record
## Description
<!-- A clear and concise description of what the bug is. -->
Sometimes when partially entering the date in the Record Search Field, it randomly throws internal server errors.
Sometimes it throws the intended `InvalidDateFormatAPIException` but other times it `UnboundLocalError`.

Here is a recording of the behaviour:

https://github.com/mathesar-foundation/mathesar/assets/31622972/6e7885c2-9d19-473a-86e0-8c63c5e68c0f


Also, it would be good UI/UX to show the user the date is invalid. In case when the user has just put an incorrect date time format and the server returns a 400 BAD REQUEST InvalidDateFormatAPIException. The user won't know as the table still shows the loading state.
<img width="1700" alt="image" src="https://github.com/mathesar-foundation/mathesar/assets/31622972/78fac439-c326-47ae-8189-08dc61963d75">


## Expected behavior
<!-- A clear and concise description of what you expected to happen. -->
- Server should not throw 500 internal server error and always throw InvalidDateFormatAPIException in case of wrong date format.
- User should be conveyed if their input date is incorrect.

## To Reproduce
<!-- How can we recreate this bug? Please try to provide a Minimal, Complete, and Verifiable (http://stackoverflow.com/help/mcve) example if code-related. -->

## Environment
 - OS: macOS
 - Browser: Chrome
 - Other info:

## Additional context
<!-- Add any other context about the problem or screenshots here. -->



Discussion:
Thanks for reporting this, @dikwickley. Your video is very helpful.

> Server should not throw 500 internal server error

Agreed. **The scope of this ticket should be constrained to this API problem.**

You've also described some UX problems. Those should be covered by #2327, which I imagine you might be interested in working on.

Resolving patch:
diff --git a/mathesar/api/db/viewsets/records.py b/mathesar/api/db/viewsets/records.py
--- a/mathesar/api/db/viewsets/records.py
+++ b/mathesar/api/db/viewsets/records.py
@@ -1,4 +1,4 @@
-from psycopg2.errors import ForeignKeyViolation, InvalidDatetimeFormat
+from psycopg2.errors import ForeignKeyViolation, InvalidDatetimeFormat, DatetimeFieldOverflow
 from rest_access_policy import AccessViewSetMixin
 from rest_framework import status, viewsets
 from rest_framework.exceptions import NotFound, MethodNotAllowed
@@ -108,6 +108,16 @@ def list(self, request, table_pk=None):
                     e,
                     status_code=status.HTTP_400_BAD_REQUEST,
                 )
+            elif isinstance(e.orig, DatetimeFieldOverflow):
+                raise database_api_exceptions.InvalidDateAPIException(
+                    e,
+                    status_code=status.HTTP_400_BAD_REQUEST,
+                )
+            else:
+                raise database_api_exceptions.MathesarAPIException(
+                    e,
+                    status_code=status.HTTP_400_BAD_REQUEST
+                )
 
         serializer = RecordSerializer(
             records,


Test patch:
diff --git a/mathesar/tests/api/test_record_api.py b/mathesar/tests/api/test_record_api.py
--- a/mathesar/tests/api/test_record_api.py
+++ b/mathesar/tests/api/test_record_api.py
@@ -322,6 +322,51 @@ def test_record_search(create_patents_table, client):
     assert len(response_data['results']) == 1
 
 
+def test_record_search_invalid_date(create_patents_table, client):
+    table_name = 'NASA Record Invalid Date'
+    table = create_patents_table(table_name)
+    columns_name_id_map = table.get_column_name_id_bidirectional_map()
+    column_id_with_date_type = table.get_column_name_id_bidirectional_map()['Patent Expiration Date']
+    column_attnum = table.columns.get(id=column_id_with_date_type).attnum
+    table.alter_column(column_attnum, {'type': 'date'})
+    search_columns = [
+        {'field': columns_name_id_map['Patent Expiration Date'], 'literal': '99/99/9999'},
+    ]
+
+    json_search_fuzzy = json.dumps(search_columns)
+
+    response = client.get(
+        f'/api/db/v0/tables/{table.id}/records/?search_fuzzy={json_search_fuzzy}'
+    )
+    response_data = response.json()
+    assert response.status_code == 400
+    assert response_data[0]['code'] == ErrorCodes.InvalidDateError.value
+    assert response_data[0]['message'] == 'Invalid date'
+
+
+def test_record_search_invalid_date_format(create_patents_table, client):
+    table_name = 'NASA Record Invalid Date Format'
+    table = create_patents_table(table_name)
+    columns_name_id_map = table.get_column_name_id_bidirectional_map()
+    column_id_with_date_type = table.get_column_name_id_bidirectional_map()['Patent Expiration Date']
+    column_attnum = table.columns.get(id=column_id_with_date_type).attnum
+    table.alter_column(column_attnum, {'type': 'date'})
+    search_columns = [
+        {'field': columns_name_id_map['Patent Expiration Date'], 'literal': '12/31'},
+    ]
+
+    json_search_fuzzy = json.dumps(search_columns)
+
+    response = client.get(
+        f'/api/db/v0/tables/{table.id}/records/?search_fuzzy={json_search_fuzzy}'
+    )
+    response_data = response.json()
+
+    assert response.status_code == 400
+    assert response_data[0]['code'] == ErrorCodes.InvalidDateFormatError.value
+    assert response_data[0]['message'] == 'Invalid date format'
+
+
 grouping_params = [
     (
         'NASA Record List Group Single',