Skip to main content

tibba_request/
lib.rs

1// Copyright 2026 Tree xie.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use snafu::Snafu;
16use tibba_error::Error as BaseError;
17
18/// 该 crate 所有日志事件的 tracing target。
19/// 可通过 `RUST_LOG=tibba:request=info`(或 `debug`)进行过滤。
20pub(crate) const LOG_TARGET: &str = "tibba:request";
21
22mod request;
23
24#[derive(Debug, Snafu)]
25pub enum Error {
26    /// 服务返回业务错误(状态码 ≥400 且响应体包含 message 字段)。
27    #[snafu(display("{service} request fail, {message}"))]
28    Common { service: String, message: String },
29    /// 构建 reqwest 请求失败(如非法 URL、头部格式错误等)。
30    #[snafu(display("{service} build http request fail, {source}"))]
31    Build {
32        service: String,
33        source: reqwest::Error,
34    },
35    /// URL 解析为 `Uri` 失败。
36    #[snafu(display("{service} uri fail, {source}"))]
37    Uri {
38        service: String,
39        source: axum::http::uri::InvalidUri,
40    },
41    /// 发送请求或读取响应体时网络层出错(含超时、连接失败等)。
42    #[snafu(display("{service} http request fail, {path} {source}"))]
43    Request {
44        service: String,
45        path: String,
46        source: reqwest::Error,
47    },
48    /// 响应体 JSON 反序列化失败。
49    #[snafu(display("{service} json fail, {source}"))]
50    Serde {
51        service: String,
52        source: serde_json::Error,
53    },
54}
55
56impl From<Error> for BaseError {
57    fn from(val: Error) -> Self {
58        let (service, err) = match val {
59            Error::Common { service, message } => (service, BaseError::new(message)),
60            Error::Build { service, source } => (service, BaseError::new(source)),
61            Error::Uri { service, source } => (service, BaseError::new(source)),
62            Error::Request {
63                service,
64                path: _,
65                source,
66            } => {
67                let status = source.status().map_or(500, |v| v.as_u16());
68                // 超时或连接失败属于基础设施异常,需告警
69                let is_network_exception = source.is_timeout() || source.is_connect();
70                (
71                    service,
72                    BaseError::new(source)
73                        .with_status(status)
74                        .with_exception(is_network_exception),
75                )
76            }
77            Error::Serde { service, source } => (service, BaseError::new(source)),
78        };
79        err.with_sub_category(&service).with_category("request")
80    }
81}
82
83pub use request::*;