Repository: cookiecutter/cookiecutter

Issue:
Variable with null default no longer being required
* Cookiecutter version: 2.3.0
* Template project url: internal
* Python version: Python 3.8.12
* Operating System: Debian GNU/Linux 11

### Description:

After updating from 2.1.1 to 2.3.0, setting a field's default value in `cookiecutter.json` to `null` no longer makes that field required. It instead passes `None` as the value of that variable. Previously, it would repeat that prompt until you gave it a value.

It looks like this happened in the switch from `click` to `rich` in 2.3.0 - `click` doesn't allow a prompt without a default value to be empty (there's [a `while True` loop](https://github.com/pallets/click/blob/388b0e14093355e30b17ffaff0c7588533d9dbc8/src/click/termui.py#L163)), while `rich` does ([it just casts the input and makes sure it doesn't cause a ValueError](https://github.com/Textualize/rich/blob/720800e6930d85ad027b1e9bd0cbb96b5e994ce3/rich/prompt.py#L230) -- which it doesn't, as `str(None)=="None"`). It seems like, with `rich`, you have to handle that logic yourself.

### What I've run:

An example (`component_name`'s value is `null`, `service_name`'s is `test`):

```
Template 1:

  [1/8] component_name: 
  [2/8] service_name (test): ^C
Aborted!
```

Compare to output with version 2.1.1:

```
Template 1:

component_name: 
component_name: 
component_name: test
service_name [test]: ^CAborted!
```


Discussion:

Resolving patch:
diff --git a/cookiecutter/prompt.py b/cookiecutter/prompt.py
--- a/cookiecutter/prompt.py
+++ b/cookiecutter/prompt.py
@@ -20,7 +20,13 @@ def read_user_variable(var_name, default_value, prompts=None, prefix=""):
         if prompts and var_name in prompts.keys() and prompts[var_name]
         else var_name
     )
-    return Prompt.ask(f"{prefix}{question}", default=default_value)
+
+    while True:
+        variable = Prompt.ask(f"{prefix}{question}", default=default_value)
+        if variable is not None:
+            break
+
+    return variable
 
 
 class YesNoPrompt(Confirm):


Test patch:
diff --git a/tests/test_read_user_variable.py b/tests/test_read_user_variable.py
--- a/tests/test_read_user_variable.py
+++ b/tests/test_read_user_variable.py
@@ -1,18 +1,36 @@
 """test_read_user_variable."""
+import pytest
 from cookiecutter.prompt import read_user_variable
 
 VARIABLE = 'project_name'
 DEFAULT = 'Kivy Project'
 
 
-def test_click_invocation(mocker):
+@pytest.fixture
+def mock_prompt(mocker):
+    """Return a mocked version of the 'Prompt.ask' function."""
+    return mocker.patch('rich.prompt.Prompt.ask')
+
+
+def test_click_invocation(mock_prompt):
     """Test click function called correctly by cookiecutter.
 
     Test for string type invocation.
     """
-    prompt = mocker.patch('rich.prompt.Prompt.ask')
-    prompt.return_value = DEFAULT
+    mock_prompt.return_value = DEFAULT
 
     assert read_user_variable(VARIABLE, DEFAULT) == DEFAULT
 
-    prompt.assert_called_once_with(VARIABLE, default=DEFAULT)
+    mock_prompt.assert_called_once_with(VARIABLE, default=DEFAULT)
+
+
+def test_input_loop_with_null_default_value(mock_prompt):
+    """Test `Prompt.ask` is run repeatedly until a valid answer is provided.
+
+    Test for `default_value` parameter equal to None.
+    """
+    # Simulate user providing None input initially and then a valid input
+    mock_prompt.side_effect = [None, DEFAULT]
+
+    assert read_user_variable(VARIABLE, None) == DEFAULT
+    assert mock_prompt.call_count == 2