26 个版本 (14 个重大更改)
0.15.0 | 2024年4月17日 |
---|---|
0.13.0 | 2022年12月17日 |
0.12.0 | 2022年6月19日 |
0.9.0 | 2022年2月20日 |
0.1.1 | 2020年3月29日 |
#13 in #array-index
66,165 每月下载量
用于 184 个crate(2 个直接使用)
120KB
2.5K SLoC
minicbor-derive
是 minicbor
的配套 crate,允许派生 minicbor::Encode
和 minicbor::Decode
特性。
文档
文档可在以下地址获取
许可证
本软件根据 Blue Oak Model License Version 1.0.0 许可。如果您有兴趣为此项目做出贡献,请首先阅读 CONTRIBUTING.md 文件。
lib.rs
:
过程宏,用于派生 minicbor 的 Encode
、Decode
和 CborLen
特性。
支持为 struct
和 enum
派生。编码针对前后兼容性进行了优化,整体方法受 Google 的 Protocol Buffers 影响。
目标是:理想情况下,类型的变化仍然允许不知道变化的旧软件解码更改后的类型值(向前兼容性),以及新软件解码由旧软件编码的类型值,这些类型值不包括对类型所做的更改(向后兼容性)。
为了实现这一目标,编码具有以下特征
-
编码不包含任何名称,即没有字段名称、类型名称或变体名称。相反,每个字段和每个构造函数都需要用(无符号)索引数字进行注释,例如
#[n(1)]
。 -
在解码过程中忽略未知字段。[^1]
-
可选类型在解码时如果其值不存在,则默认为
None
。 -
可选枚举在解码过程中遇到未知变体时默认为
None
。
项目 1 确保名称可以自由更改而无需考虑兼容性问题。项目 2 确保新字段不会影响旧软件。项目 3 确保新软件可以停止生成可选值。项目 4 确保枚举可以得到旧软件不知道的新变体。当我们提到“字段”时,指的是结构体和元组结构体的元素,以及枚举结构和枚举元组。此外,如果所有字段都是可选的,将单元变体转换为结构体或元组变体也是一种兼容的更改。
从上述内容可以明显看出,非可选字段需要永久存在,因此它们只有在经过仔细考虑后才能成为类型的一部分。
应强调,enum
本身不能以兼容的方式进行更改。未知变体会导致错误。只有在它们被声明为可选字段类型时,枚举的未知变体才会映射到 None
。换句话说,只有结构体可以作为前向和后向兼容方式中的顶级类型,枚举不能。
示例
use minicbor::{Encode, Decode};
#[derive(Encode, Decode)]
struct Point {
#[n(0)] x: f64,
#[n(1)] y: f64
}
#[derive(Encode, Decode)]
struct ConvexHull {
#[n(0)] left: Point,
#[n(1)] right: Point,
#[n(2)] points: Vec<Point>,
#[n(3)] state: Option<State>
}
#[derive(Encode, Decode)]
enum State {
#[n(0)] Start,
#[n(1)] Search { #[n(0)] info: u64 }
}
在这个示例中,以下更改在双向都是兼容的:
-
重命名每个标识符。
-
向
Point
、ConvexHull
、State::Start
或State::Search
添加可选字段。 -
如果
State
仅作为ConvexHull
的一部分进行解码,则可以向State
添加更多变体。直接解码State
会产生UnknownVariant
错误,因为这些新变体。
支持的属性
#[n(...)]
和#[cbor(n(...))]
#[b(...)]
和#[cbor(b(...))]
#[cbor(array)]
#[cbor(map)]
#[cbor(index_only)]
#[cbor(transparent)]
#[cbor(skip)]
#[cbor(tag(...))]
#[cbor(decode_with)]
#[cbor(encode_with)]
#[cbor(with)]
#[cbor(nil)]
#[cbor(has_nil)]
#[cbor(is_nil)]
#[cbor(decode_bound)]
#[cbor(encode_bound)]
#[cbor(bound)]
#[cbor(context_bound)]
#[cbor(cbor_len)]
#[n(...)]
和 #[b(...)]
(或 #[cbor(n(...))]
和 #[cbor(b(...))]
)
每个字段和变体都需要用索引数字进行注释,该数字将替代名称使用。对于编码,选择哪一个没有区别。对于解码,b
表示值从解码输入中借用,而 n
生成非借用值(但请参见下文隐式借用部分)。这意味着如果一个类型被注释为 #[b(...)]
,则其生命周期将限制在输入生命周期('bytes
)。此外,如果类型是 Cow<'_, str>
,Cow<'_, minicbor::bytes::ByteSlice>
或 Cow<'_, [u8]>
,生成的代码将解码 str
、ByteSlice
或 [u8]
并构造一个 Cow::Borrowed
变体,这与 Decode
和 DecodeBytes
的常规 Cow
实现不同,后者生成所有权的值。
#[cbor(array)]
使用 CBOR 数组对注释的 struct、enum 或 enum 变体进行编码。当与 enum 一起使用时,它适用于其所有变体,但可以按变体覆盖。有关详细信息,请参阅CBOR 编码部分。
如果没有指定 #[cbor(array)]
或 #[cbor(map)]
,则默认使用 #[cbor(array)]
。
#[cbor(map)]
使用 CBOR 映射来编码注解的结构体、枚举或枚举变体。当与枚举一起使用时,它应用于所有变体,但可以按每个变体进行覆盖。有关详细信息,请参阅CBOR 编码部分。
如果没有指定 #[cbor(array)]
或 #[cbor(map)]
,则默认使用 #[cbor(array)]
。
#[cbor(index_only)]
不包含字段的枚举可能具有此属性。这会将编码更改为仅编码变体索引(有关详细信息,请参阅CBOR 编码部分)。
#[cbor(transparent)]
此属性可以附加到只有一个字段的结构体(即 newtypes)。如果存在,生成的 Encode
和 Decode
实现将仅将相应的 encode
和 decode
调用转发到内部类型,即结果 CBOR 表示将与内部类型相同。
#[cbor(skip)]
此属性可以附加到结构体和枚举的字段,以防止这些字段被编码。字段类型必须实现 Default
,并在解码时使用 Default::default()
初始化字段。
#[cbor(tag(...))]
此属性可以附加到结构体、枚举及其字段。其参数是一个十进制无符号整数,它被编码为值的 CBOR 标签。解码也会尝试读取标签,否则失败。
#[cbor(decode_with= "<path>")]
当应用于类型 T
的字段时,由 <path>
表示的函数将用于解码 T
。该函数需要与以下类型等效
use minicbor::decode::{Decoder, Error};
fn decode<'b, Ctx, T: 'b>(d: &mut Decoder<'b>, ctx: &mut Ctx) -> Result<T, Error> {
todo!()
}
请注意,如果解码函数在其上下文参数中是通用的,则 derive 宏使用类型变量名称 Ctx
。
#[cbor(encode_with= "<path>")]
当应用于类型 T
的字段时,由 <path>
表示的函数将用于编码 T
。该函数需要与以下类型等效
use minicbor::encode::{Encoder, Error, Write};
fn encode<Ctx, T, W: Write>(v: &T, e: &mut Encoder<W>, ctx: &mut Ctx) -> Result<(), Error<W::Error>> {
todo!()
}
请注意,如果编码函数在其上下文参数中是通用的,则 derive 宏使用类型变量名称 Ctx
。
#[cbor(with= "<path>")]
结合 #[cbor(decode_with = "...")]
和 #[cbor(encode_with = "...")]
。在这里,<path>
表示一个包含名为 encode
和 decode
的函数的模块,这些函数满足 encode_with
和 decode_with
中提到的相应类型签名。如果也推导出 CborLen
,则假定该模块包含一个名为 cbor_len
的函数,其签名与以下链接中描述的相匹配:#[cbor(cbor_len = "...")]
。
#[cbor(nil= "<path>")]
仅在与 #[cbor(decode_with = "...")]
结合时有效。如果存在,<path>
表示创建类型 T
的 nil 类似值的函数。有关详细信息,请参阅 minicbor::Decode::nil
。该函数需要与以下类型等效
fn nil<T>() -> Option<T> {
todo!()
}
#[cbor(has_nil)]
仅在与 #[cbor(with = "...")]
结合时有效。如果存在,则该属性表示由 with
指示的模块还包含用于创建 nil 值和检查值是否为 nil 值的函数 nil
和 is_nil
。
#[cbor(is_nil= "<path>")]
仅在与#[cbor(encode_with = "...")]
结合时有效。如果存在,<path>
表示一个函数,用于检查类型为T
的值是否为nil-like值。有关详细信息,请参阅minicbor::Encode::is_nil
。该函数需要与以下类型等效
fn is_nil<T>(v: &T) -> bool {
todo!()
}
#[cbor(cbor_len= "<path>")]
仅当派生CborLen
时适用。当应用于类型为T
的字段时,由<path>
表示的函数将用于计算字节数的CBOR长度。该函数需要与以下类型等效
fn cbor_len<Ctx, T>(val: &T, ctx: &mut Ctx) -> usize {
todo!()
}
请注意,如果cbor_len函数在其上下文参数中是泛型的,则 derive 宏使用类型变量名 Ctx
。
#[cbor(decode_bound= "...")]
当应用于泛型字段时,此属性会覆盖由 minicbor-derive
为派生的 Decode
实现生成的任何隐式类型参数限制。
#[cbor(encode_bound= "...")]
当应用于泛型字段时,此属性会覆盖由 minicbor-derive
为派生的 Encode
实现生成的任何隐式类型参数限制。
#[cbor(bound)]
结合#[cbor(encode_bound = "...")]
和#[cbor(decode_bound = "...")]
,即该限制适用于派生的 Encode
和 Decode
实现。
#[cbor(context_bound= "...")]
当为具有约束泛型上下文类型参数的部分类型派生 Encode
或 Decode
时,可以使用此属性将所需的特征限制添加到上下文类型参数。该属性可以是重复的,也可以将限制列表为 '+' 分隔的值,例如 "A + B + C"。
示例
组合上下文。
use minicbor::{Encode, Decode};
use minicbor::decode::{self, Decoder};
// Some decodable type that uses a custom context.
struct A(u8);
// `A`'s context type.
struct AC { a: u8 }
impl AsMut<AC> for AC {
fn as_mut(&mut self) -> &mut AC { self }
}
impl<'b, C: AsMut<AC>> Decode<'b, C> for A {
fn decode(d: &mut Decoder<'b>, ctx: &mut C) -> Result<Self, decode::Error> {
Ok(A(ctx.as_mut().a))
}
}
// Another decodable type that uses a different context.
struct B(u8);
// `B`'s context type.
struct BC { b: u8 }
impl AsMut<BC> for BC {
fn as_mut(&mut self) -> &mut BC { self }
}
impl<'b, C: AsMut<BC>> Decode<'b, C> for B {
fn decode(d: &mut Decoder<'b>, ctx: &mut C) -> Result<Self, decode::Error> {
Ok(B(ctx.as_mut().b))
}
}
// Finally, a type that combines `A` and `B` and therefore also needs to provide
// a context that can be used by both of them.
#[derive(Decode)]
#[cbor(context_bound = "AsMut<AC> + AsMut<BC>")]
struct C {
#[n(0)] a: A,
#[n(1)] b: B
}
// The combined context type.
struct CC(AC, BC);
impl AsMut<AC> for CC {
fn as_mut(&mut self) -> &mut AC {
&mut self.0
}
}
impl AsMut<BC> for CC {
fn as_mut(&mut self) -> &mut BC {
&mut self.1
}
}
隐式借用
除了#[b(...)]
的显式借用之外,以下类型从解码输入中隐式借用,这意味着它们的生存期受输入生存期的限制
&'_ str
&'_ minicbor::bytes::ByteSlice
Option<&'_ str>
Option<&'_ minicbor::bytes::ByteSlice>
关于 &[u8]
呢?
&[u8]
是 &[T]
的特例。由于 Rust 中 trait 实现特殊化的缺乏,为字节切片提供优化支持变得困难。泛型 [T]
实现 Encode
生成一个 T
数组。为了专门对 CBOR 字节进行编码和解码,minicbor
提供了 ByteSlice
、ByteArray
和 ByteVec
类型。此外,在派生时,可以使用 encode_with
、decode_with
和 with
属性与 &[u8]
一起使用,例如。
use minicbor::{Encode, Decode};
#[derive(Encode, Decode)]
struct Foo<'a> {
#[cbor(n(0), with = "minicbor::bytes")]
field0: &'a [u8],
#[n(1)]
#[cbor(encode_with = "minicbor::bytes::encode")]
#[cbor(decode_with = "minicbor::bytes::decode")]
field1: &'a [u8],
#[cbor(n(2), with = "minicbor::bytes")]
field2: Option<&'a [u8]>,
#[cbor(n(3), with = "minicbor::bytes")]
field3: Vec<u8>,
#[cbor(n(4), with = "minicbor::bytes")]
field4: [u8; 16]
}
CBOR 编码
由派生的 Encode
实现产生的 CBOR 值具有以下格式。
结构体
数组编码
默认情况下,或者如果一个结构体有 #[cbor(array)]
属性,它将被表示为 CBOR 数组。其索引数字由字段值在这个数组中的位置表示。任何索引数字之间的空隙将用 CBOR NULL 值和同样以 NULL 结束的 Option
填充。
<<struct-as-array encoding>> =
`array(n)`
item_0
item_1
...
item_n
映射编码
如果一个结构体有 #[cbor(map)]
属性附加,那么它将被表示为具有与数字索引值对应的 CBOR 映射。
<<struct-as-map encoding>> =
`map(n)`
`0` item_0
`1` item_1
...
n item_n
值为 None
的可选字段不会进行编码。
枚举
除非为没有字段的枚举使用了 #[cbor(index_only)]
属性,否则每个枚举变体都将编码为包含两个元素的数组。第一个元素是变体索引,第二个是实际的变体值。否则,如果没有字段的枚举具有 index_only
属性,则仅编码变体索引。
<<enum encoding>> =
| `array(2)` n <<struct-as-array encoding>> ; if #[cbor(array)]
| `array(2)` n <<struct-as-map encoding>> ; if #[cbor(map)]
| n ; if #[cbor(index_only)]
使用哪种编码?
映射编码需要在编码中显式表示索引,这至少每字段值多消耗一个字节,而数组编码不需要编码索引。另一方面,缺失的值,即 None
和索引之间的空隙,在映射中不会进行编码,但需要显式地用数组作为 NULL 编码,每个 NULL 需要1个字节。因此,选择哪种编码取决于应编码的类型本质。
-
密集类型 是只包含少量
Option
或假设其Option
为Some
的类型。它们最好以数组的形式编码。 -
稀疏类型 是包含许多
Option
的类型,它们的Option
通常为None
。它们最好以映射的形式编码。
在选择编码时,应考虑类型未来的变化,因为它们可能随着时间的推移将密集类型转换为稀疏类型。这也适用于 #[cbor(index_only)]
,该编码仅应与不期望其变体中具有字段的枚举一起使用。
[^1]: 使用 Decoder::skip
忽略 CBOR 项目。此方法需要 "alloc" 功能才能为所有可能的 CBOR 项目工作。如果没有 "alloc",则无法跳过常规映射或数组中的不定映射或数组。如果发生此类组合,并且 Decoder::skip
未使用 "alloc" 功能编译,则会返回解码错误。
依赖项
~250–690KB
~16K SLoC