1"""Utilities for storing collections of error messages.
2
3.. warning::
4
5    This module is treated as private API.
6    Users should not need to use this module directly.
7"""
8
9from marshmallow.exceptions import SCHEMA
10
11
12class ErrorStore:
13    def __init__(self):
14        #: Dictionary of errors stored during serialization
15        self.errors = {}
16
17    def store_error(self, messages, field_name=SCHEMA, index=None):
18        # field error  -> store/merge error messages under field name key
19        # schema error -> if string or list, store/merge under _schema key
20        #              -> if dict, store/merge with other top-level keys
21        if field_name != SCHEMA or not isinstance(messages, dict):
22            messages = {field_name: messages}
23        if index is not None:
24            messages = {index: messages}
25        self.errors = merge_errors(self.errors, messages)
26
27
28def merge_errors(errors1, errors2):
29    """Deeply merge two error messages.
30
31    The format of ``errors1`` and ``errors2`` matches the ``message``
32    parameter of :exc:`marshmallow.exceptions.ValidationError`.
33    """
34    if not errors1:
35        return errors2
36    if not errors2:
37        return errors1
38    if isinstance(errors1, list):
39        if isinstance(errors2, list):
40            return errors1 + errors2
41        if isinstance(errors2, dict):
42            return dict(errors2, **{SCHEMA: merge_errors(errors1, errors2.get(SCHEMA))})
43        return errors1 + [errors2]
44    if isinstance(errors1, dict):
45        if isinstance(errors2, list):
46            return dict(errors1, **{SCHEMA: merge_errors(errors1.get(SCHEMA), errors2)})
47        if isinstance(errors2, dict):
48            errors = dict(errors1)
49            for key, val in errors2.items():
50                if key in errors:
51                    errors[key] = merge_errors(errors[key], val)
52                else:
53                    errors[key] = val
54            return errors
55        return dict(errors1, **{SCHEMA: merge_errors(errors1.get(SCHEMA), errors2)})
56    if isinstance(errors2, list):
57        return [errors1] + errors2
58    if isinstance(errors2, dict):
59        return dict(errors2, **{SCHEMA: merge_errors(errors1, errors2.get(SCHEMA))})
60    return [errors1, errors2]
61