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

stacks-network / stacks-core / 24140301216

08 Apr 2026 02:18PM UTC coverage: 46.817% (-38.9%) from 85.712%
24140301216

Pull #6959

github

279acf
web-flow
Merge efbee1783 into 882e27245
Pull Request #6959: Perf/cache epoch version in ClarityDatabase

66 of 149 new or added lines in 8 files covered. (44.3%)

85999 existing lines in 334 files now uncovered.

102056 of 217989 relevant lines covered (46.82%)

12315981.16 hits per line

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

23.05
/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 {
5,889,417✔
67
    match code {
5,889,417✔
68
        // from RFC 2616
UNCOV
69
        100 => "Continue",
×
UNCOV
70
        101 => "Switching Protocols",
×
71
        200 => "OK",
3,993,014✔
UNCOV
72
        201 => "Created",
×
73
        202 => "Accepted",
32,898✔
74
        203 => "Non-Authoritative Information",
×
UNCOV
75
        204 => "No Content",
×
76
        205 => "Reset Content",
×
UNCOV
77
        206 => "Partial Content",
×
UNCOV
78
        300 => "Multiple Choices",
×
UNCOV
79
        301 => "Moved Permanently",
×
UNCOV
80
        302 => "Found",
×
81
        303 => "See Other",
×
UNCOV
82
        304 => "Not Modified",
×
83
        305 => "Use Proxy",
×
UNCOV
84
        307 => "Temporary Redirect",
×
85
        400 => "Bad Request",
1,508,787✔
86
        401 => "Unauthorized",
9✔
UNCOV
87
        402 => "Payment Required",
×
UNCOV
88
        403 => "Forbidden",
×
89
        404 => "Not Found",
208,449✔
UNCOV
90
        405 => "Method Not Allowed",
×
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",
85,770✔
UNCOV
104
        501 => "Not Implemented",
×
UNCOV
105
        502 => "Bad Gateway",
×
UNCOV
106
        503 => "Service Unavailable",
×
UNCOV
107
        504 => "Gateway Time-out",
×
108
        505 => "HTTP Version not supported",
×
109
        _ => "Custom",
60,490✔
110
    }
111
}
5,889,417✔
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> {
9✔
125
    match code {
9✔
UNCOV
126
        400 => Box::new(HttpBadRequest::new(message)),
×
127
        401 => Box::new(HttpUnauthorized::new(message)),
9✔
UNCOV
128
        402 => Box::new(HttpPaymentRequired::new(message)),
×
UNCOV
129
        403 => Box::new(HttpForbidden::new(message)),
×
UNCOV
130
        404 => Box::new(HttpNotFound::new(message)),
×
UNCOV
131
        405 => Box::new(HttpMethodNotAllowed::new(message)),
×
132
        408 => Box::new(HttpRequestTimeout::new(message)),
×
UNCOV
133
        500 => Box::new(HttpServerError::new(message)),
×
134
        501 => Box::new(HttpNotImplemented::new(message)),
×
UNCOV
135
        503 => Box::new(HttpServiceUnavailable::new(message)),
×
UNCOV
136
        _ => Box::new(HttpError::new(code, message)),
×
137
    }
138
}
9✔
139

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

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

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

164
impl HttpErrorResponse for HttpBadRequest {
165
    fn code(&self) -> u16 {
3,017,250✔
166
        400
3,017,250✔
167
    }
3,017,250✔
168
    fn payload(&self) -> HttpResponsePayload {
1,508,625✔
169
        if self.content_type == HttpContentType::JSON {
1,508,625✔
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,508,625✔
173
                                                .unwrap_or(serde_json::from_str(
1,508,625✔
174
                                                            "{\"error\": \"Failed to decode serialized JSON text. This is a bug in the Stacks node or in serde_json.\"}"
1,508,625✔
175
                                                           ).expect("FATAL: failed to decode known-good constant JSON string")));
1,508,625✔
176
        } else {
UNCOV
177
            HttpResponsePayload::Text(self.error_text.clone())
×
178
        }
179
    }
1,508,625✔
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 {
9✔
196
        Self { error_text }
9✔
197
    }
9✔
198
}
199

200
impl HttpErrorResponse for HttpUnauthorized {
201
    fn code(&self) -> u16 {
18✔
202
        401
18✔
203
    }
18✔
204
    fn payload(&self) -> HttpResponsePayload {
9✔
205
        HttpResponsePayload::Text(self.error_text.clone())
9✔
206
    }
9✔
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 {
UNCOV
222
    pub fn new(error_text: String) -> Self {
×
UNCOV
223
        Self { error_text }
×
UNCOV
224
    }
×
225
}
226

227
impl HttpErrorResponse for HttpPaymentRequired {
UNCOV
228
    fn code(&self) -> u16 {
×
UNCOV
229
        402
×
UNCOV
230
    }
×
UNCOV
231
    fn payload(&self) -> HttpResponsePayload {
×
UNCOV
232
        HttpResponsePayload::Text(self.error_text.clone())
×
UNCOV
233
    }
×
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 {
UNCOV
249
    pub fn new(error_text: String) -> Self {
×
UNCOV
250
        Self { error_text }
×
UNCOV
251
    }
×
252
}
253

254
impl HttpErrorResponse for HttpForbidden {
UNCOV
255
    fn code(&self) -> u16 {
×
UNCOV
256
        403
×
UNCOV
257
    }
×
UNCOV
258
    fn payload(&self) -> HttpResponsePayload {
×
UNCOV
259
        HttpResponsePayload::Text(self.error_text.clone())
×
UNCOV
260
    }
×
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 {
208,449✔
277
        Self { error_text }
208,449✔
278
    }
208,449✔
279
}
280

281
impl HttpErrorResponse for HttpNotFound {
282
    fn code(&self) -> u16 {
416,898✔
283
        404
416,898✔
284
    }
416,898✔
285
    fn payload(&self) -> HttpResponsePayload {
208,449✔
286
        HttpResponsePayload::Text(self.error_text.clone())
208,449✔
287
    }
208,449✔
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 {
UNCOV
304
    pub fn new(error_text: String) -> Self {
×
UNCOV
305
        Self {
×
UNCOV
306
            error_text,
×
UNCOV
307
            allowed_methods: vec![],
×
UNCOV
308
        }
×
UNCOV
309
    }
×
310

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

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

327
impl HttpErrorResponse for HttpMethodNotAllowed {
UNCOV
328
    fn code(&self) -> u16 {
×
UNCOV
329
        405
×
UNCOV
330
    }
×
UNCOV
331
    fn payload(&self) -> HttpResponsePayload {
×
UNCOV
332
        HttpResponsePayload::Text(self.error_text.clone())
×
UNCOV
333
    }
×
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 {
85,770✔
377
        Self { error_text }
85,770✔
378
    }
85,770✔
379
}
380

381
impl HttpErrorResponse for HttpServerError {
382
    fn code(&self) -> u16 {
171,540✔
383
        500
171,540✔
384
    }
171,540✔
385
    fn payload(&self) -> HttpResponsePayload {
85,770✔
386
        HttpResponsePayload::Text(self.error_text.clone())
85,770✔
387
    }
85,770✔
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 {
UNCOV
403
    pub fn new(error_text: String) -> Self {
×
UNCOV
404
        Self { error_text }
×
UNCOV
405
    }
×
406
}
407

408
impl HttpErrorResponse for HttpNotImplemented {
UNCOV
409
    fn code(&self) -> u16 {
×
UNCOV
410
        501
×
UNCOV
411
    }
×
UNCOV
412
    fn payload(&self) -> HttpResponsePayload {
×
UNCOV
413
        HttpResponsePayload::Text(self.error_text.clone())
×
UNCOV
414
    }
×
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 {
UNCOV
430
    pub fn new(error_text: String) -> Self {
×
UNCOV
431
        Self { error_text }
×
UNCOV
432
    }
×
433
}
434

435
impl HttpErrorResponse for HttpServiceUnavailable {
UNCOV
436
    fn code(&self) -> u16 {
×
UNCOV
437
        503
×
UNCOV
438
    }
×
UNCOV
439
    fn payload(&self) -> HttpResponsePayload {
×
UNCOV
440
        HttpResponsePayload::Text(self.error_text.clone())
×
UNCOV
441
    }
×
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 {
162✔
459
        Self { error, error_text }
162✔
460
    }
162✔
461
}
462

463
impl HttpErrorResponse for HttpError {
464
    fn code(&self) -> u16 {
324✔
465
        self.error
324✔
466
    }
324✔
467
    fn payload(&self) -> HttpResponsePayload {
162✔
468
        HttpResponsePayload::Text(self.error_text.clone())
162✔
469
    }
162✔
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 · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc