tynavi 0.1.3

An immutable selector library for navigating, filtering, and backtracking through deeply nested Rust data structures.
Documentation

tynavi

tynavi(全称 Type Navigator)是一个面向深层数据导航场景的 Rust Selector 模式库。

它从 onebot-apiselector 设计中演化而来,但在这里被拆成了更通用的独立实现:支持不可变链式调用、父节点回溯、标准库容器扩展,以及可选的 derive / attribute 宏自动生成能力。

当前状态

  • 当前 crate 版本:0.1.2
  • Rust edition:2024
  • 最低工具链要求:>= 1.85
  • 主 crate 默认启用 full feature
  • default-features = false 时可退回仅标准库核心能力

需要特别注意的是:

  • 核心 Selector API 本身不依赖第三方运行时库
  • 但仓库当前已经包含可选生态扩展依赖与 tynavi-macros 过程宏子 crate
  • 因为默认 feature 是 full,直接引入 tynavi 时并不是“默认零依赖扩展面”

如果你想只使用最小核心能力,建议这样声明:

[dependencies]
tynavi = { version = "0.1.2", default-features = false }

核心设计

核心类型:

Selector<'a, Current, Parent>
  • Current:当前游标指向的类型
  • Parent:父节点快照类型
  • cursor: Option<&'a Current>:当前是否匹配

onebot-api 早期可变 Selector 不同,tynavi 采用不可变快照语义:

  • 过滤方法返回新的 Self
  • 路由方法返回 Selector<'a, Child, Self>
  • 父节点快照可通过 backtrack() / up() 安全返回

这使它很适合表达一类固定问题:

  • 从嵌套结构中持续下钻
  • 在链路中逐步过滤
  • 某一步失败后自动进入未匹配态
  • 需要在深入检查后回到上层上下文继续处理

快速开始

use tynavi::{
	selector::Selector,
	traits::AsSelector,
};

struct Profile {
	city: String,
	age: u32,
}

struct User {
	name: String,
	profile: Profile,
}

impl<'a> AsSelector<'a, User, ()> for User {
	fn as_selector(&'a self) -> Selector<'a, User, ()> {
		Selector::new(self)
	}
}

let user = User {
	name: "alice".to_owned(),
	profile: Profile {
		city: "Hangzhou".to_owned(),
		age: 20,
	},
};

let city = user
	.as_selector()
	.route_to(|u, _| Some(&u.profile))
	.route_to(|p, _| Some(&p.city))
	.starts_with("Hang")
	.extract(|city, _| city.to_owned());

assert_eq!(city, Some("Hangzhou".to_owned()));

let adult = user
	.as_selector()
	.route_to(|u, _| Some(&u.profile))
	.route_to(|p, _| Some(&p.age))
	.ge(&18)
	.is_matched();

assert!(adult);

父节点回溯

let profile = user.as_selector().route_to(|u, _| Some(&u.profile));

let city = profile
	.route_to(|p, _| Some(&p.city))
	.contains("zhou");

assert!(city.is_matched());
assert!(city.backtrack().select().is_some());
assert!(city.up().select().is_some());
  • backtrack():直接返回父节点快照
  • up():返回父节点,并在当前未匹配时把未匹配状态向上传递

已实现的核心 API

通用构造 / 路由 / 处理

  • new
  • with
  • same_parent
  • route_to
  • replace
  • map
  • select
  • extract / cond_extract
  • extract_async / cond_extract_async
  • inspect / inspect_cursor
  • filter / cond_filter
  • filter_async / cond_filter_async
  • require_matched
  • parent
  • backtrack
  • up
  • or_a_parent_a / or_a_parent_b / or_b_parent_a / or_b_parent_b

标准库类型扩展

当前已内置以下常见类型支持:

  • 数值类型:i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64
  • 字符串类型:&strstrString
  • 容器与指针:Option<T>Result<T, E>HashMap<K, V>&[T]Box<T>Rc<T>Arc<T>

其中已实现的典型能力包括:

  • 数值比较:eqnot_eqgtltgele 及其 cond_*
  • 字符串过滤:starts_withends_withcontainscontains_charempty
  • 切片导航:firstlastindexoffindanyallnone
  • HashMap 导航:keyoffind_keyfindcontains_keycontains_value
  • Option 路由:flatten
  • Result 路由:okerr
  • 智能指针解引用:as_ref

生态扩展

当前通过 feature 提供以下扩展:

  • http
  • axum
  • tungstenite
  • serde_json
  • reqwest
  • derive

full 会一次性启用:

["derive", "serde_json", "tungstenite", "http", "axum", "reqwest"]

如果只想启用某一类扩展,可以关闭默认 feature 再手动选择:

[dependencies]
tynavi = { version = "0.1.2", default-features = false, features = ["reqwest"] }

reqwest 示例

use http::Response as HttpResponse;
use reqwest::{Method, Request, Response, ResponseBuilderExt, Url};
use tynavi::traits::AsSelector;

let mut req = Request::new(
	Method::POST,
	Url::parse("https://api.example.com:8443/v1/users?active=true").unwrap(),
);
req.headers_mut().insert("x-token", "secret".parse().unwrap());
*req.body_mut() = Some("payload".into());

assert!(
	req
		.as_selector()
		.url()
		.path()
		.starts_with("/v1/")
		.is_matched()
);

assert!(req.as_selector().body().starts_with(b"pay").is_matched());

let res = Response::from(
	HttpResponse::builder()
		.status(201)
		.url(Url::parse("https://api.example.com/v1/users/42").unwrap())
		.body("hello")
		.unwrap(),
);

assert!(res.as_selector().is_success().is_matched());
assert!(res.as_selector().content_length_eq(5).is_matched());

serde_json 示例

use serde_json::json;
use tynavi::traits::AsSelector;

let value = json!({
	"users": [
		{ "name": "alice" }
	]
});

assert!(
	value
		.as_selector()
		.keyof("users")
		.first()
		.keyof("name")
		.as_str()
		.contains("alice")
		.is_matched()
);

宏支持

仓库当前已经提供 tynavi-macros 子 crate,并通过主 crate 的 derive feature 暴露两类宏:

  • #[derive(Selector)]
  • #[selector]

它们适合把结构体 / 枚举上的手写导航方法批量生成出来,尤其适用于事件模型或较大的领域对象。

[dependencies]
tynavi = { version = "0.1.2", default-features = false, features = ["derive"] }

使用示例:

use tynavi::{Selector, selector};
use tynavi::traits::AsSelector;

#[derive(Selector)]
struct Message {
	text: String,
}

#[selector]
enum Event {
	Message(Message),
}

let event = Event::Message(Message {
	text: "hello".to_string(),
});

assert!(event
	.as_selector()
	.route_message()
	.route_text()
	.contains("hello")
	.is_matched());

更完整的宏说明见 macros/docs/selector.md

与 onebot-api Selector 的差异

特性 onebot-api 旧版 selector tynavi
类型签名 Selector<'a, T> Selector<'a, Current, Parent>
可变性 &mut self 风格 不可变快照,过滤返回 Self
父节点追踪 有,支持 backtrack() / up()
生成方式 主要依赖宏生成 可手写扩展,也支持 derive / selector
适用范围 onebot-api 事件模型 任意 Rust 类型与若干生态对象

构建与测试

cargo check
cargo build
cargo clippy
cargo fmt
cargo test

项目使用 .rustfmt.toml,采用硬制表符格式。