• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

stacks-network / stacks-core / 30073655644-1

24 Jul 2026 06:53AM UTC coverage: 86.568% (+0.9%) from 85.712%
30073655644-1

Pull #7401

github

e550e3
web-flow
Merge 355f5f257 into e44da350f
Pull Request #7401: feat: Clarity eval hook call-tracing improvements

666 of 702 new or added lines in 8 files covered. (94.87%)

12118 existing lines in 171 files now uncovered.

200778 of 231930 relevant lines covered (86.57%)

19385538.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

52.88
/stackslib/src/net/http/error.rs
1
// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
2
// Copyright (C) 2020-2023 Stacks Open Internet Foundation
3
//
4
// This program is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// This program is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

17
use std::io;
18
use std::io::Read;
19

20
use serde_json;
21
use stacks_common::codec::MAX_MESSAGE_LEN;
22
use stacks_common::util::retry::BoundReader;
23

24
use crate::net::http::{Error, HttpContentType, HttpResponsePayload, HttpResponsePreamble};
25

26
/// Default implementation of `try_parse_response()` for an HTTP error message that implements
27
/// `HttpReqeust`.
28
pub fn try_parse_error_response(
×
29
    status_code: u16,
×
30
    content_type: HttpContentType,
×
31
    body: &[u8],
×
32
) -> Result<HttpResponsePayload, Error> {
×
33
    if status_code < 400 || status_code > 599 {
×
34
        return Err(Error::DecodeError(
×
35
            "Inavlid response: not an error".to_string(),
×
36
        ));
×
37
    }
×
38

39
    if content_type == HttpContentType::Text {
×
40
        let mut error_text = String::new();
×
41
        let mut ioc = io::Cursor::new(body);
×
42
        let mut bound_fd =
×
43
            BoundReader::from_reader(&mut ioc, body.len().min(MAX_MESSAGE_LEN as usize) as u64);
×
44
        bound_fd
×
45
            .read_to_string(&mut error_text)
×
46
            .map_err(Error::ReadError)?;
×
47

48
        Ok(HttpResponsePayload::Text(error_text))
×
49
    } else if content_type == HttpContentType::JSON {
×
50
        let mut ioc = io::Cursor::new(body);
×
51
        let mut bound_fd =
×
52
            BoundReader::from_reader(&mut ioc, body.len().min(MAX_MESSAGE_LEN as usize) as u64);
×
53
        let json_val = serde_json::from_reader(&mut bound_fd)
×
54
            .map_err(|_| Error::DecodeError("Failed to decode JSON".to_string()))?;
×
55

56
        Ok(HttpResponsePayload::JSON(json_val))
×
57
    } else {
58
        return Err(Error::DecodeError(format!(
×
59
            "Invalid error response: expected text/plain or application/json, got {:?}",
×
60
            &content_type
×
61
        )));
×
62
    }
63
}
×
64

65
/// Decode an HTTP status code into a reason
66
pub fn http_reason(code: u16) -> &'static str {
7,960,109✔
67
    match code {
7,960,109✔
68
        // from RFC 2616
69
        100 => "Continue",
1✔
70
        101 => "Switching Protocols",
1✔
71
        200 => "OK",
5,662,120✔
72
        201 => "Created",
1✔
73
        202 => "Accepted",
46,624✔
74
        203 => "Non-Authoritative Information",
×
75
        204 => "No Content",
1✔
76
        205 => "Reset Content",
×
77
        206 => "Partial Content",
1✔
78
        300 => "Multiple Choices",
1✔
79
        301 => "Moved Permanently",
1✔
80
        302 => "Found",
1✔
81
        303 => "See Other",
×
82
        304 => "Not Modified",
1✔
83
        305 => "Use Proxy",
×
84
        307 => "Temporary Redirect",
1✔
85
        400 => "Bad Request",
1,792,453✔
86
        401 => "Unauthorized",
19✔
87
        402 => "Payment Required",
4✔
88
        403 => "Forbidden",
5✔
89
        404 => "Not Found",
269,167✔
90
        405 => "Method Not Allowed",
6✔
91
        406 => "Not Acceptable",
×
92
        407 => "Proxy Authentication Required",
×
UNCOV
93
        408 => "Request Time-out",
×
94
        409 => "Conflict",
×
95
        410 => "Gone",
×
96
        411 => "Length Required",
×
97
        412 => "Precondition Failed",
×
98
        413 => "Request Entity Too Large",
×
99
        414 => "Request-URI Too Large",
×
100
        415 => "Unsupported Media Type",
×
101
        416 => "Requested range not satisfiable",
×
102
        417 => "Expectation Failed",
×
103
        500 => "Internal Server Error",
110,335✔
104
        501 => "Not Implemented",
2✔
105
        502 => "Bad Gateway",
5✔
106
        503 => "Service Unavailable",
5✔
107
        504 => "Gateway Time-out",
1✔
108
        505 => "HTTP Version not supported",
×
109
        _ => "Custom",
79,353✔
110
    }
111
}
7,960,109✔
112

113
/// Make HTTP error responses distinct from HttpResponses
114
pub trait HttpErrorResponse {
115
    fn code(&self) -> u16;
116
    fn payload(&self) -> HttpResponsePayload;
117
    fn try_parse_response(
118
        &self,
119
        preamble: &HttpResponsePreamble,
120
        body: &[u8],
121
    ) -> Result<HttpResponsePayload, Error>;
122
}
123

124
pub fn http_error_from_code_and_text(code: u16, message: String) -> Box<dyn HttpErrorResponse> {
36✔
125
    match code {
36✔
126
        400 => Box::new(HttpBadRequest::new(message)),
4✔
127
        401 => Box::new(HttpUnauthorized::new(message)),
16✔
128
        402 => Box::new(HttpPaymentRequired::new(message)),
2✔
129
        403 => Box::new(HttpForbidden::new(message)),
2✔
130
        404 => Box::new(HttpNotFound::new(message)),
4✔
131
        405 => Box::new(HttpMethodNotAllowed::new(message)),
2✔
132
        408 => Box::new(HttpRequestTimeout::new(message)),
×
133
        500 => Box::new(HttpServerError::new(message)),
2✔
134
        501 => Box::new(HttpNotImplemented::new(message)),
×
135
        503 => Box::new(HttpServiceUnavailable::new(message)),
2✔
136
        _ => Box::new(HttpError::new(code, message)),
2✔
137
    }
138
}
36✔
139

140
/// HTTP 400
141
pub struct HttpBadRequest {
142
    error_text: String,
143
    content_type: HttpContentType,
144
}
145

146
impl HttpBadRequest {
147
    pub fn new(error_text: String) -> Self {
16✔
148
        Self {
16✔
149
            error_text,
16✔
150
            content_type: HttpContentType::Text,
16✔
151
        }
16✔
152
    }
16✔
153

154
    pub fn new_json(value: serde_json::Value) -> Self {
1,792,263✔
155
        Self {
1,792,263✔
156
            // this .expect() should never be reachable
1,792,263✔
157
            error_text: serde_json::to_string(&value)
1,792,263✔
158
                .expect("FATAL: could not serialize JSON value to string"),
1,792,263✔
159
            content_type: HttpContentType::JSON,
1,792,263✔
160
        }
1,792,263✔
161
    }
1,792,263✔
162
}
163

164
impl HttpErrorResponse for HttpBadRequest {
165
    fn code(&self) -> u16 {
3,584,555✔
166
        400
3,584,555✔
167
    }
3,584,555✔
168
    fn payload(&self) -> HttpResponsePayload {
1,792,279✔
169
        if self.content_type == HttpContentType::JSON {
1,792,279✔
170
            // the inner error_text is serialized from a JSON value, so it should always parse
171
            // back to JSON.
172
            return HttpResponsePayload::JSON(serde_json::from_str(&self.error_text)
1,792,263✔
173
                                                .unwrap_or(serde_json::from_str(
1,792,263✔
174
                                                            "{\"error\": \"Failed to decode serialized JSON text. This is a bug in the Stacks node or in serde_json.\"}"
1,792,263✔
175
                                                           ).expect("FATAL: failed to decode known-good constant JSON string")));
1,792,263✔
176
        } else {
177
            HttpResponsePayload::Text(self.error_text.clone())
16✔
178
        }
179
    }
1,792,279✔
180
    fn try_parse_response(
×
181
        &self,
×
182
        preamble: &HttpResponsePreamble,
×
183
        body: &[u8],
×
184
    ) -> Result<HttpResponsePayload, Error> {
×
185
        try_parse_error_response(preamble.status_code, preamble.content_type, body)
×
186
    }
×
187
}
188

189
/// HTTP 401
190
pub struct HttpUnauthorized {
191
    error_text: String,
192
}
193

194
impl HttpUnauthorized {
195
    pub fn new(error_text: String) -> Self {
16✔
196
        Self { error_text }
16✔
197
    }
16✔
198
}
199

200
impl HttpErrorResponse for HttpUnauthorized {
201
    fn code(&self) -> u16 {
30✔
202
        401
30✔
203
    }
30✔
204
    fn payload(&self) -> HttpResponsePayload {
16✔
205
        HttpResponsePayload::Text(self.error_text.clone())
16✔
206
    }
16✔
207
    fn try_parse_response(
×
208
        &self,
×
209
        preamble: &HttpResponsePreamble,
×
210
        body: &[u8],
×
211
    ) -> Result<HttpResponsePayload, Error> {
×
212
        try_parse_error_response(preamble.status_code, preamble.content_type, body)
×
213
    }
×
214
}
215

216
/// HTTP 402
217
pub struct HttpPaymentRequired {
218
    error_text: String,
219
}
220

221
impl HttpPaymentRequired {
222
    pub fn new(error_text: String) -> Self {
2✔
223
        Self { error_text }
2✔
224
    }
2✔
225
}
226

227
impl HttpErrorResponse for HttpPaymentRequired {
228
    fn code(&self) -> u16 {
2✔
229
        402
2✔
230
    }
2✔
231
    fn payload(&self) -> HttpResponsePayload {
2✔
232
        HttpResponsePayload::Text(self.error_text.clone())
2✔
233
    }
2✔
234
    fn try_parse_response(
×
235
        &self,
×
236
        preamble: &HttpResponsePreamble,
×
237
        body: &[u8],
×
238
    ) -> Result<HttpResponsePayload, Error> {
×
239
        try_parse_error_response(preamble.status_code, preamble.content_type, body)
×
240
    }
×
241
}
242

243
/// HTTP 403
244
pub struct HttpForbidden {
245
    error_text: String,
246
}
247

248
impl HttpForbidden {
249
    pub fn new(error_text: String) -> Self {
2✔
250
        Self { error_text }
2✔
251
    }
2✔
252
}
253

254
impl HttpErrorResponse for HttpForbidden {
255
    fn code(&self) -> u16 {
2✔
256
        403
2✔
257
    }
2✔
258
    fn payload(&self) -> HttpResponsePayload {
2✔
259
        HttpResponsePayload::Text(self.error_text.clone())
2✔
260
    }
2✔
261
    fn try_parse_response(
×
262
        &self,
×
263
        preamble: &HttpResponsePreamble,
×
264
        body: &[u8],
×
265
    ) -> Result<HttpResponsePayload, Error> {
×
266
        try_parse_error_response(preamble.status_code, preamble.content_type, body)
×
267
    }
×
268
}
269

270
/// HTTP 404
271
pub struct HttpNotFound {
272
    error_text: String,
273
}
274

275
impl HttpNotFound {
276
    pub fn new(error_text: String) -> Self {
269,164✔
277
        Self { error_text }
269,164✔
278
    }
269,164✔
279
}
280

281
impl HttpErrorResponse for HttpNotFound {
282
    fn code(&self) -> u16 {
538,326✔
283
        404
538,326✔
284
    }
538,326✔
285
    fn payload(&self) -> HttpResponsePayload {
269,164✔
286
        HttpResponsePayload::Text(self.error_text.clone())
269,164✔
287
    }
269,164✔
288
    fn try_parse_response(
×
289
        &self,
×
290
        preamble: &HttpResponsePreamble,
×
291
        body: &[u8],
×
292
    ) -> Result<HttpResponsePayload, Error> {
×
293
        try_parse_error_response(preamble.status_code, preamble.content_type, body)
×
294
    }
×
295
}
296

297
/// HTTP 405
298
pub struct HttpMethodNotAllowed {
299
    error_text: String,
300
    allowed_methods: Vec<String>,
301
}
302

303
impl HttpMethodNotAllowed {
304
    pub fn new(error_text: String) -> Self {
2✔
305
        Self {
2✔
306
            error_text,
2✔
307
            allowed_methods: vec![],
2✔
308
        }
2✔
309
    }
2✔
310

311
    pub fn with_allowed_methods(allowed_methods: Vec<String>) -> Self {
1✔
312
        let error_text = format!(
1✔
313
            "Method Not Allowed. Allowed: {}",
314
            allowed_methods.join(", ")
1✔
315
        );
316
        Self {
1✔
317
            error_text,
1✔
318
            allowed_methods,
1✔
319
        }
1✔
320
    }
1✔
321

322
    pub fn get_allowed_methods(&self) -> &[String] {
×
323
        &self.allowed_methods
×
324
    }
×
325
}
326

327
impl HttpErrorResponse for HttpMethodNotAllowed {
328
    fn code(&self) -> u16 {
4✔
329
        405
4✔
330
    }
4✔
331
    fn payload(&self) -> HttpResponsePayload {
3✔
332
        HttpResponsePayload::Text(self.error_text.clone())
3✔
333
    }
3✔
334
    fn try_parse_response(
×
335
        &self,
×
336
        preamble: &HttpResponsePreamble,
×
337
        body: &[u8],
×
338
    ) -> Result<HttpResponsePayload, Error> {
×
339
        try_parse_error_response(preamble.status_code, preamble.content_type, body)
×
340
    }
×
341
}
342

343
/// HTTP 408
344
pub struct HttpRequestTimeout {
345
    error_text: String,
346
}
347

348
impl HttpRequestTimeout {
UNCOV
349
    pub fn new(error_text: String) -> Self {
×
UNCOV
350
        Self { error_text }
×
UNCOV
351
    }
×
352
}
353

354
impl HttpErrorResponse for HttpRequestTimeout {
UNCOV
355
    fn code(&self) -> u16 {
×
UNCOV
356
        408
×
UNCOV
357
    }
×
UNCOV
358
    fn payload(&self) -> HttpResponsePayload {
×
UNCOV
359
        HttpResponsePayload::Text(self.error_text.clone())
×
UNCOV
360
    }
×
361
    fn try_parse_response(
×
362
        &self,
×
363
        preamble: &HttpResponsePreamble,
×
364
        body: &[u8],
×
365
    ) -> Result<HttpResponsePayload, Error> {
×
366
        try_parse_error_response(preamble.status_code, preamble.content_type, body)
×
367
    }
×
368
}
369

370
/// HTTP 500
371
pub struct HttpServerError {
372
    error_text: String,
373
}
374

375
impl HttpServerError {
376
    pub fn new(error_text: String) -> Self {
110,332✔
377
        Self { error_text }
110,332✔
378
    }
110,332✔
379
}
380

381
impl HttpErrorResponse for HttpServerError {
382
    fn code(&self) -> u16 {
220,662✔
383
        500
220,662✔
384
    }
220,662✔
385
    fn payload(&self) -> HttpResponsePayload {
110,332✔
386
        HttpResponsePayload::Text(self.error_text.clone())
110,332✔
387
    }
110,332✔
388
    fn try_parse_response(
×
389
        &self,
×
390
        preamble: &HttpResponsePreamble,
×
391
        body: &[u8],
×
392
    ) -> Result<HttpResponsePayload, Error> {
×
393
        try_parse_error_response(preamble.status_code, preamble.content_type, body)
×
394
    }
×
395
}
396

397
/// HTTP 501
398
pub struct HttpNotImplemented {
399
    error_text: String,
400
}
401

402
impl HttpNotImplemented {
403
    pub fn new(error_text: String) -> Self {
1✔
404
        Self { error_text }
1✔
405
    }
1✔
406
}
407

408
impl HttpErrorResponse for HttpNotImplemented {
409
    fn code(&self) -> u16 {
2✔
410
        501
2✔
411
    }
2✔
412
    fn payload(&self) -> HttpResponsePayload {
1✔
413
        HttpResponsePayload::Text(self.error_text.clone())
1✔
414
    }
1✔
415
    fn try_parse_response(
×
416
        &self,
×
417
        preamble: &HttpResponsePreamble,
×
418
        body: &[u8],
×
419
    ) -> Result<HttpResponsePayload, Error> {
×
420
        try_parse_error_response(preamble.status_code, preamble.content_type, body)
×
421
    }
×
422
}
423

424
/// HTTP 503
425
pub struct HttpServiceUnavailable {
426
    error_text: String,
427
}
428

429
impl HttpServiceUnavailable {
430
    pub fn new(error_text: String) -> Self {
2✔
431
        Self { error_text }
2✔
432
    }
2✔
433
}
434

435
impl HttpErrorResponse for HttpServiceUnavailable {
436
    fn code(&self) -> u16 {
2✔
437
        503
2✔
438
    }
2✔
439
    fn payload(&self) -> HttpResponsePayload {
2✔
440
        HttpResponsePayload::Text(self.error_text.clone())
2✔
441
    }
2✔
442
    fn try_parse_response(
×
443
        &self,
×
444
        preamble: &HttpResponsePreamble,
×
445
        body: &[u8],
×
446
    ) -> Result<HttpResponsePayload, Error> {
×
447
        try_parse_error_response(preamble.status_code, preamble.content_type, body)
×
448
    }
×
449
}
450

451
/// Catch-all for any other HTTP error response
452
pub struct HttpError {
453
    error: u16,
454
    error_text: String,
455
}
456

457
impl HttpError {
458
    pub fn new(error: u16, error_text: String) -> Self {
173✔
459
        Self { error, error_text }
173✔
460
    }
173✔
461
}
462

463
impl HttpErrorResponse for HttpError {
464
    fn code(&self) -> u16 {
344✔
465
        self.error
344✔
466
    }
344✔
467
    fn payload(&self) -> HttpResponsePayload {
173✔
468
        HttpResponsePayload::Text(self.error_text.clone())
173✔
469
    }
173✔
470
    fn try_parse_response(
×
471
        &self,
×
472
        preamble: &HttpResponsePreamble,
×
473
        body: &[u8],
×
474
    ) -> Result<HttpResponsePayload, Error> {
×
475
        try_parse_error_response(preamble.status_code, preamble.content_type, body)
×
476
    }
×
477
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc