#互联网计算机 #idl #dfinity #二进制格式

candid

Candid是一种用于与运行在互联网计算机上的canister(也称为服务或actor)交互的接口描述语言(IDL)

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

Download history 13593/week @ 2024-05-04 13648/week @ 2024-05-11 12337/week @ 2024-05-18 16219/week @ 2024-05-25 18006/week @ 2024-06-01 14312/week @ 2024-06-08 16227/week @ 2024-06-15 13422/week @ 2024-06-22 12480/week @ 2024-06-29 12873/week @ 2024-07-06 12814/week @ 2024-07-13 13511/week @ 2024-07-20 11794/week @ 2024-07-27 13832/week @ 2024-08-03 12448/week @ 2024-08-10 12945/week @ 2024-08-17

53,779 monthly downloads
用于 195 个crate(157个直接使用)

Apache-2.0

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::ByteBufserde_bytes::Bytes

请注意,如果您从Candid派生Deserialize特质,则需要将serde作为项目中的依赖项导入,因为派生的实现将引用serde包。

大整数操作

为了支持大整数类型Candid::IntCandid::Nat,我们使用num_bigint包。我们提供了将i64u64&str&[u8]转换为大整数的接口。您还可以使用i128u128分别表示Candid的intnat类型(如果数字超过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-bindgenwasm-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