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