Write the comments in both languages, part three

This commit is contained in:
dev 2026-10-02 13:28:02 +08:00
parent e6065dd30e
commit 6bc8b1e355
19 changed files with 156 additions and 18 deletions

View File

@ -11,6 +11,15 @@
//! wraps that object and performs the typed reads and the validation, so a //! wraps that object and performs the typed reads and the validation, so a
//! handler never has to parse a value itself and never sees a value of the //! handler never has to parse a value itself and never sees a value of the
//! wrong type. //! wrong type.
//!
//!
//! 服务的第三项任务是为 `apsis-rs` 各内核提供一个在线试验台。注册表是「服务能
//! 运行什么」的唯一描述:每个工具声明其参数、类型、单位与默认值,前端据此渲染
//! 表单。因此新增一个工具意味着在此加一个条目、并在 [`handlers`] 中加一个处理
//! 函数,前端无需改动。
//!
//! 参数值以 JSON 对象形式从客户端到达。[`Params`] 包装该对象并完成带类型的读取
//! 与校验,因此处理函数从不必自行解析取值,也永远不会看到类型错误的取值。
pub mod handlers; pub mod handlers;
pub mod labels; pub mod labels;
@ -23,18 +32,30 @@ use crate::json::Value;
use crate::json_object; use crate::json_object;
/// The type of a parameter, selecting the form control the frontend draws. /// The type of a parameter, selecting the form control the frontend draws.
///
/// 参数的类型,决定前端绘制哪种表单控件。
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum ParamKind { pub enum ParamKind {
/// A floating point number, optionally bounded and with a unit. /// A floating point number, optionally bounded and with a unit.
///
/// 一个浮点数,可有上下界并带单位。
Number { Number {
/// Unit shown next to the input, or an empty string. /// Unit shown next to the input, or an empty string.
///
/// 显示在输入框旁的单位,或空字符串。
unit: &'static str, unit: &'static str,
/// Smallest accepted value, when bounded. /// Smallest accepted value, when bounded.
///
/// 可接受的最小值,当有下界时。
min: Option<f64>, min: Option<f64>,
/// Largest accepted value, when bounded. /// Largest accepted value, when bounded.
///
/// 可接受的最大值,当有上界时。
max: Option<f64>, max: Option<f64>,
}, },
/// An integer, optionally bounded. /// An integer, optionally bounded.
///
/// 一个整数,可有上下界。
Integer { Integer {
/// Smallest accepted value, when bounded. /// Smallest accepted value, when bounded.
min: Option<i64>, min: Option<i64>,
@ -42,54 +63,95 @@ pub enum ParamKind {
max: Option<i64>, max: Option<i64>,
}, },
/// A free text field of several lines, drawn as a text area. /// A free text field of several lines, drawn as a text area.
///
/// 一个多行的自由文本字段,绘制为文本区。
Multiline, Multiline,
/// A choice among a fixed set of values. /// A choice among a fixed set of values.
///
/// 在一组固定取值中作选择。
Select(&'static [SelectOption]), Select(&'static [SelectOption]),
} }
/// One option of a [`ParamKind::Select`] parameter. /// One option of a [`ParamKind::Select`] parameter.
///
/// [`ParamKind::Select`] 参数的一个选项。
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct SelectOption { pub struct SelectOption {
/// Value sent by the frontend. /// Value sent by the frontend.
///
/// 由前端发送的取值。
pub value: &'static str, pub value: &'static str,
/// Label shown to the user, in both languages. /// Label shown to the user, in both languages.
///
/// 向用户显示的标签,含两种语言。
pub label: Text, pub label: Text,
} }
/// The description of one tool parameter. /// The description of one tool parameter.
///
/// 一个工具参数的描述。
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ParamSpec { pub struct ParamSpec {
/// Machine name, used as the JSON member name. /// Machine name, used as the JSON member name.
///
/// 机器名,用作 JSON 成员名。
pub name: &'static str, pub name: &'static str,
/// Human readable label. /// Human readable label.
///
/// 供人阅读的标签。
pub label: Text, pub label: Text,
/// One sentence explanation shown as help text. /// One sentence explanation shown as help text.
///
/// 作为帮助文本显示的一句话说明。
pub help: Text, pub help: Text,
/// Type and constraints. /// Type and constraints.
///
/// 类型与约束。
pub kind: ParamKind, pub kind: ParamKind,
/// Default value, as it would be typed into the form. /// Default value, as it would be typed into the form.
///
/// 默认值,即会被键入表单的那个值。
pub default: Option<&'static str>, pub default: Option<&'static str>,
/// Whether the form must supply a value. /// Whether the form must supply a value.
///
/// 表单是否必须提供该值。
pub required: bool, pub required: bool,
} }
/// The description of one tool. /// The description of one tool.
///
/// 一个工具的描述。
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ToolSpec { pub struct ToolSpec {
/// Identifier used in the route. /// Identifier used in the route.
///
/// 用于路由的标识。
pub id: &'static str, pub id: &'static str,
/// Display name. /// Display name.
///
/// 显示名。
pub name: Text, pub name: Text,
/// One sentence summary. /// One sentence summary.
///
/// 一句话概要。
pub summary: Text, pub summary: Text,
/// Longer description, shown above the form. /// Longer description, shown above the form.
///
/// 较长的描述,显示在表单上方。
pub description: Text, pub description: Text,
/// Grouping shown in the tool list. /// Grouping shown in the tool list.
///
/// 在工具列表中显示的分组。
pub category: Text, pub category: Text,
/// Parameters, in form order. /// Parameters, in form order.
///
/// 各参数,按表单顺序。
pub params: &'static [ParamSpec], pub params: &'static [ParamSpec],
/// The `apsis-rs` items the tool calls, for cross linking to the /// The `apsis-rs` items the tool calls, for cross linking to the
/// interface documentation. /// interface documentation.
///
///
/// 该工具调用的 `apsis-rs` 条目,用于交叉链接到接口文档。
pub api_items: &'static [&'static str], pub api_items: &'static [&'static str],
} }
@ -97,12 +159,19 @@ pub struct ToolSpec {
/// ///
/// The table is a constant, so its parameter slices have a `'static` /// The table is a constant, so its parameter slices have a `'static`
/// lifetime and the registry holds no owned data. /// lifetime and the registry holds no owned data.
///
///
/// 所有工具的注册表。
///
/// 该表是常量,因此其参数切片具有 `'static` 生命周期,注册表不持有任何自有数据。
pub struct Registry { pub struct Registry {
by_id: HashMap<&'static str, usize>, by_id: HashMap<&'static str, usize>,
} }
impl Registry { impl Registry {
/// Builds the registry from the tool table. /// Builds the registry from the tool table.
///
/// 从工具表构建注册表。
pub fn new() -> Self { pub fn new() -> Self {
let by_id = TOOL_TABLE let by_id = TOOL_TABLE
.iter() .iter()
@ -113,16 +182,22 @@ impl Registry {
} }
/// Returns every tool. /// Returns every tool.
///
/// 返回每一个工具。
pub fn entries(&self) -> &'static [ToolSpec] { pub fn entries(&self) -> &'static [ToolSpec] {
TOOL_TABLE TOOL_TABLE
} }
/// Returns a tool by identifier. /// Returns a tool by identifier.
///
/// 按标识返回一个工具。
pub fn get(&self, id: &str) -> Option<&'static ToolSpec> { pub fn get(&self, id: &str) -> Option<&'static ToolSpec> {
self.by_id.get(id).map(|index| &TOOL_TABLE[*index]) self.by_id.get(id).map(|index| &TOOL_TABLE[*index])
} }
/// Returns the distinct categories in the order they first appear. /// Returns the distinct categories in the order they first appear.
///
/// 按其首次出现的顺序返回各不相同的类别。
pub fn categories(&self) -> Vec<Text> { pub fn categories(&self) -> Vec<Text> {
let mut categories: Vec<Text> = Vec::new(); let mut categories: Vec<Text> = Vec::new();
for tool in TOOL_TABLE { for tool in TOOL_TABLE {
@ -144,6 +219,12 @@ impl Default for Registry {
/// ///
/// It is a constant rather than a function so that the parameter slices are /// It is a constant rather than a function so that the parameter slices are
/// promoted to `'static` and the registry borrows it instead of owning it. /// promoted to `'static` and the registry borrows it instead of owning it.
///
///
/// 工具表。
///
/// 它是一个常量而不是函数,这样参数切片会被提升为 `'static`,注册表借用它而不是
/// 拥有它。
const TOOL_TABLE: &[ToolSpec] = &[ const TOOL_TABLE: &[ToolSpec] = &[
ToolSpec { ToolSpec {
id: "parse-tle", id: "parse-tle",
@ -807,6 +888,8 @@ const TOOL_TABLE: &[ToolSpec] = &[
]; ];
/// The gravity model options shared by the propagation tools. /// The gravity model options shared by the propagation tools.
///
/// 各传播工具共用的重力模型选项。
const GRAVITY_MODELS: [SelectOption; 2] = [ const GRAVITY_MODELS: [SelectOption; 2] = [
SelectOption { SelectOption {
value: "wgs72", value: "wgs72",
@ -822,6 +905,8 @@ const GRAVITY_MODELS: [SelectOption; 2] = [
]; ];
/// The time scale options. /// The time scale options.
///
/// 各时间尺度选项。
const TIME_SCALES: [SelectOption; 4] = [ const TIME_SCALES: [SelectOption; 4] = [
SelectOption { SelectOption {
value: "utc", value: "utc",
@ -842,6 +927,8 @@ const TIME_SCALES: [SelectOption; 4] = [
]; ];
/// The input form options of the time tool. /// The input form options of the time tool.
///
/// 时间工具的输入形式选项。
const TIME_INPUTS: [SelectOption; 2] = [ const TIME_INPUTS: [SelectOption; 2] = [
SelectOption { SelectOption {
value: "calendar", value: "calendar",
@ -854,6 +941,8 @@ const TIME_INPUTS: [SelectOption; 2] = [
]; ];
/// The rotation direction options of the frame tool. /// The rotation direction options of the frame tool.
///
/// 参考系工具的旋转方向选项。
const FRAME_DIRECTIONS: [SelectOption; 2] = [ const FRAME_DIRECTIONS: [SelectOption; 2] = [
SelectOption { SelectOption {
value: "eci-to-ecef", value: "eci-to-ecef",
@ -867,6 +956,8 @@ const FRAME_DIRECTIONS: [SelectOption; 2] = [
/// A sample element set, used as the default of the propagation tools. /// A sample element set, used as the default of the propagation tools.
/// ///
/// 一个样例元素集,用作各传播工具的默认值。
///
/// It is the International Space Station element set that the `apsis-rs` /// It is the International Space Station element set that the `apsis-rs`
/// crate documentation uses in its examples, so the values in the interface /// crate documentation uses in its examples, so the values in the interface
/// documentation and in the tool agree. /// documentation and in the tool agree.
@ -923,6 +1014,8 @@ pub struct Params<'a> {
impl<'a> Params<'a> { impl<'a> Params<'a> {
/// Wraps the values of a call to a tool. /// Wraps the values of a call to a tool.
///
/// 包装一次工具调用的各个值。
pub fn new(tool: &'a ToolSpec, values: &'a Value) -> Result<Self, ToolError> { pub fn new(tool: &'a ToolSpec, values: &'a Value) -> Result<Self, ToolError> {
if matches!(values, Value::Object(_)) { if matches!(values, Value::Object(_)) {
Ok(Self { tool, values }) Ok(Self { tool, values })
@ -939,6 +1032,12 @@ impl<'a> Params<'a> {
/// control sends text. An absent member falls back to the default /// control sends text. An absent member falls back to the default
/// declared by the parameter, and only a parameter with neither a value /// declared by the parameter, and only a parameter with neither a value
/// nor a default is reported as required. /// nor a default is reported as required.
///
///
/// 返回一个必需的数值。
///
/// 取值可以作为 JSON 数字或字符串到达,因为表单控件发送的是文本。缺失的成员
/// 回退到参数声明的默认值,只有既无取值又无默认值的参数才被报告为必需。
pub fn number(&self, name: &str) -> Result<f64, ToolError> { pub fn number(&self, name: &str) -> Result<f64, ToolError> {
let spec = self.spec(name)?; let spec = self.spec(name)?;
let text = match self.raw(name) { let text = match self.raw(name) {
@ -978,6 +1077,12 @@ impl<'a> Params<'a> {
/// ///
/// A value that is not a whole number is rejected rather than rounded, /// A value that is not a whole number is rejected rather than rounded,
/// because rounding would silently change a field such as a year. /// because rounding would silently change a field such as a year.
///
///
/// 返回一个必需的整数。
///
/// 不是整数的取值会被拒绝而不是四舍五入,因为取整会悄悄改变诸如年份这样的
/// 字段。
pub fn integer(&self, name: &str) -> Result<i64, ToolError> { pub fn integer(&self, name: &str) -> Result<i64, ToolError> {
let value = self.number(name)?; let value = self.number(name)?;
if value.fract() != 0.0 { if value.fract() != 0.0 {
@ -988,6 +1093,8 @@ impl<'a> Params<'a> {
} }
/// Returns a required string. /// Returns a required string.
///
/// 返回一个必需的字符串。
pub fn text(&self, name: &str) -> Result<String, ToolError> { pub fn text(&self, name: &str) -> Result<String, ToolError> {
let spec = self.spec(name)?; let spec = self.spec(name)?;
match self.raw(name) { match self.raw(name) {
@ -1003,6 +1110,9 @@ impl<'a> Params<'a> {
/// Returns a value that must be one of the options of a select /// Returns a value that must be one of the options of a select
/// parameter. /// parameter.
///
///
/// 返回一个必须是某个选择型参数选项的取值。
pub fn choice(&self, name: &str) -> Result<&'static str, ToolError> { pub fn choice(&self, name: &str) -> Result<&'static str, ToolError> {
let spec = self.spec(name)?; let spec = self.spec(name)?;
let accepted = match spec.kind { let accepted = match spec.kind {
@ -1027,11 +1137,15 @@ impl<'a> Params<'a> {
} }
/// Returns the raw value of a parameter. /// Returns the raw value of a parameter.
///
/// 返回某个参数的原始取值。
fn raw(&self, name: &str) -> Option<&Value> { fn raw(&self, name: &str) -> Option<&Value> {
self.values.get(name) self.values.get(name)
} }
/// Returns the specification of a parameter. /// Returns the specification of a parameter.
///
/// 返回某个参数的规格说明。
fn spec(&self, name: &str) -> Result<&'a ParamSpec, ToolError> { fn spec(&self, name: &str) -> Result<&'a ParamSpec, ToolError> {
self.tool self.tool
.params .params
@ -1041,6 +1155,8 @@ impl<'a> Params<'a> {
} }
/// Validates a number against the bounds of its specification. /// Validates a number against the bounds of its specification.
///
/// 依据规格说明的上下界校验一个数值。
fn check_number(&self, spec: &ParamSpec, value: f64) -> Result<f64, ToolError> { fn check_number(&self, spec: &ParamSpec, value: f64) -> Result<f64, ToolError> {
if !value.is_finite() { if !value.is_finite() {
return Err(ToolError::parameter(spec.name, "must be a finite number")); return Err(ToolError::parameter(spec.name, "must be a finite number"));
@ -1072,6 +1188,12 @@ impl<'a> Params<'a> {
/// The registry resolves the identifier, the parameters are validated, and /// The registry resolves the identifier, the parameters are validated, and
/// the handler runs. An unknown identifier is reported as a general failure /// the handler runs. An unknown identifier is reported as a general failure
/// so that the HTTP layer can answer with a not found status. /// so that the HTTP layer can answer with a not found status.
///
///
/// 按标识执行一个工具。
///
/// 注册表解析该标识,参数被校验,随后处理函数运行。未知标识被报告为一般性失败,
/// 使 HTTP 层能以「未找到」状态作答。
pub fn execute(registry: &Registry, id: &str, values: &Value) -> Result<Value, ToolError> { pub fn execute(registry: &Registry, id: &str, values: &Value) -> Result<Value, ToolError> {
let tool = registry let tool = registry
.get(id) .get(id)
@ -1084,6 +1206,11 @@ pub fn execute(registry: &Registry, id: &str, values: &Value) -> Result<Value, T
/// ///
/// Every label is encoded in both languages, so the frontend can switch the /// Every label is encoded in both languages, so the frontend can switch the
/// language of the interface without another request. /// language of the interface without another request.
///
///
/// 把注册表编码为供前端使用的 JSON。
///
/// 每个标签都以两种语言编码,因此前端无须再次请求即可切换界面语言。
pub fn registry_json(registry: &Registry) -> Value { pub fn registry_json(registry: &Registry) -> Value {
let tools: Vec<Value> = registry.entries().iter().map(tool_json).collect(); let tools: Vec<Value> = registry.entries().iter().map(tool_json).collect();
let categories: Vec<Value> = registry.categories().iter().map(Text::json).collect(); let categories: Vec<Value> = registry.categories().iter().map(Text::json).collect();
@ -1100,6 +1227,13 @@ pub fn registry_json(registry: &Registry) -> Value {
/// which is what the page shows. The HTML comes from the same renderer as /// which is what the page shows. The HTML comes from the same renderer as
/// the concept documentation, so a formula written between dollar signs is /// the concept documentation, so a formula written between dollar signs is
/// set the same way in both places. /// set the same way in both places.
///
///
/// 把一个工具编码为 JSON。
///
/// 描述与参数帮助都以 Markdown 书写,因此各发送两次:源码形式,供阅读 JSON 的人
/// 使用;以及 HTML 形式,即页面所显示的内容。HTML 来自与概念文档相同的渲染器,
/// 因此写在美元符号之间的公式在两处排版一致。
fn tool_json(tool: &ToolSpec) -> Value { fn tool_json(tool: &ToolSpec) -> Value {
let params: Vec<Value> = tool.params.iter().map(param_json).collect(); let params: Vec<Value> = tool.params.iter().map(param_json).collect();
json_object! { json_object! {
@ -1115,6 +1249,8 @@ fn tool_json(tool: &ToolSpec) -> Value {
} }
/// Renders both languages of a piece of Markdown to HTML. /// Renders both languages of a piece of Markdown to HTML.
///
/// 把一段 Markdown 的两种语言都渲染为 HTML。
fn rendered_text(text: &Text) -> Value { fn rendered_text(text: &Text) -> Value {
json_object! { json_object! {
"zh" => crate::docs::markdown::render(text.zh), "zh" => crate::docs::markdown::render(text.zh),
@ -1123,6 +1259,8 @@ fn rendered_text(text: &Text) -> Value {
} }
/// Encodes one parameter as JSON. /// Encodes one parameter as JSON.
///
/// 把一个参数编码为 JSON。
fn param_json(param: &ParamSpec) -> Value { fn param_json(param: &ParamSpec) -> Value {
let (kind, options) = match &param.kind { let (kind, options) = match &param.kind {
ParamKind::Number { unit, min, max } => ( ParamKind::Number { unit, min, max } => (

View File

@ -5,7 +5,7 @@
//! code, as it does in `apsis-rs`. An integration test is a separate crate and //! code, as it does in `apsis-rs`. An integration test is a separate crate and
//! reaches only what the library makes public, so the few internal items the //! reaches only what the library makes public, so the few internal items the
//! tests need are opened through `apsis_book::testkit`. //! tests need are opened through `apsis_book::testkit`.
//!
//! //!
//! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。 //! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。
//! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目 //! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目

View File

@ -5,7 +5,7 @@
//! code, as it does in `apsis-rs`. An integration test is a separate crate and //! code, as it does in `apsis-rs`. An integration test is a separate crate and
//! reaches only what the library makes public, so the few internal items the //! reaches only what the library makes public, so the few internal items the
//! tests need are opened through `apsis_book::testkit`. //! tests need are opened through `apsis_book::testkit`.
//!
//! //!
//! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。 //! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。
//! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目 //! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目

View File

@ -5,7 +5,7 @@
//! code, as it does in `apsis-rs`. An integration test is a separate crate and //! code, as it does in `apsis-rs`. An integration test is a separate crate and
//! reaches only what the library makes public, so the few internal items the //! reaches only what the library makes public, so the few internal items the
//! tests need are opened through `apsis_book::testkit`. //! tests need are opened through `apsis_book::testkit`.
//!
//! //!
//! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。 //! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。
//! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目 //! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目

View File

@ -5,7 +5,7 @@
//! code, as it does in `apsis-rs`. An integration test is a separate crate and //! code, as it does in `apsis-rs`. An integration test is a separate crate and
//! reaches only what the library makes public, so the few internal items the //! reaches only what the library makes public, so the few internal items the
//! tests need are opened through `apsis_book::testkit`. //! tests need are opened through `apsis_book::testkit`.
//!
//! //!
//! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。 //! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。
//! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目 //! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目

View File

@ -5,7 +5,7 @@
//! code, as it does in `apsis-rs`. An integration test is a separate crate and //! code, as it does in `apsis-rs`. An integration test is a separate crate and
//! reaches only what the library makes public, so the few internal items the //! reaches only what the library makes public, so the few internal items the
//! tests need are opened through `apsis_book::testkit`. //! tests need are opened through `apsis_book::testkit`.
//!
//! //!
//! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。 //! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。
//! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目 //! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目

View File

@ -5,7 +5,7 @@
//! code, as it does in `apsis-rs`. An integration test is a separate crate and //! code, as it does in `apsis-rs`. An integration test is a separate crate and
//! reaches only what the library makes public, so the few internal items the //! reaches only what the library makes public, so the few internal items the
//! tests need are opened through `apsis_book::testkit`. //! tests need are opened through `apsis_book::testkit`.
//!
//! //!
//! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。 //! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。
//! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目 //! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目

View File

@ -5,7 +5,7 @@
//! code, as it does in `apsis-rs`. An integration test is a separate crate and //! code, as it does in `apsis-rs`. An integration test is a separate crate and
//! reaches only what the library makes public, so the few internal items the //! reaches only what the library makes public, so the few internal items the
//! tests need are opened through `apsis_book::testkit`. //! tests need are opened through `apsis_book::testkit`.
//!
//! //!
//! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。 //! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。
//! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目 //! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目

View File

@ -5,7 +5,7 @@
//! code, as it does in `apsis-rs`. An integration test is a separate crate and //! code, as it does in `apsis-rs`. An integration test is a separate crate and
//! reaches only what the library makes public, so the few internal items the //! reaches only what the library makes public, so the few internal items the
//! tests need are opened through `apsis_book::testkit`. //! tests need are opened through `apsis_book::testkit`.
//!
//! //!
//! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。 //! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。
//! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目 //! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目

View File

@ -5,7 +5,7 @@
//! code, as it does in `apsis-rs`. An integration test is a separate crate and //! code, as it does in `apsis-rs`. An integration test is a separate crate and
//! reaches only what the library makes public, so the few internal items the //! reaches only what the library makes public, so the few internal items the
//! tests need are opened through `apsis_book::testkit`. //! tests need are opened through `apsis_book::testkit`.
//!
//! //!
//! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。 //! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。
//! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目 //! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目

View File

@ -5,7 +5,7 @@
//! code, as it does in `apsis-rs`. An integration test is a separate crate and //! code, as it does in `apsis-rs`. An integration test is a separate crate and
//! reaches only what the library makes public, so the few internal items the //! reaches only what the library makes public, so the few internal items the
//! tests need are opened through `apsis_book::testkit`. //! tests need are opened through `apsis_book::testkit`.
//!
//! //!
//! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。 //! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。
//! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目 //! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目

View File

@ -5,7 +5,7 @@
//! code, as it does in `apsis-rs`. An integration test is a separate crate and //! code, as it does in `apsis-rs`. An integration test is a separate crate and
//! reaches only what the library makes public, so the few internal items the //! reaches only what the library makes public, so the few internal items the
//! tests need are opened through `apsis_book::testkit`. //! tests need are opened through `apsis_book::testkit`.
//!
//! //!
//! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。 //! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。
//! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目 //! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目

View File

@ -5,7 +5,7 @@
//! code, as it does in `apsis-rs`. An integration test is a separate crate and //! code, as it does in `apsis-rs`. An integration test is a separate crate and
//! reaches only what the library makes public, so the few internal items the //! reaches only what the library makes public, so the few internal items the
//! tests need are opened through `apsis_book::testkit`. //! tests need are opened through `apsis_book::testkit`.
//!
//! //!
//! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。 //! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。
//! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目 //! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目

View File

@ -5,7 +5,7 @@
//! code, as it does in `apsis-rs`. An integration test is a separate crate and //! code, as it does in `apsis-rs`. An integration test is a separate crate and
//! reaches only what the library makes public, so the few internal items the //! reaches only what the library makes public, so the few internal items the
//! tests need are opened through `apsis_book::testkit`. //! tests need are opened through `apsis_book::testkit`.
//!
//! //!
//! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。 //! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。
//! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目 //! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目

View File

@ -5,7 +5,7 @@
//! code, as it does in `apsis-rs`. An integration test is a separate crate and //! code, as it does in `apsis-rs`. An integration test is a separate crate and
//! reaches only what the library makes public, so the few internal items the //! reaches only what the library makes public, so the few internal items the
//! tests need are opened through `apsis_book::testkit`. //! tests need are opened through `apsis_book::testkit`.
//!
//! //!
//! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。 //! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。
//! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目 //! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目

View File

@ -5,7 +5,7 @@
//! code, as it does in `apsis-rs`. An integration test is a separate crate and //! code, as it does in `apsis-rs`. An integration test is a separate crate and
//! reaches only what the library makes public, so the few internal items the //! reaches only what the library makes public, so the few internal items the
//! tests need are opened through `apsis_book::testkit`. //! tests need are opened through `apsis_book::testkit`.
//!
//! //!
//! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。 //! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。
//! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目 //! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目

View File

@ -5,7 +5,7 @@
//! code, as it does in `apsis-rs`. An integration test is a separate crate and //! code, as it does in `apsis-rs`. An integration test is a separate crate and
//! reaches only what the library makes public, so the few internal items the //! reaches only what the library makes public, so the few internal items the
//! tests need are opened through `apsis_book::testkit`. //! tests need are opened through `apsis_book::testkit`.
//!
//! //!
//! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。 //! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。
//! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目 //! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目

View File

@ -5,7 +5,7 @@
//! code, as it does in `apsis-rs`. An integration test is a separate crate and //! code, as it does in `apsis-rs`. An integration test is a separate crate and
//! reaches only what the library makes public, so the few internal items the //! reaches only what the library makes public, so the few internal items the
//! tests need are opened through `apsis_book::testkit`. //! tests need are opened through `apsis_book::testkit`.
//!
//! //!
//! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。 //! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。
//! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目 //! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目

View File

@ -5,7 +5,7 @@
//! code, as it does in `apsis-rs`. An integration test is a separate crate and //! code, as it does in `apsis-rs`. An integration test is a separate crate and
//! reaches only what the library makes public, so the few internal items the //! reaches only what the library makes public, so the few internal items the
//! tests need are opened through `apsis_book::testkit`. //! tests need are opened through `apsis_book::testkit`.
//!
//! //!
//! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。 //! 它们放在这里而不是模块内部,使 `src/` 不含测试代码,与 `apsis-rs` 一致。
//! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目 //! 集成测试是独立的 crate,只能触达库公开的部分,因此测试所需的那少数内部条目