DEBUG.WIKI
阿里云;从建站到云上业务;云服务器 · 数据库 · 对象存储;查看产品 →
搬瓦工;先看线路,再选 VPS;CN2 GIA · 对应线路套餐;查看线路 →
野草云;香港 200Mbps 峰值;不限月流量 · BGP VPS;查看套餐 →

Serde try_from 报错只剩外层 context

自定义类型用 Serde try_from 转换,anyhow 错误增加 context 后,内层原因在反序列化错误中消失。把格式解析与业务转换分开,保留可检查的错误链。

自定义类型通过 #[serde(try_from = "u8")] 读取 JSON,转换失败时本来有两层原因:外层 context 说明是哪一步,内层说明哪个值不合法。进入 serde_json::Error 以后,却只剩外层文字。再给 from_str 增加一次 anyhow context,也找不回已经丢掉的内层错误。

这不是 JSON 字符串截断。正式 Serde 1.0.229 为 try_from 生成的核心路径相当于:

WireType::deserialize(deserializer)
    .and_then(|value| Target::try_from(value).map_err(serde::de::Error::custom))

这里展示的是派生逻辑,不是需要额外粘贴进项目的独立函数。

Error::custom 接收显示信息,不承诺保存业务 source

Serde 的错误接口要求 custom 参数实现 Display,而不是要求错误可以作为任意类型的 source 保存。具体格式的错误类型负责构造自己的错误。serde_json 不会因此接管原有 anyhow 错误链。

anyhow 普通 Display 通常显示最外层 context。把这个显示结果转换成 JSON 格式错误后,随后打印完整外层错误链,能看到的也只是已经保存下来的信息。删除 context 可能让一条内层错误文字重新出现,却同时丢掉了调用位置,不能当成保留完整诊断的办法。

在格式解析之后执行业务转换

如果调用方需要区分 JSON 语法错误和业务值错误,并保留业务错误链,可以先读取中间类型,再调用原有 TryFrom。下面是依赖 anyhow、serde_json,使用 Rust 2021 edition 的独立示例;MyType 无需通过派生 Deserialize 完成业务转换:

use anyhow::{bail, Context, Result};

#[derive(Debug)]
enum MyType {
    Alpha,
    Bravo,
    Charlie,
}

impl TryFrom<u8> for MyType {
    type Error = anyhow::Error;

    fn try_from(value: u8) -> Result<Self> {
        match value {
            67 => Ok(Self::Alpha),
            68 => Ok(Self::Bravo),
            69 => Ok(Self::Charlie),
            other => bail!("invalid number: {other}"),
        }
    }
}

fn parse(input: &str) -> Result<MyType> {
    let wire: u8 = serde_json::from_str(input).context("decode JSON number")?;
    MyType::try_from(wire).context("convert number to MyType")
}

输入 128 时,业务转换错误留在 anyhow 的链中,外层仍能说明正在转换 MyType。输入破损 JSON、负数或超出 u8 范围的值,则在中间类型解析阶段失败,不会被误判为同一种业务错误。

真实输入是对象时,应建立对应的 wire DTO,保留它的必填字段、类型检查和未知字段策略,再一次性转换成业务模型。不能为了保留一个错误,把所有字段先读取为字符串或随意默认值,放宽原有输入校验。

如果还需要为其他调用方保留原来的 Deserialize 实现,可以另外提供这样的应用入口;不要悄悄改变共享模型在所有格式中的反序列化行为。

需要完整文字时,仍要区分错误链

format!("{error:#}") 可以用于需要展开 anyhow 原因的诊断文字,但展开后的内容仍是文字,不会恢复可向下转换的错误类型或 source。若日志可能包含请求数据、路径或凭据,还应按现有日志规则控制输出范围。

回查有效值、业务拒绝值、错误 JSON 和范围溢出,并检查调用方实际使用的日志格式或错误分类。以能否区分这几条路径为准,不要只看异常文本变长了。