Repository: Pylons/colander

Issue:
Nested validators: colander.All and colander.Any
Hi,

it would be great if meta-validators (All, Any and so) could be nested in each other.
For example:

```
   validator=colander.All(
                           colander.Any( 
                                            first_validator,
                                            second_validator 
                                       ),
                           third_validator
                     )
```

The latest version doesn't support this because an error occurs during the "propagation" of result message in asdict:

```
    errors['.'.join(keyparts)] = '; '.join(interpolate(msgs))
```

(a similar problem is fixed here a6b85dd9ba1042aa1ffcdd327eb4f635c802d418 )

Is there any other way to do it? Tnx.


Discussion:

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index b004541..a547106 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,3 +1,12 @@
+Unreleased
+==================
+
+- :class:`colander.All` now supports :attr:`colander.Invalid.msg` being ``None``
+  or a list, otherwise :meth:`colander.Invalid.asdict` crashes with
+  ``TypeError: sequence item 1: expected str instance, NoneType found``.
+  See https://github.com/Pylons/colander/pull/333 and
+  https://github.com/Pylons/colander/issues/194
+
 1.7.0 (2019-02-01)
 ==================
 
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 0537c2f..e38080f 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -140,3 +140,4 @@ Contributors
 - Gabriela Surita, 2017/01/31
 - Manuel Vázquez, 2018/11/22
 - Kirill Kuzminykh, 2019/01/27
+- Rangel Reale, 2020/01/09
diff --git a/colander/__init__.py b/colander/__init__.py
index 104fe8a..d6cb81e 100644
--- a/colander/__init__.py
+++ b/colander/__init__.py
@@ -257,9 +257,18 @@ class All(object):
                 excs.append(e)
 
         if excs:
-            exc = Invalid(node, [exc.msg for exc in excs])
-            for e in excs:
-                exc.children.extend(e.children)
+            children = []
+            messages = []
+            for exception in excs:
+                if exception.msg is not None:
+                    if is_nonstr_iter(exception.msg):
+                        messages.extend(exception.msg)
+                    else:
+                        messages.append(exception.msg)
+                children.extend(exception.children)
+
+            exc = Invalid(node, messages)
+            exc.children.extend(children)
             raise exc
 
 


Test patch:
diff --git a/colander/tests/test_colander.py b/colander/tests/test_colander.py
index 90b4d91..e435deb 100644
--- a/colander/tests/test_colander.py
+++ b/colander/tests/test_colander.py
@@ -171,6 +171,18 @@ class TestInvalid(unittest.TestCase):
                 },
             )
 
+    def test_asdict_with_msg_none(self):
+        # see https://github.com/Pylons/colander/pull/333
+        from colander import All
+
+        node1 = DummySchemaNode(None, 'node1')
+        validator1 = DummyValidator('validator1')
+        validator2 = DummyValidatorWithMsgNone()
+        validator = All(validator1, validator2)
+        exc = invalid_exc(validator, node1, None)
+        # TypeError: sequence item 1: expected str instance, NoneType found
+        exc.asdict()
+
     def test___str__(self):
         from colander import Positional
 
@@ -4673,6 +4685,14 @@ class DummyValidator(object):
             raise e
 
 
+class DummyValidatorWithMsgNone(object):
+    def __call__(self, node, value):
+        from colander import Invalid
+
+        e = Invalid(node)
+        raise e
+
+
 class Uncooperative(object):
     def __str__(self):
         raise ValueError('I wont cooperate')