Repository: pydantic/pydantic

Issue:
Unexpected Config Error with discriminator
### Initial Checks

- [X] I have searched GitHub for a duplicate issue and I'm sure this is something new
- [X] I have searched Google & StackOverflow for a solution and couldn't find anything
- [X] I have read and followed [the docs](https://pydantic-docs.helpmanual.io) and still think this is a bug
- [X] I am confident that the issue is with pydantic (not my code, or another library in the ecosystem like [FastAPI](https://fastapi.tiangolo.com) or [mypy](https://mypy.readthedocs.io/en/stable))


### Description

Discriminator validation.

Expected: ValidationError when passing list or dict as discriminator.

Received: ConfigError.

### Example Code

```Python
from typing import Literal

import pydantic


class Model1(pydantic.BaseModel):
    target: Literal["t1"]
    a: int


class Model2(pydantic.BaseModel):
    target: Literal["t2"]
    b: int


class Foo(pydantic.BaseModel):
    foo: Model1 | Model2 = pydantic.Field(discriminator="target")


# Foo(**{"foo": {"target": []}})
Foo(**{"foo": {"target": {}}})
```


### Python, Pydantic & OS Version

```Text
pydantic version: 1.10.2
            pydantic compiled: True
                 install path: /home/.../lib/python3.10/site-packages/pydantic
               python version: 3.10.8 (main, Oct 19 2022, 10:31:59) [GCC 9.4.0]
                     platform: Linux-5.15.0-53-generic-x86_64-with-glibc2.31
     optional deps. installed: ['typing-extensions']
```


### Affected Components

- [ ] [Compatibility between releases](https://pydantic-docs.helpmanual.io/changelog/)
- [X] [Data validation/parsing](https://pydantic-docs.helpmanual.io/usage/models/#basic-model-usage)
- [ ] [Data serialization](https://pydantic-docs.helpmanual.io/usage/exporting_models/) - `.dict()` and `.json()`
- [ ] [JSON Schema](https://pydantic-docs.helpmanual.io/usage/schema/)
- [ ] [Dataclasses](https://pydantic-docs.helpmanual.io/usage/dataclasses/)
- [ ] [Model Config](https://pydantic-docs.helpmanual.io/usage/model_config/)
- [ ] [Field Types](https://pydantic-docs.helpmanual.io/usage/types/) - adding or changing a particular data type
- [ ] [Function validation decorator](https://pydantic-docs.helpmanual.io/usage/validation_decorator/)
- [ ] [Generic Models](https://pydantic-docs.helpmanual.io/usage/models/#generic-models)
- [ ] [Other Model behaviour](https://pydantic-docs.helpmanual.io/usage/models/) - `construct()`, pickling, private attributes, ORM mode
- [ ] [Plugins](https://pydantic-docs.helpmanual.io/) and integration with other tools - mypy, FastAPI, python-devtools, Hypothesis, VS Code, PyCharm, etc.


Comments:
The real error is there `TypeError: unhashable type: 'list'` but `TypeError` is caught to raise a `ConfigError` indeed.
I wrote about error, which received by user.
I just didn't understand https://github.com/pydantic/pydantic/blob/v1.10.2/pydantic/fields.py#L1124 otherwise I would have created PR, not issue
> I wrote about error, which received by user. I just didn't understand https://github.com/pydantic/pydantic/blob/v1.10.2/pydantic/fields.py#L1124 otherwise I would have created PR, not issue

`discriminator` decide `target` can be `Model1` or `Model2`

example

```python
Foo(foo={'target': 't1', 'a': 1})  # this is Model1
```

```python
Foo(foo={'target': 't2', 'b': 1})  # this is Model2
```

Usage below is not right

```python
Foo(**{"foo": {"target": {}}})
```
this is not a bug
> this is not a bug

You mean that I need to validate `target` field separately or something else? 

> Usage below is not right

Why do you think so?

> ```python
> Foo(foo={'target': 't2', 'b': 1})
> ```

target should not be {}, target should be one of t1 / t2
> target should not be {}, target should be one of t1 / t2

sounds weird

In this case how to explain that all works correctly with 

`Foo(**{"foo": {"target": 123}})`?



The bug report seems to be about the error handling itself.

```
Foo(**{"foo": {"target": 123}})
```
raises a `ValidationError` indicating the document failed validation

```
Foo(**{"foo": {"target": []}})
```
raises a `ConfigError` indicating the Model is incorrect

---

Both documents should be invalid and thus should both raise `ValidationError`s

Patch:
diff --git a/pydantic/fields.py b/pydantic/fields.py
--- a/pydantic/fields.py
+++ b/pydantic/fields.py
@@ -1117,15 +1117,18 @@ def _validate_discriminated_union(
             except (AttributeError, TypeError):
                 return v, ErrorWrapper(MissingDiscriminator(discriminator_key=self.discriminator_key), loc)
 
-        try:
-            sub_field = self.sub_fields_mapping[discriminator_value]  # type: ignore[index]
-        except TypeError:
+        if self.sub_fields_mapping is None:
             assert cls is not None
             raise ConfigError(
                 f'field "{self.name}" not yet prepared so type is still a ForwardRef, '
                 f'you might need to call {cls.__name__}.update_forward_refs().'
             )
-        except KeyError:
+
+        try:
+            sub_field = self.sub_fields_mapping[discriminator_value]
+        except (KeyError, TypeError):
+            # KeyError: `discriminator_value` is not in the dictionary.
+            # TypeError: `discriminator_value` is unhashable.
             assert self.sub_fields_mapping is not None
             return v, ErrorWrapper(
                 InvalidDiscriminator(


Test patch:
diff --git a/tests/test_discrimated_union.py b/tests/test_discrimated_union.py
--- a/tests/test_discrimated_union.py
+++ b/tests/test_discrimated_union.py
@@ -423,3 +423,21 @@ class Container(GenericModel, Generic[T]):
 
     # coercion is done properly
     assert Container[str].parse_obj({'result': {'type': 'Success', 'data': 1}}).result.data == '1'
+
+
+def test_discriminator_with_unhashable_type():
+    """Verify an unhashable discriminator value raises a ValidationError."""
+
+    class Model1(BaseModel):
+        target: Literal['t1']
+        a: int
+
+    class Model2(BaseModel):
+        target: Literal['t2']
+        b: int
+
+    class Foo(BaseModel):
+        foo: Union[Model1, Model2] = Field(discriminator='target')
+
+    with pytest.raises(ValidationError, match=re.escape("No match for discriminator 'target' and value {}")):
+        Foo(**{'foo': {'target': {}}})