Repository: tefra/xsdata

Issue:
Improve converter/parser errors
Currently the parser/converter errors don't include any information regarding the tree/node making it difficult to troubleshoot things.


Mentions
https://github.com/tefra/xsdata/pull/608
https://github.com/tefra/xsdata/issues/610
https://github.com/tefra/xsdata/issues/636



Discussion:
sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=tefra_xsdata&pullRequest=1036) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=1036&resolved=false&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=1036&metric=new_accepted_issues&view=list)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=1036&resolved=false&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/no-data-16px.png '') No data about Coverage  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=1036&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=tefra_xsdata&pullRequest=1036)


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/tefra/xsdata/pull/1036?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 100.00%. Comparing base [(`f4b6965`)](https://app.codecov.io/gh/tefra/xsdata/commit/f4b6965f9accfb311371c667eac4f53ca5f989c3?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) to head [(`cb69d1f`)](https://app.codecov.io/gh/tefra/xsdata/pull/1036?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou).


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff            @@
##              main     #1036   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          115       115           
  Lines         9267      9282   +15     
  Branches      2191      2190    -1     
=========================================
+ Hits          9267      9282   +15     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/tefra/xsdata/pull/1036?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou).


Patch:
diff --git a/xsdata/formats/converter.py b/xsdata/formats/converter.py
index 25864a4e..9da4a854 100644
--- a/xsdata/formats/converter.py
+++ b/xsdata/formats/converter.py
@@ -2,7 +2,6 @@ import abc
 import base64
 import binascii
 import math
-import warnings
 from datetime import date, datetime, time
 from decimal import Decimal, InvalidOperation
 from enum import Enum, EnumMeta
@@ -20,7 +19,7 @@ from typing import (
 )
 from xml.etree.ElementTree import QName
 
-from xsdata.exceptions import ConverterError, ConverterWarning
+from xsdata.exceptions import ConverterError
 from xsdata.models.datatype import (
     XmlBase64Binary,
     XmlDate,
@@ -87,16 +86,16 @@ class ConverterFactory:
     def deserialize(self, value: Any, types: Sequence[Type], **kwargs: Any) -> Any:
         """Attempt to convert any value to one of the given types.
 
-        If all attempts fail return the value input value and emit a
-        warning.
-
         Args:
             value: The input value
             types: The target candidate types
             **kwargs: Additional keyword arguments needed per converter
 
+        Raises:
+            ConverterError: if the value can't be converted to any of the given types.
+
         Returns:
-            The converted value or the input value.
+            The converted value
         """
         for data_type in types:
             try:
@@ -105,10 +104,8 @@ class ConverterFactory:
             except ConverterError:
                 pass
 
-        warnings.warn(
-            f"Failed to convert value `{value}` to one of {types}", ConverterWarning
-        )
-        return value
+        type_names = " | ".join(tp.__name__ for tp in types)
+        raise ConverterError(f"`{value}` is not a valid `{type_names}`")
 
     def serialize(self, value: Any, **kwargs: Any) -> Any:
         """Convert the given value to string.
@@ -153,10 +150,9 @@ class ConverterFactory:
         if not isinstance(value, str):
             return False
 
-        with warnings.catch_warnings(record=True) as w:
+        try:
             decoded = self.deserialize(value, types, **kwargs)
-
-        if w and w[-1].category is ConverterWarning:
+        except ConverterError:
             return False
 
         if strict and isinstance(decoded, (float, int, Decimal, XmlPeriod)):
@@ -197,12 +193,14 @@ class ConverterFactory:
         """Find a suitable converter for given data type.
 
         Iterate over all but last mro items and check for registered
-        converters, fall back to str and issue a warning if there are
-        no matches.
+        converters.
 
         Args:
             data_type: The data type
 
+        Raises:
+            ConverterError: if the data type is not registered.
+
         Returns:
             A converter instance
         """
@@ -217,8 +215,7 @@ class ConverterFactory:
             if mro in self.registry:
                 return self.registry[mro]
 
-        warnings.warn(f"No converter registered for `{data_type}`", ConverterWarning)
-        return self.registry[str]
+        raise ConverterError(f"No converter registered for `{data_type.__qualname__}`")
 
     def value_converter(self, value: Any) -> Converter:
         """Get a suitable converter for the given value."""
@@ -698,9 +695,10 @@ class EnumConverter(Converter):
 
     @classmethod
     def _match_atomic(cls, raw: Any, real: Any, **kwargs: Any) -> bool:
-        with warnings.catch_warnings():
-            warnings.simplefilter("ignore")
+        try:
             cmp = converter.deserialize(raw, [type(real)], **kwargs)
+        except ConverterError:
+            cmp = raw
 
         if isinstance(real, float):
             return cmp == real or repr(cmp) == repr(real)
diff --git a/xsdata/formats/dataclass/parsers/dict.py b/xsdata/formats/dataclass/parsers/dict.py
index 9e469cdb..d225c90f 100644
--- a/xsdata/formats/dataclass/parsers/dict.py
+++ b/xsdata/formats/dataclass/parsers/dict.py
@@ -325,13 +325,11 @@ class DictDecoder:
         value = converter.serialize(value)
 
         # Convert value according to the field types
-        return ParserUtils.parse_value(
+        return ParserUtils.parse_var(
+            meta=meta,
+            var=var,
             value=value,
-            types=var.types,
-            default=var.default,
             ns_map=EMPTY_MAP,
-            tokens_factory=var.tokens_factory,
-            format=var.format,
         )
 
     def bind_complex_type(self, meta: XmlMeta, var: XmlVar, data: Dict) -> Any:
diff --git a/xsdata/formats/dataclass/parsers/mixins.py b/xsdata/formats/dataclass/parsers/mixins.py
index 9599aa73..3c739957 100644
--- a/xsdata/formats/dataclass/parsers/mixins.py
+++ b/xsdata/formats/dataclass/parsers/mixins.py
@@ -174,6 +174,8 @@ class XmlNode(abc.ABC):
     and a list of all the intermediate objects.
     """
 
+    __slots__ = ()
+
     @abc.abstractmethod
     def child(self, qname: str, attrs: Dict, ns_map: Dict, position: int) -> "XmlNode":
         """Initialize the next child node to be queued, when an element starts.
diff --git a/xsdata/formats/dataclass/parsers/nodes/element.py b/xsdata/formats/dataclass/parsers/nodes/element.py
index 4b9a1f05..5cf7489e 100644
--- a/xsdata/formats/dataclass/parsers/nodes/element.py
+++ b/xsdata/formats/dataclass/parsers/nodes/element.py
@@ -192,13 +192,11 @@ class ElementNode(XmlNode):
             var: The xml var instance
             value: The attribute value
         """
-        value = ParserUtils.parse_value(
+        value = ParserUtils.parse_var(
+            meta=self.meta,
+            var=var,
             value=value,
-            types=var.types,
-            default=var.default,
             ns_map=self.ns_map,
-            tokens_factory=var.tokens_factory,
-            format=var.format,
         )
 
         if var.init:
@@ -372,13 +370,11 @@ class ElementNode(XmlNode):
         if self.xsi_nil and not text:
             value = None
         else:
-            value = ParserUtils.parse_value(
+            value = ParserUtils.parse_var(
+                meta=self.meta,
+                var=var,
                 value=text,
-                types=var.types,
-                default=var.default,
                 ns_map=self.ns_map,
-                tokens_factory=var.tokens_factory,
-                format=var.format,
             )
 
         if var.init:
@@ -496,6 +492,7 @@ class ElementNode(XmlNode):
         """
         if var.is_clazz_union:
             return nodes.UnionNode(
+                meta=self.meta,
                 var=var,
                 attrs=attrs,
                 ns_map=ns_map,
@@ -522,12 +519,14 @@ class ElementNode(XmlNode):
             )
 
         if not var.any_type and not var.is_wildcard:
-            return nodes.PrimitiveNode(var, ns_map, self.meta.mixed_content)
+            return nodes.PrimitiveNode(self.meta, var, ns_map)
 
         datatype = DataType.from_qname(xsi_type) if xsi_type else None
         derived = var.is_wildcard
         if datatype:
             return nodes.StandardNode(
+                self.meta,
+                var,
                 datatype,
                 ns_map,
                 var.nillable,
diff --git a/xsdata/formats/dataclass/parsers/nodes/primitive.py b/xsdata/formats/dataclass/parsers/nodes/primitive.py
index 8cbcaef8..8537eb77 100644
--- a/xsdata/formats/dataclass/parsers/nodes/primitive.py
+++ b/xsdata/formats/dataclass/parsers/nodes/primitive.py
@@ -1,7 +1,7 @@
 from typing import Dict, List, Optional
 
 from xsdata.exceptions import XmlContextError
-from xsdata.formats.dataclass.models.elements import XmlVar
+from xsdata.formats.dataclass.models.elements import XmlMeta, XmlVar
 from xsdata.formats.dataclass.parsers.mixins import XmlNode
 from xsdata.formats.dataclass.parsers.utils import ParserUtils
 
@@ -10,17 +10,17 @@ class PrimitiveNode(XmlNode):
     """XmlNode for text elements with simple type values.
 
     Args:
+        meta: The parent xml meta instance
         var: The xml var instance
         ns_map: The element namespace prefix-URI map
-        mixed: Specifies if this node supports mixed content
     """
 
-    __slots__ = "var", "ns_map"
+    __slots__ = "meta", "var", "ns_map"
 
-    def __init__(self, var: XmlVar, ns_map: Dict, mixed: bool):
+    def __init__(self, meta: XmlMeta, var: XmlVar, ns_map: Dict):
+        self.meta = meta
         self.var = var
         self.ns_map = ns_map
-        self.mixed = mixed
 
     def bind(
         self,
@@ -44,13 +44,8 @@ class PrimitiveNode(XmlNode):
         Returns:
             Whether the binding process was successful or not.
         """
-        obj = ParserUtils.parse_value(
-            value=text,
-            types=self.var.types,
-            default=self.var.default,
-            ns_map=self.ns_map,
-            tokens_factory=self.var.tokens_factory,
-            format=self.var.format,
+        obj = ParserUtils.parse_var(
+            meta=self.meta, var=self.var, value=text, ns_map=self.ns_map
         )
 
         if obj is None and not self.var.nillable:
@@ -58,7 +53,7 @@ class PrimitiveNode(XmlNode):
 
         objects.append((qname, obj))
 
-        if self.mixed:
+        if self.meta.mixed_content:
             tail = ParserUtils.normalize_content(tail)
             if tail:
                 objects.append((None, tail))
diff --git a/xsdata/formats/dataclass/parsers/nodes/standard.py b/xsdata/formats/dataclass/parsers/nodes/standard.py
index 362d1dcd..297444ab 100644
--- a/xsdata/formats/dataclass/parsers/nodes/standard.py
+++ b/xsdata/formats/dataclass/parsers/nodes/standard.py
@@ -1,6 +1,7 @@
 from typing import Dict, List, Optional, Type
 
 from xsdata.exceptions import XmlContextError
+from xsdata.formats.dataclass.models.elements import XmlMeta, XmlVar
 from xsdata.formats.dataclass.parsers.mixins import XmlNode
 from xsdata.formats.dataclass.parsers.utils import ParserUtils
 from xsdata.models.enums import DataType
@@ -16,15 +17,19 @@ class StandardNode(XmlNode):
         derived_factory: The derived element factory
     """
 
-    __slots__ = "datatype", "ns_map", "nillable", "derived_factory"
+    __slots__ = "meta", "var", "datatype", "ns_map", "nillable", "derived_factory"
 
     def __init__(
         self,
+        meta: XmlMeta,
+        var: XmlVar,
         datatype: DataType,
         ns_map: Dict,
         nillable: bool,
         derived_factory: Optional[Type],
     ):
+        self.meta = meta
+        self.var = var
         self.datatype = datatype
         self.ns_map = ns_map
         self.nillable = nillable
@@ -52,7 +57,9 @@ class StandardNode(XmlNode):
             Always true, it's not possible to fail during parsing
             for this node.
         """
-        obj = ParserUtils.parse_value(
+        obj = ParserUtils.parse_var(
+            meta=self.meta,
+            var=self.var,
             value=text,
             types=[self.datatype.type],
             ns_map=self.ns_map,
diff --git a/xsdata/formats/dataclass/parsers/nodes/union.py b/xsdata/formats/dataclass/parsers/nodes/union.py
index 5d42b453..fa842df9 100644
--- a/xsdata/formats/dataclass/parsers/nodes/union.py
+++ b/xsdata/formats/dataclass/parsers/nodes/union.py
@@ -4,7 +4,7 @@ from typing import Any, Dict, List, Optional, Tuple, Type
 
 from xsdata.exceptions import ConverterWarning, ParserError
 from xsdata.formats.dataclass.context import XmlContext
-from xsdata.formats.dataclass.models.elements import XmlVar
+from xsdata.formats.dataclass.models.elements import XmlMeta, XmlVar
 from xsdata.formats.dataclass.parsers.bases import NodeParser
 from xsdata.formats.dataclass.parsers.config import ParserConfig
 from xsdata.formats.dataclass.parsers.mixins import EventsHandler, XmlNode
@@ -31,6 +31,7 @@ class UnionNode(XmlNode):
     """
 
     __slots__ = (
+        "meta",
         "var",
         "attrs",
         "ns_map",
@@ -43,6 +44,7 @@ class UnionNode(XmlNode):
 
     def __init__(
         self,
+        meta: XmlMeta,
         var: XmlVar,
         attrs: Dict,
         ns_map: Dict,
@@ -50,6 +52,7 @@ class UnionNode(XmlNode):
         config: ParserConfig,
         context: XmlContext,
     ):
+        self.meta = meta
         self.var = var
         self.attrs = attrs
         self.ns_map = ns_map
@@ -169,8 +172,12 @@ class UnionNode(XmlNode):
         try:
             with warnings.catch_warnings():
                 warnings.filterwarnings("error", category=ConverterWarning)
-                return ParserUtils.parse_value(
-                    value=value, types=types, ns_map=self.ns_map
+                return ParserUtils.parse_var(
+                    meta=self.meta,
+                    var=self.var,
+                    value=value,
+                    types=types,
+                    ns_map=self.ns_map,
                 )
         except Exception:
             return None
diff --git a/xsdata/formats/dataclass/parsers/utils.py b/xsdata/formats/dataclass/parsers/utils.py
index 305e49a6..bac892c9 100644
--- a/xsdata/formats/dataclass/parsers/utils.py
+++ b/xsdata/formats/dataclass/parsers/utils.py
@@ -1,8 +1,9 @@
 import math
+import warnings
 from collections import UserList
-from typing import Any, Callable, Dict, Iterable, Optional, Sequence, Type
+from typing import Any, Callable, Dict, Iterable, Optional, Sequence, Type, Union
 
-from xsdata.exceptions import ParserError
+from xsdata.exceptions import ConverterError, ConverterWarning, ParserError
 from xsdata.formats.converter import QNameConverter, converter
 from xsdata.formats.dataclass.models.elements import XmlMeta, XmlVar
 from xsdata.models.enums import QNames
@@ -10,6 +11,12 @@ from xsdata.utils import collections, constants, text
 from xsdata.utils.namespaces import build_qname
 
 
+class _MissingType: ...
+
+
+MISSING = _MissingType()
+
+
 class PendingCollection(UserList):
     """An iterable implementation of parsed values.
 
@@ -71,6 +78,51 @@ class ParserUtils:
         xsi_nil = attrs.get(QNames.XSI_NIL)
         return xsi_nil == constants.XML_TRUE if xsi_nil else None
 
+    @classmethod
+    def parse_var(
+        cls,
+        meta: XmlMeta,
+        var: XmlVar,
+        value: Any,
+        ns_map: Optional[Dict] = None,
+        default: Any = None,
+        types: Optional[Sequence[Type]] = None,
+        tokens_factory: Optional[Callable] = None,
+        format: Optional[str] = None,
+    ) -> Any:
+        """Convert a value to a python primitive type.
+
+        Args:
+            meta: The xml meta instance
+            var: The xml var instance
+            value: A primitive value or a list of primitive values
+            ns_map: The element namespace prefix-URI map
+            default: Override the var default value
+            types: Override the var types
+            tokens_factory: Override the var tokens factory
+            format: Override the var format
+
+        Returns:
+            The converted value or values.
+        """
+        try:
+            value = cls.parse_value(
+                value=value,
+                types=types or var.types,
+                default=default or var.default,
+                ns_map=ns_map,
+                tokens_factory=tokens_factory or var.tokens_factory,
+                format=format or var.format,
+            )
+        except ConverterError as ex:
+            message = f"  {str(ex)}"
+            warnings.warn(
+                f"Failed to convert value for `{meta.clazz.__qualname__}.{var.name}`\n{message}",
+                ConverterWarning,
+            )
+
+        return value
+
     @classmethod
     def parse_value(
         cls,


Test patch:
diff --git a/tests/formats/dataclass/parsers/nodes/test_element.py b/tests/formats/dataclass/parsers/nodes/test_element.py
index a1e5369e..dde50d59 100644
--- a/tests/formats/dataclass/parsers/nodes/test_element.py
+++ b/tests/formats/dataclass/parsers/nodes/test_element.py
@@ -538,5 +538,5 @@ class ElementNodeTests(FactoryTestCase):
 
         self.assertIsInstance(actual, PrimitiveNode)
         self.assertEqual(ns_map, actual.ns_map)
+        self.assertEqual(self.meta, actual.meta)
         self.assertEqual(var, actual.var)
-        self.assertEqual(self.node.meta.mixed_content, actual.mixed)
diff --git a/tests/formats/dataclass/parsers/nodes/test_primitive.py b/tests/formats/dataclass/parsers/nodes/test_primitive.py
index 5e8f76d0..ffddb333 100644
--- a/tests/formats/dataclass/parsers/nodes/test_primitive.py
+++ b/tests/formats/dataclass/parsers/nodes/test_primitive.py
@@ -1,33 +1,33 @@
 from unittest import TestCase, mock
 
+from tests.fixtures.artists import Artist
 from xsdata.exceptions import XmlContextError
 from xsdata.formats.dataclass.models.elements import XmlType
 from xsdata.formats.dataclass.parsers.nodes import PrimitiveNode
 from xsdata.formats.dataclass.parsers.utils import ParserUtils
-from xsdata.utils.testing import XmlVarFactory
+from xsdata.utils.testing import XmlMetaFactory, XmlVarFactory
 
 
 class PrimitiveNodeTests(TestCase):
-    @mock.patch.object(ParserUtils, "parse_value")
-    def test_bind(self, mock_parse_value):
-        mock_parse_value.return_value = 13
+    def setUp(self):
+        super().setUp()
+        self.meta = XmlMetaFactory.create(clazz=Artist)
+
+    @mock.patch.object(ParserUtils, "parse_var")
+    def test_bind(self, mock_parse_var):
+        mock_parse_var.return_value = 13
         var = XmlVarFactory.create(
             xml_type=XmlType.TEXT, name="foo", types=(int,), format="Nope"
         )
         ns_map = {"foo": "bar"}
-        node = PrimitiveNode(var, ns_map, False)
+        node = PrimitiveNode(self.meta, var, ns_map)
         objects = []
 
         self.assertTrue(node.bind("foo", "13", "Impossible", objects))
         self.assertEqual(("foo", 13), objects[-1])
 
-        mock_parse_value.assert_called_once_with(
-            value="13",
-            types=var.types,
-            default=var.default,
-            ns_map=ns_map,
-            tokens_factory=var.tokens_factory,
-            format=var.format,
+        mock_parse_var.assert_called_once_with(
+            meta=self.meta, var=var, value="13", ns_map=ns_map
         )
 
     def test_bind_nillable_content(self):
@@ -35,7 +35,7 @@ class PrimitiveNodeTests(TestCase):
             xml_type=XmlType.TEXT, name="foo", types=(str,), nillable=False
         )
         ns_map = {"foo": "bar"}
-        node = PrimitiveNode(var, ns_map, False)
+        node = PrimitiveNode(self.meta, var, ns_map)
         objects = []
 
         self.assertTrue(node.bind("foo", None, None, objects))
@@ -53,7 +53,7 @@ class PrimitiveNodeTests(TestCase):
             nillable=False,
         )
         ns_map = {"foo": "bar"}
-        node = PrimitiveNode(var, ns_map, False)
+        node = PrimitiveNode(self.meta, var, ns_map)
         objects = []
 
         self.assertTrue(node.bind("foo", None, None, objects))
@@ -64,8 +64,9 @@ class PrimitiveNodeTests(TestCase):
         self.assertIsNone(objects[-1][1])
 
     def test_bind_mixed_with_tail_content(self):
+        self.meta.mixed_content = True
         var = XmlVarFactory.create(xml_type=XmlType.TEXT, name="foo", types=(int,))
-        node = PrimitiveNode(var, {}, True)
+        node = PrimitiveNode(self.meta, var, {})
         objects = []
 
         self.assertTrue(node.bind("foo", "13", "tail", objects))
@@ -73,8 +74,9 @@ class PrimitiveNodeTests(TestCase):
         self.assertEqual(13, objects[-2][1])
 
     def test_bind_mixed_without_tail_content(self):
+        self.meta.mixed_content = True
         var = XmlVarFactory.create(xml_type=XmlType.TEXT, name="foo", types=(int,))
-        node = PrimitiveNode(var, {}, True)
+        node = PrimitiveNode(self.meta, var, {})
         objects = []
 
         self.assertTrue(node.bind("foo", "13", "", objects))
@@ -82,7 +84,7 @@ class PrimitiveNodeTests(TestCase):
 
     def test_child(self):
         var = XmlVarFactory.create(xml_type=XmlType.TEXT, name="foo")
-        node = PrimitiveNode(var, {}, False)
+        node = PrimitiveNode(self.meta, var, {})
 
         with self.assertRaises(XmlContextError):
             node.child("foo", {}, {}, 0)
diff --git a/tests/formats/dataclass/parsers/nodes/test_standard.py b/tests/formats/dataclass/parsers/nodes/test_standard.py
index 19a5e719..4f00d3b9 100644
--- a/tests/formats/dataclass/parsers/nodes/test_standard.py
+++ b/tests/formats/dataclass/parsers/nodes/test_standard.py
@@ -1,39 +1,46 @@
 from unittest import TestCase
 
+from tests.fixtures.artists import Artist
 from xsdata.exceptions import XmlContextError
 from xsdata.formats.dataclass.models.generics import DerivedElement
 from xsdata.formats.dataclass.parsers.nodes import StandardNode
 from xsdata.models.enums import DataType
+from xsdata.utils.testing import XmlMetaFactory, XmlVarFactory
 
 
 class StandardNodeTests(TestCase):
+    def setUp(self):
+        super().setUp()
+        self.meta = XmlMetaFactory.create(clazz=Artist)
+        self.var = XmlVarFactory.create()
+
     def test_bind_simple(self):
-        var = DataType.INT
-        node = StandardNode(var, {}, False, False)
+        datatype = DataType.INT
+        node = StandardNode(self.meta, self.var, datatype, {}, False, False)
         objects = []
 
         self.assertTrue(node.bind("a", "13", None, objects))
         self.assertEqual(("a", 13), objects[-1])
 
     def test_bind_derived(self):
-        var = DataType.INT
-        node = StandardNode(var, {}, False, DerivedElement)
+        datatype = DataType.INT
+        node = StandardNode(self.meta, self.var, datatype, {}, False, DerivedElement)
         objects = []
 
         self.assertTrue(node.bind("a", "13", None, objects))
         self.assertEqual(("a", DerivedElement("a", 13)), objects[-1])
 
     def test_bind_wrapper_type(self):
-        var = DataType.HEX_BINARY
-        node = StandardNode(var, {}, False, DerivedElement)
+        datatype = DataType.HEX_BINARY
+        node = StandardNode(self.meta, self.var, datatype, {}, False, DerivedElement)
         objects = []
 
         self.assertTrue(node.bind("a", "13", None, objects))
         self.assertEqual(("a", DerivedElement(qname="a", value=b"\x13")), objects[-1])
 
     def test_bind_nillable(self):
-        var = DataType.STRING
-        node = StandardNode(var, {}, True, None)
+        datatype = DataType.STRING
+        node = StandardNode(self.meta, self.var, datatype, {}, True, None)
         objects = []
 
         self.assertTrue(node.bind("a", None, None, objects))
@@ -44,7 +51,8 @@ class StandardNodeTests(TestCase):
         self.assertEqual(("a", ""), objects[-1])
 
     def test_child(self):
-        node = StandardNode(DataType.STRING, {}, False, False)
+        datatype = DataType.STRING
+        node = StandardNode(self.meta, self.var, datatype, {}, False, False)
 
         with self.assertRaises(XmlContextError):
             node.child("foo", {}, {}, 0)
diff --git a/tests/formats/dataclass/parsers/nodes/test_union.py b/tests/formats/dataclass/parsers/nodes/test_union.py
index 2bb8c2df..15a44b51 100644
--- a/tests/formats/dataclass/parsers/nodes/test_union.py
+++ b/tests/formats/dataclass/parsers/nodes/test_union.py
@@ -2,6 +2,7 @@ from dataclasses import make_dataclass
 from typing import Union
 from unittest import TestCase
 
+from tests.fixtures.artists import Artist
 from tests.fixtures.models import UnionType
 from xsdata.exceptions import ParserError
 from xsdata.formats.dataclass.context import XmlContext
@@ -9,7 +10,7 @@ from xsdata.formats.dataclass.models.elements import XmlType
 from xsdata.formats.dataclass.parsers.config import ParserConfig
 from xsdata.formats.dataclass.parsers.nodes import UnionNode
 from xsdata.models.mixins import attribute
-from xsdata.utils.testing import XmlVarFactory
+from xsdata.utils.testing import XmlMetaFactory, XmlVarFactory
 
 
 class UnionNodeTests(TestCase):
@@ -22,10 +23,12 @@ class UnionNodeTests(TestCase):
     def test_child(self):
         attrs = {"id": "1"}
         ns_map = {"ns0": "xsdata"}
+        meta = XmlMetaFactory.create(clazz=Artist)
         var = XmlVarFactory.create(xml_type=XmlType.TEXT, name="foo")
         node = UnionNode(
-            position=0,
+            meta=meta,
             var=var,
+            position=0,
             config=self.config,
             context=self.context,
             attrs={},
@@ -38,10 +41,12 @@ class UnionNodeTests(TestCase):
         self.assertIsNot(attrs, node.events[0][2])
 
     def test_bind_appends_end_event_when_level_not_zero(self):
+        meta = XmlMetaFactory.create(clazz=Artist)
         var = XmlVarFactory.create(xml_type=XmlType.TEXT, name="foo")
         node = UnionNode(
-            position=0,
+            meta=meta,
             var=var,
+            position=0,
             config=self.config,
             context=self.context,
             attrs={},
@@ -67,8 +72,9 @@ class UnionNodeTests(TestCase):
         attrs = {"a": "1", "b": 2}
         ns_map = {}
         node = UnionNode(
-            position=0,
+            meta=meta,
             var=var,
+            position=0,
             config=self.config,
             context=self.context,
             attrs=attrs,
@@ -102,8 +108,9 @@ class UnionNodeTests(TestCase):
         var = next(meta.find_children("element"))
 
         node = UnionNode(
-            position=0,
+            meta=meta,
             var=var,
+            position=0,
             config=self.config,
             context=self.context,
             attrs={},
diff --git a/tests/formats/dataclass/parsers/test_dict.py b/tests/formats/dataclass/parsers/test_dict.py
index 59842477..e9b7c783 100644
--- a/tests/formats/dataclass/parsers/test_dict.py
+++ b/tests/formats/dataclass/parsers/test_dict.py
@@ -102,7 +102,7 @@ class DictDecoderTests(FactoryTestCase):
             self.decoder.decode(json_str, TypeA)
 
         self.assertEqual(
-            "Failed to convert value `foo` to one of (<class 'int'>,)",
+            "Failed to convert value for `TypeA.x`\n  `foo` is not a valid `int`",
             str(cm.exception),
         )
 
diff --git a/tests/formats/dataclass/parsers/test_node.py b/tests/formats/dataclass/parsers/test_node.py
index 0b7452e1..4497a70c 100644
--- a/tests/formats/dataclass/parsers/test_node.py
+++ b/tests/formats/dataclass/parsers/test_node.py
@@ -47,7 +47,7 @@ class NodeParserTests(TestCase):
             parser.from_string(xml, TypeA)
 
         self.assertEqual(
-            "Failed to convert value `foo` to one of (<class 'int'>,)",
+            "Failed to convert value for `TypeA.x`\n  `foo` is not a valid `int`",
             str(cm.exception),
         )
 
diff --git a/tests/formats/dataclass/parsers/test_utils.py b/tests/formats/dataclass/parsers/test_utils.py
index 78abdf94..81871bdb 100644
--- a/tests/formats/dataclass/parsers/test_utils.py
+++ b/tests/formats/dataclass/parsers/test_utils.py
@@ -1,3 +1,4 @@
+import warnings
 from unittest import mock
 
 from tests.fixtures.models import TypeA
@@ -114,3 +115,18 @@ class ParserUtilsTests(FactoryTestCase):
 
         var = XmlVarFactory.create("fixed", default=lambda: float("nan"))
         ParserUtils.validate_fixed_value(meta, var, float("nan"))
+
+    def test_parse_var_with_warnings(self):
+        meta = XmlMetaFactory.create(clazz=TypeA, qname="foo")
+        var = XmlVarFactory.create("fixed", default="a")
+
+        with warnings.catch_warnings(record=True) as w:
+            result = ParserUtils.parse_var(meta, var, "a", types=[int, float])
+
+        expected = (
+            "Failed to convert value for `TypeA.fixed`\n"
+            "  `a` is not a valid `int | float`"
+        )
+        self.assertEqual("a", result)
+
+        self.assertEqual(expected, str(w[-1].message))
diff --git a/tests/formats/dataclass/parsers/test_xml.py b/tests/formats/dataclass/parsers/test_xml.py
index fa5af97b..6667569e 100644
--- a/tests/formats/dataclass/parsers/test_xml.py
+++ b/tests/formats/dataclass/parsers/test_xml.py
@@ -1,11 +1,12 @@
 from unittest import mock
 
+from tests.fixtures.artists import Artist
 from tests.fixtures.books import Books
 from xsdata.formats.dataclass.models.elements import XmlType
 from xsdata.formats.dataclass.parsers.nodes import PrimitiveNode, SkipNode
 from xsdata.formats.dataclass.parsers.xml import UserXmlParser
 from xsdata.models.enums import EventType
-from xsdata.utils.testing import FactoryTestCase, XmlVarFactory
+from xsdata.utils.testing import FactoryTestCase, XmlMetaFactory, XmlVarFactory
 
 
 class UserXmlParserTests(FactoryTestCase):
@@ -30,8 +31,9 @@ class UserXmlParserTests(FactoryTestCase):
     def test_end(self, mock_emit_event):
         objects = []
         queue = []
+        meta = XmlMetaFactory.create(clazz=Artist)
         var = XmlVarFactory.create(xml_type=XmlType.TEXT, name="foo", types=(bool,))
-        queue.append(PrimitiveNode(var, {}, False))
+        queue.append(PrimitiveNode(meta, var, {}))
 
         result = self.parser.end(queue, objects, "enabled", "true", None)
         self.assertTrue(result)
diff --git a/tests/formats/test_converter.py b/tests/formats/test_converter.py
index 493b18fc..a1d0d6fe 100644
--- a/tests/formats/test_converter.py
+++ b/tests/formats/test_converter.py
@@ -1,5 +1,4 @@
 import sys
-import warnings
 from datetime import date, datetime, time
 from decimal import Decimal
 from enum import Enum
@@ -18,12 +17,10 @@ from xsdata.models.enums import UseType
 
 class ConverterFactoryTests(TestCase):
     def test_deserialize(self):
-        with warnings.catch_warnings(record=True) as w:
-            self.assertEqual("a", converter.deserialize("a", [int]))
+        with self.assertRaises(ConverterError) as cm:
+            converter.deserialize("a", [int])
 
-        self.assertEqual(
-            "Failed to convert value `a` to one of [<class 'int'>]", str(w[-1].message)
-        )
+        self.assertEqual("`a` is not a valid `int`", str(cm.exception))
 
         self.assertFalse(converter.deserialize("false", [int, bool]))
         self.assertEqual(1, converter.deserialize("1", [int, bool]))
@@ -73,13 +70,12 @@ class ConverterFactoryTests(TestCase):
         class A:
             pass
 
-        class B(A):
-            pass
-
-        with warnings.catch_warnings(record=True) as w:
-            converter.serialize(B())
+        with self.assertRaises(ConverterError) as cm:
+            converter.serialize(A())
 
-        self.assertEqual(f"No converter registered for `{B}`", str(w[-1].message))
+        self.assertEqual(
+            f"No converter registered for `{A.__qualname__}`", str(cm.exception)
+        )
 
     def test_register_converter(self):
         class MinusOneInt(int):