通用字符串、格式化、截断和模板工具
这一阶段像全系统共用的“文字工具箱”,不是主流程,而是在后台帮各处把内容变得安全、短、好读。数字和耗时会改成 12,345、250ms 这类顺眼写法;JSON、标签、UUID、代码位置等字符串会被整理,中文表情也能转成安全写法。长文本、命令输出、对话历史和终端路径会保留头尾再剪短,防止塞爆屏幕或模型。命令行会隐藏环境变量真值,并安全生成 resume 命令;网页搜索动作会翻成人话;严格模板负责替换变量并抓出填错。
核心字符串工具
这些文件定义共享字符串辅助接口,包括 ASCII 安全的 JSON 输出和 UTF-8 安全的截断原语,供其他调用方构建使用。
utils/string/src/json.rs源码 ↗
JSON 本来可以直接放中文、土耳其字母、表情符号等非 ASCII 字符。但有些传输通道比较“老派”或限制严格,只适合传 ASCII(可以理解成最基础的英文字符表)。这个文件做的事就是:仍然生成合法 JSON,但把字符串里的非 ASCII 字符改写成类似 \u6771 这样的转义写法。这样机器读回去时含义不变,人看到的是编码后的形式。核心零件是 AsciiJsonFormatter,它接管 JSON 序列化时“写字符串片段”的那一步:普通英文照写,遇到非 ASCII 字符就拆成 UTF-16 编码单元,再写成 \uXXXX。公开入口 to_ascii_json_string 会创建一个带这个格式器的 JSON 序列化器,把任意可序列化的数据转成字符串。文件底部的测试专门用中文、土耳其字母和火箭表情验证:输出全是 ASCII,但再解析回来仍然是原来的 JSON 内容。
AsciiJsonFormatter::write_string_fragment13–39 ↗
fn write_string_fragment(&mut self, writer: &mut W, fragment: &str) -> io::Result<()>
作用:这是自定义 JSON 写法里最关键的一步:写字符串内容时,保留普通 ASCII 字符,把中文、表情等非 ASCII 字符改成 \uXXXX。这样 JSON 仍然合法,同时能通过只接受 ASCII 的通道。
数据流:进去的是一个输出位置 writer,以及一小段 JSON 字符串内容 fragment。它从头扫描这段文字:遇到 ASCII 就先攒着原样写出;遇到非 ASCII 字符,就先把前面的普通部分写出去,再把这个字符编码成一个或两个 UTF-16 编码单元,并写成 \u0000 这种格式。最后把剩余的普通部分也写出去。出来的是写入成功或失败的结果;它不会改原始字符串,只会向 writer 里追加内容。
调用关系:它不是直接给外部调用的按钮,而是被 serde_json 的序列化器在写 JSON 字符串时自动调用。to_ascii_json_string 创建序列化器时把 AsciiJsonFormatter 装进去,于是每次 JSON 里需要写字符串片段,实际就会走到这里。它底层把数据交给标准写入能力 write_all 和格式化写入 write! 完成真正输出。
to_ascii_json_string46–55 ↗
fn to_ascii_json_string(value: &T) -> serde_json::Result<String>
作用:这是这个文件给外部使用的主入口:传入一个能被序列化的数据,得到一个只含 ASCII 字符的 JSON 字符串。适合把 JSON 放进对字符集很挑剔的地方,比如 HTTP 头。
数据流:进去的是任意实现了 Serialize 的值,也就是“知道怎么把自己变成 JSON”的数据。函数先准备一块字节缓冲区,再创建一个带 AsciiJsonFormatter 的 JSON 序列化器,让输入值把自己写进去。写完后,它把这些字节转成 Rust 字符串。如果序列化失败或字节不是合法 UTF-8,就返回错误;成功时返回最终 JSON 字符串,并且字符串里的非 ASCII 内容已经被转义。
调用关系:这是外部代码最可能调用的函数。它把具体工作交给 serde_json 的 Serializer,并把 AsciiJsonFormatter 塞进去改变字符串的写法。测试函数 tests::to_ascii_json_string_escapes_non_ascii_strings 会调用它,检查它确实能把中文、特殊字母和表情转成 ASCII 转义,同时还能被正常解析回原值。
调用图:被 1 处调用(to_ascii_json_string_escapes_non_ascii_strings);外部调用 4 个(from_utf8, serialize, new, with_formatter)。
tests::to_ascii_json_string_escapes_non_ascii_strings70–121 ↗
fn to_ascii_json_string_escapes_non_ascii_strings()
作用:这个测试用来证明 to_ascii_json_string 没有只是“看起来能用”,而是真的把非 ASCII 字符转义了,并且没有破坏 JSON 的原始含义。
数据流:它先构造一份测试数据,里面包含路径里的“東京”、标签里的“ı”和火箭表情。然后调用 to_ascii_json_string 得到序列化结果。接着它检查几件事:输出字符串必须等于预期的 \uXXXX 写法;输出必须全是 ASCII;原始中文、特殊字母和表情不能直接出现;最后再把输出当 JSON 解析回来,确认解析后的值和原始期望一致。
调用关系:它只在测试时运行,用来保护这个工具函数以后不被改坏。它直接调用 to_ascii_json_string,然后借助断言、json! 构造期望值,以及 serde_json::from_str 重新解析结果,形成一条完整验证链:生成 ASCII JSON,再确认它仍然是正确 JSON。
调用图:调用 1 个内部函数(to_ascii_json_string);外部调用 4 个(assert!, assert_eq!, json!, from_str)。
utils/string/src/truncate.rs源码 ↗
很多场景里,程序要展示一大段文本,比如日志、模型回复、错误信息。全文太长会占内存、刷屏,甚至超过外部系统的长度限制。这个文件做的事就像把一根很长的绳子中间剪掉一段,再挂上一张牌子写着“这里省略了多少”。它会尽量保留文字开头和结尾,并且特别注意 UTF-8 边界。UTF-8 是常见的文字编码,一个汉字可能占多个字节;如果随便按字节切,可能把一个汉字切坏,变成乱码。文件里还提供了“token(大致可以理解为模型看文字时的计量单位)”的粗略换算:这里按 1 个 token 约等于 4 个字节估算。主要流程是:先把预算换成字节数,再把预算一半给开头、一半给结尾,中间放入省略提示,最后拼成新字符串。
truncate_middle_chars7–9 ↗
fn truncate_middle_chars(s: &str, max_bytes: usize) -> String
作用:按最大字节数剪短一段文字,并在中间提示省略了多少个字符。别人想限制输出长度、但还想保留首尾内容时会用它。
数据流:进去的是原始字符串和最多允许的大约字节数。它不自己做切割,而是告诉核心函数按“字符数”来写省略提示;出来的是一个可能被剪短的新字符串,原字符串本身不会被改。
调用关系:这是给外部调用的简单入口。它把真正的工作交给 truncate_with_byte_estimate,只是固定选择“不按 token,而按字符显示省略数量”。
调用图:调用 1 个内部函数(truncate_with_byte_estimate)。
truncate_middle_with_token_budget15–36 ↗
fn truncate_middle_with_token_budget(s: &str, max_tokens: usize) -> (String, Option<u64>)
作用:按 token 预算剪短文字,并告诉调用者如果真的剪了,原文大约有多少 token。token 可以粗略理解为大语言模型处理文字时的计量单位。
数据流:进去的是原始字符串和最多允许的 token 数。它先把 token 预算粗略换成字节预算,如果原文已经够短就直接返回原文和 None;如果太长,就调用核心截断函数,再估算原文 token 总数。出来的是“处理后的字符串”和“是否被截断以及原文 token 数”。
调用关系:这是面向 token 限制场景的入口,比如要把文本塞进模型上下文之前。它会调用 approx_bytes_for_tokens 做预算换算,调用 truncate_with_byte_estimate 做真正截断,再用 approx_token_count 估算原文规模。
调用图:调用 3 个内部函数(approx_bytes_for_tokens, approx_token_count, truncate_with_byte_estimate);外部调用 2 个(new, try_from)。
truncate_with_byte_estimate38–69 ↗
fn truncate_with_byte_estimate(s: &str, max_bytes: usize, use_tokens: bool) -> String
作用:这是这个文件的核心剪短机器:根据字节预算保留开头和结尾,把中间换成省略提示。它同时支持按字符数或按 token 数显示省略量。
数据流:进去的是原文、最大字节预算,以及一个开关:省略提示用 token 还是字符。它先处理空字符串、零预算、原文已够短这些特殊情况;需要截断时,它把预算拆成左右两份,安全地切出前后片段,算出中间少了多少,再拼上提示。出来的是最终展示用的字符串。
调用关系:truncate_middle_chars 和 truncate_middle_with_token_budget 都把活交给它。它再分派给 split_budget 分预算,split_string 找安全切点,removed_units 和 format_truncation_marker 生成省略说明,最后由 assemble_truncated_output 拼接结果。
调用图:调用 5 个内部函数(assemble_truncated_output, format_truncation_marker, removed_units, split_budget, split_string);被 2 处调用(truncate_middle_chars, truncate_middle_with_token_budget);外部调用 1 个(new)。
approx_token_count71–74 ↗
fn approx_token_count(text: &str) -> usize
作用:粗略估算一段文字有多少 token。这里不做复杂分词,只按“大约 4 个字节算 1 个 token”来估。
数据流:进去的是一段文字。它读取文字的字节长度,把长度向上除以 4;出来的是估算出的 token 数。比如 5 个字节会算成 2 个 token,而不是 1 个。
调用关系:truncate_middle_with_token_budget 在需要报告原文大约 token 数时会用它。它是一个简单换算工具,不调用本文件里的其他函数。
调用图:被 1 处调用(truncate_middle_with_token_budget)。
approx_bytes_for_tokens76–78 ↗
fn approx_bytes_for_tokens(tokens: usize) -> usize
作用:把 token 预算换成大约能容纳多少字节。比如 10 个 token 大约对应 40 个字节。
数据流:进去的是 token 数。它乘以每个 token 约 4 字节的固定比例;出来的是估算字节数。乘法使用安全方式,避免数字过大时出错。
调用关系:truncate_middle_with_token_budget 会先用它把 token 限制变成字节限制,然后才能交给按字节工作的截断核心函数。
调用图:被 1 处调用(truncate_middle_with_token_budget)。
approx_tokens_from_byte_count80–84 ↗
fn approx_tokens_from_byte_count(bytes: usize) -> u64
作用:把一段被删掉的字节数粗略换算成 token 数,用来写“省略了多少 token”的提示。
数据流:进去的是字节数。它把字节数向上除以 4;出来的是 u64 类型的 token 估算值,也就是更大的整数格式,方便放进提示文字里。
调用关系:removed_units 在需要按 token 显示省略量时会调用它。它只负责换算,不负责截断字符串。
调用图:被 1 处调用(removed_units)。
split_string86–124 ↗
fn split_string(s: &str, beginning_bytes: usize, end_bytes: usize) -> (usize, &str, &str)
作用:在不切坏 UTF-8 字符的前提下,从原文里切出开头和结尾,并统计中间删掉了多少个字符。
数据流:进去的是原文、开头最多保留多少字节、结尾最多保留多少字节。它逐个字符走一遍字符串,因为字符边界是安全切点;符合开头预算的放进前半段,符合结尾目标的放进后半段,中间的计为被删字符。出来的是被删字符数、开头片段、结尾片段。
调用关系:truncate_with_byte_estimate 需要真正切字符串时会调用它。它是防乱码的关键零件,因为它不是粗暴按字节下刀,而是沿着字符边界切。
调用图:被 1 处调用(truncate_with_byte_estimate)。
split_budget126–129 ↗
fn split_budget(budget: usize) -> (usize, usize)
作用:把总长度预算分成两份:一份给开头,一份给结尾。这样截断后还能同时看到文字的起点和终点。
数据流:进去的是总字节预算。它把一半给左边,剩下的给右边;如果是奇数,右边会多拿 1 个字节。出来的是两个预算数字。
调用关系:truncate_with_byte_estimate 在截断前先调用它决定首尾各能保留多少。它只是做预算分配,不碰具体字符串。
调用图:被 1 处调用(truncate_with_byte_estimate)。
format_truncation_marker131–137 ↗
fn format_truncation_marker(use_tokens: bool, removed_count: u64) -> String
作用:生成放在中间的省略提示,比如“省略了多少 chars”或“省略了多少 tokens”。这相当于剪掉内容后贴上的说明牌。
数据流:进去的是一个开关,表示提示单位用 token 还是字符,以及被省略的数量。它按不同单位拼出一段带省略号的文字;出来的是这个提示字符串。
调用关系:truncate_with_byte_estimate 在知道删了多少之后会调用它。它只负责把数字变成人能读懂的提示,不负责计算数字。
调用图:被 1 处调用(truncate_with_byte_estimate);外部调用 1 个(format!)。
removed_units139–145 ↗
fn removed_units(use_tokens: bool, removed_bytes: usize, removed_chars: usize) -> u64
作用:决定省略提示里的数字应该怎么算:如果按 token,就用被删字节估算;如果按字符,就用实际被删字符数。
数据流:进去的是单位选择、被删字节数、被删字符数。它根据单位选择一条路:token 模式下调用 approx_tokens_from_byte_count,字符模式下把字符数转成更大的整数类型。出来的是最终要显示的省略数量。
调用关系:truncate_with_byte_estimate 会先让它算出“省略了多少”,再交给 format_truncation_marker 生成提示文字。它连接了底层计数和用户可见提示。
调用图:调用 1 个内部函数(approx_tokens_from_byte_count);被 1 处调用(truncate_with_byte_estimate);外部调用 1 个(try_from)。
assemble_truncated_output147–153 ↗
fn assemble_truncated_output(prefix: &str, suffix: &str, marker: &str) -> String
作用:把保留下来的开头、省略提示、保留下来的结尾拼成最终字符串。
数据流:进去的是前缀、后缀和中间提示。它先按大致需要的长度准备好空间,然后依次追加前缀、提示、后缀;出来的是完整的新字符串。
调用关系:truncate_with_byte_estimate 在所有计算都完成后调用它做最后拼装。它是流水线的收尾步骤,把前面切出来的零件组合成可展示结果。
调用图:被 1 处调用(truncate_with_byte_estimate);外部调用 1 个(with_capacity)。
utils/string/src/lib.rs源码 ↗
字符串看起来简单,但很容易踩坑:比如中文和 emoji 不能随便按字节切,否则会切坏;监控标签里不能出现乱七八糟的字符,否则指标系统可能拒收;GitHub 风格的“#L74C3”行号,在终端里又更适合写成“:74:3”。这个文件就像一个小工具箱,把这些常见但细碎的活儿集中放好。它还把 json 和 truncate 两个子模块里的功能公开出来,方便外部直接使用。这里的函数大多不改变外部状态,只是拿到一段文字,检查、清洗或转换后返回新结果。比较重要的一点是,它在截断时会尊重字符边界,避免把一个完整字符切成乱码;在找 UUID 时会缓存正则表达式(匹配文本模式的工具),避免每次都重新准备。
take_bytes_at_char_boundary13–26 ↗
fn take_bytes_at_char_boundary(s: &str, maxb: usize) -> &str
作用:把一段字符串按“最多多少字节”截短,但不会把一个字符切到一半。有人需要限制文本大小时会用它,尤其是文本里可能有中文、emoji 这类多字节字符的时候。
数据流:进去的是原始字符串和字节上限 → 它先看字符串本来是否已经够短;如果太长,就一个字符一个字符往后数,找到不超过上限的最后一个安全位置 → 出来的是原字符串前半段的引用,不新造字符串,也不会产生乱码。
调用关系:这是一个底层小工具,供需要安全截断字符串的地方调用。它不再把工作交给别的项目函数,只依赖 Rust 字符串本身提供的字符遍历能力。
sanitize_metric_tag_value30–51 ↗
fn sanitize_metric_tag_value(value: &str) -> String
作用:把一段文字清洗成监控指标标签能接受的格式。监控标签可以理解成给数据贴的小标签,如果标签里有非法字符,监控系统可能不认。
数据流:进去的是任意字符串 → 它把允许的 ASCII 字母数字以及 '.', '_', '-', '/' 保留下来,把其它字符替换成下划线;再去掉开头和结尾多余的下划线;如果结果没有有效字母数字,就改成 'unspecified';最后还会限制最长 256 个字符 → 出来的是一段安全可用的标签值。
调用关系:它通常会在记录指标、打监控标签之前被使用,确保传给指标系统的值不会因为格式问题出错。它不调用文件里的其它函数,是独立的清洗步骤。
find_uuids55–65 ↗
fn find_uuids(s: &str) -> Vec<String>
作用:从一大段文字里找出所有看起来像 UUID 的编号。UUID 是一种常见的长唯一标识,比如日志里的请求编号、对象编号。
数据流:进去的是一段文本 → 它使用一个正则表达式,也就是“按固定文字模式找东西”的工具,扫描形如 8-4-4-4-12 位十六进制字符的 UUID → 出来的是找到的 UUID 字符串列表。
调用关系:它会在需要从日志、错误信息或普通文本中提取 UUID 时被调用。它内部调用外部库的 regex_lite::Regex::new 来创建匹配规则,并用 OnceLock 缓存这条规则,让后续调用不用重复创建。
调用图:外部调用 1 个(new)。
normalize_markdown_hash_location_suffix69–92 ↗
fn normalize_markdown_hash_location_suffix(suffix: &str) -> Option<String>
作用:把 Markdown 或网页里常见的代码位置后缀,转换成终端更容易识别的写法。比如把 '#L74C3' 变成 ':74:3'。
数据流:进去的是一个位置后缀字符串 → 它先确认必须以 '#' 开头,再拆出起点和可选的终点;每个点交给 parse_markdown_hash_location_point 解析出行号和可选列号;最后拼成 ':行:列-行:列' 这种形式 → 如果格式不对就返回空结果,如果格式正确就返回转换后的字符串。
调用关系:它是这个小转换流程的主入口,外部想把网页行号格式改成终端格式时会调用它。它把单个位置点的解析交给 parse_markdown_hash_location_point,自己负责整体拆分和重新拼接。
调用图:调用 1 个内部函数(parse_markdown_hash_location_point);外部调用 1 个(from)。
parse_markdown_hash_location_point94–100 ↗
fn parse_markdown_hash_location_point(point: &str) -> Option<(&str, Option<&str>)>
作用:解析一个 Markdown 风格的位置点,比如 'L74C3'。它只处理单个点,不处理范围。
数据流:进去的是一个位置点字符串 → 它要求字符串先有 'L',表示行号;然后看看里面有没有 'C',有的话把前面当行号、后面当列号,没有的话就只有行号 → 出来的是行号和可选列号;格式不合要求就返回空结果。
调用关系:它是 normalize_markdown_hash_location_suffix 的内部帮手。主函数负责处理完整后缀和范围,这个函数只负责把一个点拆成行和列,让主函数的逻辑更清楚。
调用图:被 1 处调用(normalize_markdown_hash_location_suffix)。
tests::find_uuids_finds_multiple111–121 ↗
fn find_uuids_finds_multiple()
作用:检查 find_uuids 能不能从一段文字里一次找出多个 UUID。这个测试防止以后改代码时只找到第一个或漏掉后面的编号。
数据流:进去的是一段包含两个合法 UUID 的测试文本 → 测试调用 find_uuids 得到列表 → 用 assert_eq! 对比预期列表,确认两个 UUID 都被按顺序找出来。
调用关系:这是 find_uuids 的测试用例之一,在运行测试时由 Rust 测试框架调用。它通过 assert_eq! 这个外部测试宏判断实际结果和预期是否一样。
调用图:外部调用 1 个(assert_eq!)。
tests::find_uuids_ignores_invalid124–127 ↗
fn find_uuids_ignores_invalid()
作用:检查 find_uuids 不会把格式不完整或不合法的文本误认成 UUID。这样可以减少误报。
数据流:进去的是一段看起来有点像 UUID、但并不符合完整格式的文本 → 测试调用 find_uuids → 期望得到空列表,并用 assert_eq! 验证。
调用关系:这是 find_uuids 的负面测试,也就是专门验证“不该找到时别找到”。它在测试阶段运行,帮助保证 UUID 匹配规则不会放得太宽。
调用图:外部调用 1 个(assert_eq!)。
tests::find_uuids_handles_non_ascii_without_overlap130–136 ↗
fn find_uuids_handles_non_ascii_without_overlap()
作用:检查 find_uuids 遇到 emoji 这类非 ASCII 字符时仍能正常工作。ASCII 可以粗略理解成英文字符集,emoji 和中文都不在里面。
数据流:进去的是一段前面带 emoji、后面带 UUID 加额外字符的文本 → 测试调用 find_uuids → 期望只取出合法长度的 UUID 部分,不被 emoji 或后面的字符干扰。
调用关系:这是 find_uuids 的边界情况测试。它在测试时运行,用 assert_eq! 确认正则匹配在包含非英文字符的字符串里也不会错位。
调用图:外部调用 1 个(assert_eq!)。
tests::sanitize_metric_tag_value_trims_and_fills_unspecified139–142 ↗
fn sanitize_metric_tag_value_trims_and_fills_unspecified()
作用:检查 sanitize_metric_tag_value 在清洗后没有有效内容时,会返回 'unspecified'。这能避免生成空标签或只有符号的标签。
数据流:进去的是 '///' 这样的测试字符串 → 函数清洗并去掉边缘无效内容后发现没有字母数字 → 返回 'unspecified',测试用 assert_eq! 验证。
调用关系:这是 sanitize_metric_tag_value 的测试之一,专门覆盖“清洗后没剩下有意义内容”的情况。它在测试运行时由测试框架调用。
调用图:外部调用 1 个(assert_eq!)。
tests::sanitize_metric_tag_value_replaces_invalid_chars145–148 ↗
fn sanitize_metric_tag_value_replaces_invalid_chars()
作用:检查 sanitize_metric_tag_value 会把空格、感叹号这类不允许的字符替换掉。这样监控标签能保持合法。
数据流:进去的是 'bad value!' → 函数把空格和感叹号替换成下划线,再修掉末尾多余下划线 → 得到 'bad_value',测试用 assert_eq! 确认。
调用关系:这是 sanitize_metric_tag_value 的常见场景测试。它确保以后修改清洗规则时,不会破坏最基本的非法字符替换行为。
调用图:外部调用 1 个(assert_eq!)。
tests::normalize_markdown_hash_location_suffix_converts_single_location151–156 ↗
fn normalize_markdown_hash_location_suffix_converts_single_location()
作用:检查 normalize_markdown_hash_location_suffix 能把单个位置从网页格式转换成终端格式。比如 '#L74C3' 应该变成 ':74:3'。
数据流:进去的是 '#L74C3' → 测试调用转换函数 → 期望得到 Some(':74:3'),也就是成功转换后的结果,并用 assert_eq! 验证。
调用关系:这是 normalize_markdown_hash_location_suffix 的基础测试,在测试阶段运行。它验证主函数和内部的 parse_markdown_hash_location_point 能配合处理单点位置。
调用图:外部调用 1 个(assert_eq!)。
tests::normalize_markdown_hash_location_suffix_converts_ranges159–164 ↗
fn normalize_markdown_hash_location_suffix_converts_ranges()
作用:检查 normalize_markdown_hash_location_suffix 能转换一段范围,而不只是单个位置。比如从第 74 行第 3 列到第 76 行第 9 列。
数据流:进去的是 '#L74C3-L76C9' → 测试调用转换函数 → 期望得到 Some(':74:3-76:9'),说明起点和终点都被正确拆开并重新拼好。
调用关系:这是 normalize_markdown_hash_location_suffix 的范围测试。它在测试时运行,确保主函数处理 '-' 分隔的起止位置时,会正确调用内部解析帮手并生成终端友好的格式。
调用图:外部调用 1 个(assert_eq!)。
格式化与展示辅助工具
这些工具将值和面向用户的文本格式化为简洁、易读的显示字符串,涵盖时长、数字、环境设置、恢复提示和网页搜索操作详情。
protocol/src/num_format.rs源码 ↗
这个文件专门做“数字美化”。它先尽量读取用户电脑的语言和地区设置,比如中文、美国英语、德语等,因为不同地区的千位分隔符可能不一样;如果读不到,就退回到固定的 en-US,也就是美国英语格式。为了避免每次格式化数字都重新准备一套格式规则,它用 OnceLock(一种只初始化一次的安全缓存)把格式器保存起来,后面大家共用。文件提供两类常用能力:一种是给整数加本地化分隔符,比如 1234567 变成 1,234,567;另一种是把很大的数量缩短成 K、M、G 这种十进制单位,比如 1200 变成 1.20K。它还会控制有效数字,避免显示太长,同时保留大概精度。测试部分用固定的 en-US 格式检查这些缩写规则,防止以后改代码时把边界情况改坏。
make_local_formatter8–11 ↗
fn make_local_formatter() -> Option<DecimalFormatter>
作用:这个函数尝试按用户当前系统的语言地区来创建数字格式器。这样同一个数字在不同地区可以显示成当地人习惯的样子。
数据流:进去时不需要调用者传参数;它读取系统语言地区设置,把读到的文本解析成 Locale(语言地区标识,比如 en-US),再用默认格式选项创建 DecimalFormatter(小数和整数的显示工具)。如果任何一步失败,就返回空结果;如果成功,就返回可用的本地格式器。
调用关系:它是全局格式器准备过程里的第一选择。虽然调用图只显示它调用了系统取语言和 ICU 创建格式器这些外部能力,但从文件整体看,它会被 formatter 的初始化流程用来优先生成符合用户本地习惯的格式规则。
make_en_us_formatter13–18 ↗
fn make_en_us_formatter() -> DecimalFormatter
作用:这个函数创建一个固定的美国英语数字格式器,作为可靠的备用方案。这样即使系统语言读取失败,程序也还能正常显示数字。
数据流:进去时不需要参数;它把字符串 en-US 解析成语言地区标识,再用默认选项创建 DecimalFormatter。这里认为 en-US 必然有效,所以如果创建失败会直接报错;正常情况下出来的是一个美国英语格式器。
调用关系:它在两处很重要:运行时作为本地格式器失败后的兜底方案;测试函数 tests::kmg 也直接调用它,保证测试结果固定,不会因为测试机器的地区设置不同而变化。
formatter20–23 ↗
fn formatter() -> &'static DecimalFormatter
作用:这个函数提供全文件共用的数字格式器。它保证格式器只创建一次,之后每次格式化数字都复用同一个对象,省事也省资源。
数据流:进去时不需要参数;它检查静态缓存里有没有已经建好的 DecimalFormatter。没有的话就初始化一次,优先用本地格式器,失败时用 en-US 格式器;出来的是一个长期有效的格式器引用,不会把所有权交出去。
调用关系:它是公开格式化函数背后的共同入口。format_with_separators 和 format_si_suffix 都会先找它拿格式器,然后再把具体数字交给后续格式化逻辑。
调用图:被 2 处调用(format_si_suffix, format_with_separators);外部调用 1 个(new)。
format_with_separators27–29 ↗
fn format_with_separators(n: i64) -> String
作用:这个公开函数把一个整数格式化成带分隔符的字符串。别人想显示“更好读的大整数”时会用它。
数据流:进去的是一个 i64 整数;它先通过 formatter 拿到当前应使用的数字格式器,再把整数转换成 Decimal(ICU 用来格式化的数字对象),最后输出字符串。它不修改外部状态,只可能触发一次全局格式器初始化。
调用关系:它是给其他模块调用的简单入口。调用图显示 format_credit_amount 会用到它,说明金额或额度这类数字显示时,会把加分隔符这件事交给这里完成。
调用图:调用 1 个内部函数(formatter);被 1 处调用(format_credit_amount);外部调用 1 个(from)。
format_with_separators_with_formatter31–33 ↗
fn format_with_separators_with_formatter(n: i64, formatter: &DecimalFormatter) -> String
作用:这个函数和 format_with_separators 做的事类似,但格式器由调用者传进来。它适合内部复用,尤其是已经有指定格式器时,避免再去取全局格式器。
数据流:进去的是一个整数和一个 DecimalFormatter;它把整数转换成 Decimal,再用传入的格式器生成字符串。出来的是带该格式器规则的数字文本,不会创建新的全局缓存。
调用关系:它主要服务于 format_si_suffix_with_formatter 的超大数字分支。当数字大到超过这个文件支持的最高缩写单位时,那里会用它把 G 单位前面的整数也加上分隔符。
调用图:外部调用 2 个(from, format)。
format_si_suffix_with_formatter35–67 ↗
fn format_si_suffix_with_formatter(n: i64, formatter: &DecimalFormatter) -> String
作用:这个函数把数量缩写成 K、M、G 形式,并尽量保留 3 位有效数字。它让很大的计数看起来短而清楚,比如把 1234000 变成 1.23M。
数据流:进去的是一个整数和一个指定格式器;它先把负数按 0 处理,小于 1000 的数字直接正常格式化。大于等于 1000 时,它按 K、M、G 这些单位逐级尝试,根据数字大小决定保留 2 位、1 位或 0 位小数,四舍五入后格式化,再拼上单位后缀。超过 1000G 时,它不再引入新单位,而是按整数 G 显示,并给这个整数加分隔符。
调用关系:它是 SI 缩写规则真正落地的地方。公开函数 format_si_suffix 会把全局格式器交给它;测试函数 tests::kmg 也通过固定格式器间接检查它的各种边界输出。
调用图:被 1 处调用(format_si_suffix);外部调用 3 个(from, format, format!)。
format_si_suffix75–77 ↗
fn format_si_suffix(n: i64) -> String
作用:这个公开函数把一个数量格式化成带 K、M、G 后缀的短字符串。调用者不用关心地区格式器怎么创建,只要把数字传进来即可。
数据流:进去的是一个 i64 整数;它先通过 formatter 拿到全局格式器,再把数字和格式器一起交给 format_si_suffix_with_formatter。出来的是缩短后的字符串,比如 999、1.20K、123M。
调用关系:它是外部代码使用 SI 缩写功能的入口。它自己不实现复杂规则,而是负责拿到合适的格式器,然后把实际计算和拼后缀的工作交给 format_si_suffix_with_formatter。
调用图:调用 2 个内部函数(format_si_suffix_with_formatter, formatter)。
tests::kmg84–102 ↗
fn kmg()
作用:这个测试确认 K、M、G 缩写在关键边界上都符合预期。它防止以后有人改四舍五入或单位切换规则时,不小心让显示结果变怪。
数据流:进去时没有外部输入;它先创建固定的 en-US 格式器,再用一组代表性数字调用缩写格式化逻辑,最后用 assert_eq! 对比实际字符串和预期字符串。测试通过时没有输出;失败时会指出哪一个数字格式化错了。
调用关系:它只在测试阶段运行。它调用 make_en_us_formatter 来避免本地语言环境影响测试结果,然后围绕 format_si_suffix_with_formatter 的行为做检查,尤其覆盖 999 到 K、M、G 以及超过 1000G 的情况。
调用图:调用 1 个内部函数(make_en_us_formatter);外部调用 1 个(assert_eq!)。
utils/elapsed/src/lib.rs源码 ↗
程序里经常要告诉人“这件事花了多久”。机器内部用的是 Duration,也就是一段精确的时间长度,但直接给普通人看不够友好。这个文件做的事很简单:先把 Duration 转成毫秒数,再按长短换成不同格式。不到 1 秒,就显示多少毫秒;1 秒到 1 分钟之间,就显示秒并保留两位小数;超过 1 分钟,就显示“几分钟 几秒钟”。它像一个小翻译,把机器计时翻译成人更容易扫一眼看懂的形式。文件后半部分是测试,专门检查几个边界情况,比如 0 毫秒、59.999 秒、正好 1 分钟、正好 1 小时,防止以后改代码时不小心把显示格式弄坏。一个值得注意的点是:这里超过一小时也不会写成“1h”,而是继续写成“60m 00s”。
format_duration9–12 ↗
fn format_duration(duration: Duration) -> String
作用:这是给外部使用的入口函数。别人传进来一段标准时间长度,它就返回一段适合展示给人看的短文字。
数据流:进去的是一个 Duration,也就是 Rust 标准库里表示“过了多久”的时间值。它先用 as_millis 取出总毫秒数,再交给内部函数 format_elapsed_millis 按规则排版。出来的是一个 String,比如“250ms”“1.50s”或“1m 15s”,它不改动外部数据。
调用关系:它是这个小工具对外的门面。调用者不用关心具体格式规则,只要把 Duration 给它;它再把真正的格式判断交给 format_elapsed_millis 完成。
调用图:调用 1 个内部函数(format_elapsed_millis);外部调用 1 个(as_millis)。
format_elapsed_millis14–24 ↗
fn format_elapsed_millis(millis: i64) -> String
作用:这个函数真正决定耗时文字长什么样。它根据毫秒数落在哪个区间,选择用毫秒、秒,还是分钟加秒来显示。
数据流:进去的是一个整数毫秒数。它先判断是否小于 1000,小于就直接拼成“多少ms”;如果小于 60000,就除以 1000 变成秒,并保留两位小数;如果更长,就算出分钟数和剩余秒数,并把秒补成两位。出来的是格式化后的 String。
调用关系:它是 format_duration 背后的实际干活者。format_duration 负责把标准时间类型转成毫秒,这个函数负责把毫秒变成最终文字,并使用 format! 这个 Rust 的字符串格式化工具来拼出结果。
调用图:被 1 处调用(format_duration);外部调用 1 个(format!)。
tests::test_format_duration_subsecond31–39 ↗
fn test_format_duration_subsecond()
作用:这个测试确认不到 1 秒的耗时会按毫秒显示。它还检查 0 毫秒这种最小情况不会出错。
数据流:进去的是测试里用 from_millis 造出来的 250 毫秒和 0 毫秒。测试调用 format_duration,把结果和预期文字“250ms”“0ms”比较。出来的结果不是业务数据,而是测试通过或失败;如果格式不对,assert_eq! 会让测试失败。
调用关系:它在运行测试时使用标准库的 from_millis 造时间,再用 assert_eq! 检查输出。它保护 format_duration 的毫秒显示规则,避免以后被误改。
调用图:外部调用 2 个(from_millis, assert_eq!)。
tests::test_format_duration_seconds42–51 ↗
fn test_format_duration_seconds()
作用:这个测试确认 1 秒到 1 分钟之间的耗时会用秒显示,并且保留两位小数。它也覆盖了接近 60 秒时的显示效果。
数据流:进去的是 1500 毫秒和 59999 毫秒两个测试时间。测试把它们传给 format_duration,然后分别期待得到“1.50s”和“60.00s”。如果实际文字不同,测试就失败。
调用关系:它专门守住“秒格式”这段规则。它通过 from_millis 构造输入,通过 assert_eq! 验证 format_duration 的输出,帮助发现秒数格式或四舍五入显示相关的问题。
调用图:外部调用 2 个(from_millis, assert_eq!)。
tests::test_format_duration_minutes54–64 ↗
fn test_format_duration_minutes()
作用:这个测试确认 1 分钟及以上的耗时会显示成“分钟 + 两位秒数”。它覆盖了普通分钟、正好一分钟,以及很长时间的情况。
数据流:进去的是 75000、60000、3601000 毫秒这些测试值。它们经过 format_duration 后,分别应该变成“1m 15s”“1m 00s”“60m 01s”。测试用 assert_eq! 对比实际结果和预期结果。
调用关系:它保护分钟显示规则。只要 format_duration 或内部格式化规则发生变化,这个测试会提醒开发者:分钟和秒的写法是否仍然符合约定。
调用图:外部调用 2 个(from_millis, assert_eq!)。
tests::test_format_duration_one_hour_has_space67–70 ↗
fn test_format_duration_one_hour_has_space()
作用:这个测试确认正好一小时会显示成“60m 00s”,中间有空格。它防止输出格式在这种边界值上变得不一致。
数据流:进去的是 3600000 毫秒,也就是一小时。测试调用 format_duration,然后要求结果必须等于“60m 00s”。如果少了空格、改成小时格式,或秒数不对,测试都会失败。
调用关系:它是一个很具体的边界测试。它和其他测试一起约束 format_duration 的公开表现,特别说明这个工具不会把一小时写成“1h”,而是继续沿用分钟格式。
调用图:外部调用 2 个(from_millis, assert_eq!)。
utils/cli/src/format_env_display.rs源码 ↗
命令行工具经常需要展示将要传给子程序的环境变量,比如 TOKEN、HOME、PATH 等。但这些变量的值可能包含密钥或私人路径,直接打印很危险。这个文件提供的 format_env_display 就像给快递单打码:只显示变量名,值统一写成 *****。它可以接收两类输入:一类是已经给出具体值的环境变量表,另一类是只列出名字、值从当前环境里读取的变量名。函数会先把环境变量表按名字排序,这样每次显示顺序稳定,方便人看,也方便测试;再把额外的变量名接在后面。如果什么都没有,就返回 -,表示没有要显示的环境变量。文件下面的测试覆盖了空输入、排序、只传变量名、两类输入合并这些常见情况,保证这个小工具既安全又可预期。
format_env_display3–24 ↗
fn format_env_display(
env: Option<&HashMap<String, String>>,
env_vars: &[S],
) -> String
作用:把环境变量做成一段给人看的字符串,但不显示真实值,只显示 变量名=*****。有人会在命令行输出、日志或预览配置时用它,避免不小心泄露秘密。
数据流:进去的是一个可选的环境变量表,以及一串额外的变量名。它先新建一个文字片段列表;如果有环境变量表,就读取里面的键值对,按变量名排序,然后只保留变量名,把值替换成 *****;如果还有额外变量名,也逐个变成 变量名=*****。最后,如果列表为空就出来 -,否则把所有片段用逗号和空格连成一整段文字。
调用关系:这是这个文件的核心工具函数。外部代码在需要展示环境变量时直接调用它;它自己只用标准库提供的空列表创建、遍历、判空等基础能力,不再把工作交给项目里的其他模块。下面的测试函数分别从不同场景调用它,确认显示结果安全、稳定。
tests::returns_dash_when_empty31–37 ↗
fn returns_dash_when_empty()
作用:确认没有任何环境变量可显示时,函数会返回 -。这能避免界面上出现空白,让读者明确知道“这里没有内容”。
数据流:进去的是两种空情况:完全没有环境变量表,以及有一个空的环境变量表。测试把这些输入交给 format_env_display,再检查出来的文字是否都是 -。它不改动业务数据,只验证结果是否符合预期。
调用关系:这是测试模块里的基础兜底检查。它在测试运行时调用 format_env_display,用断言工具比较结果,确保核心函数在最简单、最容易被忽略的空输入场景下也表现清楚。
调用图:外部调用 2 个(new, assert_eq!)。
tests::formats_sorted_env_pairs40–49 ↗
fn formats_sorted_env_pairs()
作用:确认环境变量表里的项目会按变量名排序后再显示。这样输出不会因为哈希表本身顺序不固定而忽前忽后。
数据流:进去的是一个包含 B=two 和 A=one 的环境变量表。测试把它传给 format_env_display,函数应该忽略真实值,把它们排成 A 在前、B 在后,并输出 A=*****, B=*****。测试最后用断言检查这段文字完全一致。
调用关系:这个测试专门盯住排序行为。因为 format_env_display 内部会遍历环境变量表并排序,这个测试就在测试运行时调用它,确认最终显示顺序稳定,方便人读,也方便日志比较。
调用图:外部调用 2 个(new, assert_eq!)。
tests::formats_env_vars_with_dollar_prefix52–59 ↗
fn formats_env_vars_with_dollar_prefix()
作用:确认只给变量名、不直接给变量值时,也能生成安全的显示文字。这里的重点是显示名字,仍然不暴露任何实际值。
数据流:进去的是一个变量名列表,包含 TOKEN 和 PATH,没有环境变量表。测试把列表交给 format_env_display,函数逐个生成 TOKEN=***** 和 PATH=*****,再用逗号连起来。出来的结果会被断言检查是否等于预期字符串。
调用关系:这个测试覆盖 format_env_display 的第二类输入:只有变量名的情况。它不关心变量真实值从哪里来,只确认显示层会统一打码,避免以后有人改代码时把敏感值打印出来。
调用图:外部调用 2 个(assert_eq!, vec!)。
tests::combines_env_pairs_and_vars62–71 ↗
fn combines_env_pairs_and_vars()
作用:确认两种来源的环境变量可以合在一起显示:既有明确给出的变量,也有只列名字的变量。这样调用者可以一次得到完整的展示结果。
数据流:进去的是一个环境变量表,里面有 HOME=/tmp,再加一个变量名列表 TOKEN。测试把两者一起交给 format_env_display,函数先把 HOME 打码成 HOME=*****,再把 TOKEN 打码成 TOKEN=*****,最后输出 HOME=*****, TOKEN=*****。测试用断言确认合并顺序和打码结果都正确。
调用关系:这个测试模拟更真实的使用方式:调用方可能同时传入固定环境变量和从外部继承的变量名。它调用核心函数,检查两个来源能顺利拼到同一段显示文字里。
调用图:外部调用 3 个(new, assert_eq!, vec!)。
utils/cli/src/resume_command.rs源码 ↗
用户想继续以前的 Codex 会话时,需要知道该敲哪条 codex resume 命令。这个文件就像一个“小抄生成器”:优先用好读的线程名;如果没有名字,就退回用线程 ID(一串唯一编号)。它还会照顾命令行里的坑,比如名字里有空格要加引号,名字以 - 开头时要加 --,不然终端可能把它当成参数选项。另一个函数 resume_hint 生成更温和的提示:如果有名字和 ID,就告诉用户先运行 codex resume,再从列表里选那个名字;如果没有名字,就直接给出按 ID 恢复的命令。文件下半部分是测试,用一组常见和容易出错的例子保证提示文字不会乱变。
resume_command6–20 ↗
fn resume_command(thread_name: Option<&str>, thread_id: Option<ThreadId>) -> Option<String>
作用:生成一条可以直接在终端里运行的 codex resume ... 命令。它会优先使用线程名,没有线程名时才使用线程 ID;如果两者都没有,就不生成命令。
数据流:输入是可选的线程名和可选的线程 ID。函数先挑一个“恢复目标”:非空线程名优先,否则用线程 ID 转成文字;然后用 shlex_join 做命令行转义,也就是把空格、引号这类容易让终端误会的字符包好;如果目标以 - 开头,还会在前面加 --,告诉命令行“后面是普通内容,不是选项”。输出是 Some("codex resume ..."),如果没有可用目标则输出 None,不改动外部状态。
调用关系:这是这个文件最核心的拼命令函数。resume_hint 在没有线程名时会调用它来生成直接按 ID 恢复的提示;多个测试也直接调用它,检查优先用名字、缺名字时用 ID、没有目标时返回空,以及特殊名字是否被安全处理。
调用图:被 5 处调用(resume_hint, formats_thread_id_when_name_is_missing, prefers_name_over_id, quotes_thread_names_when_needed, returns_none_without_a_resume_target)。
resume_hint22–30 ↗
fn resume_hint(thread_name: Option<&str>, thread_id: Option<ThreadId>) -> Option<String>
作用:生成更适合展示给人的“恢复提示”。它不只是给命令,还会在有线程名时告诉用户进入选择界面后该选哪一项。
数据流:输入是可选线程名和可选线程 ID。它先要求必须有线程 ID;没有 ID 就直接返回 None,因为提示里需要一个可靠的唯一编号。有非空线程名时,它输出类似“运行 codex resume,然后选择 某名字(某 ID)”的文字;没有线程名时,它把线程 ID 交给 resume_command,生成一条可直接运行的恢复命令。
调用关系:它站在用户提示这一层,复用 resume_command 的安全命令拼接能力。对应的测试会调用它,确认有名字时提示选择项、没名字时给直接命令、没 ID 时不给误导性的提示。
调用图:调用 1 个内部函数(resume_command);被 3 处调用(resume_hint_names_picker_item_with_id, resume_hint_requires_thread_id, resume_hint_uses_direct_id_command_without_name);外部调用 1 个(format!)。
tests::prefers_name_over_id38–42 ↗
fn prefers_name_over_id()
作用:验证当线程名和线程 ID 都存在时,命令会优先使用更容易读懂的线程名。
数据流:测试先把一串固定文字转成 ThreadId,再把线程名 my-thread 和这个 ID 一起传给 resume_command。它期望得到 codex resume my-thread,说明 ID 被作为备用信息,没有抢在线程名前面。
调用关系:这个测试直接检查 resume_command 的优先级规则,防止以后有人改代码时不小心变成优先显示难读的 ID。
调用图:调用 2 个内部函数(from_string, resume_command);外部调用 1 个(assert_eq!)。
tests::formats_thread_id_when_name_is_missing45–52 ↗
fn formats_thread_id_when_name_is_missing()
作用:验证没有线程名时,仍然能用线程 ID 生成可执行的恢复命令。
数据流:测试准备一个固定的线程 ID,把线程名留空,然后调用 resume_command。结果应该是 codex resume <这个ID>,也就是用户仍然有办法恢复会话。
调用关系:这个测试覆盖 resume_command 的备用路线:名字缺失时不能放弃,而要用 ID。它保证 resume_hint 依赖这条路线时也能得到正确命令。
调用图:调用 2 个内部函数(from_string, resume_command);外部调用 1 个(assert_eq!)。
tests::returns_none_without_a_resume_target55–58 ↗
fn returns_none_without_a_resume_target()
作用:验证既没有线程名也没有线程 ID 时,函数不会编出一条假的恢复命令。
数据流:测试把两个输入都设为空,然后调用 resume_command。期望结果是 None,意思是“没有足够信息生成命令”。
调用关系:这个测试守住安全边界:resume_command 只有在真的知道恢复目标时才给用户提示,避免显示没法运行或会误导人的命令。
调用图:调用 1 个内部函数(resume_command);外部调用 1 个(assert_eq!)。
tests::quotes_thread_names_when_needed61–73 ↗
fn quotes_thread_names_when_needed()
作用:验证线程名里有特殊情况时,生成的命令仍然能被终端正确理解。
数据流:测试分别传入三种容易出问题的名字:以 - 开头、包含空格、包含单引号。它检查 resume_command 是否加上 --,以及是否正确加引号或换用合适的引号形式。输出都必须是安全、可复制的命令文字。
调用关系:这个测试专门盯住命令行转义这件事。它保护 resume_command 对 shlex_join 和 -- 的使用,防止用户因为一个奇怪名字而运行失败。
调用图:调用 1 个内部函数(resume_command);外部调用 1 个(assert_eq!)。
tests::resume_hint_names_picker_item_with_id76–86 ↗
fn resume_hint_names_picker_item_with_id()
作用:验证当线程名和线程 ID 都有时,提示会告诉用户进入选择列表后选哪个具体条目。
数据流:测试先构造线程 ID,再把 my-thread 和 ID 传给 resume_hint。期望输出是一句包含线程名和 ID 的提示,让用户能在选择器里认出正确会话。
调用关系:这个测试检查 resume_hint 的“有人类可读名字”分支。它不走 resume_command,而是确认提示文案会把名字和唯一 ID 一起展示出来。
调用图:调用 2 个内部函数(from_string, resume_hint);外部调用 1 个(assert_eq!)。
tests::resume_hint_uses_direct_id_command_without_name89–96 ↗
fn resume_hint_uses_direct_id_command_without_name()
作用:验证没有线程名但有线程 ID 时,恢复提示会退回成一条直接按 ID 恢复的命令。
数据流:测试准备一个线程 ID,把线程名设为空,然后调用 resume_hint。它期望得到 codex resume <这个ID>,说明提示没有要求用户去选择一个不存在的名字。
调用关系:这个测试覆盖 resume_hint 调用 resume_command 的那条路。它确认两个函数配合时,缺少名字也能给出清楚可用的恢复方法。
调用图:调用 2 个内部函数(from_string, resume_hint);外部调用 1 个(assert_eq!)。
tests::resume_hint_requires_thread_id99–102 ↗
fn resume_hint_requires_thread_id()
作用:验证 resume_hint 必须有线程 ID 才会生成提示,即使有线程名也不够。
数据流:测试传入线程名 my-thread,但不传线程 ID,然后调用 resume_hint。期望结果是 None,表示信息不完整时不显示提示。
调用关系:这个测试保护 resume_hint 的前置条件:提示需要可靠的 ID 来避免选错会话。它确保函数不会只凭一个可能重复或不稳定的名字就给用户建议。
调用图:调用 1 个内部函数(resume_hint);外部调用 1 个(assert_eq!)。
core/src/web_search.rs源码 ↗
网页搜索工具做事时,内部拿到的是结构化动作,比如“搜索这个词”“打开这个网址”“在网页里找某个字”。这些数据适合程序读,但不适合直接给人看。这个文件就像一个“动作翻译员”:它从搜索动作里挑出最关键的信息,拼成一句短文字。搜索时优先显示明确的 query;如果有多条搜索词,就显示第一条,并用省略号提醒后面还有。打开网页时显示网址。页内查找时,会把“要找的词”和“在哪个网址里找”组合起来。它还做了兜底:如果动作里缺信息,就返回空字符串,外层可以再用原始查询词补上,避免界面上出现难懂的空白或错误。
search_action_detail3–16 ↗
fn search_action_detail(query: &Option<String>, queries: &Option<Vec<String>>) -> String
作用:这个函数专门给“搜索”动作挑一句最合适的展示文字。它会优先用单个搜索词;如果没有,就从多个搜索词列表里拿第一条。
数据流:进去的是一个可有可无的单个搜索词 query,以及一个可有可无的搜索词列表 queries。它先看 query 是否存在且不是空字符串;如果可用,就直接拿它。否则它去 queries 里找第一条。如果列表里不止一条,并且第一条不是空的,就在后面加上“ ...”,表示还有更多搜索词。出来的是一段用于展示的字符串,不会改动原数据。
调用关系:它是更底层的小帮手,只处理“搜索”这一种动作的文字选择。web_search_action_detail 遇到 WebSearchAction::Search 时会把活交给它,这样主函数不用塞太多挑搜索词的细节。
调用图:被 1 处调用(web_search_action_detail)。
web_search_action_detail18–30 ↗
fn web_search_action_detail(action: &WebSearchAction) -> String
作用:这个函数把一种具体的网页搜索动作翻译成短文字。别人拿到 WebSearchAction 这种程序内部动作后,可以用它得到适合显示给人的说明。
数据流:进去的是一个 WebSearchAction,也就是网页搜索相关动作的枚举值,枚举可以理解成“几种固定选项中的一种”。如果是搜索,它交给 search_action_detail 选出搜索词;如果是打开网页,它取网址;如果是在网页里查找,它把查找内容和网址拼成类似“'关键词' in 网址”的文字;如果信息缺失或动作是 Other,就返回空字符串。出来的是一段展示用字符串,原动作不被修改。
调用关系:它是这个文件的主要翻译入口。上游的 parse_turn_item 在解析一轮对话或工具记录时会调用它,把内部动作变成页面或日志里能读懂的文字。它内部会在搜索动作分支中调用 search_action_detail,并在需要拼接文字时使用格式化功能。
调用图:调用 1 个内部函数(search_action_detail);被 1 处调用(parse_turn_item);外部调用 2 个(new, format!)。
web_search_detail32–39 ↗
fn web_search_detail(action: Option<&WebSearchAction>, query: &str) -> String
作用:这个函数提供一个更保险的展示入口:如果有具体搜索动作,就用动作生成说明;如果没有或生成不出内容,就退回显示原始查询词。
数据流:进去的是一个可有可无的 WebSearchAction 引用,以及一个原始 query 字符串。它先尝试从动作里得到展示文字;如果得到的是空字符串,就改用传入的 query。出来的是最终一定尽量可读的说明文字,不改动传入的动作和查询词。
调用关系:它适合放在更外层的展示流程里当兜底包装。给出的调用图没有显示谁直接调用它;从作用上看,它补上了 web_search_action_detail 可能返回空内容时的最后一道保险。
文本整形与截断流程
这些文件将可复用的格式化和预算限制逻辑应用于较大的文本载荷,从面向 TUI 的整形到输出和响应历史裁剪。
tui/src/text_formatting.rs源码 ↗
终端界面不像网页那样宽松,文字一长就容易挤爆、换行难看,甚至把一个 emoji 切成乱码。这个文件就是一组文字小工具,像给界面配了一把“修剪刀”和“排版尺”。它能把首字母转成大写;能把工具返回的大段内容压到指定大小;如果内容是 JSON(一种常见的数据文本格式),会先整理成紧凑但带空格的单行,方便 Ratatui 这个终端界面库在空格处换行;还能安全截断普通文字,避免切断由多个编码组成的字符。它还会把很长的文件路径从中间省略,尽量保留开头和结尾,因为这两端通常最有用。最后还有一个英文列表拼接工具,把多个词拼成 apple, banana and cherry 这种自然写法。下面的测试覆盖了短文本、超长文本、emoji、JSON、路径等容易出错的情况。
capitalize_first5–15 ↗
fn capitalize_first(input: &str) -> String
作用:把一段文字的第一个字符变成大写,其余部分原样保留。适合用在界面标题、状态词这类需要看起来更规整的地方。
数据流:输入是一段字符串 → 它取出第一个字符,把这个字符转成大写,再把剩下的文字接回去;如果输入为空,就直接给出空字符串 → 输出是一段新的字符串,不会改原来的输入。
调用关系:这是一个独立的小工具,只调用标准字符串构造能力来生成结果。别的界面代码需要把一个词或短句显示得更像标题时,可以直接用它。
调用图:外部调用 1 个(new)。
format_and_truncate_tool_result19–34 ↗
fn format_and_truncate_tool_result(
text: &str,
max_lines: usize,
line_width: usize,
) -> String
作用:把工具返回的内容整理到一个能放进指定宽高的小框里。它会优先识别 JSON 并压成更适合终端换行的样子,然后再截短。
数据流:输入是原始文本、最多显示几行、每行多宽 → 它先估算总共能放多少个“用户看见的字符”,再尝试用 format_json_compact 整理 JSON;整理成功就截整理后的文本,失败就截原文本 → 输出是一段不会太长、适合放进界面的字符串。
调用关系:它是显示工具结果时的组合步骤,自己不做所有细活,而是把 JSON 整理交给 format_json_compact,把安全截断交给 truncate_text。
调用图:调用 2 个内部函数(format_json_compact, truncate_text)。
format_json_compact44–88 ↗
fn format_json_compact(text: &str) -> Option<String>
作用:把合法 JSON 文本变成紧凑的一行,同时在冒号和逗号后保留必要空格。这样既省屏幕行数,又能让终端界面在空格处比较自然地换行。
数据流:输入是一段文本 → 它先尝试把文本解析成 JSON;解析失败就返回 None;解析成功后先转成漂亮格式,再逐字符去掉多余换行和缩进,但不会动字符串引号里面的内容 → 输出是 Some(整理后的 JSON 字符串),或者在输入不是 JSON 时输出 None。
调用关系:format_and_truncate_tool_result 会先找它帮忙整理工具结果。其他显示工具参数的地方也会调用它。测试函数从简单对象、数组、嵌套对象、空对象、非法 JSON 等角度检查它不会乱改内容。
调用图:被 10 处调用(format_tool_approval_display_param_value, format_and_truncate_tool_result, test_format_json_compact_already_compact, test_format_json_compact_array, test_format_json_compact_empty_array, test_format_json_compact_empty_object, test_format_json_compact_invalid_json, test_format_json_compact_nested_object, test_format_json_compact_simple_object, test_format_json_compact_with_whitespace);外部调用 3 个(new, matches!, to_string_pretty)。
truncate_text91–115 ↗
fn truncate_text(text: &str, max_graphemes: usize) -> String
作用:把文字截到指定长度,并尽量用省略号表示后面还有内容。它按“字素簇”截断,也就是用户眼里看到的一个字符,避免把 emoji 或带重音符号的字符切坏。
数据流:输入是一段文本和最多允许的字素数量 → 它检查是否超过限制;没超过就原样返回;超过时,如果空间足够,会留下前面一部分再加三个点;空间太小就只取能放下的开头 → 输出是安全截短后的新字符串。
调用关系:很多界面摘要和提示行都会用它,例如活动摘要、错误摘要、状态摘要、提示行等。format_and_truncate_tool_result 也把最终截断交给它。测试集中验证了空字符串、边界长度、emoji、组合字符和超长文本。
调用图:被 25 处调用(activity_summary, bounded_summary, format_tool_approval_display_param_value, build_rows, error_summary_spans, prompt_line, status_summary_spans, dense_column_text, push_footer_part, render_comfortable_session_lines (+15 more));外部调用 1 个(format!)。
center_truncate_path120–328 ↗
fn center_truncate_path(path: &str, max_width: usize) -> String
作用:把很长的文件路径缩短到指定显示宽度,同时尽量保留开头和结尾。因为路径的开头常说明位置,结尾常说明文件名,中间最适合用省略号代替。
数据流:输入是一条像路径的字符串和最大显示宽度 → 它先用终端实际显示宽度判断是否需要截断;需要时按路径分隔符拆成一段段,尝试保留左边若干段和右边若干段,中间插入“…”;如果某一段本身太长,就从这一段前面再截掉一部分 → 输出是一条能放进宽度限制的路径字符串。
调用关系:目录显示相关代码会调用它,比如 format_directory_inner 和 format_directory_display。它内部会用字符显示宽度来处理中文、emoji 等宽度不一定等于字节数的字符,测试覆盖了普通长路径、Windows 风格路径和单段特别长的路径。
调用图:被 6 处调用(format_directory_inner, format_directory_display, test_center_truncate_doesnt_truncate_short_path, test_center_truncate_handles_long_segment, test_center_truncate_truncates_long_path, test_center_truncate_truncates_long_windows_path);外部调用 4 个(new, width, new, min)。
proper_join336–355 ↗
tests::test_truncate_text363–367 ↗
fn test_truncate_text()
作用:检查普通长文本被截断时,会保留开头并补上省略号。它确保最常见的截短效果符合预期。
数据流:输入固定文本 Hello, world! 和长度 8 → 调用 truncate_text → 期望输出 Hello...,并用断言比较实际结果。
调用关系:这是 truncate_text 的基础测试之一。它直接调用 truncate_text,再交给断言工具判断结果是否正确。
调用图:调用 1 个内部函数(truncate_text);外部调用 1 个(assert_eq!)。
tests::test_truncate_empty_string370–374 ↗
fn test_truncate_empty_string()
作用:检查空字符串不会被截成奇怪的内容。空输入应该仍然是空输出。
数据流:输入空字符串和长度 5 → 调用 truncate_text → 得到空字符串,并用断言确认。
调用关系:这是 truncate_text 的边界测试,保证界面拿到空内容时不会显示多余符号。
调用图:调用 1 个内部函数(truncate_text);外部调用 1 个(assert_eq!)。
tests::test_truncate_max_graphemes_zero377–381 ↗
fn test_truncate_max_graphemes_zero()
作用:检查允许长度为 0 时,结果就是空字符串。也就是完全没有显示空间时不能硬塞文字。
数据流:输入 Hello 和最大长度 0 → 调用 truncate_text → 输出空字符串,然后断言检查。
调用关系:它覆盖 truncate_text 的极小空间情况,防止之后改代码时误加省略号或留下字符。
调用图:调用 1 个内部函数(truncate_text);外部调用 1 个(assert_eq!)。
tests::test_truncate_max_graphemes_one384–388 ↗
fn test_truncate_max_graphemes_one()
作用:检查只允许显示 1 个字符时,函数只返回第一个字符,不加省略号。因为省略号本身也需要空间。
数据流:输入 Hello 和最大长度 1 → 调用 truncate_text → 输出 H,再由断言确认。
调用关系:这是 truncate_text 的小宽度边界测试,和长度 0、2、3 的测试一起保护省略号规则。
调用图:调用 1 个内部函数(truncate_text);外部调用 1 个(assert_eq!)。
tests::test_truncate_max_graphemes_two391–395 ↗
fn test_truncate_max_graphemes_two()
作用:检查只允许显示 2 个字符时,函数返回前两个字符。空间太小,所以仍然不加三个点。
数据流:输入 Hello 和最大长度 2 → 调用 truncate_text → 输出 He,并用断言确认。
调用关系:它帮助确认 truncate_text 在不足以放省略号时会简单截断,而不是超出限制。
调用图:调用 1 个内部函数(truncate_text);外部调用 1 个(assert_eq!)。
tests::test_truncate_max_graphemes_three_boundary398–402 ↗
fn test_truncate_max_graphemes_three_boundary()
作用:检查长度刚好为 3 时,超长文本会变成三个点。因为三个点正好占满允许空间。
数据流:输入 Hello 和最大长度 3 → 调用 truncate_text → 输出 ...,然后断言比较。
调用关系:这是 truncate_text 省略号规则的关键边界测试,保证刚够放省略号时行为稳定。
调用图:调用 1 个内部函数(truncate_text);外部调用 1 个(assert_eq!)。
tests::test_truncate_text_shorter_than_limit405–409 ↗
fn test_truncate_text_shorter_than_limit()
作用:检查短文本在空间足够时不会被改动。没有必要截断时,函数应该保持原样。
数据流:输入 Hi 和最大长度 10 → 调用 truncate_text → 输出 Hi,再用断言确认。
调用关系:这是 truncate_text 的“不该动就不动”测试,避免界面无故出现省略号。
调用图:调用 1 个内部函数(truncate_text);外部调用 1 个(assert_eq!)。
tests::test_truncate_text_exact_length412–416 ↗
fn test_truncate_text_exact_length()
作用:检查文本长度刚好等于限制时不会被截断。刚好放得下,就应该完整显示。
数据流:输入 Hello 和最大长度 5 → 调用 truncate_text → 输出 Hello,并断言确认。
调用关系:它补上 truncate_text 的等长边界,防止把刚好合适的内容误判为超长。
调用图:调用 1 个内部函数(truncate_text);外部调用 1 个(assert_eq!)。
tests::test_truncate_emoji419–426 ↗
fn test_truncate_emoji()
作用:检查 emoji 被截断时不会被切坏。emoji 往往不是简单的一个字节,错误截断可能显示乱码。
数据流:输入一串 emoji → 分别用最大长度 3 和 4 调用 truncate_text → 期望得到 ... 和 ...,并用断言确认。
调用关系:这是 truncate_text 使用字素簇而不是字节截断的重要测试,保护终端里 emoji 显示正常。
调用图:调用 1 个内部函数(truncate_text);外部调用 1 个(assert_eq!)。
tests::test_truncate_unicode_combining_characters429–433 ↗
fn test_truncate_unicode_combining_characters()
作用:检查带组合符号的字符不会被从中间切开。比如一个字母加重音符号,在用户眼里应算一个整体。
数据流:输入带组合标记的文本和最大长度 2 → 调用 truncate_text → 期望原样返回,并用断言确认。
调用关系:它验证 truncate_text 对复杂 Unicode 字符的安全处理,避免显示半个字符。
调用图:调用 1 个内部函数(truncate_text);外部调用 1 个(assert_eq!)。
tests::test_truncate_very_long_text436–441 ↗
fn test_truncate_very_long_text()
作用:检查很长的文本也能按限制稳定截断。它同时确认输出长度没有超过预期。
数据流:输入 1000 个 a 和最大长度 10 → 调用 truncate_text → 期望输出 aaaaaaa...,并检查长度是 10。
调用关系:这是 truncate_text 的压力式测试,确保面对大段工具输出或日志时仍然可靠。
调用图:调用 1 个内部函数(truncate_text);外部调用 1 个(assert_eq!)。
tests::test_format_json_compact_simple_object444–448 ↗
fn test_format_json_compact_simple_object()
作用:检查简单 JSON 对象会被整理成紧凑且带必要空格的一行。比如冒号后会有空格。
数据流:输入一个包含 name 和 age 的 JSON 字符串 → 调用 format_json_compact → 取出结果并断言它等于预期文本。
调用关系:这是 format_json_compact 的基础测试,确认最普通的对象格式化结果可读。
调用图:调用 1 个内部函数(format_json_compact);外部调用 1 个(assert_eq!)。
tests::test_format_json_compact_nested_object451–458 ↗
fn test_format_json_compact_nested_object()
作用:检查嵌套 JSON 对象也能被整理成一行,而且层级结构不丢。嵌套对象是工具返回结果里很常见的情况。
数据流:输入带 user、details 等多层结构的 JSON → 调用 format_json_compact → 断言输出保留结构并去掉多余换行。
调用关系:它直接测试 format_json_compact,确保整理复杂一点的 JSON 时不会只适用于浅层对象。
调用图:调用 1 个内部函数(format_json_compact);外部调用 1 个(assert_eq!)。
tests::test_center_truncate_doesnt_truncate_short_path461–467 ↗
fn test_center_truncate_doesnt_truncate_short_path()
作用:检查路径本来就够短时不会被省略。能完整显示就不应该自作主张缩短。
数据流:输入一条较短路径和宽度 40 → 调用 center_truncate_path → 期望输出仍是原路径,并断言确认。
调用关系:这是 center_truncate_path 的基本保护测试,目录显示代码依赖这种“不需要就不截”的行为。
调用图:调用 1 个内部函数(center_truncate_path);外部调用 2 个(assert_eq!, format!)。
tests::test_center_truncate_truncates_long_path470–479 ↗
fn test_center_truncate_truncates_long_path()
作用:检查普通长路径会从中间省略,并保留有用的头尾部分。这样用户还能看出路径从哪里来、到哪里去。
数据流:输入一条很长的类 Unix 路径和宽度 24 → 调用 center_truncate_path → 期望中间出现 …,两端关键目录保留。
调用关系:它验证 center_truncate_path 的核心场景,也是目录显示里最常见的路径压缩方式。
调用图:调用 1 个内部函数(center_truncate_path);外部调用 2 个(assert_eq!, format!)。
tests::test_center_truncate_truncates_long_windows_path482–492 ↗
fn test_center_truncate_truncates_long_windows_path()
作用:检查较长的 Windows 风格路径也能正确省略。路径分隔符在不同系统上可能不同,这个测试能防止跨平台显示出错。
数据流:输入 C: 开头的长路径和宽度 36 → 调用 center_truncate_path → 断言输出保留前面的盘符和目录,以及末尾文件名附近的部分。
调用关系:它测试 center_truncate_path 在 Windows 类路径上的表现,和普通长路径测试一起覆盖不同平台习惯。
调用图:调用 1 个内部函数(center_truncate_path);外部调用 2 个(assert_eq!, format!)。
tests::test_center_truncate_handles_long_segment495–501 ↗
fn test_center_truncate_handles_long_segment()
作用:检查单个路径段特别长时,也能在这一段内部截断。否则即使省略中间目录,某个超长文件名仍可能撑爆界面。
数据流:输入包含 supercalifragilisticexpialidocious 的路径和宽度 18 → 调用 center_truncate_path → 期望长段前面被替换成 …,后半段保留。
调用关系:它验证 center_truncate_path 的兜底能力:不只会省略路径段,也会处理单段过长的问题。
调用图:调用 1 个内部函数(center_truncate_path);外部调用 2 个(assert_eq!, format!)。
tests::test_format_json_compact_array504–508 ↗
fn test_format_json_compact_array()
作用:检查 JSON 数组能被整理成紧凑的一行。数组里还混有对象和字符串,用来确认多种元素能一起处理。
数据流:输入一个数组 JSON → 调用 format_json_compact → 断言输出是带合适空格的单行数组。
调用关系:这是 format_json_compact 的数组场景测试,补充对象测试没有覆盖的情况。
调用图:调用 1 个内部函数(format_json_compact);外部调用 1 个(assert_eq!)。
tests::test_format_json_compact_already_compact511–515 ↗
fn test_format_json_compact_already_compact()
作用:检查原本已经很紧凑的 JSON 也会被规范化。它会补上可读性需要的空格,而不是完全原样返回。
数据流:输入 {"compact":true} → 调用 format_json_compact → 期望得到 {"compact": true}。
调用关系:它验证 format_json_compact 不只是压缩,也会统一成界面想要的可读格式。
调用图:调用 1 个内部函数(format_json_compact);外部调用 1 个(assert_eq!)。
tests::test_format_json_compact_with_whitespace518–533 ↗
fn test_format_json_compact_with_whitespace()
作用:检查带很多换行和缩进的 JSON 会被压成一行。这样大块 JSON 不会浪费太多终端行数。
数据流:输入一个多行 JSON → 调用 format_json_compact → 输出去掉多余空白,但在逗号和冒号后保留必要空格。
调用关系:它直接测试 format_json_compact 对“漂亮但太占地方”的 JSON 的处理,这正是该函数存在的主要原因。
调用图:调用 1 个内部函数(format_json_compact);外部调用 1 个(assert_eq!)。
tests::test_format_json_compact_invalid_json536–540 ↗
fn test_format_json_compact_invalid_json()
作用:检查非法 JSON 不会被硬格式化。无法解析时应该明确返回没有结果,而不是猜测或产生错误文本。
数据流:输入语法错误的 JSON 文本 → 调用 format_json_compact → 期望结果是 None,并用断言确认。
调用关系:它保护 format_json_compact 的失败路径。format_and_truncate_tool_result 依赖这个行为来决定是否退回处理普通文本。
调用图:调用 1 个内部函数(format_json_compact);外部调用 1 个(assert!)。
tests::test_format_json_compact_empty_object543–547 ↗
fn test_format_json_compact_empty_object()
作用:检查空 JSON 对象会保持为 {}。空结构虽然简单,但也是合法输入。
数据流:输入 {} → 调用 format_json_compact → 取出结果并断言仍是 {}。
调用关系:这是 format_json_compact 的边界测试,确保没有内容的对象不会被加上多余空格。
调用图:调用 1 个内部函数(format_json_compact);外部调用 1 个(assert_eq!)。
tests::test_format_json_compact_empty_array550–554 ↗
fn test_format_json_compact_empty_array()
作用:检查空 JSON 数组会保持为 []。它保证空数组不会被错误展开或改形。
数据流:输入 [] → 调用 format_json_compact → 取出结果并断言仍是 []。
调用关系:这是 format_json_compact 的另一个空结构边界测试,和空对象测试配套。
调用图:调用 1 个内部函数(format_json_compact);外部调用 1 个(assert_eq!)。
tests::test_format_json_compact_primitive_values557–563 ↗
fn test_format_json_compact_primitive_values()
作用:检查 JSON 的基本值,比如数字、true、false、null 和字符串,都能保持正确显示。JSON 不一定总是对象或数组。
数据流:输入多个基本 JSON 值 → 对每个结果做相等断言 → 确认这些值不会被多余包装或改写。
调用关系:这个测试补齐 format_json_compact 的输入范围,确保它面对 JSON 基本类型时也符合预期。
调用图:外部调用 1 个(assert_eq!)。
tests::test_proper_join566–579 ↗
fn test_proper_join()
作用:检查英文列表拼接规则是否正确。不同数量的元素应该有不同写法。
数据流:输入空列表、一个元素、两个元素、三个元素和四个元素 → 分别检查 proper_join 的输出写法 → 用断言确认逗号和 and 的位置正确。
调用关系:这是 proper_join 的完整行为测试,覆盖它按元素数量分支的所有主要情况。
调用图:外部调用 2 个(assert_eq!, vec!)。
utils/output-truncation/src/lib.rs源码 ↗
有些工具会一次吐出几千行日志、报错或文本。如果不控制长度,后面的系统可能装不下,也会浪费很多成本。这个文件就像一个“自动裁纸刀”:先看输出有没有超过预算,预算可以按字节算,也可以按 token 算。token 可以粗略理解成模型阅读文本时使用的小片段单位。普通纯文本会被从中间截短,这样开头和结尾通常还能保留下来;带图片或加密内容的输出,则不会乱剪这些非文本部分。这里还提供了两种风格:一种会加上“已截断、原来大概多少 token、总共多少行”的说明,适合展示给人或模型看;另一种按每个内容块逐个消耗预算,预算用完后会省略后面的文字,并留下省略提示。
formatted_truncate_text12–23 ↗
fn formatted_truncate_text(content: &str, policy: TruncationPolicy) -> String
作用:把一整段文本按给定规则剪短,并且在真的剪短时加上一段醒目的说明。别人用它,是为了让接收方知道“这不是完整输出”。
数据流:输入是一段文本和一个截断策略。它先看文本长度是否还在允许范围内;如果没超,就原样返回。超了以后,它会估算原文 token 数、统计总行数,再调用 truncate_text 真正剪短文本,最后输出一段带警告头的字符串。
调用关系:它是面向展示的包装函数:自己不直接决定怎么剪,而是把具体裁剪交给 truncate_text。formatted_truncate_text_content_items_with_policy 在需要把多个文本内容合并后截断时,也会用到它来生成带说明的文本。
调用图:调用 2 个内部函数(byte_budget, truncate_text);外部调用 2 个(approx_token_count, format!)。
truncate_text25–30 ↗
fn truncate_text(content: &str, policy: TruncationPolicy) -> String
作用:按指定预算剪短一段纯文本。预算可以是字节数,也可以是 token 数,它负责选择合适的剪法。
数据流:输入是一段文本和截断策略。策略如果说按字节剪,它就用按字符居中截断的工具;策略如果说按 token 剪,它就用按 token 预算截断的工具。输出是一段已经被缩短、但尽量保留前后内容的文本。
调用关系:它是这个文件里最核心的小工具,formatted_truncate_text 靠它做实际截断,truncate_function_output_items_with_policy 在某个文本块太长、只能保留一部分时也会调用它。
调用图:被 2 处调用(formatted_truncate_text, truncate_function_output_items_with_policy);外部调用 2 个(truncate_middle_chars, truncate_middle_with_token_budget)。
formatted_truncate_text_content_items_with_policy32–81 ↗
fn formatted_truncate_text_content_items_with_policy(
items: &[FunctionCallOutputContentItem],
policy: TruncationPolicy,
) -> (Vec<FunctionCallOutputContentItem>, Option<usize>)
作用:处理一组工具输出内容,里面可能有文本、图片或加密内容;它只把文本合并后截短,并保留图片和加密内容。适合需要给最终输出加“已截断”说明的场景。
数据流:输入是一组内容项和一个截断策略。它先挑出所有文本项,把它们用换行拼成一整段;如果没有文本,或者拼完后没超预算,就返回原内容,并说明没有截断。超预算时,它会估算原文本 token 数,把合并后的文本做带说明的截断,然后把原来的图片和加密内容原样复制到结果里;输出是新的内容项列表,以及原始 token 数。
调用关系:它处理的是“多种内容混在一起”的输出。它内部借用 formatted_truncate_text 来生成人能看懂的截断文本,同时避免改动图片和加密内容,因为这些东西不能像普通文字那样随便剪。
调用图:调用 1 个内部函数(byte_budget);外部调用 5 个(new, iter, to_vec, approx_token_count, vec!)。
truncate_function_output_items_with_policy83–145 ↗
fn truncate_function_output_items_with_policy(
items: &[FunctionCallOutputContentItem],
policy: TruncationPolicy,
) -> Vec<FunctionCallOutputContentItem>
作用:按预算逐个处理函数调用的输出内容,尽量从前往后保留文本,预算不够时只截当前这一块,后面的文字会被省略。它适合需要严格控制总输出大小的地方。
数据流:输入是一组内容项和一个截断策略。它先算出总预算,然后依次看每个内容项:图片和加密内容直接复制;文本则计算它会花掉多少预算,够就整块保留,不够就只截出还能放下的一小段。预算用完后,再遇到文本就只计数不保留。最后如果有文字被省略,会在输出末尾加一条类似“省略了几个文本项”的提示。
调用关系:它和 formatted_truncate_text_content_items_with_policy 的思路不同:这里不把所有文本合成一块,而是按内容项一个个花预算。遇到单个文本块太长时,它会调用 truncate_text 来剪出可放下的片段。
调用图:调用 3 个内部函数(byte_budget, token_budget, truncate_text);外部调用 6 个(with_capacity, len, approx_token_count, format!, Bytes, Tokens)。
approx_tokens_from_byte_count_i64147–154 ↗
fn approx_tokens_from_byte_count_i64(bytes: i64) -> i64
作用:把字节数粗略换算成 token 数,并且使用 i64 这种常见整数类型。它主要是为了让其他地方用更方便的数字类型时,也能复用 token 估算能力。
数据流:输入是一个 i64 字节数。如果输入小于等于 0,它直接返回 0。否则它先把这个数安全转换成 usize,太大就按最大值处理;再调用底层的字节到 token 估算函数;最后把结果安全转换回 i64,太大就返回 i64 的最大值。
调用关系:它是一个小型适配函数,外部代码如果手里拿的是 i64 字节数,可以通过它得到大概 token 数。它把真正的估算工作交给 approx_tokens_from_byte_count,自己主要负责处理负数和超大数字这些边界情况。
调用图:外部调用 3 个(approx_tokens_from_byte_count, try_from, try_from)。
tools/src/response_history.rs源码 ↗
这份文件像一个聊天记录清理工。对话越聊越长,如果每次都带上全部历史,系统会变慢,也可能超过“令牌”(token,可以粗略理解成模型读文本时用的小块单位)限制。这里有两个主要工具:一个从最近的若干条用户消息开始保留,丢掉更早内容,并且会截到最新用户消息为止,不把最新用户消息之后的助手回复留下;另一个专门压缩助手输出,把所有助手文本共享同一个令牌预算,先保留前面的内容,预算不够时截短,预算用完后删掉后续助手文本。文件后半部分是测试,用假消息搭出小对话,检查这两个清理动作是否真的按预期工作。
retain_tail_from_last_n_user_messages9–34 ↗
fn retain_tail_from_last_n_user_messages(
items: &mut Vec<ResponseItem>,
user_message_count: usize,
)
作用:这个函数把一串对话记录裁成“从最近 N 条用户消息中最早那条开始,到最新用户消息为止”的尾巴。它用于保留最有用的近期上下文,同时丢掉太早或最新用户消息之后暂时不该带上的内容。
数据流:进去的是一个可修改的消息列表和要保留的用户消息数量。它先处理特殊情况:如果数量是 0,或者列表里根本没有用户消息,就直接清空;否则找到最新的一条用户消息,先删掉它后面的内容,再往前数出要保留的最早用户消息位置,最后删除这个位置之前的旧记录。出来时,原来的列表被原地改短,只剩需要的近期片段。
调用关系:在这份调用图里,它由测试函数 tests::retains_tail_through_latest_user_message 调用。测试先造出一段包含 system、user、assistant 的对话,再让它裁剪,最后检查结果是不是只留下最近两条用户消息相关的那段。实际使用时,它会作为上层流程里的小工具,被拿来控制对话历史长度。
调用图:被 1 处调用(retains_tail_through_latest_user_message)。
truncate_assistant_output_text_to_token_budget37–71 ↗
fn truncate_assistant_output_text_to_token_budget(
items: &mut Vec<ResponseItem>,
max_tokens: usize,
)
作用:这个函数把助手回复的文本压到一个总令牌预算以内。它不是每条消息各有一个预算,而是所有助手输出一起共用一份预算,防止历史里的助手长回复占太多空间。
数据流:进去的是一个可修改的消息列表和最大令牌数。它从前往后看每条消息,只处理角色是 assistant 的输出文本;非助手消息和非文本内容会保留。每遇到一段助手文本,就估算它占多少令牌:够预算就原样保留并扣掉预算,不够就把这段文本截到剩余预算大小;如果预算已经用完,后面的助手输出文本会被删除。最后,如果某条助手消息的内容全被删空,这条消息也会被移除。
调用关系:在这份调用图里,它由测试函数 tests::truncates_assistant_output_text_across_items 调用。测试准备一条很长的助手消息和一条后续助手消息,然后给很小的预算,确认第一条被截短、后面的助手消息被删掉,而用户消息仍然保留。它内部会借助文本截断工具来完成真正的截短。
调用图:被 1 处调用(truncates_assistant_output_text_across_items)。
tests::message84–100 ↗
fn message(role: &str, text: &str) -> ResponseItem
作用:这是测试用的小帮手,用来快速造一条假的对话消息。测试不需要手写复杂结构,只要给角色和文字,它就能生成对应的消息对象。
数据流:进去的是角色名称和文本。它根据角色判断内容类型:如果角色是 assistant,就把文本做成输出文本;否则做成输入文本。出来的是一个 ResponseItem::Message,其中 id、阶段、元数据这些测试不关心的字段都留空。
调用关系:它服务于本文件里的两个测试函数,帮助它们搭建对话列表。调用图里显示它会使用 vec! 这个外部宏来创建内容列表;它不参与正式运行逻辑,只让测试更短、更清楚。
调用图:外部调用 1 个(vec!)。
tests::retains_tail_through_latest_user_message103–124 ↗
fn retains_tail_through_latest_user_message()
作用:这个测试确认“保留最近 N 条用户消息的尾部历史”这个规则没有写错。它特别检查:最新用户消息后面的助手回复会被去掉,太早的历史也会被去掉。
数据流:它先用 tests::message 造出一段对话:有系统消息、旧用户消息、旧助手回复、较新的用户消息、当前用户消息,以及当前用户消息之后的助手回复。然后它调用 retain_tail_from_last_n_user_messages,要求保留最近 2 条用户消息。最后它把实际列表和期望列表做比较,确认只剩“previous user、previous assistant、current user”。
调用关系:它是 retain_tail_from_last_n_user_messages 的直接验证者。它把测试数据交给被测函数,再用 assert_eq! 这个外部断言工具检查结果;如果以后有人改坏了裁剪规则,这个测试会失败并提醒。
调用图:调用 1 个内部函数(retain_tail_from_last_n_user_messages);外部调用 2 个(assert_eq!, vec!)。
tests::truncates_assistant_output_text_across_items127–149 ↗
fn truncates_assistant_output_text_across_items()
作用:这个测试确认助手输出会按共享预算截短,而不是每条助手消息都单独保留一份额度。它还检查预算用完后,后面的助手文本会被删掉,但用户消息不受影响。
数据流:它先造一段对话,其中第一条助手消息很长,后面还有一条助手消息,再加上用户消息。接着它调用 truncate_assistant_output_text_to_token_budget,并把预算设得很小。函数运行后,测试期望第一条助手文本被截到预算大小,第二条助手消息因为没有预算而消失,用户消息仍然留在列表里。
调用关系:它是 truncate_assistant_output_text_to_token_budget 的直接验证者。它还调用同一个文本截断工具来计算期望结果,然后用 assert_eq! 比较实际输出和期望输出,保证测试和正式逻辑对“截短”的理解一致。
调用图:调用 1 个内部函数(truncate_assistant_output_text_to_token_budget);外部调用 2 个(assert_eq!, vec!)。
严格文本模板
此文件提供该阶段的独立模板引擎,用于解析和渲染基于占位符的文本,并进行明确的错误处理。
utils/template/src/lib.rs源码 ↗
这个文件像一台“填空机”。模板文本里可以写 {{ name }} 这样的占位符,运行时再把 name 换成真正内容。它不追求复杂功能,反而故意很严格:空占位符、少了结尾 }}、多出来的 }}、占位符里又套占位符,都会直接报错。渲染时也一样严格:模板需要的值必须都给;模板没用到的值不能乱给;同一个值名不能给两次。这样做的好处是,生成提示词或文本资产时,不会悄悄产出半坏不坏的内容。核心部件是 Template:先把原始字符串拆成普通文字片段和占位符片段;之后渲染时按顺序拼回去。TemplateParseError、TemplateRenderError 和 TemplateError 则负责把出错原因说清楚,方便调用方定位问题。
TemplateParseError::fmt22–43 ↗
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
作用:把“模板写法错了”的错误变成普通人能读懂的一句话。比如告诉你哪个字节位置的占位符是空的,或者哪里少了 }}。
数据流:进去的是一个解析错误类型和一个输出格式器 → 它根据错误种类挑选对应的说明文字,并写入格式器 → 出来的是格式化结果,错误本身不被改变。
调用关系:当外部代码想打印或记录 TemplateParseError 时会走到这里;它把内部错误枚举翻译成人类可读的文本,底层用标准的 write! 写出消息。
调用图:外部调用 1 个(write!)。
TemplateRenderError::fmt56–68 ↗
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
作用:把“模板填值时出错”的原因写成清楚的文字。它会说明是值重复了、多给了,还是模板需要的值没给。
数据流:进去的是一个渲染错误和一个输出格式器 → 它看错误属于哪一类,并把变量名嵌进说明里 → 出来的是格式化结果,不会修复或改变错误。
调用关系:当调用方打印 TemplateRenderError 时会用到它;它和 TemplateParseError::fmt 分工,一个解释模板语法问题,一个解释填值问题。
调用图:外部调用 1 个(write!)。
TemplateError::fmt80–85 ↗
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
作用:把统一错误 TemplateError 显示出来。它自己不重新编话,而是把工作交给里面包着的解析错误或渲染错误。
数据流:进去的是一个总错误和格式器 → 它判断里面装的是解析错误还是渲染错误 → 出来的是对应子错误生成的文字说明。
调用关系:顶层 render 函数会把解析和渲染两类错误都包装成 TemplateError;调用方打印这个总错误时,就通过这里转交给具体错误。
TemplateError::source89–94 ↗
fn source(&self) -> Option<&(dyn Error + 'static)>
作用:告诉错误系统:这个总错误背后真正的原始错误是谁。这样调试工具或日志系统可以追溯到更具体的原因。
数据流:进去的是一个 TemplateError → 它查看里面包的是哪种错误 → 出来的是那个内部错误的引用,或者说“源头错误就是它”。
调用关系:这是 Rust 标准错误接口的一部分;当外部工具想查看错误链时,它把 TemplateError 和 TemplateParseError、TemplateRenderError 连接起来。
TemplateError::from104–106 ↗
fn from(value: TemplateRenderError) -> Self
作用:把具体错误自动装进统一错误 TemplateError。这样调用方只需要处理一种错误类型,不必分别接住解析错误和渲染错误。
数据流:进去的是一个解析错误或渲染错误 → 它把这个错误包进 TemplateError::Parse 或 TemplateError::Render → 出来的是统一格式的 TemplateError。
调用关系:顶层 render 函数在解析或渲染失败时会依赖这种转换;它让 ? 这类错误传递写法可以顺手使用,不必手动写很多转换代码。
调用图:外部调用 2 个(Parse, Render)。
Template::parse122–168 ↗
fn parse(source: &str) -> Result<Self, TemplateParseError>
作用:把一整段模板文字拆成机器更容易处理的结构。它会认出普通文字、{{ name }} 占位符,以及用 {{{{、}}}} 表示真正的大括号。
数据流:进去的是原始模板字符串 → 它从头扫到尾,把普通文字放进文字片段,把占位符名字收集起来,并检查各种写法错误 → 成功时出来一个可重复使用的 Template,失败时出来具体的解析错误。
调用关系:这是模板进入系统的第一关;顶层 render 会先调用它,其他解析嵌入模板的地方也会调用它。它遇到普通文字时交给 push_literal 合并,遇到占位符时交给 parse_placeholder 读取名字。
调用图:调用 2 个内部函数(parse_placeholder, push_literal);被 13 处调用(parse_embedded_template, parse_embedded_template, parse_embedded_template, render, parse_errors_when_closing_delimiter_is_unmatched, parse_errors_when_placeholder_is_empty, parse_errors_when_placeholder_is_nested, parse_errors_when_placeholder_is_unterminated, parsed_templates_can_be_reused, placeholders_are_sorted_and_unique (+3 more));外部调用 3 个(new, new, Placeholder)。
Template::placeholders170–172 ↗
fn placeholders(&self) -> impl ExactSizeIterator<Item = &str>
作用:列出这个模板里需要哪些变量名。结果是去重并排序过的,方便调用方提前检查自己要准备什么值。
数据流:进去的是已经解析好的 Template → 它读取内部保存的占位符集合 → 出来的是一个可以逐个取出变量名的迭代器,模板本身不变。
调用关系:在模板已经通过 Template::parse 解析后使用;测试会确认它返回的名字既不会重复,也有稳定顺序。
调用图:外部调用 1 个(iter)。
Template::render174–209 ↗
fn render(&self, variables: I) -> Result<String, TemplateRenderError>
作用:把一个已经解析好的模板真正填成最终文本。它特别严格:缺值、多值、重复值都会报错,不会假装没事继续生成。
数据流:进去的是 Template 和一组变量名到变量值的配对 → 它先用 build_variable_map 整理并检查重复,再检查模板需要的值是否都有、是否多给了无用值,最后按片段顺序拼接文字和变量值 → 出来的是渲染后的字符串,或一个渲染错误。
调用关系:它是实际产出文本的核心步骤;像 render_memory_extensions_block、render_review_prompt 这类生成具体文本的流程会用到它。顶层便捷函数 render 也会在解析模板后走到这一步。
调用图:调用 1 个内部函数(build_variable_map);被 2 处调用(render_memory_extensions_block, render_review_prompt);外部调用 5 个(contains, contains_key, get, keys, new)。
render212–221 ↗
fn render(template: &str, variables: I) -> Result<String, TemplateError>
作用:这是最方便的一步到位入口:给它模板字符串和变量,它直接返回填好的文本。适合只渲染一次、不想自己先创建 Template 的场景。
数据流:进去的是原始模板和变量列表 → 它先调用 Template::parse 把模板解析好,再调用模板的渲染能力填值,并把两类错误统一成 TemplateError → 出来的是最终字符串或统一错误。
调用关系:测试里大量直接调用它来验证常见用法;调用方如果需要复用同一个模板多次,可以跳过它,改用 Template::parse 后反复调用 Template::render。
调用图:调用 1 个内部函数(parse);被 5 处调用(render_function_wraps_parse_errors, render_function_wraps_render_errors, render_replaces_placeholders_with_and_without_whitespace, render_supports_literal_delimiter_escapes, render_supports_multiline_templates_and_adjacent_placeholders)。
push_literal223–233 ↗
fn push_literal(segments: &mut Vec<Segment>, literal: &str)
作用:把一段普通文字放进模板片段列表里,并尽量和前一段普通文字合并。这样内部结构更干净,不会产生很多零碎空片段。
数据流:进去的是片段列表和一段普通文字 → 如果文字为空就什么也不做;如果前一个片段也是普通文字,就接到后面;否则新建一个普通文字片段 → 出来的是被更新后的片段列表。
调用关系:Template::parse 在扫描模板时反复调用它;它只处理普通文字,不理解占位符,属于解析过程里的小帮手。
调用图:被 1 处调用(parse);外部调用 1 个(Literal)。
parse_placeholder235–259 ↗
fn parse_placeholder(source: &str, start: usize) -> Result<(String, usize), TemplateParseError>
作用:专门读取一个 {{ ... }} 占位符里的名字。它会去掉名字两边的空白,并检查占位符是不是空的、没关上,或者里面又出现了 {{。
数据流:进去的是完整模板字符串和占位符起始位置 → 它从 {{ 后面继续往前找,直到找到对应的 }} 或发现错误 → 成功时出来占位符名字和下一个继续扫描的位置,失败时出来解析错误。
调用关系:Template::parse 一旦看到 {{ 就把细节交给它;它负责占位符内部的规则,Template::parse 负责整段模板的整体扫描。
调用图:被 1 处调用(parse)。
build_variable_map261–280 ↗
fn build_variable_map(
variables: I,
) -> Result<BTreeMap<String, String>, TemplateRenderError>
作用:把调用方给的一串变量整理成一张查找表,并检查同名变量是否给了两次。这样后面渲染时可以快速按名字取值。
数据流:进去的是变量名和值的列表 → 它逐个转成字符串并放进有序映射表;如果发现同一个名字已经存在,就立刻报重复值错误 → 成功时出来一张变量名到变量值的表。
调用关系:Template::render 在真正拼接文本前调用它;它解决“输入变量是否干净”这个问题,后续缺值和多值检查再基于这张表完成。
tests::render_replaces_placeholders_with_and_without_whitespace292–303 ↗
fn render_replaces_placeholders_with_and_without_whitespace()
作用:验证占位符两边有没有空格都能正常替换。它也检查同一个占位符出现多次时,每次都会被替换。
数据流:进去的是带有 {{ name }}、{{place}} 的模板和两组变量 → 调用顶层 render 渲染 → 断言出来的文本和预期完全一致。
调用关系:这是对最常见使用方式的测试;它通过 render 间接覆盖解析、变量检查和拼接流程。
调用图:调用 1 个内部函数(render);外部调用 1 个(assert_eq!)。
tests::parsed_templates_can_be_reused306–317 ↗
fn parsed_templates_can_be_reused()
作用:验证模板解析一次后可以反复使用不同变量渲染。这样适合多次生成同一种格式的文本。
数据流:进去的是一个模板字符串 → 先调用 Template::parse 得到模板对象,再用两组不同变量分别渲染 → 两次都应得到对应的完整句子。
调用关系:它展示了和顶层 render 不同的用法:先解析,再复用;这对应性能或结构上需要重复渲染的场景。
调用图:调用 1 个内部函数(parse);外部调用 1 个(assert_eq!)。
tests::placeholders_are_sorted_and_unique320–324 ↗
fn placeholders_are_sorted_and_unique()
作用:验证模板报告的占位符名字会去重并排序。即使 b 出现两次,结果也只保留一个。
数据流:进去的是含有重复占位符的模板 → 调用 Template::parse 后再调用 placeholders 收集名字 → 出来应是按顺序排列的 a、b。
调用关系:它直接检查 Template::placeholders 的承诺,确保调用方拿到的是稳定、干净的变量清单。
调用图:调用 1 个内部函数(parse);外部调用 1 个(assert_eq!)。
tests::render_supports_multiline_templates_and_adjacent_placeholders327–335 ↗
fn render_supports_multiline_templates_and_adjacent_placeholders()
作用:验证模板可以跨多行,也可以把两个占位符紧挨着写。比如 {{first}}{{second}} 应该直接拼成 AB。
数据流:进去的是包含换行和相邻占位符的模板,以及三组变量 → 调用 render → 出来应保留换行,并把相邻变量无缝拼接。
调用关系:它覆盖更真实的长文本场景,确保解析器不是只适合单行简单句子。
调用图:调用 1 个内部函数(render);外部调用 1 个(assert_eq!)。
tests::render_supports_literal_delimiter_escapes338–349 ↗
fn render_supports_literal_delimiter_escapes()
作用:验证如果想在最终文本里真的显示 {{ 或 }},可以用 {{{{ 和 }}}} 转义。转义就是“别把它当占位符,按原样输出”。
数据流:进去的是包含转义大括号和一个普通占位符的模板 → 调用 render → 出来应显示真正的 {{、}},并正常替换变量。
调用关系:它检查 Template::parse 对特殊写法的处理,避免用户想输出大括号时被误判成模板语法。
调用图:调用 1 个内部函数(render);外部调用 1 个(assert_eq!)。
tests::parse_errors_when_placeholder_is_empty352–356 ↗
fn parse_errors_when_placeholder_is_empty()
作用:验证空占位符会被拒绝。像 {{ }} 没有变量名,应该直接报错。
数据流:进去的是含空占位符的模板 → 调用 Template::parse → 出来应是 EmptyPlaceholder 错误,并标出起始位置。
调用关系:它检查 parse_placeholder 的防呆规则;这个错误会阻止后续渲染拿不到变量名。
调用图:调用 1 个内部函数(parse);外部调用 1 个(assert_eq!)。
tests::parse_errors_when_placeholder_is_unterminated359–366 ↗
fn parse_errors_when_placeholder_is_unterminated()
作用:验证占位符少了结尾 }} 时会报错。这样不会把半截模板当正常文本悄悄放过去。
数据流:进去的是 {{ name 没有关闭的模板 → 调用 Template::parse → 出来应是 UnterminatedPlaceholder 错误。
调用关系:它覆盖 parse_placeholder 找不到结尾的情况,保证模板语法错误能在解析阶段暴露。
调用图:调用 1 个内部函数(parse);外部调用 1 个(assert_eq!)。
tests::parse_errors_when_placeholder_is_nested369–373 ↗
fn parse_errors_when_placeholder_is_nested()
作用:验证占位符里面不能再写一个 {{。这个工具不支持嵌套模板,所以要明确拒绝。
数据流:进去的是含有嵌套 {{ 的模板 → 调用 Template::parse → 出来应是 NestedPlaceholder 错误。
调用关系:它确保 parse_placeholder 遇到复杂、可能歧义的写法时不会猜测,而是立刻报清楚的错。
调用图:调用 1 个内部函数(parse);外部调用 1 个(assert_eq!)。
tests::parse_errors_when_closing_delimiter_is_unmatched376–383 ↗
fn parse_errors_when_closing_delimiter_is_unmatched()
作用:验证单独出现的 }} 会被认为是错误。因为它没有对应的开头 {{。
数据流:进去的是含孤立 }} 的模板 → 调用 Template::parse → 出来应是 UnmatchedClosingDelimiter 错误,并带位置。
调用关系:它检查 Template::parse 的整体扫描逻辑,避免模板里多出的关闭符号被当成普通文字漏过去。
调用图:调用 1 个内部函数(parse);外部调用 1 个(assert_eq!)。
tests::render_errors_when_placeholder_is_missing386–395 ↗
fn render_errors_when_placeholder_is_missing()
作用:验证模板需要某个变量但调用方没给时会报错。比如模板要 name,变量列表为空,就不能生成文本。
数据流:进去的是需要 name 的模板和空变量列表 → 先解析模板,再调用渲染 → 出来应是 MissingValue 错误。
调用关系:它检查 Template::render 在拼接前的缺值检查,保证生成内容不会留下未填的洞。
调用图:调用 1 个内部函数(parse);外部调用 1 个(assert_eq!)。
tests::render_errors_when_extra_value_is_provided398–407 ↗
fn render_errors_when_extra_value_is_provided()
作用:验证调用方多给了模板没用到的变量时会报错。这个严格规则能帮助发现变量名写错或配置写错。
数据流:进去的是只需要 name 的模板,以及 name 和 unused 两个变量 → 渲染前检查变量清单 → 出来应是 ExtraValue 错误。
调用关系:它检查 Template::render 的多值检查;这能防止无用输入被悄悄忽略,造成隐藏问题。
调用图:调用 1 个内部函数(parse);外部调用 1 个(assert_eq!)。
tests::render_errors_when_duplicate_value_is_provided410–419 ↗
fn render_errors_when_duplicate_value_is_provided()
作用:验证同一个变量名给两次会报错。否则系统不知道该用前一个值还是后一个值。
数据流:进去的是需要 name 的模板,以及两个同名 name 变量 → build_variable_map 整理变量时发现重复 → 出来应是 DuplicateValue 错误。
调用关系:它重点覆盖 build_variable_map;这个检查发生在真正渲染之前。
调用图:调用 1 个内部函数(parse);外部调用 1 个(assert_eq!)。
tests::render_function_wraps_parse_errors422–429 ↗
fn render_function_wraps_parse_errors()
作用:验证顶层 render 遇到模板语法错误时,会把它包装成统一的 TemplateError::Parse。调用方因此只处理一种总错误类型就够了。
数据流:进去的是含孤立 }} 的坏模板和一组变量 → 调用顶层 render,解析阶段失败 → 出来应是包着解析错误的 TemplateError。
调用关系:它检查便捷入口 render 的错误包装行为,确保解析失败不会以散乱的错误类型冒出去。
调用图:调用 1 个内部函数(render);外部调用 1 个(assert_eq!)。
tests::render_function_wraps_render_errors432–441 ↗
fn render_function_wraps_render_errors()
作用:验证顶层 render 遇到填值错误时,会包装成统一的 TemplateError::Render。这样解析错和渲染错都能从一个入口返回。
数据流:进去的是需要 name 的模板,但只给了 extra 变量 → 模板解析成功,渲染检查发现缺少 name → 出来应是包着渲染错误的 TemplateError。
调用关系:它和解析错误包装测试配成一组,确认顶层 render 对两类失败都有一致的外部表现。
调用图:调用 1 个内部函数(render);外部调用 1 个(assert_eq!)。