1
2#
3# spyne - Copyright (C) Spyne contributors.
4#
5# This library is free software; you can redistribute it and/or
6# modify it under the terms of the GNU Lesser General Public
7# License as published by the Free Software Foundation; either
8# version 2.1 of the License, or (at your option) any later version.
9#
10# This library is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13# Lesser General Public License for more details.
14#
15# You should have received a copy of the GNU Lesser General Public
16# License along with this library; if not, write to the Free Software
17# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
18#
19
20from warnings import warn
21from collections import defaultdict
22
23import spyne.const
24
25from spyne.model.primitive import Any
26
27from spyne.util.six import add_metaclass
28
29from spyne.model.complex import ComplexModelMeta
30from spyne.model.complex import ComplexModelBase
31
32
33class FaultMeta(ComplexModelMeta):
34    def __init__(self, cls_name, cls_bases, cls_dict):
35        super(FaultMeta, self).__init__(cls_name, cls_bases, cls_dict)
36
37        code = cls_dict.get('CODE', None)
38
39        if code is not None:
40            target = Fault.REGISTERED[code]
41            target.add(self)
42            if spyne.const.WARN_ON_DUPLICATE_FAULTCODE and len(target) > 1:
43                warn("Duplicate faultcode {} detected for classes {}"
44                                                          .format(code, target))
45
46
47@add_metaclass(FaultMeta)
48class Fault(ComplexModelBase, Exception):
49    """Use this class as a base for all public exceptions.
50    The Fault object adheres to the
51    `SOAP 1.1 Fault definition <http://www.w3.org/TR/2000/NOTE-SOAP-20000508/#_Toc478383507>`_,
52
53    which has three main attributes:
54
55    :param faultcode: It's a dot-delimited string whose first fragment is
56        either 'Client' or 'Server'. Just like HTTP 4xx and 5xx codes,
57        'Client' indicates that something was wrong with the input, and 'Server'
58        indicates something went wrong during the processing of an otherwise
59        legitimate request.
60
61        Protocol implementors should heed the values in ``faultcode`` to set
62        proper return codes in the protocol level when necessary. E.g. HttpRpc
63        protocol will return a HTTP 404 error when a
64        :class:`spyne.error.ResourceNotFound` is raised, and a general HTTP 400
65        when the ``faultcode`` starts with ``'Client.'`` or is ``'Client'``.
66
67        Soap would return Http 500 for any kind of exception, and denote the
68        nature of the exception in the Soap response body. (because that's what
69        the standard says... Yes, soap is famous for a reason :))
70    :param faultstring: It's the human-readable explanation of the exception.
71    :param detail: Additional information dict.
72    :param lang: Language code corresponding to the language of faultstring.
73    """
74
75    REGISTERED = defaultdict(set)
76    """Class-level variable that holds a multimap of all fault codes and the
77    associated classes."""
78
79    __type_name__ = "Fault"
80
81    CODE = None
82
83    def __init__(self, faultcode='Server', faultstring="", faultactor="",
84                                      detail=None, lang=spyne.DEFAULT_LANGUAGE):
85        self.faultcode = faultcode
86        self.faultstring = faultstring or self.get_type_name()
87        self.faultactor = faultactor
88        self.detail = detail
89        self.lang = lang
90
91    def __len__(self):
92        return 1
93
94    def __str__(self):
95        return repr(self)
96
97    def __repr__(self):
98        if self.detail is None:
99            return "%s(%s: %r)" % (self.__class__.__name__,
100                                               self.faultcode, self.faultstring)
101
102        return "%s(%s: %r detail: %r)" % (self.__class__.__name__,
103                                  self.faultcode, self.faultstring, self.detail)
104
105    @staticmethod
106    def to_dict(cls, value, prot):
107        if not issubclass(cls, Fault):
108            return {
109                "faultcode": "Server.Unknown",
110                "faultstring": cls.__name__,
111                "detail": str(value),
112            }
113
114        retval =  {
115            "faultcode": value.faultcode,
116            "faultstring": value.faultstring,
117        }
118
119        if value.faultactor is not None:
120            if len(value.faultactor) > 0 or (not prot.ignore_empty_faultactor):
121                retval["faultactor"] = value.faultactor
122
123        if value.detail is not None:
124            retval["detail"] = value.detail_to_doc(prot)
125
126        return retval
127
128    #
129    # From http://schemas.xmlsoap.org/soap/envelope/
130    #
131    # <xs:element name="faultcode" type="xs:QName"/>
132    # <xs:element name="faultstring" type="xs:string"/>
133    # <xs:element name="faultactor" type="xs:anyURI" minOccurs="0"/>
134    # <xs:element name="detail" type="tns:detail" minOccurs="0"/>
135    #
136    @staticmethod
137    def to_list(cls, value, prot=None):
138        if not issubclass(cls, Fault):
139            return [
140                "Server.Unknown",  # faultcode
141                cls.__name__,      # faultstring
142                "",                # faultactor
143                str(value),        # detail
144            ]
145
146        retval = [
147            value.faultcode,
148            value.faultstring,
149        ]
150
151        if value.faultactor is not None:
152            retval.append(value.faultactor)
153        else:
154            retval.append("")
155
156        if value.detail is not None:
157            retval.append(value.detail_to_doc(prot))
158        else:
159            retval.append("")
160
161        return retval
162
163    @classmethod
164    def to_bytes_iterable(cls, value):
165        return [
166            value.faultcode.encode('utf8'),
167            b'\n\n',
168            value.faultstring.encode('utf8'),
169        ]
170
171    def detail_to_doc(self, prot):
172        return self.detail
173
174    def detail_from_doc(self, prot, doc):
175        self.detail = doc
176