1"""
2    Exceptions raised and handled in Conan server.
3    These exceptions are mapped between server (as an HTTP response) and client
4    through the REST API. When an error happens in server its translated to an HTTP
5    error code that its sent to client. Client reads the server code and raise the
6    matching exception.
7
8    see return_plugin.py
9
10"""
11from contextlib import contextmanager
12from subprocess import CalledProcessError
13
14from conans.util.env_reader import get_env
15from conans.util.files import decode_text
16
17
18class CalledProcessErrorWithStderr(CalledProcessError):
19    def __str__(self):
20        ret = super(CalledProcessErrorWithStderr, self).__str__()
21        if self.output:
22            ret += "\n" + decode_text(self.output)
23        return ret
24
25
26@contextmanager
27def conanfile_exception_formatter(conanfile_name, func_name):
28    """
29    Decorator to throw an exception formatted with the line of the conanfile where the error ocurrs.
30    :param reference: Reference of the conanfile
31    :return:
32    """
33    try:
34        yield
35    # TODO: Move ConanInvalidSystemRequirements, ConanInvalidConfiguration from here?
36    except ConanInvalidSystemRequirements as exc:
37        msg = "{}: Invalid system requirements: {}".format(conanfile_name, exc)
38        raise ConanInvalidSystemRequirements(msg)
39    except ConanInvalidConfiguration as exc:
40        msg = "{}: Invalid configuration: {}".format(conanfile_name, exc)
41        raise ConanInvalidConfiguration(msg)
42    except Exception as exc:
43        msg = _format_conanfile_exception(conanfile_name, func_name, exc)
44        raise ConanExceptionInUserConanfileMethod(msg)
45
46
47def _format_conanfile_exception(scope, method, exception):
48    """
49    It will iterate the traceback lines, when it finds that the source code is inside the users
50    conanfile it "start recording" the messages, when the trace exits the conanfile we return
51    the traces.
52    """
53    import sys
54    import traceback
55    if get_env("CONAN_VERBOSE_TRACEBACK", False):
56        return traceback.format_exc()
57    try:
58        conanfile_reached = False
59        tb = sys.exc_info()[2]
60        index = 0
61        content_lines = []
62
63        while True:  # If out of index will raise and will be captured later
64            # 40 levels of nested functions max, get the latest
65            filepath, line, name, contents = traceback.extract_tb(tb, 40)[index]
66            if "conanfile.py" not in filepath:  # Avoid show trace from internal conan source code
67                if conanfile_reached:  # The error goes to internal code, exit print
68                    break
69            else:
70                if not conanfile_reached:  # First line
71                    msg = "%s: Error in %s() method" % (scope, method)
72                    msg += ", line %d\n\t%s" % (line, contents)
73                else:
74                    msg = ("while calling '%s', line %d\n\t%s" % (name, line, contents)
75                           if line else "\n\t%s" % contents)
76                content_lines.append(msg)
77                conanfile_reached = True
78            index += 1
79    except Exception:
80        pass
81    ret = "\n".join(content_lines)
82    ret += "\n\t%s: %s" % (exception.__class__.__name__, str(exception))
83    return ret
84
85
86class ConanException(Exception):
87    """
88         Generic conans exception
89    """
90    def __init__(self, *args, **kwargs):
91        self.info = None
92        self.remote = kwargs.pop("remote", None)
93        super(ConanException, self).__init__(*args, **kwargs)
94
95    def remote_message(self):
96        if self.remote:
97            return " [Remote: {}]".format(self.remote.name)
98        return ""
99
100    def __str__(self):
101        from conans.util.files import exception_message_safe
102        msg = super(ConanException, self).__str__()
103        if self.remote:
104            return "{}.{}".format(exception_message_safe(msg), self.remote_message())
105
106        return exception_message_safe(msg)
107
108
109class ConanV2Exception(ConanException):
110    def __str__(self):
111        msg = super(ConanV2Exception, self).__str__()
112        # TODO: Add a link to a public webpage with Conan roadmap to v2
113        return "Conan v2 incompatible: {}".format(msg)
114
115
116class OnlyV2Available(ConanException):
117
118    def __init__(self, remote_url):
119        msg = "The remote at '%s' only works with revisions enabled. " \
120              "Set CONAN_REVISIONS_ENABLED=1 " \
121              "or set 'general.revisions_enabled = 1' at the 'conan.conf'" % remote_url
122        super(OnlyV2Available, self).__init__(msg)
123
124
125class NoRestV2Available(ConanException):
126    pass
127
128
129class NoRemoteAvailable(ConanException):
130    """ No default remote configured or the specified remote do not exists
131    """
132    pass
133
134
135class InvalidNameException(ConanException):
136    pass
137
138
139class ConanConnectionError(ConanException):
140    pass
141
142
143class ConanOutdatedClient(ConanException):
144    pass
145
146
147class ConanExceptionInUserConanfileMethod(ConanException):
148    pass
149
150
151class ConanInvalidSystemRequirements(ConanException):
152    pass
153
154
155class ConanInvalidConfiguration(ConanExceptionInUserConanfileMethod):
156    pass
157
158
159class ConanMigrationError(ConanException):
160    pass
161
162
163# Remote exceptions #
164class InternalErrorException(ConanException):
165    """
166         Generic 500 error
167    """
168    pass
169
170
171class RequestErrorException(ConanException):
172    """
173         Generic 400 error
174    """
175    pass
176
177
178class AuthenticationException(ConanException):  # 401
179    """
180        401 error
181    """
182    pass
183
184
185class ForbiddenException(ConanException):  # 403
186    """
187        403 error
188    """
189    pass
190
191
192class NotFoundException(ConanException):  # 404
193    """
194        404 error
195    """
196
197    def __init__(self, *args, **kwargs):
198        self.remote = kwargs.pop("remote", None)
199        super(NotFoundException, self).__init__(*args, **kwargs)
200
201
202class RecipeNotFoundException(NotFoundException):
203
204    def __init__(self, ref, remote=None, print_rev=False):
205        from conans.model.ref import ConanFileReference
206        assert isinstance(ref, ConanFileReference), "RecipeNotFoundException requires a " \
207                                                    "ConanFileReference"
208        self.ref = ref
209        self.print_rev = print_rev
210        super(RecipeNotFoundException, self).__init__(remote=remote)
211
212    def __str__(self):
213        tmp = self.ref.full_str() if self.print_rev else str(self.ref)
214        return "Recipe not found: '{}'".format(tmp, self.remote_message())
215
216
217class PackageNotFoundException(NotFoundException):
218
219    def __init__(self, pref, remote=None, print_rev=False):
220        from conans.model.ref import PackageReference
221        assert isinstance(pref, PackageReference), "PackageNotFoundException requires a " \
222                                                   "PackageReference"
223        self.pref = pref
224        self.print_rev = print_rev
225
226        super(PackageNotFoundException, self).__init__(remote=remote)
227
228    def __str__(self):
229        tmp = self.pref.full_str() if self.print_rev else str(self.pref)
230        return "Binary package not found: '{}'{}".format(tmp, self.remote_message())
231
232
233class UserInterfaceErrorException(RequestErrorException):
234    """
235        420 error
236    """
237    pass
238
239
240EXCEPTION_CODE_MAPPING = {InternalErrorException: 500,
241                          RequestErrorException: 400,
242                          AuthenticationException: 401,
243                          ForbiddenException: 403,
244                          NotFoundException: 404,
245                          RecipeNotFoundException: 404,
246                          PackageNotFoundException: 404,
247                          UserInterfaceErrorException: 420}
248