#toml #operator #pointers #json-path #node #value #overload

toml_ops

根据json路径语法实现TOML指针,类型为Option<&toml::Value>。重载/作为路径操作符以指向TOML树中的节点,以及一些其他有意义的操作符重载

1个不稳定版本

0.1.0 2023年3月13日

#5#overload

MIT/Apache

43KB
767

Toml路径和指针

概述

根据json路径语法实现TOML指针,类型为Option<&toml::Value>,依赖于crate toml。重载/作为路径操作符以指向TOML树中的节点,以及一些其他有意义的操作符重载。例如,管道操作符|用于从标量叶节点获取原始值,推入操作符<<用于覆盖标量节点或将新项目推入数组或表,以及推入赋值操作符<<=用于无条件地重新分配给TOML节点。而/或操作符<<可能会使指针失效,我们可以使用!操作符或is_none()方法来测试这种失败情况。

示例

use toml_ops::PathOperator;
let tv = r#"
[host]
ip="127.0.0.1"
port=8080
proto=["tcp", "udp"]
"#;
let mut v: toml::Value = tv.parse().unwrap(); 

let port = v.path() / "host" / "port" | 0;
assert_eq!(port, 8080);

let node = v.path_mut() / "host" / "port" << 8989;
let port = node | 0;
assert_eq!(port, 8989);

let proto = v.path() / "host" / "proto" / 0 | "";
assert_eq!(proto, "tcp");

let host = v.path_mut() / "host";
let host = host << ("newkey", "newval") << ("morekey", 1234);
assert_eq!(host.is_none(), false);
assert_eq!(!host, false);

let mut proto = v.path_mut() / "host" / "proto";
proto = proto << ("json", ) << ["protobuf"];
assert_eq!(proto.as_ref().unwrap().as_array().unwrap().len(), 4);

proto <<= "default";
assert_eq!(proto.as_ref().unwrap().is_str(), true);
let proto = v.path() / "host" / "proto" | "";
assert_eq!(proto, "default");

let invalid = v.path() / "host" / "no-key";
assert_eq!(!invalid, true);
assert_eq!(invalid.is_none(), true);

路径语法

类似于Unix文件系统中的文件路径,每个组件通过斜杠/分隔,特别是对于数组索引也使用/而不是[]。因此,您不应在表中使用数字键以避免混淆。

请参阅详细或标准json路径语法,因为TOML在数据模型上与JSON大致相同。

Toml操作符重载指南

操作符触发

我们不能在 toml::Value 的外部直接重载操作符形式。因此,我在 toml_ops 包中定义了一个名为 PathOperator 的 trait,并为 toml::Value 实现了它。然后,您可以从 toml::Value 调用以下方法来创建 toml 指针

  • path():指向自身的 toml 值。
  • pathto(subpath: &str):指向自身 toml 值的某个子节点。
  • path_mut()path() 的可变版本。
  • pathto_mut(subpath: &str)pathto() 的可变版本。

指针只是 Option<&toml::Value> 或可变版本的 Option<&mut toml::Value> 的结构包装。但通常不需要关心这一点,只需使用重载运算符。

可变版本指针不能实现 Copy trait,请注意,许多后续运算符会消耗(移动)它。

路径操作符 /

它是 toml 路径指针的核心操作符,重载 /(除法)作为路径操作符的目的非常直接。

例如,以下语句大致等价

let node = toml_value.path() / "path" / "to" / "node";
let node = toml_value.pathto("path/to/node");
let node = toml_value.path() / "path/to/node";
let node = toml_value.path() / "path.to.node";

请注意,后两种使用单个字符串表示的长路径在性能上略低效,因为它涉及到拆分和解析路径语法,并且在表节点中有数字键时可能会造成混淆(现在只有可变版本存在细微的错误)。

最好像这样指向数组项

let node = toml_value.path() / "sub-array" / 0 / "sub-key";
// not so good to use:
// let node = toml_value.path() / "sub-array/0/sub-key";

虽然不能重载操作符 .,但点 . 也可以在长单路径形式中用作路径分隔符。但不要混用斜杠和点来混淆自己。例如,path/../to/node 并不指向父节点,就像在文件系统中一样,它只是等于 path/to/nodepath.to.node,因为路径操作符忽略了连续的分隔符。

管道操作符 |

用户使用管道操作符 | 从 toml 节点获取原始标量值。您可以将 | 视为 / 的垂直形式,并读作 "或默认",意味着如果节点无效或类型错误,则返回 | 右侧的默认值。

let str_val: &str = toml_value().path() / "path" / "to" / "node" | "";
let int_val: i64 = toml_value().path() / "path" / "to" / "node" | 0;
let float_val: f64 = toml_value().path() / "path" / "to" / "node" | 0.0;
let bool_val: bool = toml_value().path() / "path" / "to" / "node" | false;

类型符号,如: i64并非必要,因为它可以从|的右侧推导出来。

显然,|将结束/运算符链,因为它返回一个原始类型的值。

Put和Push运算符<<

它在可变指针版本中使用,用于修改其所引用的节点。

Put运算符<<适用于叶节点。因为TOML叶节点只能存储一个标量值,所以Put运算符会覆盖它所持有的值,前提是值类型与原始类型匹配。

let node = toml_vaule.path_mut() / "path" / "to" / "int-node" << 314;
let node = toml_vaule.path_mut() / "path" / "to" / "float-node" << 3.14;
let _ = toml_vaule.path_mut() / "path" / "to" / "string-node" << "PI";
let _ = toml_vaule.path_mut() / "path" / "to" / "bool-node" << true;

Put运算符消耗左指针并返回一个新指针,如果节点不存在或类型不匹配,则可能返回None指针。如果不希望使用返回的指针,可以使用let _来抑制警告。

Push运算符也重载了<<,它可以将新项目推送到数组节点或表节点。

let node = toml_vaule.path_mut() / "table" << ("int", 314) << ("float", 3.14);
let _ = toml_vaule.path_mut() / "array" << (314,) << [3.14];

由于<<也返回一个指针,因此Push运算符可以链式调用以向数组或表追加更多项目,前提是原始引用的数组或表是有效的。您可以推送任何可以转换为toml::Value的值类型。

Push赋值运算符<<=

因为在Rust中不能重载赋值运算符=,所以我们选择<<=,它与之前的Put或Push运算符具有一些一致的含义。它会无条件地将节点重新赋值给任何可以转换为toml::Value的值。

let node = toml_vaule.path_mut() / "path" / "to" / "bool-node" << true;
node <<= "true";

请注意,<<=接收对自身的引用而不会返回新指针,并且不应该像<<那样链式调用。此外,以下两个语句在叶节点上具有相同的效果,如果<<成功

let node = node << val; // may fail when mistype
node <<= val; // always sucesse expect node already invalid pointer.

有效运算符!

重载非运算符!可以用来测试指针是否无效。在以下情况下将返回无效指针

  • 路径运算符/当指向不存在的节点时。
  • Put运算符<<当尝试放入错误类型的值时。

Deref运算符*也被重载,支持隐式地将指针用作Option<&toml::Value>。因此,is_none()方法的行为将与运算符!相同。

let node = toml_value.path() / "path" / "to" / "nil";
if (!node) { return; }
if (node.is_none()) { return; }

然而,在可变版本指针中,操作符 ! 会消耗指针,因此最好使用 is_none() 方法。

保存中间指针

您可以按需保存中间指针,原因如下

let root = toml_value.path();
let table = root / "path" / "to" / "table";
let val1 = table / "key1" | 0;
let val2 = table / "key2" | "";

尽管可变指针可能存在一些限制,但请遵循编译器的提示来修复任何问题。

路径操作符与嵌套结构

处理简单的 toml 时,将其反序列化为嵌套结构相当方便,然后可以使用链式点 . 操作符来访问深层字段。

rustletnode=toml_value.path.to.node;

在上面的例子中,假设 pathtonode 都是不同级别的某个结构的字段,可以映射到 toml 键。

然而,这种简单的方法在某些实践案例中也可能被轻易破坏

  • 当某些键(字段)在编译时未确定但在运行时更改,或者只是在其运行时更改其类型。
  • 当您想以严格的方式处理可选字段,并将许多字段包裹在 Option<> 中。

在这种情况下,再也不能保持最简单的 path.to.node 风格,而且比 "path"/"to"/"node" 方式更繁琐。

而在任何情况下,都可以以一致的方式使用路径操作符,根据样本数据结构编写业务代码可能更直接,json 路径语法在一定程度上是语言中立的。

这种灵活性的代价是编码中的错误和可能丢失 IDE 完成提示。

许可证

该项目受以下任一许可证的许可

任您选择。

依赖关系

~285–540KB
~12K SLoC