#module #norgopolis #own #api #database-access #built #multi-threading

norgopolis-module

一个用于在Rust中创建自己的Norgopolis模块的库

8个版本 (5个稳定版)

2.0.3 2024年2月21日
2.0.2 2024年2月18日
2.0.1 2023年12月31日
1.0.0 2023年12月31日
0.1.0 2023年7月8日

#647数据库接口

Download history 21/week @ 2024-03-10 2/week @ 2024-03-17 41/week @ 2024-03-31 1/week @ 2024-04-07

每月208次下载

MIT 许可证

20KB
146

创建Norgopolis模块的库

有关Norgopolis的信息,请参阅 https://github.com/nvim-neorg/norgopolis

此库公开了一个API,用于创建和维护与Norgopolis路由器的连接。Norgopolis模块提供特定的功能集,例如多线程解析、数据库访问等。Neorg团队创建的所有默认模块都是基于这个库构建的。

[!注意] 这是一个面向开发者的库。如果您是普通用户,可以自由查看主线的 Neorg 仓库或 Norgopolis本身

设置

cargo add norgopolis-module

模块是异步应用程序,通过stdin/stdout与Norgopolis通信。通过stdin/stdout传输的格式是gRPC + MessagePack。因此,强烈建议将tokiotokio-stream添加到您的依赖中。

通过gRPC传输的数据必须是可序列化的。请确保也将serde添加到您的依赖列表中!

规划

创建模块的第一阶段是决定

  • 模块将提供什么类型的功能
  • 它将接收什么样的输入以及它将返回什么样的数据

输入/输出数据在传输层不受强制 - 也就是说,应用程序不知道你期望什么样的输入数据以及你将返回什么样的数据。这是动态gRPC + mpack通信的天然技术细节。因此,建议在您的存储库中的某个地方编写一个技术文档来描述您的API,以便其他人可以看到。

使用

一般设置

首先,为您的模块创建一个结构体。命名它 whatever you'd like

use norgopolis_module::{
    invoker_service::Service, module_communication::MessagePack, Code, Module, Status,
};

#[derive(Default)]
struct MyModule {
    // add any data or state you might need to maintain here...
}

首先,为您自己的结构体实现 norgopolis_module::invoker_service::Service 特性。这会强制您实现一个 call 函数,该函数将在有人将消息路由到您的模块时被调用。由于 Rust 中异步特性尚未稳定,请在您的特性实现上使用 #[norgopolis_module::async_trait]

use tokio_stream::wrappers::UnboundedReceiverStream;

#[norgopolis_module::async_trait]
impl Service for MyModule {
    type Stream = UnboundedReceiverStream<Result<MessagePack, Status>>;

    async fn call(
        &self,
        function: String,
        args: Option<MessagePack>,
    ) -> Result<Self::Stream, Status> {
        todo!()
    }
}

Stream 类型定义了将通过 gRPC 返回的数据类型。我们建议将其设置为 UnboundedReceiverStream<Result<MessagePack, Status>>。这意味着在收到一个请求后,您的模块将能够返回无限数量的 MessagePack 响应,或者在出错的情况下返回状态码。

调用

call 函数在客户端将消息路由到您时被调用。该消息包含

  • 他们想要调用的函数
  • 他们想要提供给函数的可选参数集。

创建基本粘合剂

call 函数中,建议匹配所有可能的函数名称,如果该模块不支持则返回错误代码

match function.as_str() {
    "my-function" => todo!(),
    _ => Err(Status::new(Code::NotFound, "Requested function not found!")),
}

[!IMPORTANT] 总是返回某种状态码,而不是崩溃。崩溃将终止 Norgopolis 的连接,用户将不会收到任何错误或警告。

解码参数

如果您的函数需要任何数量的参数,那么现在就是解码它们的时候了。如果您的参数很复杂(例如,一个字典),那么建议创建一个指定用于它的结构体。请确保为它派生 serde::Serialize

#[derive(serde::Serialize)]
struct MyParameters {
    name: String,
}

然后,只需在您的参数上运行 decode

match function.as_str() {
    "my-function" => {
        let args: MyParameters = args
            .unwrap() // WARNING: Don't actually use unwrap() in your code :)
            .decode()
            .map_err(|err| Status::new(Code::InvalidArgument, err.to_string()))?;

        // TODO: Do something with the parameters...
    },
}

我们手动提供 args 的类型,这样 Rust 就知道要序列化为什么类型。之后,我们将任何可能的错误包装成一个状态码,并将其返回给客户端。

将数据发送回客户端

现在我们已经检查了所有输入数据,我们可以处理我们的数据并将其返回给客户端。我们这样做的方式是以数据流的形式。由于数据流,我们可以长时间返回数据段,而不是必须一次性返回所有数据。当我们返回数据段时,我们也以 Result<> 的形式返回它。这是因为数据段可能包含错误,但整个过程可以成功完成。当存在不可恢复的错误时,您应该在 call 函数中返回错误,但当内部逻辑的某个部分失败时,应该发送回错误数据包。

让我们通过一个示例来展示所有这些

match function.as_str() {
    "my-function" => {
        let args: MyParameters = args
            .unwrap() // WARNING: Don't actually use unwrap() in your code :)
            .decode()
            .map_err(|err| Status::new(Code::InvalidArgument, err.to_string()))?;

        let (tx, rx) = tokio::sync::mpsc::unbounded_channel();

        // We send back an Ok() packet to the client with an encoded message of our choice
        // (it can be anything that's serializable with serde!)
        tx.send(Ok(MessagePack::encode(format!("Hello, {}!", args.name)))).unwrap();

        Ok(UnboundedReceiverStream::new(rx))
    },
}

首先,我们通过 tokio 的 unbounded_channel() 创建发送者和接收者。这允许我们向客户端发送数据,并允许客户端从模块中读取数据。所有返回的消息都必须通过 MessagePack::encode 编码。

运行模块

现在我们已经设置了所有代码,创建一个异步主函数。在这里,我们将实例化我们的模块并将其启动

#[tokio::main]
async fn main() {
    Module::new().start(MyModule::default())
        .await
        .unwrap()
}

好了!您现在对模块如何与 Norgopolis 通信以及如何编写自己的 norgopolis 模块有了基本的了解。祝您编码愉快!

依赖项

~6–18MB
~201K (估计) SLoC(约额外代码行数)