92个版本
0.10.10 | 2024年7月29日 |
---|---|
0.10.8 | 2024年5月3日 |
0.10.6 | 2024年3月25日 |
0.10.1 | 2023年12月15日 |
0.5.0 | 2020年7月30日 |
4 in #dfinity
53,779 monthly downloads
用于 195 个crate(157个直接使用)
255KB
6K SLoC
Candid
Candid是一种用于与运行在互联网计算机上的canister(也称为服务或actor)交互的接口描述语言(IDL)。
Candid crate是一个序列化和反序列化库,用于Candid。您可以在二进制和文本格式之间无缝转换Rust值和Candid。
用法
请参阅此处文档。
lib.rs
:
Candid
Candid是一种用于与运行在互联网计算机上的canister(也称为服务或actor)交互的接口描述语言(IDL)。
您可能会以三种常见方式在Rust中使用Candid。
- 作为有类型Rust数据结构。当您使用Rust编写canister或前端时,您希望有一种无缝的方式在Rust和Candid之间转换数据。
- 作为无类型Candid值。当您编写不熟悉Candid数据类型的互联网计算机通用工具时。
- 作为文本数据。当您从CLI获取数据或从文件读取时,您可以使用提供的解析器发送/接收消息。
Candid提供高效、灵活和安全的将数据在这三种表示之间转换的方式。
在原生Rust值上操作
我们使用构建者模式来编码/解码Candid消息,请参阅candid::ser::IDLBuilder
用于序列化和candid::de::IDLDeserialize
用于反序列化。
// Serialize 10 numbers to Candid binary format
let mut ser = candid::ser::IDLBuilder::new();
for i in 0..10 {
ser.arg(&i)?;
}
let bytes: Vec<u8> = ser.serialize_to_vec()?;
// Deserialize Candid message and verify the values match
let mut de = candid::de::IDLDeserialize::new(&bytes)?;
let mut i = 0;
while !de.is_done() {
let x = de.get_value::<i32>()?;
assert_eq!(x, i);
i += 1;
}
de.done()?;
Candid提供了以类型安全的方式编码/解码Candid消息的函数。
use candid::{encode_args, decode_args};
// Serialize two values [(42, "text")] and (42u32, "text")
let bytes: Vec<u8> = encode_args((&[(42, "text")], &(42u32, "text")))?;
// Deserialize the first value as type Vec<(i32, &str)>,
// and the second value as type (u32, String)
let (a, b): (Vec<(i32, &str)>, (u32, String)) = decode_args(&bytes)?;
assert_eq!(a, [(42, "text")]);
assert_eq!(b, (42u32, "text".to_string()));
我们还提供了宏,方便地编码/解码Candid消息。
use candid::{Encode, Decode};
// Serialize two values [(42, "text")] and (42u32, "text")
let bytes: Vec<u8> = Encode!(&[(42, "text")], &(42u32, "text"))?;
// Deserialize the first value as type Vec<(i32, &str)>,
// and the second value as type (u32, String)
let (a, b) = Decode!(&bytes, Vec<(i32, &str)>, (u32, String))?;
assert_eq!(a, [(42, "text")]);
assert_eq!(b, (42u32, "text".to_string()));
Encode!
宏接受一系列Rust值,并返回一个二进制格式Vec<u8>
,该格式可以通过网络发送。而Decode!
宏接受一个二进制消息和一系列Rust类型,您希望将其解码为,并返回给定类型的Rust值元组。
请注意,一个固定的Candid消息可能被解码为多个Rust类型。例如,我们可以将Candid text
类型解码为Rust中的String
或&str
。
对用户定义的结构体/枚举进行操作
我们使用特性CandidType
进行序列化。反序列化需要CandidType
和Serde的Deserialize
特性。任何实现这两个特性的类型都可以用于序列化和反序列化。这包括Rust标准库中的内置类型,如Vec<T>
和Result<T>
,以及任何使用#[derive(CandidType, Deserialize)]
注解的结构体或枚举。
我们不使用Serde的Serialize
特性,因为Candid需要序列化类型及其值。这在Serialize
中难以实现,特别是对于枚举类型。除了序列化之外,CandidType
特性还将Rust类型转换为Candid定义的类型candid::types::Type
。
#[cfg(feature = "serde_bytes")]
use candid::{Encode, Decode, CandidType, Deserialize};
#[derive(CandidType, Deserialize)]
enum List {
#[serde(rename = "nil")]
Nil,
#[serde(with = "serde_bytes")]
Node(Vec<u8>),
Cons(i32, Box<List>),
}
let list = List::Cons(42, Box::new(List::Nil));
let bytes = Encode!(&list)?;
let res = Decode!(&bytes, List)?;
assert_eq!(res, list);
我们支持为每个字段使用serde的重命名属性,即#[serde(rename = "foo")]
和#[serde(rename(serialize = "foo", deserialize = "foo"))]
。这在Rust与Motoko canisters之间进行交互时非常有用,因为它们使用不同的命名约定来表示字段名称。
我们支持使用#[serde(with = "serde_bytes")]
来高效处理&[u8]
和Vec<u8>
。您还可以使用包装类型serde_bytes::ByteBuf
和serde_bytes::Bytes
。
请注意,如果您从Candid派生Deserialize
特质,则需要将serde
作为项目中的依赖项导入,因为派生的实现将引用serde
包。
大整数操作
为了支持大整数类型Candid::Int
和Candid::Nat
,我们使用num_bigint
包。我们提供了将i64
、u64
、&str
和&[u8]
转换为大整数的接口。您还可以使用i128
和u128
分别表示Candid的int
和nat
类型(如果数字超过128位,则解码将失败)。
#[cfg(feature = "bignum")]
use candid::{Int, Nat, Encode, Decode};
let x = "-10000000000000000000".parse::<Int>()?;
let bytes = Encode!(&Nat::from(1024u32), &x)?;
let (a, b) = Decode!(&bytes, Nat, Int)?;
let (c, d) = Decode!(&bytes, u128, i128)?;
assert_eq!(a + 1u8, 1025u32);
assert_eq!(b, Int::parse(b"-10000000000000000000")?);
引用类型操作
函数和服务的类型引用无法自动推导。我们提供了两个宏 define_function!
和 define_service!
以帮助定义引用类型。在宏中指定引用类型时,需要使用相应的 Rust 类型,而不是 Candid 类型。
#[cfg(feature = "bignum")]
use candid::{define_function, define_service, func, Encode, Decode, Principal};
let principal = Principal::from_text("aaaaa-aa").unwrap();
define_function!(pub CustomFunc : (u8, &str) -> (u128));
let func = CustomFunc::new(principal, "method_name".to_string());
assert_eq!(func, Decode!(&Encode!(&func)?, CustomFunc)?);
define_service!(MyService : {
"f": CustomFunc::ty();
"g": func!((candid::Int) -> (candid::Nat, CustomFunc) query)
});
let serv = MyService::new(principal);
assert_eq!(serv, Decode!(&Encode!(&serv)?, MyService)?);
在未类型化的 Candid 值上操作
任何有效的 Candid 值都可以在递归枚举表示 candid::parser::value::IDLValue
中进行操作。我们使用 ser.value_arg(v)
和 de.get_value::<IDLValue>()
对值进行编码和解码。可以混合使用 Rust 值和 IDLValue
。
#[cfg(feature = "value")]
use candid::types::value::IDLValue;
// Serialize Rust value Some(42u8) and IDLValue "hello"
let bytes = candid::ser::IDLBuilder::new()
.arg(&Some(42u8))?
.value_arg(&IDLValue::Text("hello".to_string()))?
.serialize_to_vec()?;
// Deserialize the first Rust value into IDLValue,
// and the second IDLValue into Rust value
let mut de = candid::de::IDLDeserialize::new(&bytes)?;
let x = de.get_value::<IDLValue>()?;
let y = de.get_value::<&str>()?;
de.done()?;
assert_eq!(x, IDLValue::Opt(Box::new(IDLValue::Nat8(42))));
assert_eq!(y, "hello");
将库构建为 JS/Wasm 包
借助 wasm-bindgen
和 wasm-pack
,我们可以将库构建为 Wasm 二进制文件并在浏览器中运行。这对于客户端 UI 和用 JavaScript 解析 did 文件非常有用。
使用以下 Cargo.toml
创建新项目。
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
candid = "0.9.0"
candid_parser = "0.1.0"
[profile.release]
lto = true
opt-level = 'z'
在 lib.rs
中公开方法
use candid::TypeEnv;
use candid_parser::{check_prog, IDLProg};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn did_to_js(prog: String) -> Option<String> {
let ast = prog.parse::<IDLProg>().ok()?;
let mut env = TypeEnv::new();
let actor = check_prog(&mut env, &ast).ok()?;
Some(candid_parser::bindings::javascript::compile(&env, &actor))
}
构建
cargo install wasm-pack
wasm-pack build --target bundler
wasm-opt --strip-debug -Oz pkg/didc_bg.wasm -o pkg/didc_bg.wasm
用法
const didc = import("pkg/didc");
didc.then((mod) => {
const service = "service : {}";
const js = mod.did_to_js(service);
});
依赖
~2–11MB
~98K SLoC