├── .gitignore ├── src ├── prelude.rs ├── lib.rs ├── store.rs ├── path.rs ├── block.rs └── codec_impl.rs ├── .github ├── dependabot.yml └── workflows │ ├── stale.yml │ ├── generated-pr.yml │ └── ci.yml ├── .editorconfig ├── dag-cbor-derive ├── examples │ ├── renamed-package │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── name_attr.rs │ ├── repr_attr.rs │ └── basic.rs ├── Cargo.toml ├── tests │ ├── enum.rs │ ├── union.rs │ └── struct.rs └── src │ ├── lib.rs │ ├── ast.rs │ ├── attr.rs │ └── parse.rs ├── macro ├── Cargo.toml └── src │ └── lib.rs ├── dag-json ├── Cargo.toml └── src │ ├── lib.rs │ └── codec.rs ├── dag-pb ├── Cargo.toml ├── src │ ├── lib.rs │ └── codec.rs └── tests │ └── compat.rs ├── dag-cbor ├── Cargo.toml ├── src │ ├── error.rs │ ├── cbor.rs │ ├── lib.rs │ └── encode.rs └── tests │ ├── roundtrip.rs │ └── serde_interop.rs ├── core ├── src │ ├── lib.rs │ ├── arb.rs │ ├── link.rs │ ├── raw_value.rs │ ├── raw.rs │ ├── error.rs │ ├── serde │ │ └── mod.rs │ ├── codec.rs │ ├── ipld.rs │ └── convert.rs ├── Cargo.toml └── tests │ ├── serde_serialize.rs │ ├── serde_deserialize.rs │ └── serde_serializer.rs ├── LICENSE-MIT ├── benches └── codec.rs ├── README.md ├── Cargo.toml └── LICENSE-APACHE /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | //! Prelude 2 | pub use crate::codec::{Codec, Decode, Encode, References}; 3 | pub use crate::store::StoreParams; 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /dag-cbor-derive/examples/renamed-package/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "renamed-package" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [package.metadata.release] 8 | release = false 9 | 10 | [dependencies] 11 | ipld = { path = "../../../", package = "libipld"} 12 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Close Stale Issues 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | 12 | jobs: 13 | stale: 14 | uses: ipdxco/unified-github-workflows/.github/workflows/reusable-stale-issue.yml@v1 15 | -------------------------------------------------------------------------------- /.github/workflows/generated-pr.yml: -------------------------------------------------------------------------------- 1 | name: Close Generated PRs 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | 12 | jobs: 13 | stale: 14 | uses: ipdxco/unified-github-workflows/.github/workflows/reusable-generated-pr.yml@v1 15 | -------------------------------------------------------------------------------- /dag-cbor-derive/examples/renamed-package/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! The purpose of this example is to test whether the derive compiles if the libipld package was 2 | //! renamed in the `Cargo.toml` file. 3 | use ipld::DagCbor; 4 | 5 | #[derive(Clone, DagCbor, Debug, Default, PartialEq)] 6 | struct NamedStruct { 7 | boolean: bool, 8 | integer: u32, 9 | float: f64, 10 | string: String, 11 | } 12 | -------------------------------------------------------------------------------- /macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "libipld-macro" 3 | version = "0.16.0" 4 | authors = ["David Craven "] 5 | edition = "2021" 6 | license = "MIT OR Apache-2.0" 7 | description = "ipld macro" 8 | repository = "https://github.com/ipfs-rust/rust-ipld" 9 | 10 | [dependencies] 11 | libipld-core = { version = "0.16.0", path = "../core" } 12 | 13 | [dev-dependencies] 14 | multihash = { version = "0.18.0", features = ["blake3"] } 15 | -------------------------------------------------------------------------------- /dag-json/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "libipld-json" 3 | version = "0.16.0" 4 | authors = ["Irakli Gozalishvili "] 5 | edition = "2021" 6 | license = "MIT OR Apache-2.0" 7 | description = "ipld json codec" 8 | repository = "https://github.com/ipfs-rust/rust-ipld" 9 | 10 | [dependencies] 11 | libipld-core = { version = "0.16.0", path = "../core" } 12 | multihash = "0.18.0" 13 | serde_json = { version = "1.0.64", features = ["float_roundtrip"] } 14 | serde = { version = "1.0.126", features = ["derive"] } 15 | -------------------------------------------------------------------------------- /dag-cbor-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "libipld-cbor-derive" 3 | version = "0.16.0" 4 | authors = ["David Craven "] 5 | edition = "2021" 6 | license = "MIT OR Apache-2.0" 7 | description = "ipld cbor codec proc macro" 8 | repository = "https://github.com/ipfs-rust/rust-ipld" 9 | 10 | [lib] 11 | proc-macro = true 12 | 13 | [dependencies] 14 | proc-macro-crate = "1.1.0" 15 | proc-macro2 = "1.0.27" 16 | quote = "1.0.9" 17 | syn = "1.0.72" 18 | synstructure = "0.12.4" 19 | 20 | [dev-dependencies] 21 | libipld = { path = ".." } 22 | trybuild = "1.0.42" 23 | -------------------------------------------------------------------------------- /dag-pb/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "libipld-pb" 3 | version = "0.16.0" 4 | authors = ["David Craven "] 5 | edition = "2021" 6 | license = "MIT OR Apache-2.0" 7 | description = "ipld protobuf codec" 8 | repository = "https://github.com/ipfs-rust/rust-ipld" 9 | 10 | [dependencies] 11 | libipld-core = { version = "0.16.0", path = "../core" } 12 | thiserror = "1.0.25" 13 | quick-protobuf = "0.8.1" 14 | bytes = "1.3.0" 15 | 16 | [dev-dependencies] 17 | libipld-macro = { path = "../macro" } 18 | libipld = { path = "../" } 19 | multihash = "0.18.0" 20 | hex = "0.4.3" 21 | -------------------------------------------------------------------------------- /dag-cbor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "libipld-cbor" 3 | version = "0.16.0" 4 | authors = ["David Craven "] 5 | edition = "2021" 6 | license = "MIT OR Apache-2.0" 7 | description = "ipld cbor codec" 8 | repository = "https://github.com/ipfs-rust/rust-ipld" 9 | 10 | [dependencies] 11 | byteorder = "1.4.3" 12 | libipld-core = { version = "0.16.0", path = "../core" } 13 | thiserror = "1.0.25" 14 | 15 | [dev-dependencies] 16 | hex = "0.4.3" 17 | libipld-macro = { path = "../macro" } 18 | multihash = "0.17.0" 19 | quickcheck = "1.0.3" 20 | serde_cbor = { version = "0.11.1", features = ["tags"] } 21 | -------------------------------------------------------------------------------- /core/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Core ipld types used by ipld codecs. 2 | #![deny(missing_docs)] 3 | #![deny(warnings)] 4 | #![cfg_attr(not(feature = "std"), no_std)] 5 | 6 | extern crate alloc; 7 | 8 | pub mod codec; 9 | pub mod convert; 10 | pub mod error; 11 | pub mod ipld; 12 | pub mod link; 13 | pub mod raw; 14 | pub mod raw_value; 15 | #[cfg(feature = "serde-codec")] 16 | pub mod serde; 17 | 18 | #[cfg(feature = "arb")] 19 | mod arb; 20 | 21 | pub use cid; 22 | #[cfg(feature = "std")] 23 | pub use multibase; 24 | pub use multihash; 25 | 26 | #[cfg(not(feature = "std"))] 27 | use core2::io; 28 | #[cfg(feature = "std")] 29 | use std::io; 30 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! The `Ipld` crate. 2 | #![deny(missing_docs)] 3 | #![deny(warnings)] 4 | 5 | pub mod block; 6 | pub mod codec_impl; 7 | pub mod path; 8 | pub mod prelude; 9 | pub mod store; 10 | 11 | #[cfg(feature = "dag-cbor")] 12 | pub use libipld_cbor as cbor; 13 | #[cfg(all(feature = "dag-cbor", feature = "derive"))] 14 | pub use libipld_cbor_derive::DagCbor; 15 | pub use libipld_core::*; 16 | #[cfg(feature = "dag-json")] 17 | pub use libipld_json as json; 18 | pub use libipld_macro::*; 19 | #[cfg(feature = "dag-pb")] 20 | pub use libipld_pb as pb; 21 | 22 | pub use block::Block; 23 | pub use cid::Cid; 24 | pub use codec_impl::IpldCodec; 25 | pub use error::Result; 26 | pub use ipld::Ipld; 27 | pub use link::Link; 28 | pub use multihash::Multihash; 29 | pub use path::{DagPath, Path}; 30 | pub use store::DefaultParams; 31 | -------------------------------------------------------------------------------- /dag-cbor-derive/tests/enum.rs: -------------------------------------------------------------------------------- 1 | use libipld::cbor::DagCborCodec; 2 | use libipld::codec::assert_roundtrip; 3 | use libipld::{ipld, DagCbor}; 4 | 5 | #[derive(Clone, Copy, DagCbor, Debug, Eq, PartialEq)] 6 | #[ipld(repr = "int")] 7 | pub enum EnumInt { 8 | Variant = 1, 9 | Other = 0, 10 | } 11 | 12 | #[test] 13 | fn enum_int() { 14 | assert_roundtrip(DagCborCodec, &EnumInt::Variant, &ipld!(1)); 15 | assert_roundtrip(DagCborCodec, &EnumInt::Other, &ipld!(0)); 16 | } 17 | 18 | #[derive(Clone, DagCbor, Debug, Eq, PartialEq)] 19 | #[ipld(repr = "string")] 20 | pub enum EnumString { 21 | #[ipld(rename = "test")] 22 | Variant, 23 | Other, 24 | } 25 | 26 | #[test] 27 | fn enum_string() { 28 | assert_roundtrip(DagCborCodec, &EnumString::Variant, &ipld!("test")); 29 | assert_roundtrip(DagCborCodec, &EnumString::Other, &ipld!("Other")); 30 | } 31 | -------------------------------------------------------------------------------- /dag-cbor-derive/examples/name_attr.rs: -------------------------------------------------------------------------------- 1 | use libipld::cbor::DagCborCodec; 2 | use libipld::codec::{Decode, Encode}; 3 | use libipld::ipld::Ipld; 4 | use libipld::{ipld, DagCbor}; 5 | use std::io::Cursor; 6 | 7 | #[derive(Clone, Debug, Default, PartialEq, DagCbor)] 8 | struct RenameFields { 9 | #[ipld(rename = "hashAlg")] 10 | hash_alg: String, 11 | } 12 | 13 | fn main() -> Result<(), Box> { 14 | let data = RenameFields { 15 | hash_alg: "murmur3".to_string(), 16 | }; 17 | let mut bytes = Vec::new(); 18 | data.encode(DagCborCodec, &mut bytes)?; 19 | let ipld: Ipld = Decode::decode(DagCborCodec, &mut Cursor::new(bytes.as_slice()))?; 20 | let expect = ipld!({ 21 | "hashAlg": "murmur3", 22 | }); 23 | assert_eq!(ipld, expect); 24 | let data2 = RenameFields::decode(DagCborCodec, &mut Cursor::new(bytes.as_slice()))?; 25 | assert_eq!(data, data2); 26 | Ok(()) 27 | } 28 | -------------------------------------------------------------------------------- /dag-cbor-derive/examples/repr_attr.rs: -------------------------------------------------------------------------------- 1 | use libipld::cbor::DagCborCodec; 2 | use libipld::codec::assert_roundtrip; 3 | use libipld::{ipld, DagCbor}; 4 | 5 | #[derive(Clone, DagCbor, Debug, Default, Eq, PartialEq)] 6 | #[ipld(repr = "tuple")] 7 | struct ListRepr { 8 | a: bool, 9 | b: bool, 10 | } 11 | 12 | #[derive(Clone, DagCbor, Debug, Eq, PartialEq)] 13 | #[ipld(repr = "kinded")] 14 | enum KindedRepr { 15 | A(bool), 16 | B { a: u32 }, 17 | } 18 | 19 | #[derive(Clone, DagCbor, Debug, Eq, PartialEq)] 20 | #[ipld(repr = "string")] 21 | enum KindedRepr2 { 22 | A, 23 | B, 24 | } 25 | 26 | fn main() { 27 | assert_roundtrip(DagCborCodec, &ListRepr::default(), &ipld!([false, false])); 28 | 29 | assert_roundtrip(DagCborCodec, &KindedRepr::A(true), &ipld!([true])); 30 | 31 | assert_roundtrip(DagCborCodec, &KindedRepr::B { a: 42 }, &ipld!({ "a": 42 })); 32 | 33 | assert_roundtrip(DagCborCodec, &KindedRepr2::B, &ipld!("B")); 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /benches/codec.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 2 | use libipld::cbor::DagCborCodec; 3 | use libipld::cid::Cid; 4 | use libipld::codec::Codec; 5 | use libipld::{ipld, Ipld}; 6 | 7 | fn bench_codec(c: &mut Criterion) { 8 | c.bench_function("roundtrip", |b| { 9 | let cid = 10 | Cid::try_from("bafyreibvjvcv745gig4mvqs4hctx4zfkono4rjejm2ta6gtyzkqxfjeily").unwrap(); 11 | let ipld = ipld!({ 12 | "number": 1, 13 | "list": [true, null, false], 14 | "bytes": vec![0, 1, 2, 3], 15 | "map": { "float": 0.0, "string": "hello" }, 16 | "link": cid, 17 | }); 18 | b.iter(|| { 19 | for _ in 0..1000 { 20 | let bytes = DagCborCodec.encode(&ipld).unwrap(); 21 | let ipld2: Ipld = DagCborCodec.decode(&bytes).unwrap(); 22 | black_box(ipld2); 23 | } 24 | }); 25 | }); 26 | } 27 | 28 | criterion_group! { 29 | name = codec; 30 | config = Criterion::default(); 31 | targets = bench_codec 32 | } 33 | 34 | criterion_main!(codec); 35 | -------------------------------------------------------------------------------- /core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "libipld-core" 3 | version = "0.16.0" 4 | authors = ["David Craven "] 5 | edition = "2021" 6 | license = "MIT OR Apache-2.0" 7 | description = "Base traits and definitions used by ipld codecs." 8 | repository = "https://github.com/ipfs-rust/rust-ipld" 9 | 10 | [features] 11 | default = ["std"] 12 | std = ["anyhow/std", "cid/std", "multibase/std", "multihash/std", "thiserror"] 13 | serde-codec = ["cid/serde-codec", "serde"] 14 | arb = ["quickcheck", "cid/arb"] 15 | 16 | [dependencies] 17 | anyhow = { version = "1.0.40", default-features = false } 18 | cid = { version = "0.10.0", default-features = false, features = ["alloc"] } 19 | core2 = { version = "0.4", default-features = false, features = ["alloc"] } 20 | multihash = { version = "0.18.0", default-features = false, features = ["alloc"] } 21 | 22 | multibase = { version = "0.9.1", default-features = false, optional = true } 23 | serde = { version = "1.0.132", default-features = false, features = ["alloc"], optional = true } 24 | thiserror = {version = "1.0.25", optional = true } 25 | quickcheck = { version = "1.0", optional = true } 26 | 27 | [dev-dependencies] 28 | multihash = { version = "0.18.0", default-features = false, features = ["multihash-impl", "blake3"] } 29 | serde_test = "1.0.132" 30 | serde_bytes = "0.11.5" 31 | serde_json = "1.0.79" 32 | -------------------------------------------------------------------------------- /dag-cbor-derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Span, TokenStream}; 2 | use proc_macro_crate::{crate_name, FoundCrate}; 3 | use quote::quote; 4 | use synstructure::{decl_derive, Structure}; 5 | 6 | decl_derive!([DagCbor, attributes(ipld)] => dag_cbor_derive); 7 | 8 | mod ast; 9 | mod attr; 10 | mod gen; 11 | mod parse; 12 | 13 | fn dag_cbor_derive(s: Structure) -> TokenStream { 14 | let libipld = match use_crate("libipld") { 15 | Ok(ident) => ident, 16 | Err(error) => return error, 17 | }; 18 | let ast = parse::parse(&s); 19 | let encode = gen::gen_encode(&ast, &libipld); 20 | let decode = gen::gen_decode(&ast, &libipld); 21 | quote! { 22 | #encode 23 | #decode 24 | } 25 | } 26 | 27 | /// Get the name of a crate based on its original name. 28 | /// 29 | /// This works even if the crate was renamed in the `Cargo.toml` file. If the crate is not a 30 | /// dependency, it will lead to a compile-time error. 31 | fn use_crate(name: &str) -> Result { 32 | match crate_name(name) { 33 | Ok(FoundCrate::Name(n)) => Ok(syn::Ident::new(&n, Span::call_site())), 34 | Ok(FoundCrate::Itself) => Ok(syn::Ident::new("crate", Span::call_site())), 35 | Err(err) => Err(syn::Error::new(Span::call_site(), err).to_compile_error()), 36 | } 37 | } 38 | 39 | #[cfg(test)] 40 | mod tests { 41 | #[test] 42 | fn test() { 43 | let t = trybuild::TestCases::new(); 44 | t.pass("examples/basic.rs"); 45 | t.pass("examples/name_attr.rs"); 46 | t.pass("examples/repr_attr.rs"); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > # ⛔️ DEPRECATED: [libipld](https://github.com/ipld/libipld) has been superseded by [ipld-core](https://github.com/ipld/rust-ipld-core) 2 | > The migration to `ipld-core` should be straight forward. If you run into problems during the migration or need help, feel free to [open a bug report on the `ipld-core` repo](https://github.com/ipld/rust-ipld-core/issues). 3 | 4 | 5 | # Rust IPLD library 6 | 7 | > Basic rust ipld library supporting `dag-cbor`, `dag-json` and `dag-pb` formats. 8 | 9 | Originally authored by [@dvc94ch](https://github.com/dvc94ch) as a part of the [ipfs-rust](https://github.com/ipfs-rust/) project. 10 | 11 | The `Ipld` enum from the `libipld-core` crate is the central piece that most of the users of this library use. 12 | 13 | The codec implementations use custom traits. In order to be more compatible with the rest of the Rust ecosystem, it's *strongly recommended*, to use new implementations, that use [Serde](https://serde.rs/) as a basis instead. Currently, the list of implementations is limited, please let us know if you crate one and we'll add it to the list: 14 | 15 | - DAG-CBOR: https://github.com/ipld/serde_ipld_dagcbor 16 | 17 | ## Community 18 | 19 | For chats with the developers and the community: Join us in any of these (bridged) locations: 20 | - On Matrix: [#ipld:ipfs.io](https://matrix.to/#/#ipld:ipfs.io) 21 | - On Discord: join the [IPLD community on IPFS Discord](https://discord.gg/xkUC8bqSCP). 22 | 23 | ## License 24 | 25 | Dual licensed under MIT or Apache License (Version 2.0). See [LICENSE-MIT](./LICENSE-MIT) and [LICENSE-APACHE](./LICENSE-APACHE) for more details. 26 | -------------------------------------------------------------------------------- /dag-cbor-derive/src/ast.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | 3 | #[derive(Clone, Debug)] 4 | pub struct TokenStreamEq(pub TokenStream); 5 | 6 | impl std::ops::Deref for TokenStreamEq { 7 | type Target = TokenStream; 8 | 9 | fn deref(&self) -> &Self::Target { 10 | &self.0 11 | } 12 | } 13 | 14 | impl PartialEq for TokenStreamEq { 15 | fn eq(&self, other: &Self) -> bool { 16 | self.0.to_string() == other.0.to_string() 17 | } 18 | } 19 | 20 | impl Eq for TokenStreamEq {} 21 | #[derive(Clone, Debug, Eq, PartialEq)] 22 | pub enum SchemaType { 23 | Struct(Struct), 24 | Union(Union), 25 | } 26 | 27 | #[derive(Clone, Debug, Eq, PartialEq)] 28 | pub struct Struct { 29 | pub name: syn::Ident, 30 | pub generics: Option, 31 | pub rename: Option, 32 | pub fields: Vec, 33 | pub repr: StructRepr, 34 | pub pat: TokenStreamEq, 35 | pub construct: TokenStreamEq, 36 | } 37 | 38 | #[derive(Clone, Debug, Eq, PartialEq)] 39 | pub struct StructField { 40 | pub name: syn::Member, 41 | pub rename: Option, 42 | pub default: Option>, 43 | pub binding: syn::Ident, 44 | } 45 | 46 | #[derive(Clone, Debug, Eq, PartialEq)] 47 | pub enum StructRepr { 48 | Map, 49 | Tuple, 50 | Value, 51 | Null, 52 | } 53 | 54 | #[derive(Clone, Debug, Eq, PartialEq)] 55 | pub struct Union { 56 | pub name: syn::Ident, 57 | pub generics: syn::Generics, 58 | pub variants: Vec, 59 | pub repr: UnionRepr, 60 | } 61 | 62 | #[derive(Clone, Debug, Eq, PartialEq)] 63 | pub enum UnionRepr { 64 | Keyed, 65 | Kinded, 66 | String, 67 | Int, 68 | IntTuple, 69 | } 70 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "libipld" 3 | version = "0.16.0" 4 | authors = ["David Craven "] 5 | edition = "2021" 6 | license = "MIT OR Apache-2.0" 7 | description = "library for dealing with ipld" 8 | repository = "https://github.com/ipld/libipld" 9 | 10 | [package.metadata.release] 11 | consolidate-commits = true 12 | shared-version = true 13 | 14 | [dependencies] 15 | fnv = "1.0.7" 16 | libipld-cbor = { version = "0.16.0", path = "dag-cbor", optional = true } 17 | libipld-cbor-derive = { version = "0.16.0", path = "dag-cbor-derive", optional = true } 18 | libipld-core = { version = "0.16.0", path = "core" } 19 | libipld-json = { version = "0.16.0", path = "dag-json", optional = true } 20 | libipld-macro = { version = "0.16.0", path = "macro" } 21 | libipld-pb = { version = "0.16.0", path = "dag-pb", optional = true } 22 | log = "0.4.14" 23 | multihash = { version = "0.18.0", default-features = false, features = ["multihash-impl"] } 24 | thiserror = "1.0.25" 25 | 26 | [dev-dependencies] 27 | async-std = { version = "1.9.0", features = ["attributes"] } 28 | criterion = "0.3.4" 29 | proptest = "1.0.0" 30 | model = "0.1.2" 31 | 32 | [features] 33 | default = ["dag-cbor", "dag-json", "dag-pb", "derive"] 34 | dag-cbor = ["libipld-cbor"] 35 | dag-json = ["libipld-json"] 36 | dag-pb = ["libipld-pb"] 37 | derive = ["libipld-cbor-derive"] 38 | serde-codec = ["libipld-core/serde-codec"] 39 | arb = ["libipld-core/arb"] 40 | 41 | [workspace] 42 | members = [ 43 | "core", 44 | "dag-cbor", 45 | "dag-cbor-derive", 46 | "dag-json", 47 | "dag-pb", 48 | "macro", 49 | "dag-cbor-derive/examples/renamed-package", 50 | ] 51 | 52 | [profile.release] 53 | debug = true 54 | 55 | [[bench]] 56 | name = "codec" 57 | harness = false 58 | -------------------------------------------------------------------------------- /src/store.rs: -------------------------------------------------------------------------------- 1 | //! Store traits. 2 | //! 3 | //! ## Aliases 4 | //! An alias is a named root of a dag. When a root is aliased, none of the leaves of the dag 5 | //! pointed to by the root will be collected by gc. However, a root being aliased does not 6 | //! mean that the dag must be complete. 7 | //! 8 | //! ## Temporary pin 9 | //! A temporary pin is an unnamed set of roots of a dag, that is just for the purpose of protecting 10 | //! blocks from gc while a large tree is constructed. While an alias maps a single name to a 11 | //! single root, a temporary alias can be assigned to an arbitrary number of blocks before the 12 | //! dag is finished. 13 | //! 14 | //! ## Garbage collection (GC) 15 | //! GC refers to the process of removing unaliased blocks. When it runs is implementation defined. 16 | //! However it is intended to run only when the configured size is exceeded at when it will start 17 | //! incrementally deleting unaliased blocks until the size target is no longer exceeded. It is 18 | //! implementation defined in which order unaliased blocks get removed. 19 | use crate::codec::Codec; 20 | use crate::multihash::MultihashDigest; 21 | 22 | /// The store parameters. 23 | pub trait StoreParams: std::fmt::Debug + Clone + Send + Sync + Unpin + 'static { 24 | /// The multihash type of the store. 25 | type Hashes: MultihashDigest<64>; 26 | /// The codec type of the store. 27 | type Codecs: Codec; 28 | /// The maximum block size supported by the store. 29 | const MAX_BLOCK_SIZE: usize; 30 | } 31 | 32 | /// Default store parameters. 33 | #[derive(Clone, Debug, Default)] 34 | pub struct DefaultParams; 35 | 36 | impl StoreParams for DefaultParams { 37 | const MAX_BLOCK_SIZE: usize = 1_048_576; 38 | type Codecs = crate::IpldCodec; 39 | type Hashes = crate::multihash::Code; 40 | } 41 | -------------------------------------------------------------------------------- /dag-cbor-derive/examples/basic.rs: -------------------------------------------------------------------------------- 1 | use libipld::cbor::DagCborCodec; 2 | use libipld::codec::assert_roundtrip; 3 | use libipld::{ipld, DagCbor, Ipld}; 4 | use std::collections::BTreeMap; 5 | 6 | #[derive(Clone, DagCbor, Debug, Default, PartialEq)] 7 | struct NamedStruct { 8 | boolean: bool, 9 | integer: u32, 10 | float: f64, 11 | string: String, 12 | bytes: Vec, 13 | list: Vec, 14 | map: BTreeMap, 15 | //link: Cid, 16 | } 17 | 18 | #[derive(Clone, DagCbor, Debug, Default, Eq, PartialEq)] 19 | struct TupleStruct(bool, u32); 20 | 21 | #[derive(Clone, DagCbor, Debug, Default, Eq, PartialEq)] 22 | struct UnitStruct; 23 | 24 | #[derive(Clone, DagCbor, Debug, Eq, PartialEq)] 25 | enum Enum { 26 | A, 27 | B(bool, u32), 28 | C { boolean: bool, int: u32 }, 29 | } 30 | 31 | #[derive(Clone, DagCbor, Debug, PartialEq)] 32 | struct Nested { 33 | ipld: Ipld, 34 | list_of_derived: Vec, 35 | map_of_derived: BTreeMap, 36 | } 37 | 38 | fn main() { 39 | assert_roundtrip( 40 | DagCborCodec, 41 | &NamedStruct::default(), 42 | &ipld!({ 43 | "boolean": false, 44 | "integer": 0, 45 | "float": 0.0, 46 | "string": "", 47 | "bytes": [], 48 | "list": [], 49 | "map": {}, 50 | }), 51 | ); 52 | 53 | assert_roundtrip(DagCborCodec, &TupleStruct::default(), &ipld!([false, 0])); 54 | 55 | assert_roundtrip(DagCborCodec, &UnitStruct, &ipld!(null)); 56 | 57 | assert_roundtrip(DagCborCodec, &Enum::A, &ipld!({ "A": null })); 58 | 59 | assert_roundtrip( 60 | DagCborCodec, 61 | &Enum::B(true, 42), 62 | &ipld!({ "B": [true, 42] }), 63 | ); 64 | 65 | assert_roundtrip( 66 | DagCborCodec, 67 | &Enum::C { 68 | boolean: true, 69 | int: 42, 70 | }, 71 | &ipld!({ "C": { "boolean": true, "int": 42} }), 72 | ); 73 | } 74 | -------------------------------------------------------------------------------- /dag-cbor-derive/src/attr.rs: -------------------------------------------------------------------------------- 1 | use syn::parse::{Parse, ParseStream}; 2 | use syn::punctuated::Punctuated; 3 | 4 | mod kw { 5 | use syn::custom_keyword; 6 | 7 | custom_keyword!(repr); 8 | 9 | custom_keyword!(rename); 10 | custom_keyword!(default); 11 | } 12 | 13 | #[derive(Debug)] 14 | pub struct Attrs { 15 | pub paren: syn::token::Paren, 16 | pub attrs: Punctuated, 17 | } 18 | 19 | impl Parse for Attrs { 20 | fn parse(input: ParseStream) -> syn::Result { 21 | let content; 22 | let paren = syn::parenthesized!(content in input); 23 | let attrs = content.parse_terminated(A::parse)?; 24 | Ok(Self { paren, attrs }) 25 | } 26 | } 27 | 28 | #[derive(Debug)] 29 | pub struct Attr { 30 | pub key: K, 31 | pub eq: syn::token::Eq, 32 | pub value: V, 33 | } 34 | 35 | impl Parse for Attr { 36 | fn parse(input: ParseStream) -> syn::Result { 37 | Ok(Self { 38 | key: input.parse()?, 39 | eq: input.parse()?, 40 | value: input.parse()?, 41 | }) 42 | } 43 | } 44 | 45 | #[derive(Debug)] 46 | pub enum DeriveAttr { 47 | Repr(Attr), 48 | } 49 | 50 | impl Parse for DeriveAttr { 51 | fn parse(input: ParseStream) -> syn::Result { 52 | if input.peek(kw::repr) { 53 | Ok(DeriveAttr::Repr(input.parse()?)) 54 | } else { 55 | Err(syn::Error::new(input.span(), "unknown attribute")) 56 | } 57 | } 58 | } 59 | 60 | #[derive(Debug)] 61 | pub enum FieldAttr { 62 | Rename(Attr), 63 | Default(Attr>), 64 | } 65 | 66 | impl Parse for FieldAttr { 67 | fn parse(input: ParseStream) -> syn::Result { 68 | if input.peek(kw::rename) { 69 | Ok(FieldAttr::Rename(input.parse()?)) 70 | } else if input.peek(kw::default) { 71 | Ok(FieldAttr::Default(input.parse()?)) 72 | } else { 73 | Err(syn::Error::new(input.span(), "unknown attribute")) 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /core/src/arb.rs: -------------------------------------------------------------------------------- 1 | //! Ipld representation. 2 | use alloc::{boxed::Box, string::String, vec::Vec}; 3 | 4 | use crate::{cid::Cid, ipld::Ipld}; 5 | use quickcheck::empty_shrinker; 6 | use quickcheck::Arbitrary; 7 | 8 | impl quickcheck::Arbitrary for Ipld { 9 | fn arbitrary(g: &mut quickcheck::Gen) -> Self { 10 | Self::arbitrary_ipld(g, &mut g.size()) 11 | } 12 | 13 | fn shrink(&self) -> Box> { 14 | match self { 15 | Ipld::Null => empty_shrinker(), 16 | Ipld::Bool(v) => Box::new(v.shrink().map(Ipld::Bool)), 17 | Ipld::Integer(v) => Box::new(v.shrink().map(Ipld::Integer)), 18 | Ipld::Float(v) => Box::new(v.shrink().map(Ipld::Float)), 19 | Ipld::String(v) => Box::new(v.shrink().map(Ipld::String)), 20 | Ipld::Bytes(v) => Box::new(v.shrink().map(Ipld::Bytes)), 21 | Ipld::List(v) => Box::new(v.shrink().map(Ipld::List)), 22 | Ipld::Map(v) => Box::new(v.shrink().map(Ipld::Map)), 23 | Ipld::Link(v) => Box::new(v.shrink().map(Ipld::Link)), 24 | } 25 | } 26 | } 27 | 28 | impl Ipld { 29 | /// Special version on `arbitrary` to battle possible recursion 30 | fn arbitrary_ipld(g: &mut quickcheck::Gen, size: &mut usize) -> Self { 31 | if *size == 0 { 32 | return Ipld::Null; 33 | } 34 | *size -= 1; 35 | let index = usize::arbitrary(g) % 9; 36 | match index { 37 | 0 => Ipld::Null, 38 | 1 => Ipld::Bool(bool::arbitrary(g)), 39 | 2 => Ipld::Integer(i128::arbitrary(g)), 40 | 3 => Ipld::Float(f64::arbitrary(g)), 41 | 4 => Ipld::String(String::arbitrary(g)), 42 | 5 => Ipld::Bytes(Vec::arbitrary(g)), 43 | 6 => Ipld::List( 44 | (0..Self::arbitrary_size(g, size)) 45 | .map(|_| Self::arbitrary_ipld(g, size)) 46 | .collect(), 47 | ), 48 | 7 => Ipld::Map( 49 | (0..Self::arbitrary_size(g, size)) 50 | .map(|_| (String::arbitrary(g), Self::arbitrary_ipld(g, size))) 51 | .collect(), 52 | ), 53 | 8 => Ipld::Link(Cid::arbitrary(g)), 54 | // unreachable due to the fact that 55 | // we know that the index is always < 9 56 | _ => unreachable!(), 57 | } 58 | } 59 | 60 | fn arbitrary_size(g: &mut quickcheck::Gen, size: &mut usize) -> usize { 61 | if *size == 0 { 62 | return 0; 63 | } 64 | usize::arbitrary(g) % *size 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /core/src/link.rs: -------------------------------------------------------------------------------- 1 | //! Typed cid. 2 | use core::{ 3 | cmp::Ordering, 4 | fmt, 5 | hash::{Hash, Hasher}, 6 | marker::PhantomData, 7 | ops::Deref, 8 | }; 9 | 10 | use crate::cid::Cid; 11 | use crate::codec::{Codec, Decode, Encode}; 12 | use crate::error::Result; 13 | use crate::io::{Read, Seek, Write}; 14 | 15 | /// Typed cid. 16 | #[derive(Debug)] 17 | pub struct Link { 18 | cid: Cid, 19 | _marker: PhantomData, 20 | } 21 | 22 | impl Link { 23 | /// Creates a new `Link`. 24 | pub fn new(cid: Cid) -> Self { 25 | Self { 26 | cid, 27 | _marker: PhantomData, 28 | } 29 | } 30 | 31 | /// Returns a reference to the cid. 32 | pub fn cid(&self) -> &Cid { 33 | &self.cid 34 | } 35 | } 36 | 37 | impl fmt::Display for Link { 38 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 39 | self.cid.fmt(f) 40 | } 41 | } 42 | 43 | impl Clone for Link { 44 | fn clone(&self) -> Self { 45 | *self 46 | } 47 | } 48 | 49 | impl Copy for Link {} 50 | 51 | impl PartialEq for Link { 52 | fn eq(&self, other: &Self) -> bool { 53 | self.cid.eq(other.cid()) 54 | } 55 | } 56 | 57 | impl Eq for Link {} 58 | 59 | impl PartialOrd for Link { 60 | fn partial_cmp(&self, other: &Self) -> Option { 61 | Some(self.cid.cmp(other.cid())) 62 | } 63 | } 64 | 65 | impl Ord for Link { 66 | fn cmp(&self, other: &Self) -> Ordering { 67 | self.cid.cmp(other.cid()) 68 | } 69 | } 70 | 71 | impl Hash for Link { 72 | fn hash(&self, hasher: &mut H) { 73 | Hash::hash(self.cid(), hasher) 74 | } 75 | } 76 | 77 | impl Encode for Link 78 | where 79 | Cid: Encode, 80 | { 81 | fn encode(&self, c: C, w: &mut W) -> Result<()> { 82 | self.cid().encode(c, w) 83 | } 84 | } 85 | 86 | impl Decode for Link 87 | where 88 | Cid: Decode, 89 | { 90 | fn decode(c: C, r: &mut R) -> Result { 91 | Ok(Self::new(Cid::decode(c, r)?)) 92 | } 93 | } 94 | 95 | impl Deref for Link { 96 | type Target = Cid; 97 | 98 | fn deref(&self) -> &Self::Target { 99 | self.cid() 100 | } 101 | } 102 | 103 | impl AsRef for Link { 104 | fn as_ref(&self) -> &Cid { 105 | self.cid() 106 | } 107 | } 108 | 109 | impl From for Link { 110 | fn from(cid: Cid) -> Self { 111 | Self::new(cid) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /core/src/raw_value.rs: -------------------------------------------------------------------------------- 1 | //! misc stuff 2 | use alloc::{boxed::Box, vec, vec::Vec}; 3 | use core::{convert::TryFrom, marker::PhantomData}; 4 | 5 | use crate::codec::{Codec, Decode, Encode}; 6 | use crate::io::{Read, Seek, SeekFrom, Write}; 7 | 8 | /// A raw value for a certain codec. 9 | /// 10 | /// Contains the raw, unprocessed data for a single item for that particular codec 11 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] 12 | pub struct RawValue { 13 | data: Box<[u8]>, 14 | _p: PhantomData, 15 | } 16 | 17 | impl RawValue { 18 | fn new(data: Box<[u8]>) -> Self { 19 | Self { 20 | data, 21 | _p: PhantomData, 22 | } 23 | } 24 | } 25 | 26 | impl AsRef<[u8]> for RawValue { 27 | fn as_ref(&self) -> &[u8] { 28 | &self.data 29 | } 30 | } 31 | 32 | impl From> for Box<[u8]> { 33 | fn from(value: RawValue) -> Self { 34 | value.data 35 | } 36 | } 37 | 38 | impl From> for Vec { 39 | fn from(value: RawValue) -> Self { 40 | value.data.into() 41 | } 42 | } 43 | 44 | /// trait to implement to skip a single item at the current position 45 | pub trait SkipOne: Codec { 46 | /// assuming r is at the start of an item, advance r to the end 47 | fn skip(&self, r: &mut R) -> anyhow::Result<()>; 48 | } 49 | 50 | impl Decode for RawValue { 51 | // `core2` doesn't support `stream_position()`, hence silence this Clippy warning. 52 | #[allow(clippy::seek_from_current)] 53 | fn decode(c: C, r: &mut R) -> anyhow::Result { 54 | let p0 = r.seek(SeekFrom::Current(0)).map_err(anyhow::Error::msg)?; 55 | c.skip(r)?; 56 | let p1 = r.seek(SeekFrom::Current(0)).map_err(anyhow::Error::msg)?; 57 | // seeking backward is not allowed 58 | anyhow::ensure!(p1 > p0); 59 | // this will fail if usize is 4 bytes and an item is > 32 bit of length 60 | let len = usize::try_from(p1 - p0).map_err(anyhow::Error::msg)?; 61 | r.seek(SeekFrom::Start(p0)).map_err(anyhow::Error::msg)?; 62 | let mut buf = vec![0u8; len]; 63 | r.read_exact(&mut buf).map_err(anyhow::Error::msg)?; 64 | Ok(Self::new(buf.into())) 65 | } 66 | } 67 | 68 | impl Encode for RawValue { 69 | fn encode(&self, _: C, w: &mut W) -> anyhow::Result<()> { 70 | w.write_all(&self.data).map_err(anyhow::Error::msg)?; 71 | Ok(()) 72 | } 73 | } 74 | 75 | /// Allows to ignore a single item 76 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Default)] 77 | pub struct IgnoredAny; 78 | 79 | impl Decode for IgnoredAny { 80 | fn decode(c: C, r: &mut R) -> anyhow::Result { 81 | c.skip(r)?; 82 | Ok(Self) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /dag-cbor-derive/tests/union.rs: -------------------------------------------------------------------------------- 1 | use libipld::cbor::DagCborCodec; 2 | use libipld::codec::assert_roundtrip; 3 | use libipld::{ipld, DagCbor}; 4 | 5 | #[derive(Clone, Copy, DagCbor, Debug, Eq, PartialEq)] 6 | #[ipld(repr = "keyed")] 7 | pub enum Keyed { 8 | A, 9 | #[ipld(rename = "b")] 10 | #[ipld(repr = "value")] 11 | B(bool), 12 | #[ipld(repr = "value")] 13 | C { 14 | n: u32, 15 | }, 16 | D(bool), 17 | E { 18 | boolean: bool, 19 | }, 20 | } 21 | 22 | #[test] 23 | fn union_keyed() { 24 | assert_roundtrip(DagCborCodec, &Keyed::A, &ipld!({ "A": null })); 25 | assert_roundtrip(DagCborCodec, &Keyed::B(true), &ipld!({"b": true})); 26 | assert_roundtrip(DagCborCodec, &Keyed::B(false), &ipld!({"b": false})); 27 | assert_roundtrip(DagCborCodec, &Keyed::C { n: 1 }, &ipld!({"C": 1})); 28 | assert_roundtrip(DagCborCodec, &Keyed::D(true), &ipld!({"D": [true]})); 29 | assert_roundtrip( 30 | DagCborCodec, 31 | &Keyed::E { boolean: true }, 32 | &ipld!({"E": { "boolean": true }}), 33 | ); 34 | } 35 | 36 | #[derive(Clone, Copy, DagCbor, Debug, Eq, PartialEq)] 37 | #[ipld(repr = "kinded")] 38 | pub enum Kinded { 39 | A, 40 | #[ipld(rename = "b")] 41 | #[ipld(repr = "value")] 42 | B(bool), 43 | #[ipld(repr = "value")] 44 | C { 45 | n: u32, 46 | }, 47 | D(bool), 48 | E { 49 | boolean: bool, 50 | }, 51 | } 52 | 53 | #[test] 54 | fn union_kinded() { 55 | assert_roundtrip(DagCborCodec, &Kinded::A, &ipld!(null)); 56 | assert_roundtrip(DagCborCodec, &Kinded::B(true), &ipld!(true)); 57 | assert_roundtrip(DagCborCodec, &Kinded::B(false), &ipld!(false)); 58 | assert_roundtrip(DagCborCodec, &Kinded::C { n: 1 }, &ipld!(1)); 59 | assert_roundtrip(DagCborCodec, &Kinded::D(true), &ipld!([true])); 60 | assert_roundtrip( 61 | DagCborCodec, 62 | &Kinded::E { boolean: true }, 63 | &ipld!({ "boolean": true }), 64 | ); 65 | } 66 | 67 | #[derive(Clone, Copy, DagCbor, Debug, Eq, PartialEq)] 68 | #[ipld(repr = "int-tuple")] 69 | pub enum IntTuple { 70 | A, 71 | #[ipld(rename = "b")] 72 | #[ipld(repr = "value")] 73 | B(bool), 74 | #[ipld(repr = "value")] 75 | C { 76 | n: u32, 77 | }, 78 | D(bool), 79 | E { 80 | boolean: bool, 81 | }, 82 | } 83 | 84 | #[test] 85 | fn union_int_tuple() { 86 | assert_roundtrip(DagCborCodec, &IntTuple::A, &ipld!([0, null])); 87 | assert_roundtrip(DagCborCodec, &IntTuple::B(true), &ipld!([1, true])); 88 | assert_roundtrip(DagCborCodec, &IntTuple::B(false), &ipld!([1, false])); 89 | assert_roundtrip(DagCborCodec, &IntTuple::C { n: 1 }, &ipld!([2, 1])); 90 | assert_roundtrip(DagCborCodec, &IntTuple::D(true), &ipld!([3, [true]])); 91 | assert_roundtrip( 92 | DagCborCodec, 93 | &IntTuple::E { boolean: true }, 94 | &ipld!([4, { "boolean": true }]), 95 | ); 96 | } 97 | -------------------------------------------------------------------------------- /dag-pb/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Protobuf codec. 2 | #![deny(missing_docs)] 3 | 4 | use crate::codec::PbNodeRef; 5 | pub use crate::codec::{PbLink, PbNode}; 6 | 7 | use core::convert::{TryFrom, TryInto}; 8 | use libipld_core::cid::Cid; 9 | use libipld_core::codec::{Codec, Decode, Encode, References}; 10 | use libipld_core::error::{Result, UnsupportedCodec}; 11 | use libipld_core::ipld::Ipld; 12 | use std::io::{Read, Seek, Write}; 13 | 14 | mod codec; 15 | 16 | /// Protobuf codec. 17 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] 18 | pub struct DagPbCodec; 19 | 20 | impl Codec for DagPbCodec {} 21 | 22 | impl From for u64 { 23 | fn from(_: DagPbCodec) -> Self { 24 | 0x70 25 | } 26 | } 27 | 28 | impl TryFrom for DagPbCodec { 29 | type Error = UnsupportedCodec; 30 | 31 | fn try_from(_: u64) -> core::result::Result { 32 | Ok(Self) 33 | } 34 | } 35 | 36 | impl Encode for Ipld { 37 | fn encode(&self, _: DagPbCodec, w: &mut W) -> Result<()> { 38 | let pb_node: PbNodeRef = self.try_into()?; 39 | let bytes = pb_node.into_bytes(); 40 | w.write_all(&bytes)?; 41 | Ok(()) 42 | } 43 | } 44 | 45 | impl Decode for Ipld { 46 | fn decode(_: DagPbCodec, r: &mut R) -> Result { 47 | let mut bytes = Vec::new(); 48 | r.read_to_end(&mut bytes)?; 49 | let node = PbNode::from_bytes(bytes.into())?; 50 | Ok(node.into()) 51 | } 52 | } 53 | 54 | impl References for Ipld { 55 | fn references>( 56 | _: DagPbCodec, 57 | r: &mut R, 58 | set: &mut E, 59 | ) -> Result<()> { 60 | let mut bytes = Vec::new(); 61 | r.read_to_end(&mut bytes)?; 62 | PbNode::links(bytes.into(), set) 63 | } 64 | } 65 | 66 | #[cfg(test)] 67 | mod tests { 68 | use super::*; 69 | use libipld_core::cid::Cid; 70 | use libipld_core::multihash::{Code, MultihashDigest}; 71 | use std::collections::BTreeMap; 72 | 73 | #[test] 74 | fn test_encode_decode() { 75 | let digest = Code::Blake3_256.digest(&b"cid"[..]); 76 | let cid = Cid::new_v1(0x55, digest); 77 | let mut pb_link = BTreeMap::::new(); 78 | pb_link.insert("Hash".to_string(), cid.into()); 79 | pb_link.insert("Name".to_string(), "block".to_string().into()); 80 | pb_link.insert("Tsize".to_string(), 13.into()); 81 | 82 | let links: Vec = vec![pb_link.into()]; 83 | let mut pb_node = BTreeMap::::new(); 84 | pb_node.insert("Data".to_string(), b"Here is some data\n".to_vec().into()); 85 | pb_node.insert("Links".to_string(), links.into()); 86 | let data: Ipld = pb_node.into(); 87 | 88 | let bytes = DagPbCodec.encode(&data).unwrap(); 89 | let data2 = DagPbCodec.decode(&bytes).unwrap(); 90 | assert_eq!(data, data2); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /dag-json/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Json codec. 2 | #![deny(missing_docs)] 3 | #![deny(warnings)] 4 | 5 | use core::convert::TryFrom; 6 | use libipld_core::cid::Cid; 7 | use libipld_core::codec::{Codec, Decode, Encode, References}; 8 | use libipld_core::error::{Result, UnsupportedCodec}; 9 | use libipld_core::ipld::Ipld; 10 | // TODO vmx 2020-05-28: Don't expose the `serde_json` error directly, but wrap it in a custom one 11 | pub use serde_json::Error; 12 | use std::io::{Read, Seek, Write}; 13 | 14 | mod codec; 15 | 16 | /// Json codec. 17 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] 18 | pub struct DagJsonCodec; 19 | 20 | impl Codec for DagJsonCodec {} 21 | 22 | impl From for u64 { 23 | fn from(_: DagJsonCodec) -> Self { 24 | 0x0129 25 | } 26 | } 27 | 28 | impl TryFrom for DagJsonCodec { 29 | type Error = UnsupportedCodec; 30 | 31 | fn try_from(_: u64) -> core::result::Result { 32 | Ok(Self) 33 | } 34 | } 35 | 36 | impl Encode for Ipld { 37 | fn encode(&self, _: DagJsonCodec, w: &mut W) -> Result<()> { 38 | Ok(codec::encode(self, w)?) 39 | } 40 | } 41 | 42 | impl Decode for Ipld { 43 | fn decode(_: DagJsonCodec, r: &mut R) -> Result { 44 | Ok(codec::decode(r)?) 45 | } 46 | } 47 | 48 | impl References for Ipld { 49 | fn references>( 50 | c: DagJsonCodec, 51 | r: &mut R, 52 | set: &mut E, 53 | ) -> Result<()> { 54 | Ipld::decode(c, r)?.references(set); 55 | Ok(()) 56 | } 57 | } 58 | 59 | #[cfg(test)] 60 | mod tests { 61 | use super::*; 62 | use libipld_core::cid::Cid; 63 | use libipld_core::multihash::{Code, MultihashDigest}; 64 | use std::collections::BTreeMap; 65 | 66 | #[test] 67 | fn encode_struct() { 68 | let digest = Code::Blake3_256.digest(&b"block"[..]); 69 | let cid = Cid::new_v1(0x55, digest); 70 | 71 | // Create a contact object that looks like: 72 | // Contact { name: "Hello World", details: CID } 73 | let mut map = BTreeMap::new(); 74 | map.insert("name".to_string(), Ipld::String("Hello World!".to_string())); 75 | map.insert("details".to_string(), Ipld::Link(cid)); 76 | let contact = Ipld::Map(map); 77 | 78 | let contact_encoded = DagJsonCodec.encode(&contact).unwrap(); 79 | println!("encoded: {:02x?}", contact_encoded); 80 | println!( 81 | "encoded string {}", 82 | std::str::from_utf8(&contact_encoded).unwrap() 83 | ); 84 | 85 | assert_eq!( 86 | std::str::from_utf8(&contact_encoded).unwrap(), 87 | format!(r#"{{"details":{{"/":"{}"}},"name":"Hello World!"}}"#, cid) 88 | ); 89 | 90 | let contact_decoded: Ipld = DagJsonCodec.decode(&contact_encoded).unwrap(); 91 | assert_eq!(contact_decoded, contact); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /dag-pb/tests/compat.rs: -------------------------------------------------------------------------------- 1 | use libipld::{cid::Cid, ipld, prelude::Codec, Ipld, IpldCodec}; 2 | 3 | struct TestCase { 4 | name: &'static str, 5 | node: Ipld, 6 | expected_bytes: &'static str, 7 | } 8 | 9 | #[test] 10 | fn test_compat_roundtrip() { 11 | // Cases based on 12 | // https://github.com/ipld/go-codec-dagpb/blob/master/compat_test.go 13 | 14 | let empty_links = Vec::::new(); 15 | let data_some = vec![0, 1, 2, 3, 4]; 16 | let acid = Cid::try_from(&[1, 85, 0, 5, 0, 1, 2, 3, 4][..]).unwrap(); 17 | let zero_name = ""; 18 | let some_name = "some name"; 19 | let zero_tsize: u64 = 0; 20 | let large_tsize: u64 = 9007199254740991; // JavaScript Number.MAX_SAFE_INTEGER 21 | 22 | let cases = [ 23 | TestCase { 24 | name: "Links zero", 25 | node: ipld!({ "Links": empty_links.clone() }), 26 | expected_bytes: "", 27 | }, 28 | TestCase { 29 | name: "Data some Links zero", 30 | node: ipld!({ 31 | "Links": empty_links, 32 | "Data": data_some 33 | }), 34 | expected_bytes: "0a050001020304", 35 | }, 36 | TestCase { 37 | name: "Links Hash some", 38 | node: ipld!({ 39 | "Links": vec![ipld!({ "Hash": acid})], 40 | }), 41 | expected_bytes: "120b0a09015500050001020304", 42 | }, 43 | TestCase { 44 | name: "Links Hash some Name zero", 45 | node: ipld!({ 46 | "Links": vec![ipld!({ 47 | "Hash": acid, 48 | "Name": zero_name, 49 | })] 50 | }), 51 | expected_bytes: "120d0a090155000500010203041200", 52 | }, 53 | TestCase { 54 | name: "Links Hash some Name some", 55 | node: ipld!({ 56 | "Links": vec![ipld!({ 57 | "Hash": acid, 58 | "Name": some_name, 59 | })] 60 | }), 61 | expected_bytes: "12160a090155000500010203041209736f6d65206e616d65", 62 | }, 63 | TestCase { 64 | name: "Links Hash some Tsize zero", 65 | node: ipld!({ 66 | "Links": vec![ipld!({ 67 | "Hash": acid, 68 | "Tsize": zero_tsize, 69 | })] 70 | }), 71 | expected_bytes: "120d0a090155000500010203041800", 72 | }, 73 | TestCase { 74 | name: "Links Hash some Tsize some", 75 | node: ipld!({ 76 | "Links": vec![ipld!({ 77 | "Hash": acid, 78 | "Tsize": large_tsize, 79 | })] 80 | }), 81 | expected_bytes: "12140a0901550005000102030418ffffffffffffff0f", 82 | }, 83 | ]; 84 | 85 | for case in cases { 86 | println!("case {}", case.name); 87 | let result = IpldCodec::DagPb.encode(&case.node).unwrap(); 88 | assert_eq!(result, hex::decode(case.expected_bytes).unwrap()); 89 | 90 | let ipld: Ipld = IpldCodec::DagPb.decode(&result).unwrap(); 91 | assert_eq!(ipld, case.node); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/path.rs: -------------------------------------------------------------------------------- 1 | //! Path 2 | use crate::cid::Cid; 3 | 4 | /// Represents a path in an ipld dag. 5 | #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] 6 | pub struct Path(Vec); 7 | 8 | impl Path { 9 | /// Iterate over path segments. 10 | pub fn iter(&self) -> impl Iterator { 11 | self.0.iter().map(|s| &**s) 12 | } 13 | 14 | /// Join segment. 15 | pub fn join>(&mut self, segment: T) { 16 | for seg in segment.as_ref().split('/').filter(|s| !s.is_empty()) { 17 | self.0.push(seg.to_owned()) 18 | } 19 | } 20 | } 21 | 22 | impl From> for Path { 23 | fn from(segments: Vec) -> Self { 24 | Path(segments) 25 | } 26 | } 27 | 28 | impl From> for Path { 29 | fn from(segments: Vec<&str>) -> Self { 30 | Path(segments.into_iter().map(String::from).collect()) 31 | } 32 | } 33 | 34 | impl From<&str> for Path { 35 | fn from(s: &str) -> Self { 36 | let mut path = Path::default(); 37 | path.join(s); 38 | path 39 | } 40 | } 41 | 42 | impl From for Path { 43 | fn from(s: String) -> Self { 44 | Path::from(s.as_str()) 45 | } 46 | } 47 | 48 | impl ToString for Path { 49 | fn to_string(&self) -> String { 50 | let mut path = "".to_string(); 51 | for seg in &self.0 { 52 | path.push_str(seg.as_str()); 53 | path.push('/'); 54 | } 55 | path.pop(); 56 | path 57 | } 58 | } 59 | 60 | /// Path in a dag. 61 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 62 | pub struct DagPath<'a>(&'a Cid, Path); 63 | 64 | impl<'a> DagPath<'a> { 65 | /// Create a new dag path. 66 | pub fn new>(cid: &'a Cid, path: T) -> Self { 67 | Self(cid, path.into()) 68 | } 69 | 70 | /// Returns the root of the path. 71 | pub fn root(&self) -> &Cid { 72 | self.0 73 | } 74 | 75 | /// Returns the ipld path. 76 | pub fn path(&self) -> &Path { 77 | &self.1 78 | } 79 | } 80 | 81 | impl<'a> From<&'a Cid> for DagPath<'a> { 82 | fn from(cid: &'a Cid) -> Self { 83 | Self(cid, Default::default()) 84 | } 85 | } 86 | 87 | #[cfg(test)] 88 | mod tests { 89 | use super::*; 90 | 91 | #[test] 92 | fn test_parsing_one_segment() { 93 | assert_eq!(Path::from("0"), Path::from(vec!["0"])); 94 | } 95 | 96 | #[test] 97 | fn test_parsing_three_segments() { 98 | assert_eq!(Path::from("0/foo/2"), Path::from(vec!["0", "foo", "2"])); 99 | } 100 | 101 | #[test] 102 | fn test_eliding_empty_segments() { 103 | assert_eq!(Path::from("0//2"), Path::from(vec!["0", "2"])); 104 | } 105 | 106 | #[test] 107 | fn test_eliding_leading_slashes() { 108 | assert_eq!(Path::from("/0/2"), Path::from(vec!["0", "2"])); 109 | } 110 | 111 | #[test] 112 | fn test_eliding_trailing_slashes() { 113 | assert_eq!(Path::from("0/2/"), Path::from(vec!["0", "2"])); 114 | } 115 | 116 | #[test] 117 | fn test_to_string() { 118 | assert_eq!(Path::from(vec!["0", "foo", "2"]).to_string(), "0/foo/2"); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /dag-cbor/src/error.rs: -------------------------------------------------------------------------------- 1 | //! CBOR error types. 2 | use std::any::type_name; 3 | use thiserror::Error; 4 | 5 | /// Number larger than u64. 6 | #[derive(Debug, Error)] 7 | #[error("Number larger than {ty}.")] 8 | pub struct NumberOutOfRange { 9 | /// Type. 10 | pub ty: &'static str, 11 | } 12 | 13 | impl NumberOutOfRange { 14 | /// Creates a new `NumberOutOfRange` error. 15 | pub fn new() -> Self { 16 | Self { 17 | ty: type_name::(), 18 | } 19 | } 20 | } 21 | 22 | /// Number is not minimally encoded. 23 | #[derive(Debug, Error)] 24 | #[error("Number not minimally encoded.")] 25 | pub struct NumberNotMinimal; 26 | 27 | /// Length larger than usize or too small, for example zero length cid field. 28 | #[derive(Debug, Error)] 29 | #[error("Length out of range when decoding {ty}.")] 30 | pub struct LengthOutOfRange { 31 | /// Type. 32 | pub ty: &'static str, 33 | } 34 | 35 | impl LengthOutOfRange { 36 | /// Creates a new `LengthOutOfRange` error. 37 | pub fn new() -> Self { 38 | Self { 39 | ty: type_name::(), 40 | } 41 | } 42 | } 43 | 44 | /// Unexpected cbor code. 45 | #[derive(Debug, Error)] 46 | #[error("Unexpected cbor code `0x{code:x}` when decoding `{ty}`.")] 47 | pub struct UnexpectedCode { 48 | /// Code. 49 | pub code: u8, 50 | /// Type. 51 | pub ty: &'static str, 52 | } 53 | 54 | impl UnexpectedCode { 55 | /// Creates a new `UnexpectedCode` error. 56 | pub fn new(code: u8) -> Self { 57 | Self { 58 | code, 59 | ty: type_name::(), 60 | } 61 | } 62 | } 63 | 64 | /// Unexpected key. 65 | #[derive(Debug, Error)] 66 | #[error("Unexpected key `{key}` when decoding `{ty}`.")] 67 | pub struct UnexpectedKey { 68 | /// Key. 69 | pub key: String, 70 | /// Type. 71 | pub ty: &'static str, 72 | } 73 | 74 | impl UnexpectedKey { 75 | /// Creates a new `UnexpectedKey` error. 76 | pub fn new(key: String) -> Self { 77 | Self { 78 | key, 79 | ty: type_name::(), 80 | } 81 | } 82 | } 83 | 84 | /// Missing key. 85 | #[derive(Debug, Error)] 86 | #[error("Missing key `{key}` for decoding `{ty}`.")] 87 | pub struct MissingKey { 88 | /// Key. 89 | pub key: &'static str, 90 | /// Type. 91 | pub ty: &'static str, 92 | } 93 | 94 | impl MissingKey { 95 | /// Creates a new `MissingKey` error. 96 | pub fn new(key: &'static str) -> Self { 97 | Self { 98 | key, 99 | ty: type_name::(), 100 | } 101 | } 102 | } 103 | 104 | /// Unknown cbor tag. 105 | #[derive(Debug, Error)] 106 | #[error("Unknown cbor tag `{0}`.")] 107 | pub struct UnknownTag(pub u64); 108 | 109 | /// Unexpected eof. 110 | #[derive(Debug, Error)] 111 | #[error("Unexpected end of file.")] 112 | pub struct UnexpectedEof; 113 | 114 | /// The byte before Cid was not multibase identity prefix. 115 | #[derive(Debug, Error)] 116 | #[error("Invalid Cid prefix: {0}")] 117 | pub struct InvalidCidPrefix(pub u8); 118 | 119 | /// A duplicate key within a map. 120 | #[derive(Debug, Error)] 121 | #[error("Duplicate map key.")] 122 | pub struct DuplicateKey; 123 | -------------------------------------------------------------------------------- /core/src/raw.rs: -------------------------------------------------------------------------------- 1 | //! Implements the raw codec. 2 | use alloc::{boxed::Box, vec, vec::Vec}; 3 | use core::{convert::TryFrom, iter::Extend}; 4 | 5 | use crate::cid::Cid; 6 | use crate::codec::{Codec, Decode, Encode, References}; 7 | use crate::error::{Result, UnsupportedCodec}; 8 | use crate::io::{Read, Seek, Write}; 9 | use crate::ipld::Ipld; 10 | 11 | /// Raw codec. 12 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] 13 | pub struct RawCodec; 14 | 15 | impl Codec for RawCodec {} 16 | 17 | impl From for u64 { 18 | fn from(_: RawCodec) -> Self { 19 | 0x55 20 | } 21 | } 22 | 23 | impl TryFrom for RawCodec { 24 | type Error = UnsupportedCodec; 25 | 26 | fn try_from(_: u64) -> core::result::Result { 27 | Ok(Self) 28 | } 29 | } 30 | 31 | impl Encode for [u8] { 32 | fn encode(&self, _: RawCodec, w: &mut W) -> Result<()> { 33 | w.write_all(self).map_err(anyhow::Error::msg) 34 | } 35 | } 36 | 37 | impl Encode for Box<[u8]> { 38 | fn encode(&self, _: RawCodec, w: &mut W) -> Result<()> { 39 | w.write_all(&self[..]).map_err(anyhow::Error::msg) 40 | } 41 | } 42 | 43 | impl Encode for Vec { 44 | fn encode(&self, _: RawCodec, w: &mut W) -> Result<()> { 45 | w.write_all(&self[..]).map_err(anyhow::Error::msg) 46 | } 47 | } 48 | 49 | impl Encode for Ipld { 50 | fn encode(&self, c: RawCodec, w: &mut W) -> Result<()> { 51 | if let Ipld::Bytes(bytes) = self { 52 | bytes.encode(c, w) 53 | } else { 54 | Err(anyhow::Error::msg(crate::error::TypeError::new( 55 | crate::error::TypeErrorType::Bytes, 56 | self, 57 | ))) 58 | } 59 | } 60 | } 61 | 62 | impl Decode for Box<[u8]> { 63 | fn decode(c: RawCodec, r: &mut R) -> Result { 64 | let buf: Vec = Decode::decode(c, r)?; 65 | Ok(buf.into_boxed_slice()) 66 | } 67 | } 68 | 69 | impl Decode for Vec { 70 | fn decode(_: RawCodec, r: &mut R) -> Result { 71 | let mut buf = vec![]; 72 | r.read_to_end(&mut buf).map_err(anyhow::Error::msg)?; 73 | Ok(buf) 74 | } 75 | } 76 | 77 | impl Decode for Ipld { 78 | fn decode(c: RawCodec, r: &mut R) -> Result { 79 | let bytes: Vec = Decode::decode(c, r)?; 80 | Ok(Ipld::Bytes(bytes)) 81 | } 82 | } 83 | 84 | impl References for T { 85 | fn references>(_c: RawCodec, _r: &mut R, _set: &mut E) -> Result<()> { 86 | Ok(()) 87 | } 88 | } 89 | 90 | #[cfg(test)] 91 | mod tests { 92 | use super::*; 93 | 94 | #[test] 95 | fn test_raw_codec() { 96 | let data: &[u8] = &[0, 1, 2, 3]; 97 | let bytes = RawCodec.encode(data).unwrap(); 98 | assert_eq!(data, &*bytes); 99 | let data2: Vec = RawCodec.decode(&bytes).unwrap(); 100 | assert_eq!(data, &*data2); 101 | 102 | let ipld = Ipld::Bytes(data2); 103 | let bytes = RawCodec.encode(&ipld).unwrap(); 104 | assert_eq!(data, &*bytes); 105 | let ipld2: Ipld = RawCodec.decode(&bytes).unwrap(); 106 | assert_eq!(ipld, ipld2); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /dag-cbor/src/cbor.rs: -------------------------------------------------------------------------------- 1 | //! CBOR helper types for encoding and decoding. 2 | use std::convert::TryFrom; 3 | 4 | use crate::error::UnexpectedCode; 5 | use libipld_core::ipld::Ipld; 6 | 7 | /// Represents a major "byte". This includes both the major bits and the additional info. 8 | #[repr(transparent)] 9 | #[derive(Clone, Copy, Eq, PartialEq)] 10 | pub struct Major(u8); 11 | 12 | /// The constant TRUE. 13 | pub const FALSE: Major = Major::new(MajorKind::Other, 20); 14 | /// The constant FALSE. 15 | pub const TRUE: Major = Major::new(MajorKind::Other, 21); 16 | /// The constant NULL. 17 | pub const NULL: Major = Major::new(MajorKind::Other, 22); 18 | /// The major "byte" indicating that a 16 bit float follows. 19 | pub const F16: Major = Major::new(MajorKind::Other, 25); 20 | /// The major "byte" indicating that a 32 bit float follows. 21 | pub const F32: Major = Major::new(MajorKind::Other, 26); 22 | /// The major "byte" indicating that a 64 bit float follows. 23 | pub const F64: Major = Major::new(MajorKind::Other, 27); 24 | 25 | impl Major { 26 | const fn new(kind: MajorKind, info: u8) -> Self { 27 | Major(((kind as u8) << 5) | info) 28 | } 29 | 30 | /// Returns the major type. 31 | #[inline(always)] 32 | pub const fn kind(self) -> MajorKind { 33 | // This is a 3 bit value, so value 0-7 are covered. 34 | unsafe { std::mem::transmute(self.0 >> 5) } 35 | } 36 | 37 | /// Returns the additional info. 38 | #[inline(always)] 39 | pub const fn info(self) -> u8 { 40 | self.0 & 0x1f 41 | } 42 | 43 | /// Interprets the additioanl info as a number of additional bytes that should be consumed. 44 | #[inline(always)] 45 | #[allow(clippy::len_without_is_empty)] 46 | pub const fn len(self) -> u8 { 47 | // All major types follow the same rules for "additioanl bytes". 48 | // 24 -> 1, 25 -> 2, 26 -> 4, 27 -> 8 49 | match self.info() { 50 | info @ 24..=27 => 1 << (info - 24), 51 | _ => 0, 52 | } 53 | } 54 | } 55 | 56 | impl From for u8 { 57 | fn from(m: Major) -> u8 { 58 | m.0 59 | } 60 | } 61 | 62 | // This is the core of the validation logic. Every major type passes through here giving us a chance 63 | // to determine if it's something we allow. 64 | impl TryFrom for Major { 65 | type Error = UnexpectedCode; 66 | fn try_from(value: u8) -> Result { 67 | // We don't allow any major types with additional info 28-31 inclusive. 68 | // Or the bitmask 0b00011100 = 28. 69 | if value & 28 == 28 { 70 | return Err(UnexpectedCode::new::(value)); 71 | } else if (value >> 5) == MajorKind::Other as u8 { 72 | match value & 0x1f { 73 | // False, True, Null. TODO: Allow undefined? 74 | 20..=22 => (), 75 | // Floats. TODO: forbid f16 & f32? 76 | 25..=27 => (), 77 | // Everything is forbidden. 78 | _ => { 79 | return Err(UnexpectedCode::new::(value)); 80 | } 81 | } 82 | } 83 | Ok(Major(value)) 84 | } 85 | } 86 | 87 | /// The type 88 | #[repr(u8)] 89 | #[derive(Clone, Copy, Eq, PartialEq)] 90 | #[allow(dead_code)] 91 | pub enum MajorKind { 92 | /// Non-negative integer (major type 0). 93 | UnsignedInt = 0, 94 | /// Negative integer (major type 1). 95 | NegativeInt = 1, 96 | /// Byte string (major type 2). 97 | ByteString = 2, 98 | /// Unicode text string (major type 3). 99 | TextString = 3, 100 | /// Array (major type 4). 101 | Array = 4, 102 | /// Map (major type 5). 103 | Map = 5, 104 | /// Tag (major type 6). 105 | Tag = 6, 106 | /// Other (major type 7). 107 | Other = 7, 108 | } 109 | -------------------------------------------------------------------------------- /core/tests/serde_serialize.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "serde-codec")] 2 | 3 | extern crate alloc; 4 | 5 | use alloc::collections::BTreeMap; 6 | use core::convert::TryFrom; 7 | 8 | use serde_test::{assert_ser_tokens, Token}; 9 | 10 | use libipld_core::cid::{serde::CID_SERDE_PRIVATE_IDENTIFIER, Cid}; 11 | use libipld_core::ipld::Ipld; 12 | 13 | #[test] 14 | fn ipld_serialize_null() { 15 | let ipld = Ipld::Null; 16 | assert_ser_tokens(&ipld, &[Token::None]); 17 | } 18 | 19 | #[test] 20 | fn ipld_serialize_bool() { 21 | let bool = true; 22 | let ipld = Ipld::Bool(bool); 23 | assert_ser_tokens(&ipld, &[Token::Bool(bool)]); 24 | } 25 | 26 | // NOTE vmx 2022-02-15: assert_ser_tokens doesn't support i128 27 | //#[test] 28 | //fn ipld_serialize_integer() { 29 | // let integer = 32u8; 30 | // let ipld = Ipld::Integer(integer.into()); 31 | //} 32 | 33 | #[test] 34 | fn ipld_serialize_float() { 35 | let float = 32.41f32; 36 | let ipld = Ipld::Float(float.into()); 37 | assert_ser_tokens(&ipld, &[Token::F64(float.into())]); 38 | } 39 | 40 | #[test] 41 | fn ipld_serialize_string() { 42 | let string = "hello"; 43 | let ipld = Ipld::String(string.into()); 44 | assert_ser_tokens(&ipld, &[Token::Str(string)]); 45 | assert_ser_tokens(&ipld, &[Token::BorrowedStr(string)]); 46 | assert_ser_tokens(&ipld, &[Token::String(string)]); 47 | } 48 | 49 | #[test] 50 | fn ipld_serialize_bytes() { 51 | let bytes = vec![0x68, 0x65, 0x6c, 0x6c, 0x6f]; 52 | let ipld = Ipld::Bytes(bytes); 53 | assert_ser_tokens(&ipld, &[Token::Bytes(b"hello")]); 54 | assert_ser_tokens(&ipld, &[Token::BorrowedBytes(b"hello")]); 55 | assert_ser_tokens(&ipld, &[Token::ByteBuf(b"hello")]); 56 | } 57 | 58 | #[test] 59 | fn ipld_serialize_list() { 60 | let ipld = Ipld::List(vec![Ipld::Bool(false), Ipld::Float(22.7)]); 61 | assert_ser_tokens( 62 | &ipld, 63 | &[ 64 | Token::Seq { len: Some(2) }, 65 | Token::Bool(false), 66 | Token::F64(22.7), 67 | Token::SeqEnd, 68 | ], 69 | ); 70 | } 71 | 72 | #[test] 73 | fn ipld_serialize_map() { 74 | let ipld = Ipld::Map(BTreeMap::from([ 75 | ("hello".to_string(), Ipld::Bool(true)), 76 | ("world!".to_string(), Ipld::Bool(false)), 77 | ])); 78 | assert_ser_tokens( 79 | &ipld, 80 | &[ 81 | Token::Map { len: Some(2) }, 82 | Token::Str("hello"), 83 | Token::Bool(true), 84 | Token::Str("world!"), 85 | Token::Bool(false), 86 | Token::MapEnd, 87 | ], 88 | ); 89 | } 90 | 91 | #[test] 92 | fn ipld_serialize_link() { 93 | let cid = Cid::try_from("bafkreie74tgmnxqwojhtumgh5dzfj46gi4mynlfr7dmm7duwzyvnpw7h7m").unwrap(); 94 | let ipld = Ipld::Link(cid); 95 | assert_ser_tokens( 96 | &ipld, 97 | &[ 98 | Token::NewtypeStruct { 99 | name: CID_SERDE_PRIVATE_IDENTIFIER, 100 | }, 101 | Token::Bytes(&[ 102 | 1, 85, 18, 32, 159, 228, 204, 198, 222, 22, 114, 79, 58, 48, 199, 232, 242, 84, 103 | 243, 198, 71, 25, 134, 172, 177, 248, 216, 207, 142, 150, 206, 42, 215, 219, 231, 104 | 251, 105 | ]), 106 | ], 107 | ); 108 | } 109 | 110 | #[test] 111 | #[should_panic(expected = "expected Token::Bytes")] 112 | fn ipld_serialize_link_not_as_bytes() { 113 | let cid = Cid::try_from("bafkreie74tgmnxqwojhtumgh5dzfj46gi4mynlfr7dmm7duwzyvnpw7h7m").unwrap(); 114 | let ipld = Ipld::Link(cid); 115 | assert_ser_tokens( 116 | &ipld, 117 | &[Token::Bytes(&[ 118 | 1, 85, 18, 32, 159, 228, 204, 198, 222, 22, 114, 79, 58, 48, 199, 232, 242, 84, 243, 119 | 198, 71, 25, 134, 172, 177, 248, 216, 207, 142, 150, 206, 42, 215, 219, 231, 251, 120 | ])], 121 | ); 122 | } 123 | -------------------------------------------------------------------------------- /dag-cbor/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! CBOR codec. 2 | #![deny(missing_docs)] 3 | #![deny(warnings)] 4 | 5 | use core::convert::TryFrom; 6 | use libipld_core::codec::{Codec, Decode, Encode}; 7 | pub use libipld_core::error::{Result, UnsupportedCodec}; 8 | 9 | pub mod cbor; 10 | pub mod decode; 11 | pub mod encode; 12 | pub mod error; 13 | 14 | /// CBOR codec. 15 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] 16 | pub struct DagCborCodec; 17 | 18 | impl Codec for DagCborCodec {} 19 | 20 | impl From for u64 { 21 | fn from(_: DagCborCodec) -> Self { 22 | 0x71 23 | } 24 | } 25 | 26 | impl TryFrom for DagCborCodec { 27 | type Error = UnsupportedCodec; 28 | 29 | fn try_from(_: u64) -> core::result::Result { 30 | Ok(Self) 31 | } 32 | } 33 | 34 | /// Marker trait for types supporting the `DagCborCodec`. 35 | pub trait DagCbor: Encode + Decode {} 36 | 37 | impl + Decode> DagCbor for T {} 38 | 39 | #[cfg(test)] 40 | mod tests { 41 | use super::*; 42 | use libipld_core::cid::Cid; 43 | use libipld_core::codec::assert_roundtrip; 44 | use libipld_core::ipld::Ipld; 45 | use libipld_core::multihash::{Code, MultihashDigest}; 46 | use libipld_macro::ipld; 47 | use std::collections::HashSet; 48 | 49 | #[test] 50 | fn test_encode_decode_cbor() { 51 | let cid = Cid::new_v1(0, Code::Blake3_256.digest(&b"cid"[..])); 52 | let ipld = ipld!({ 53 | "number": 1, 54 | "list": [true, null, false], 55 | "bytes": vec![0, 1, 2, 3], 56 | "map": { "float": 0.0, "string": "hello" }, 57 | "link": cid, 58 | }); 59 | let bytes = DagCborCodec.encode(&ipld).unwrap(); 60 | let ipld2 = DagCborCodec.decode(&bytes).unwrap(); 61 | assert_eq!(ipld, ipld2); 62 | } 63 | 64 | #[test] 65 | fn test_references() { 66 | let cid = Cid::new_v1(0, Code::Blake3_256.digest(&b"0"[..])); 67 | let ipld = ipld!({ 68 | "list": [true, cid], 69 | }); 70 | let bytes = DagCborCodec.encode(&ipld).unwrap(); 71 | let mut set = HashSet::new(); 72 | DagCborCodec 73 | .references::(&bytes, &mut set) 74 | .unwrap(); 75 | assert!(set.contains(&cid)); 76 | } 77 | 78 | #[test] 79 | fn test_encode_max() { 80 | assert_roundtrip(DagCborCodec, &i8::MAX, &Ipld::Integer(i8::MAX as i128)); 81 | assert_roundtrip(DagCborCodec, &i16::MAX, &Ipld::Integer(i16::MAX as i128)); 82 | assert_roundtrip(DagCborCodec, &i32::MAX, &Ipld::Integer(i32::MAX as i128)); 83 | assert_roundtrip(DagCborCodec, &i64::MAX, &Ipld::Integer(i64::MAX as i128)); 84 | assert_roundtrip(DagCborCodec, &u8::MAX, &Ipld::Integer(u8::MAX as i128)); 85 | assert_roundtrip(DagCborCodec, &u16::MAX, &Ipld::Integer(u16::MAX as i128)); 86 | assert_roundtrip(DagCborCodec, &u32::MAX, &Ipld::Integer(u32::MAX as i128)); 87 | assert_roundtrip(DagCborCodec, &u64::MAX, &Ipld::Integer(u64::MAX as i128)); 88 | } 89 | 90 | #[test] 91 | fn test_encode_min() { 92 | assert_roundtrip(DagCborCodec, &i8::MIN, &Ipld::Integer(i8::MIN as i128)); 93 | assert_roundtrip(DagCborCodec, &i16::MIN, &Ipld::Integer(i16::MIN as i128)); 94 | assert_roundtrip(DagCborCodec, &i32::MIN, &Ipld::Integer(i32::MIN as i128)); 95 | assert_roundtrip(DagCborCodec, &i64::MIN, &Ipld::Integer(i64::MIN as i128)); 96 | assert_roundtrip(DagCborCodec, &u8::MIN, &Ipld::Integer(u8::MIN as i128)); 97 | assert_roundtrip(DagCborCodec, &u16::MIN, &Ipld::Integer(u16::MIN as i128)); 98 | assert_roundtrip(DagCborCodec, &u32::MIN, &Ipld::Integer(u32::MIN as i128)); 99 | assert_roundtrip(DagCborCodec, &u64::MIN, &Ipld::Integer(u64::MIN as i128)); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /dag-cbor/tests/roundtrip.rs: -------------------------------------------------------------------------------- 1 | use libipld_cbor::DagCborCodec; 2 | use libipld_core::{ 3 | codec::{assert_roundtrip, Codec, Decode, Encode}, 4 | ipld::Ipld, 5 | raw_value::{IgnoredAny, RawValue, SkipOne}, 6 | }; 7 | use std::{io::Cursor, result}; 8 | 9 | #[test] 10 | fn roundtrip_with_cid() { 11 | // generated with go-ipfs 12 | // $ echo foobar > file1 13 | // $ ipfs add foobar 14 | // QmRgutAxd8t7oGkSm4wmeuByG6M51wcTso6cubDdQtuEfL 15 | // $ echo -n '{ "foo": { "/": "QmRgutAxd8t7oGkSm4wmeuByG6M51wcTso6cubDdQtuEfL" } }' \ 16 | // | ipfs dag put 17 | // bafyreibvjvcv745gig4mvqs4hctx4zfkono4rjejm2ta6gtyzkqxfjeily 18 | // $ ipfs block get bafyreibvjvcv745gig4mvqs4hctx4zfkono4rjejm2ta6gtyzkqxfjeily \ 19 | // | xxd -ps | paste -s --delimiters= 20 | 21 | let input = 22 | "a163666f6fd82a582300122031c3d57080d8463a3c63b2923df5a1d40ad7a73eae5a14af584213e5f504ac33"; 23 | let input = hex::decode(input).unwrap(); 24 | 25 | let ipld: Ipld = DagCborCodec.decode(&input).unwrap(); 26 | let bytes = DagCborCodec.encode(&ipld).unwrap().to_vec(); 27 | 28 | assert_eq!(input, bytes); 29 | } 30 | 31 | #[test] 32 | #[should_panic] 33 | fn invalid_cid_prefix() { 34 | let input = 35 | "a163666f6fd82a582301122031c3d57080d8463a3c63b2923df5a1d40ad7a73eae5a14af584213e5f504ac33"; 36 | let input = hex::decode(input).unwrap(); 37 | let _: Ipld = DagCborCodec.decode(&input).unwrap(); 38 | } 39 | 40 | #[test] 41 | #[should_panic] 42 | fn zero_length_cid() { 43 | let input = "a163666f6fd82a5800"; 44 | let input = hex::decode(input).unwrap(); 45 | let _: Ipld = DagCborCodec.decode(&input).unwrap(); 46 | } 47 | 48 | // 3x some cbor and then some garbage 49 | fn cbor_seq() -> Vec { 50 | let mut buf = Vec::new(); 51 | 1u8.encode(DagCborCodec, &mut buf).unwrap(); 52 | (u16::MAX as u64 + 1) 53 | .encode(DagCborCodec, &mut buf) 54 | .unwrap(); 55 | vec![String::from("foo")] 56 | .encode(DagCborCodec, &mut buf) 57 | .unwrap(); 58 | buf.extend_from_slice(&[0xff, 0xff, 0xff, 0xff]); 59 | buf 60 | } 61 | 62 | // test SkipOne trait for cbor 63 | #[test] 64 | fn skip() { 65 | let input = cbor_seq(); 66 | let mut r = Cursor::new(&input); 67 | DagCborCodec.skip(&mut r).unwrap(); 68 | assert_eq!(r.position(), 1); 69 | DagCborCodec.skip(&mut r).unwrap(); 70 | assert_eq!(r.position(), 6); 71 | DagCborCodec.skip(&mut r).unwrap(); 72 | assert_eq!(r.position(), 11); 73 | assert!(DagCborCodec.skip(&mut r).is_err()); 74 | } 75 | 76 | // test IgnoredAny, which does use skip internally 77 | #[test] 78 | fn ignored_any() { 79 | // 3x some cbor and then some garbage 80 | let input = cbor_seq(); 81 | let mut r = Cursor::new(&input); 82 | let _x: IgnoredAny = Decode::decode(DagCborCodec, &mut r).unwrap(); 83 | assert_eq!(r.position(), 1); 84 | let _x: IgnoredAny = Decode::decode(DagCborCodec, &mut r).unwrap(); 85 | assert_eq!(r.position(), 6); 86 | let _x: IgnoredAny = Decode::decode(DagCborCodec, &mut r).unwrap(); 87 | assert_eq!(r.position(), 11); 88 | let r: result::Result = Decode::decode(DagCborCodec, &mut r); 89 | assert!(r.is_err()); 90 | } 91 | 92 | // test RawValue, which does use skip internally 93 | #[test] 94 | fn raw_value() { 95 | // 3x some cbor and then some garbage 96 | let input = cbor_seq(); 97 | let mut r = Cursor::new(&input); 98 | let raw: RawValue = Decode::decode(DagCborCodec, &mut r).unwrap(); 99 | assert_eq!(r.position(), 1); 100 | assert_eq!(raw.as_ref(), &input[0..1]); 101 | let raw: RawValue = Decode::decode(DagCborCodec, &mut r).unwrap(); 102 | assert_eq!(r.position(), 6); 103 | assert_eq!(raw.as_ref(), &input[1..6]); 104 | let raw: RawValue = Decode::decode(DagCborCodec, &mut r).unwrap(); 105 | assert_eq!(r.position(), 11); 106 | assert_eq!(raw.as_ref(), &input[6..11]); 107 | let r: result::Result, _> = Decode::decode(DagCborCodec, &mut r); 108 | assert!(r.is_err()); 109 | } 110 | 111 | #[test] 112 | #[should_panic] 113 | fn test_assert_roundtrip() { 114 | assert_roundtrip(DagCborCodec, &1u64, &Ipld::Integer(2)); 115 | } 116 | -------------------------------------------------------------------------------- /dag-cbor-derive/tests/struct.rs: -------------------------------------------------------------------------------- 1 | use libipld::cbor::{DagCbor, DagCborCodec}; 2 | use libipld::codec::assert_roundtrip; 3 | use libipld::{ipld, DagCbor}; 4 | 5 | #[derive(Clone, Copy, DagCbor, Debug, Eq, PartialEq)] 6 | #[ipld(repr = "map")] 7 | pub struct Map { 8 | boolean: bool, 9 | } 10 | 11 | #[test] 12 | fn struct_map() { 13 | assert_roundtrip( 14 | DagCborCodec, 15 | &Map { boolean: true }, 16 | &ipld!({"boolean": true}), 17 | ); 18 | assert_roundtrip( 19 | DagCborCodec, 20 | &Map { boolean: false }, 21 | &ipld!({"boolean": false}), 22 | ); 23 | } 24 | 25 | #[derive(Clone, Copy, DagCbor, Debug, Eq, PartialEq)] 26 | pub struct Rename { 27 | #[ipld(rename = "bool")] 28 | boolean: bool, 29 | } 30 | 31 | #[test] 32 | fn struct_rename() { 33 | assert_roundtrip( 34 | DagCborCodec, 35 | &Rename { boolean: true }, 36 | &ipld!({"bool": true}), 37 | ); 38 | assert_roundtrip( 39 | DagCborCodec, 40 | &Rename { boolean: false }, 41 | &ipld!({"bool": false}), 42 | ); 43 | } 44 | 45 | #[derive(Clone, Copy, DagCbor, Debug, Eq, PartialEq)] 46 | pub struct Nullable { 47 | nullable: Option, 48 | } 49 | 50 | #[test] 51 | fn struct_nullable() { 52 | assert_roundtrip( 53 | DagCborCodec, 54 | &Nullable { 55 | nullable: Some(true), 56 | }, 57 | &ipld!({"nullable": true}), 58 | ); 59 | assert_roundtrip( 60 | DagCborCodec, 61 | &Nullable { 62 | nullable: Some(false), 63 | }, 64 | &ipld!({"nullable": false}), 65 | ); 66 | assert_roundtrip( 67 | DagCborCodec, 68 | &Nullable { nullable: None }, 69 | &ipld!({ "nullable": null }), 70 | ); 71 | } 72 | 73 | #[derive(Clone, Copy, DagCbor, Debug, Eq, PartialEq)] 74 | pub struct Implicit { 75 | #[ipld(default = false)] 76 | default: bool, 77 | } 78 | 79 | #[test] 80 | fn struct_implicit() { 81 | assert_roundtrip( 82 | DagCborCodec, 83 | &Implicit { default: true }, 84 | &ipld!({"default": true}), 85 | ); 86 | assert_roundtrip(DagCborCodec, &Implicit { default: false }, &ipld!({})); 87 | } 88 | 89 | #[derive(Clone, Copy, DagCbor, Debug, Eq, PartialEq)] 90 | pub struct OptionalNullable { 91 | #[ipld(default = None)] 92 | nullable: Option, 93 | } 94 | 95 | #[test] 96 | fn struct_optional_nullable() { 97 | assert_roundtrip( 98 | DagCborCodec, 99 | &OptionalNullable { 100 | nullable: Some(true), 101 | }, 102 | &ipld!({"nullable": true}), 103 | ); 104 | assert_roundtrip( 105 | DagCborCodec, 106 | &OptionalNullable { 107 | nullable: Some(false), 108 | }, 109 | &ipld!({"nullable": false}), 110 | ); 111 | assert_roundtrip( 112 | DagCborCodec, 113 | &OptionalNullable { nullable: None }, 114 | &ipld!({}), 115 | ); 116 | } 117 | 118 | #[derive(Clone, Copy, DagCbor, Debug, Eq, PartialEq)] 119 | #[ipld(repr = "tuple")] 120 | pub struct Tuple(bool); 121 | 122 | #[test] 123 | fn struct_tuple() { 124 | assert_roundtrip(DagCborCodec, &Tuple(true), &ipld!([true])); 125 | assert_roundtrip(DagCborCodec, &Tuple(false), &ipld!([false])); 126 | } 127 | 128 | #[derive(Clone, Copy, DagCbor, Debug, Eq, PartialEq)] 129 | pub struct TupleNullable(Option); 130 | 131 | #[test] 132 | fn struct_tuple_nullable() { 133 | assert_roundtrip(DagCborCodec, &TupleNullable(Some(true)), &ipld!([true])); 134 | assert_roundtrip(DagCborCodec, &TupleNullable(Some(false)), &ipld!([false])); 135 | assert_roundtrip(DagCborCodec, &TupleNullable(None), &ipld!([null])); 136 | } 137 | 138 | #[derive(Clone, Copy, DagCbor, Debug, Eq, PartialEq)] 139 | #[ipld(repr = "value")] 140 | pub struct Value(bool); 141 | 142 | #[test] 143 | fn struct_value() { 144 | assert_roundtrip(DagCborCodec, &Value(true), &ipld!(true)); 145 | assert_roundtrip(DagCborCodec, &Value(false), &ipld!(false)); 146 | } 147 | 148 | #[derive(Clone, Copy, DagCbor, Debug, Eq, PartialEq)] 149 | pub struct IlMap { 150 | #[ipld(rename = "Fun")] 151 | fun: bool, 152 | #[ipld(rename = "Amt")] 153 | amt: i32, 154 | } 155 | 156 | #[derive(DagCbor)] 157 | pub struct Generic(T); 158 | -------------------------------------------------------------------------------- /core/src/error.rs: -------------------------------------------------------------------------------- 1 | //! `Ipld` error definitions. 2 | #[cfg(feature = "serde-codec")] 3 | use alloc::string::ToString; 4 | use alloc::{string::String, vec::Vec}; 5 | 6 | use crate::cid::Cid; 7 | use crate::ipld::{Ipld, IpldIndex}; 8 | pub use anyhow::{Error, Result}; 9 | #[cfg(feature = "std")] 10 | use thiserror::Error; 11 | 12 | /// Block exceeds 1MiB. 13 | #[derive(Clone, Copy, Debug)] 14 | #[cfg_attr(feature = "std", derive(Error), error("Block size {0} exceeds 1MiB."))] 15 | pub struct BlockTooLarge(pub usize); 16 | 17 | /// The codec is unsupported. 18 | #[derive(Clone, Copy, Debug)] 19 | #[cfg_attr(feature = "std", derive(Error), error("Unsupported codec {0:?}."))] 20 | pub struct UnsupportedCodec(pub u64); 21 | 22 | /// The multihash is unsupported. 23 | #[derive(Clone, Copy, Debug)] 24 | #[cfg_attr(feature = "std", derive(Error), error("Unsupported multihash {0:?}."))] 25 | pub struct UnsupportedMultihash(pub u64); 26 | 27 | /// Hash does not match the CID. 28 | #[derive(Clone, Debug)] 29 | #[cfg_attr( 30 | feature = "std", 31 | derive(Error), 32 | error("Hash of data does not match the CID.") 33 | )] 34 | pub struct InvalidMultihash(pub Vec); 35 | 36 | /// The block wasn't found. The supplied string is a CID. 37 | #[derive(Clone, Copy, Debug)] 38 | #[cfg_attr(feature = "std", derive(Error), error("Failed to retrieve block {0}."))] 39 | pub struct BlockNotFound(pub Cid); 40 | 41 | /// Error during Serde operations. 42 | #[cfg(feature = "serde-codec")] 43 | #[derive(Clone, Debug)] 44 | pub struct SerdeError(String); 45 | 46 | #[cfg(feature = "serde-codec")] 47 | impl core::fmt::Display for SerdeError { 48 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 49 | write!(f, "Serde error: {}", self.0) 50 | } 51 | } 52 | 53 | #[cfg(feature = "serde-codec")] 54 | impl serde::de::Error for SerdeError { 55 | fn custom(msg: T) -> Self { 56 | Self(msg.to_string()) 57 | } 58 | } 59 | 60 | #[cfg(feature = "serde-codec")] 61 | impl serde::ser::Error for SerdeError { 62 | fn custom(msg: T) -> Self { 63 | Self(msg.to_string()) 64 | } 65 | } 66 | 67 | #[cfg(feature = "serde-codec")] 68 | impl serde::ser::StdError for SerdeError {} 69 | 70 | /// Type error. 71 | #[derive(Clone, Debug)] 72 | #[cfg_attr( 73 | feature = "std", 74 | derive(Error), 75 | error("Expected {expected:?} but found {found:?}") 76 | )] 77 | pub struct TypeError { 78 | /// The expected type. 79 | pub expected: TypeErrorType, 80 | /// The actual type. 81 | pub found: TypeErrorType, 82 | } 83 | 84 | impl TypeError { 85 | /// Creates a new type error. 86 | pub fn new, B: Into>(expected: A, found: B) -> Self { 87 | Self { 88 | expected: expected.into(), 89 | found: found.into(), 90 | } 91 | } 92 | } 93 | 94 | #[cfg(not(feature = "std"))] 95 | impl core::fmt::Display for TypeError { 96 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 97 | write!(f, "Expected {:?} but found {:?}", self.expected, self.found) 98 | } 99 | } 100 | 101 | /// Type error type. 102 | #[derive(Clone, Debug)] 103 | pub enum TypeErrorType { 104 | /// Null type. 105 | Null, 106 | /// Boolean type. 107 | Bool, 108 | /// Integer type. 109 | Integer, 110 | /// Float type. 111 | Float, 112 | /// String type. 113 | String, 114 | /// Bytes type. 115 | Bytes, 116 | /// List type. 117 | List, 118 | /// Map type. 119 | Map, 120 | /// Link type. 121 | Link, 122 | /// Key type. 123 | Key(String), 124 | /// Index type. 125 | Index(usize), 126 | } 127 | 128 | impl From for TypeErrorType { 129 | fn from(ipld: Ipld) -> Self { 130 | Self::from(&ipld) 131 | } 132 | } 133 | 134 | impl From<&Ipld> for TypeErrorType { 135 | fn from(ipld: &Ipld) -> Self { 136 | match ipld { 137 | Ipld::Null => Self::Null, 138 | Ipld::Bool(_) => Self::Bool, 139 | Ipld::Integer(_) => Self::Integer, 140 | Ipld::Float(_) => Self::Float, 141 | Ipld::String(_) => Self::String, 142 | Ipld::Bytes(_) => Self::Bytes, 143 | Ipld::List(_) => Self::List, 144 | Ipld::Map(_) => Self::Map, 145 | Ipld::Link(_) => Self::Link, 146 | } 147 | } 148 | } 149 | 150 | impl From> for TypeErrorType { 151 | fn from(index: IpldIndex<'_>) -> Self { 152 | match index { 153 | IpldIndex::List(i) => Self::Index(i), 154 | IpldIndex::Map(s) => Self::Key(s), 155 | IpldIndex::MapRef(s) => Self::Key(s.into()), 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /dag-cbor/tests/serde_interop.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use libipld_cbor::DagCborCodec; 4 | use libipld_core::{codec::Codec, raw_value::RawValue}; 5 | use quickcheck::{empty_shrinker, Arbitrary, Gen, QuickCheck}; 6 | 7 | #[test] 8 | fn raw_serde_roundtrip_test() { 9 | QuickCheck::new().quickcheck(raw_serde_roundtrip as fn(ValueArb) -> bool) 10 | } 11 | 12 | fn raw_serde_roundtrip(input: ValueArb) -> bool { 13 | let bytes = serde_cbor::to_vec(&input.0).unwrap(); 14 | let value: RawValue = DagCborCodec.decode(&bytes).unwrap(); 15 | let bytes2 = DagCborCodec.encode(&value).unwrap(); 16 | 17 | let output: serde_cbor::Value = serde_cbor::from_slice(&bytes2[..]).unwrap(); 18 | input.0 == output 19 | } 20 | 21 | #[derive(Clone, Debug)] 22 | struct ValueArb(pub serde_cbor::Value); 23 | impl Arbitrary for ValueArb { 24 | fn arbitrary(g: &mut Gen) -> Self { 25 | Self(gen_value(g, 2)) 26 | } 27 | fn shrink(&self) -> Box> { 28 | match &self.0 { 29 | Value::Null => empty_shrinker(), 30 | Value::Bool(v) => Box::new(Arbitrary::shrink(v).map(Value::Bool).map(Self)), 31 | Value::Integer(v) => Box::new( 32 | Arbitrary::shrink(&(*v as i64)) 33 | .map(|i| Value::Integer(i as i128)) 34 | .map(Self), 35 | ), 36 | Value::Float(f) => Box::new(Arbitrary::shrink(f).map(Value::Float).map(Self)), 37 | Value::Bytes(v) => Box::new(Arbitrary::shrink(v).map(Value::Bytes).map(Self)), 38 | Value::Text(v) => Box::new(Arbitrary::shrink(v).map(Value::Text).map(Self)), 39 | Value::Array(a) => Box::new( 40 | a.clone() 41 | .into_iter() 42 | .map(|x| { 43 | let slf = Self(x); 44 | slf.shrink().map(|x| x.0).collect::>() 45 | }) 46 | .map(Value::Array) 47 | .map(Self), 48 | ), 49 | Value::Map(m) => Box::new( 50 | m.clone() 51 | .into_iter() 52 | .map(|(v0, v1)| { 53 | let iter2 = Self(v1.clone()); 54 | Self(v0.clone()) 55 | .shrink() 56 | .map(move |n| (n.0, v1.clone())) 57 | .chain(iter2.shrink().map(move |n| (v0.clone(), n.0))) 58 | .collect() 59 | }) 60 | .map(Value::Map) 61 | .map(Self), 62 | ), 63 | 64 | Value::Tag(t, v) => { 65 | let v = v.clone(); 66 | let vc = v.clone(); 67 | let t = *t; 68 | Box::new( 69 | t.shrink() 70 | .map(move |n| (n, vc.clone())) 71 | .chain(Self(*v).shrink().map(move |n| (t, Box::new(n.0)))) 72 | .map(|(t, b)| Value::Tag(t, b)) 73 | .map(Self), 74 | ) 75 | } 76 | _ => unreachable!(), 77 | } 78 | } 79 | } 80 | 81 | use serde_cbor::Value; 82 | fn gen_value(g: &mut Gen, depth: u16) -> Value { 83 | let upper = if depth > 0 { 8 } else { 5 }; 84 | match gen_range(g, 0, upper) { 85 | 0 => Value::Null, 86 | 1 => Value::Bool(Arbitrary::arbitrary(g)), 87 | // Range 2^64-1 to -2^64 88 | 2 => { 89 | let sgn = bool::arbitrary(g); 90 | let v = u64::arbitrary(g) as i128; 91 | Value::Integer(if sgn { v } else { -v }) 92 | } 93 | 3 => Value::Float(f64::arbitrary(g)), 94 | 4 => Value::Bytes(Arbitrary::arbitrary(g)), 95 | 5 => Value::Text(Arbitrary::arbitrary(g)), 96 | // recursive variants 97 | 6 => Value::Array(gen_vec(g, depth - 1)), 98 | 7 => Value::Map(gen_map(g, depth - 1)), 99 | 8 => Value::Tag(u64::arbitrary(g), Box::new(gen_value(g, depth - 1))), 100 | _ => unreachable!(), 101 | } 102 | } 103 | 104 | fn gen_range(g: &mut Gen, lower: usize, upper: usize) -> usize { 105 | assert!(lower < upper); 106 | let range = (0..=upper).collect::>(); 107 | *g.choose(&range[..]).unwrap() 108 | } 109 | 110 | fn gen_vec(g: &mut Gen, depth: u16) -> Vec { 111 | let size = gen_range(g, 0, g.size()); 112 | (0..size).map(|_| gen_value(g, depth)).collect() 113 | } 114 | 115 | fn gen_map(g: &mut Gen, depth: u16) -> BTreeMap { 116 | let size = gen_range(g, 0, g.size()); 117 | (0..size) 118 | .map(|_| (gen_value(g, depth), gen_value(g, depth))) 119 | .collect() 120 | } 121 | -------------------------------------------------------------------------------- /core/src/serde/mod.rs: -------------------------------------------------------------------------------- 1 | //! Serde (de)serialization for [`crate::ipld::Ipld`]. 2 | //! 3 | //! This implementation enables Serde to serialize to/deserialize from [`crate::ipld::Ipld`] 4 | //! values. The `Ipld` enum is similar to the `Value` enum in `serde_json` or `serde_cbor`. 5 | mod de; 6 | mod ser; 7 | 8 | pub use de::from_ipld; 9 | pub use ser::{to_ipld, Serializer}; 10 | 11 | #[cfg(test)] 12 | mod tests { 13 | use std::collections::BTreeMap; 14 | use std::convert::TryFrom; 15 | use std::fmt; 16 | 17 | use cid::serde::CID_SERDE_PRIVATE_IDENTIFIER; 18 | use cid::Cid; 19 | use serde::{de::DeserializeOwned, Deserialize, Serialize}; 20 | use serde_test::{assert_tokens, Token}; 21 | 22 | use crate::ipld::Ipld; 23 | use crate::serde::{from_ipld, to_ipld}; 24 | 25 | /// Utility for testing (de)serialization of [`Ipld`]. 26 | /// 27 | /// Checks if `data` and `ipld` match if they are encoded into each other. 28 | fn assert_roundtrip(data: &T, ipld: &Ipld) 29 | where 30 | T: Serialize + DeserializeOwned + PartialEq + fmt::Debug, 31 | { 32 | let encoded: Ipld = to_ipld(data).unwrap(); 33 | assert_eq!(&encoded, ipld); 34 | let decoded: T = from_ipld(ipld.clone()).unwrap(); 35 | assert_eq!(&decoded, data); 36 | } 37 | 38 | #[derive(Debug, Deserialize, PartialEq, Serialize)] 39 | struct Person { 40 | name: String, 41 | age: u8, 42 | hobbies: Vec, 43 | is_cool: bool, 44 | link: Cid, 45 | } 46 | 47 | impl Default for Person { 48 | fn default() -> Self { 49 | Self { 50 | name: "Hello World!".into(), 51 | age: 52, 52 | hobbies: vec!["geography".into(), "programming".into()], 53 | is_cool: true, 54 | link: Cid::try_from("bafyreibvjvcv745gig4mvqs4hctx4zfkono4rjejm2ta6gtyzkqxfjeily") 55 | .unwrap(), 56 | } 57 | } 58 | } 59 | 60 | #[test] 61 | fn test_tokens() { 62 | let person = Person::default(); 63 | 64 | assert_tokens( 65 | &person, 66 | &[ 67 | Token::Struct { 68 | name: "Person", 69 | len: 5, 70 | }, 71 | Token::Str("name"), 72 | Token::Str("Hello World!"), 73 | Token::Str("age"), 74 | Token::U8(52), 75 | Token::Str("hobbies"), 76 | Token::Seq { len: Some(2) }, 77 | Token::Str("geography"), 78 | Token::Str("programming"), 79 | Token::SeqEnd, 80 | Token::Str("is_cool"), 81 | Token::Bool(true), 82 | Token::Str("link"), 83 | Token::NewtypeStruct { 84 | name: CID_SERDE_PRIVATE_IDENTIFIER, 85 | }, 86 | Token::Bytes(&[ 87 | 0x01, 0x71, 0x12, 0x20, 0x35, 0x4d, 0x45, 0x5f, 0xf3, 0xa6, 0x41, 0xb8, 0xca, 88 | 0xc2, 0x5c, 0x38, 0xa7, 0x7e, 0x64, 0xaa, 0x73, 0x5d, 0xc8, 0xa4, 0x89, 0x66, 89 | 0xa6, 0xf, 0x1a, 0x78, 0xca, 0xa1, 0x72, 0xa4, 0x88, 0x5e, 90 | ]), 91 | Token::StructEnd, 92 | ], 93 | ); 94 | } 95 | 96 | /// Test if converting to a struct from [`crate::ipld::Ipld`] and back works. 97 | #[test] 98 | fn test_ipld() { 99 | let person = Person::default(); 100 | 101 | let expected_ipld = Ipld::Map({ 102 | BTreeMap::from([ 103 | ("name".into(), Ipld::String("Hello World!".into())), 104 | ("age".into(), Ipld::Integer(52)), 105 | ( 106 | "hobbies".into(), 107 | Ipld::List(vec![ 108 | Ipld::String("geography".into()), 109 | Ipld::String("programming".into()), 110 | ]), 111 | ), 112 | ("is_cool".into(), Ipld::Bool(true)), 113 | ("link".into(), Ipld::Link(person.link)), 114 | ]) 115 | }); 116 | 117 | assert_roundtrip(&person, &expected_ipld); 118 | } 119 | 120 | /// Test that deserializing arbitrary bytes are not accidentally recognized as CID. 121 | #[test] 122 | fn test_bytes_not_cid() { 123 | let cid = 124 | Cid::try_from("bafyreibvjvcv745gig4mvqs4hctx4zfkono4rjejm2ta6gtyzkqxfjeily").unwrap(); 125 | 126 | let bytes_not_cid = Ipld::Bytes(cid.to_bytes()); 127 | let not_a_cid: Result = from_ipld(bytes_not_cid); 128 | assert!(not_a_cid.is_err()); 129 | 130 | // Make sure that a Ipld::Link deserializes correctly though. 131 | let link = Ipld::Link(cid); 132 | let a_cid: Cid = from_ipld(link).unwrap(); 133 | assert_eq!(a_cid, cid); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /core/tests/serde_deserialize.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "serde-codec")] 2 | 3 | extern crate alloc; 4 | 5 | use alloc::collections::BTreeMap; 6 | use core::convert::TryFrom; 7 | 8 | use serde_test::{assert_de_tokens, Token}; 9 | 10 | use libipld_core::cid::{serde::CID_SERDE_PRIVATE_IDENTIFIER, Cid}; 11 | use libipld_core::ipld::Ipld; 12 | 13 | #[test] 14 | fn ipld_deserialize_null() { 15 | let ipld = Ipld::Null; 16 | assert_de_tokens(&ipld, &[Token::None]); 17 | } 18 | 19 | #[test] 20 | #[should_panic(expected = "invalid type")] 21 | fn ipld_deserialize_null_not_as_unit() { 22 | let ipld = Ipld::Null; 23 | assert_de_tokens(&ipld, &[Token::Unit]); 24 | } 25 | 26 | #[test] 27 | #[should_panic(expected = "invalid type")] 28 | fn ipld_deserialize_null_not_as_unit_struct() { 29 | let ipld = Ipld::Null; 30 | assert_de_tokens(&ipld, &[Token::UnitStruct { name: "foo" }]); 31 | } 32 | 33 | #[test] 34 | fn ipld_deserialize_bool() { 35 | let bool = true; 36 | let ipld = Ipld::Bool(bool); 37 | assert_de_tokens(&ipld, &[Token::Bool(bool)]); 38 | } 39 | 40 | #[test] 41 | fn ipld_deserialize_integer_u() { 42 | let integer = 32u8; 43 | let ipld = Ipld::Integer(integer.into()); 44 | assert_de_tokens(&ipld, &[Token::U8(integer)]); 45 | assert_de_tokens(&ipld, &[Token::U16(integer.into())]); 46 | assert_de_tokens(&ipld, &[Token::U32(integer.into())]); 47 | assert_de_tokens(&ipld, &[Token::U64(integer.into())]); 48 | } 49 | 50 | #[test] 51 | fn ipld_deserialize_integer_i() { 52 | let integer = -32i8; 53 | let ipld = Ipld::Integer(integer.into()); 54 | assert_de_tokens(&ipld, &[Token::I8(integer)]); 55 | assert_de_tokens(&ipld, &[Token::I16(integer.into())]); 56 | assert_de_tokens(&ipld, &[Token::I32(integer.into())]); 57 | assert_de_tokens(&ipld, &[Token::I64(integer.into())]); 58 | } 59 | 60 | #[test] 61 | fn ipld_deserialize_float() { 62 | let float = 32.41f32; 63 | let ipld = Ipld::Float(float.into()); 64 | assert_de_tokens(&ipld, &[Token::F32(float)]); 65 | assert_de_tokens(&ipld, &[Token::F64(float.into())]); 66 | } 67 | 68 | #[test] 69 | fn ipld_deserialize_string() { 70 | let string = "hello"; 71 | let ipld = Ipld::String(string.into()); 72 | assert_de_tokens(&ipld, &[Token::Str(string)]); 73 | assert_de_tokens(&ipld, &[Token::BorrowedStr(string)]); 74 | assert_de_tokens(&ipld, &[Token::String(string)]); 75 | } 76 | 77 | #[test] 78 | fn ipld_deserialize_string_char() { 79 | let char = 'h'; 80 | let ipld = Ipld::String(char.into()); 81 | assert_de_tokens(&ipld, &[Token::Char(char)]); 82 | } 83 | 84 | #[test] 85 | fn ipld_deserialize_bytes() { 86 | let bytes = vec![0x68, 0x65, 0x6c, 0x6c, 0x6f]; 87 | let ipld = Ipld::Bytes(bytes); 88 | assert_de_tokens(&ipld, &[Token::Bytes(b"hello")]); 89 | assert_de_tokens(&ipld, &[Token::BorrowedBytes(b"hello")]); 90 | assert_de_tokens(&ipld, &[Token::ByteBuf(b"hello")]); 91 | } 92 | 93 | #[test] 94 | fn ipld_deserialize_list() { 95 | let ipld = Ipld::List(vec![Ipld::Bool(false), Ipld::Float(22.7)]); 96 | assert_de_tokens( 97 | &ipld, 98 | &[ 99 | Token::Seq { len: Some(2) }, 100 | Token::Bool(false), 101 | Token::F64(22.7), 102 | Token::SeqEnd, 103 | ], 104 | ); 105 | } 106 | 107 | #[test] 108 | fn ipld_deserialize_map() { 109 | let ipld = Ipld::Map(BTreeMap::from([ 110 | ("hello".to_string(), Ipld::Bool(true)), 111 | ("world!".to_string(), Ipld::Bool(false)), 112 | ])); 113 | assert_de_tokens( 114 | &ipld, 115 | &[ 116 | Token::Map { len: Some(2) }, 117 | Token::Str("hello"), 118 | Token::Bool(true), 119 | Token::Str("world!"), 120 | Token::Bool(false), 121 | Token::MapEnd, 122 | ], 123 | ); 124 | } 125 | 126 | #[test] 127 | fn ipld_deserialize_link() { 128 | let cid = Cid::try_from("bafkreie74tgmnxqwojhtumgh5dzfj46gi4mynlfr7dmm7duwzyvnpw7h7m").unwrap(); 129 | let ipld = Ipld::Link(cid); 130 | assert_de_tokens( 131 | &ipld, 132 | &[ 133 | Token::NewtypeStruct { 134 | name: CID_SERDE_PRIVATE_IDENTIFIER, 135 | }, 136 | Token::Bytes(&[ 137 | 1, 85, 18, 32, 159, 228, 204, 198, 222, 22, 114, 79, 58, 48, 199, 232, 242, 84, 138 | 243, 198, 71, 25, 134, 172, 177, 248, 216, 207, 142, 150, 206, 42, 215, 219, 231, 139 | 251, 140 | ]), 141 | ], 142 | ); 143 | } 144 | 145 | #[test] 146 | #[should_panic] 147 | fn ipld_deserialize_link_not_as_bytes() { 148 | let cid = Cid::try_from("bafkreie74tgmnxqwojhtumgh5dzfj46gi4mynlfr7dmm7duwzyvnpw7h7m").unwrap(); 149 | let ipld = Ipld::Link(cid); 150 | assert_de_tokens( 151 | &ipld, 152 | &[Token::Bytes(&[ 153 | 1, 85, 18, 32, 159, 228, 204, 198, 222, 22, 114, 79, 58, 48, 199, 232, 242, 84, 243, 154 | 198, 71, 25, 134, 172, 177, 248, 216, 207, 142, 150, 206, 42, 215, 219, 231, 251, 155 | ])], 156 | ); 157 | } 158 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: Rust IPLD 4 | 5 | jobs: 6 | ci: 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | toolchain: 11 | - rust: stable 12 | #- rust: nightly 13 | platform: 14 | - target: x86_64-unknown-linux-gnu 15 | host: ubuntu-latest 16 | cross: false 17 | 18 | - target: x86_64-apple-darwin 19 | host: macos-latest 20 | cross: false 21 | 22 | - target: x86_64-pc-windows-msvc 23 | host: windows-latest 24 | cross: false 25 | 26 | - target: armv7-linux-androideabi 27 | host: ubuntu-latest 28 | cross: true 29 | - target: aarch64-linux-android 30 | host: ubuntu-latest 31 | cross: true 32 | 33 | - target: aarch64-apple-ios 34 | host: macos-latest 35 | cross: true 36 | 37 | - target: wasm32-unknown-unknown 38 | host: ubuntu-latest 39 | cross: true 40 | env: 41 | RUST_BACKTRACE: 1 42 | CARGO_INCREMENTAL: 0 43 | LLVM_CONFIG_PATH: /usr/local/opt/llvm/bin/llvm-config 44 | NDK_HOME: /usr/local/lib/android/sdk/ndk-bundle 45 | 46 | runs-on: ${{ matrix.platform.host }} 47 | steps: 48 | - name: Checkout sources 49 | uses: actions/checkout@v3 50 | 51 | - name: Cache cargo folder 52 | uses: actions/cache@v3 53 | with: 54 | path: ~/.cargo 55 | key: ${{ matrix.platform.target }}-cargo-${{ matrix.toolchain.rust }} 56 | 57 | - name: Install dependencies ubuntu 58 | if: matrix.platform.host == 'ubuntu-latest' 59 | run: sudo apt-get install llvm-dev 60 | 61 | - name: Install dependencies macos 62 | if: matrix.platform.host == 'macos-latest' 63 | run: brew install llvm 64 | 65 | - name: Install dependencies windows 66 | if: matrix.platform.host == 'windows-latest' 67 | run: choco install llvm 68 | 69 | - name: Install rust toolchain 70 | uses: hecrj/setup-rust-action@v1 71 | with: 72 | rust-version: ${{ matrix.toolchain.rust }} 73 | targets: ${{ matrix.platform.target }} 74 | 75 | - name: Install cargo-apk 76 | if: contains(matrix.platform.target, 'android') 77 | uses: baptiste0928/cargo-install@30f432979e99f3ea66a8fa2eede53c07063995d8 # v2.1.0 78 | with: 79 | crate: cargo-apk 80 | 81 | - name: Build 82 | if: contains(matrix.platform.target, 'android') == false && contains(matrix.platform.target, 'wasm') == false 83 | run: cargo build --workspace --all-features --target ${{ matrix.platform.target }} 84 | 85 | - name: Build android 86 | if: contains(matrix.platform.target, 'android') 87 | run: | 88 | cargo apk -- build --all-features --target ${{ matrix.platform.target }} -p libipld-core 89 | cargo apk -- build --all-features --target ${{ matrix.platform.target }} -p libipld-cbor 90 | cargo apk -- build --all-features --target ${{ matrix.platform.target }} -p libipld-macro 91 | cargo apk -- build --all-features --target ${{ matrix.platform.target }} -p libipld-cbor-derive 92 | cargo apk -- build --all-features --target ${{ matrix.platform.target }} -p libipld 93 | cargo apk -- build --all-features --target ${{ matrix.platform.target }} -p libipld-json 94 | cargo apk -- build --all-features --target ${{ matrix.platform.target }} -p libipld-pb 95 | 96 | - name: Build WASM 97 | if: contains(matrix.platform.target, 'wasm') 98 | run: cargo build --workspace --features std,default,dag-cbor,dag-json,dag-pb,derive,serde-codec --target ${{ matrix.platform.target }} 99 | 100 | - name: Rust tests 101 | if: matrix.platform.cross == false 102 | run: cargo test --workspace --all-features 103 | 104 | lint-rust: 105 | runs-on: ubuntu-latest 106 | steps: 107 | - name: Checkout sources 108 | uses: actions/checkout@v3 109 | 110 | - name: Cache cargo folder 111 | uses: actions/cache@v3 112 | with: 113 | path: ~/.cargo 114 | key: lint-cargo 115 | 116 | - name: Install rust toolchain 117 | uses: hecrj/setup-rust-action@v1 118 | with: 119 | rust-version: stable 120 | components: clippy, rustfmt 121 | 122 | - name: cargo fmt 123 | run: cargo fmt --all -- --check 124 | 125 | - name: cargo clippy 126 | run: cargo clippy --workspace --all-features --examples --tests -- -D warnings 127 | 128 | build-no-std: 129 | name: Build no_std (libipld-core) 130 | runs-on: ubuntu-latest 131 | steps: 132 | - name: Checkout Sources 133 | uses: actions/checkout@v3 134 | 135 | - name: Install Rust Toolchain 136 | uses: dtolnay/rust-toolchain@stable 137 | with: 138 | targets: thumbv6m-none-eabi 139 | 140 | - name: Build 141 | run: cargo build --no-default-features --target thumbv6m-none-eabi --manifest-path core/Cargo.toml 142 | shell: bash 143 | 144 | build-no-std-serde: 145 | name: Build no_std (libipld-core), but with the `serde-codec` feature enabled 146 | runs-on: ubuntu-latest 147 | steps: 148 | - name: Checkout Sources 149 | uses: actions/checkout@v3 150 | 151 | - name: Install Rust Toolchain 152 | uses: dtolnay/rust-toolchain@stable 153 | 154 | - name: Build 155 | # `thumbv6m-none-eabi` can't be used as Serde doesn't compile there. 156 | run: cargo build --no-default-features --features serde-codec --manifest-path core/Cargo.toml 157 | -------------------------------------------------------------------------------- /core/src/codec.rs: -------------------------------------------------------------------------------- 1 | //! `Ipld` codecs. 2 | use alloc::{string::String, vec::Vec}; 3 | use core::{convert::TryFrom, fmt::Write as _}; 4 | 5 | use crate::cid::Cid; 6 | use crate::error::{Result, UnsupportedCodec}; 7 | use crate::io::{Cursor, Read, Seek, Write}; 8 | use crate::ipld::Ipld; 9 | 10 | /// Codec trait. 11 | pub trait Codec: 12 | Copy + Unpin + Send + Sync + 'static + Sized + TryFrom + Into 13 | { 14 | /// Encodes an encodable type. 15 | fn encode + ?Sized>(&self, obj: &T) -> Result> { 16 | let mut buf = Vec::with_capacity(u16::MAX as usize); 17 | obj.encode(*self, &mut buf)?; 18 | Ok(buf) 19 | } 20 | 21 | /// Decodes a decodable type. 22 | fn decode>(&self, bytes: &[u8]) -> Result { 23 | T::decode(*self, &mut Cursor::new(bytes)) 24 | } 25 | 26 | /// Scrapes the references. 27 | fn references, E: Extend>( 28 | &self, 29 | bytes: &[u8], 30 | set: &mut E, 31 | ) -> Result<()> { 32 | T::references(*self, &mut Cursor::new(bytes), set) 33 | } 34 | } 35 | 36 | /// Encode trait. 37 | /// 38 | /// This trait is generic over a codec, so that different codecs can be implemented for the same 39 | /// type. 40 | pub trait Encode { 41 | /// Encodes into a `impl Write`. 42 | /// 43 | /// It takes a specific codec as parameter, so that the [`Encode`] can be generic over an enum 44 | /// that contains multiple codecs. 45 | fn encode(&self, c: C, w: &mut W) -> Result<()>; 46 | } 47 | 48 | impl> Encode for &T { 49 | fn encode(&self, c: C, w: &mut W) -> Result<()> { 50 | T::encode(*self, c, w) 51 | } 52 | } 53 | 54 | /// Decode trait. 55 | /// 56 | /// This trait is generic over a codec, so that different codecs can be implemented for the same 57 | /// type. 58 | pub trait Decode: Sized { 59 | /// Decode from an `impl Read`. 60 | /// 61 | /// It takes a specific codec as parameter, so that the [`Decode`] can be generic over an enum 62 | /// that contains multiple codecs. 63 | fn decode(c: C, r: &mut R) -> Result; 64 | } 65 | 66 | /// References trait. 67 | /// 68 | /// This trait is generic over a codec, so that different codecs can be implemented for the same 69 | /// type. 70 | pub trait References: Sized { 71 | /// Scrape the references from an `impl Read`. 72 | /// 73 | /// It takes a specific codec as parameter, so that the [`References`] can be generic over an 74 | /// enum that contains multiple codecs. 75 | fn references>(c: C, r: &mut R, set: &mut E) -> Result<()>; 76 | } 77 | 78 | /// Utility for testing codecs. 79 | /// 80 | /// Encodes the `data` using the codec `c` and checks that it matches the `ipld`. 81 | pub fn assert_roundtrip(c: C, data: &T, ipld: &Ipld) 82 | where 83 | C: Codec, 84 | T: Decode + Encode + core::fmt::Debug + PartialEq, 85 | Ipld: Decode + Encode, 86 | { 87 | fn hex(bytes: &[u8]) -> String { 88 | bytes.iter().fold(String::new(), |mut output, byte| { 89 | let _ = write!(output, "{byte:02x}"); 90 | output 91 | }) 92 | } 93 | let mut bytes = Vec::new(); 94 | data.encode(c, &mut bytes).unwrap(); 95 | let mut bytes2 = Vec::new(); 96 | ipld.encode(c, &mut bytes2).unwrap(); 97 | if bytes != bytes2 { 98 | panic!( 99 | r#"assertion failed: `(left == right)` 100 | left: `{}`, 101 | right: `{}`"#, 102 | hex(&bytes), 103 | hex(&bytes2) 104 | ); 105 | } 106 | let ipld2: Ipld = Decode::decode(c, &mut Cursor::new(bytes.as_slice())).unwrap(); 107 | assert_eq!(&ipld2, ipld); 108 | let data2: T = Decode::decode(c, &mut Cursor::new(bytes.as_slice())).unwrap(); 109 | assert_eq!(&data2, data); 110 | } 111 | 112 | #[cfg(test)] 113 | mod tests { 114 | use super::*; 115 | use crate::ipld::Ipld; 116 | use anyhow::anyhow; 117 | 118 | #[derive(Clone, Copy, Debug)] 119 | struct CodecImpl; 120 | 121 | impl Codec for CodecImpl {} 122 | 123 | impl From for u64 { 124 | fn from(_: CodecImpl) -> Self { 125 | 0 126 | } 127 | } 128 | 129 | impl TryFrom for CodecImpl { 130 | type Error = UnsupportedCodec; 131 | 132 | fn try_from(_: u64) -> core::result::Result { 133 | Ok(Self) 134 | } 135 | } 136 | 137 | impl Encode for Ipld { 138 | fn encode(&self, _: CodecImpl, w: &mut W) -> Result<()> { 139 | match self { 140 | Self::Null => Ok(w.write_all(&[0]).map_err(anyhow::Error::msg)?), 141 | _ => Err(anyhow!("not null")), 142 | } 143 | } 144 | } 145 | 146 | impl Decode for Ipld { 147 | fn decode(_: CodecImpl, r: &mut R) -> Result { 148 | let mut buf = [0; 1]; 149 | r.read_exact(&mut buf).map_err(anyhow::Error::msg)?; 150 | if buf[0] == 0 { 151 | Ok(Ipld::Null) 152 | } else { 153 | Err(anyhow!("not null")) 154 | } 155 | } 156 | } 157 | 158 | #[test] 159 | fn test_codec() { 160 | let bytes = CodecImpl.encode(&Ipld::Null).unwrap(); 161 | let ipld: Ipld = CodecImpl.decode(&bytes).unwrap(); 162 | assert_eq!(ipld, Ipld::Null); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/block.rs: -------------------------------------------------------------------------------- 1 | //! Block validation 2 | use crate::cid::Cid; 3 | use crate::codec::{Codec, Decode, Encode, References}; 4 | use crate::error::{BlockTooLarge, InvalidMultihash, Result, UnsupportedMultihash}; 5 | use crate::ipld::Ipld; 6 | use crate::multihash::MultihashDigest; 7 | use crate::store::StoreParams; 8 | use core::borrow::Borrow; 9 | use core::convert::TryFrom; 10 | use core::marker::PhantomData; 11 | use core::ops::Deref; 12 | 13 | /// Block 14 | #[derive(Clone)] 15 | pub struct Block { 16 | _marker: PhantomData, 17 | /// Content identifier. 18 | cid: Cid, 19 | /// Binary data. 20 | data: Vec, 21 | } 22 | 23 | impl core::fmt::Debug for Block { 24 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 25 | f.debug_struct("Block") 26 | .field("cid", &self.cid) 27 | .field("data", &self.data) 28 | .finish() 29 | } 30 | } 31 | 32 | impl Deref for Block { 33 | type Target = Cid; 34 | 35 | fn deref(&self) -> &Self::Target { 36 | &self.cid 37 | } 38 | } 39 | 40 | impl core::hash::Hash for Block { 41 | fn hash(&self, hasher: &mut SH) { 42 | core::hash::Hash::hash(&self.cid, hasher) 43 | } 44 | } 45 | 46 | impl PartialEq for Block { 47 | fn eq(&self, other: &Self) -> bool { 48 | self.cid == other.cid 49 | } 50 | } 51 | 52 | impl Eq for Block {} 53 | 54 | impl Borrow for Block { 55 | fn borrow(&self) -> &Cid { 56 | &self.cid 57 | } 58 | } 59 | 60 | impl AsRef for Block { 61 | fn as_ref(&self) -> &Cid { 62 | &self.cid 63 | } 64 | } 65 | 66 | impl AsRef<[u8]> for Block { 67 | fn as_ref(&self) -> &[u8] { 68 | &self.data 69 | } 70 | } 71 | 72 | // TODO: move to tiny_cid 73 | fn verify_cid, const S: usize>(cid: &Cid, payload: &[u8]) -> Result<()> { 74 | let mh = M::try_from(cid.hash().code()) 75 | .map_err(|_| UnsupportedMultihash(cid.hash().code()))? 76 | .digest(payload); 77 | 78 | if mh.digest() != cid.hash().digest() { 79 | return Err(InvalidMultihash(mh.to_bytes()).into()); 80 | } 81 | Ok(()) 82 | } 83 | 84 | impl Block { 85 | /// Creates a new block. Returns an error if the hash doesn't match 86 | /// the data. 87 | pub fn new(cid: Cid, data: Vec) -> Result { 88 | verify_cid::(&cid, &data)?; 89 | Ok(Self::new_unchecked(cid, data)) 90 | } 91 | 92 | /// Creates a new block without verifying the cid. 93 | pub fn new_unchecked(cid: Cid, data: Vec) -> Self { 94 | Self { 95 | _marker: PhantomData, 96 | cid, 97 | data, 98 | } 99 | } 100 | 101 | /// Returns the cid. 102 | pub fn cid(&self) -> &Cid { 103 | &self.cid 104 | } 105 | 106 | /// Returns the payload. 107 | pub fn data(&self) -> &[u8] { 108 | &self.data 109 | } 110 | 111 | /// Returns the inner cid and data. 112 | pub fn into_inner(self) -> (Cid, Vec) { 113 | (self.cid, self.data) 114 | } 115 | 116 | /// Encode a block.` 117 | pub fn encode + ?Sized>( 118 | codec: CE, 119 | hcode: S::Hashes, 120 | payload: &T, 121 | ) -> Result 122 | where 123 | CE: Into, 124 | { 125 | debug_assert_eq!( 126 | Into::::into(codec), 127 | Into::::into(Into::::into(codec)) 128 | ); 129 | let data = codec.encode(payload)?; 130 | if data.len() > S::MAX_BLOCK_SIZE { 131 | return Err(BlockTooLarge(data.len()).into()); 132 | } 133 | let mh = hcode.digest(&data); 134 | let cid = Cid::new_v1(codec.into(), mh); 135 | Ok(Self { 136 | _marker: PhantomData, 137 | cid, 138 | data, 139 | }) 140 | } 141 | 142 | /// Decodes a block. 143 | /// 144 | /// # Example 145 | /// 146 | /// Decoding to [`Ipld`]: 147 | /// 148 | /// ``` 149 | /// use libipld::block::Block; 150 | /// use libipld::cbor::DagCborCodec; 151 | /// use libipld::ipld::Ipld; 152 | /// use libipld::multihash::Code; 153 | /// use libipld::store::DefaultParams; 154 | /// 155 | /// let block = 156 | /// Block::::encode(DagCborCodec, Code::Blake3_256, "Hello World!").unwrap(); 157 | /// let ipld = block.decode::().unwrap(); 158 | /// 159 | /// assert_eq!(ipld, Ipld::String("Hello World!".to_string())); 160 | /// ``` 161 | pub fn decode>(&self) -> Result 162 | where 163 | S::Codecs: Into, 164 | { 165 | debug_assert_eq!( 166 | Into::::into(CD::try_from(self.cid.codec()).unwrap()), 167 | Into::::into(S::Codecs::try_from(self.cid.codec()).unwrap()), 168 | ); 169 | CD::try_from(self.cid.codec())?.decode(&self.data) 170 | } 171 | 172 | /// Returns the decoded ipld. 173 | pub fn ipld(&self) -> Result 174 | where 175 | Ipld: Decode, 176 | { 177 | self.decode::() 178 | } 179 | 180 | /// Returns the references. 181 | pub fn references>(&self, set: &mut E) -> Result<()> 182 | where 183 | Ipld: References, 184 | { 185 | S::Codecs::try_from(self.cid.codec())?.references::(&self.data, set) 186 | } 187 | } 188 | 189 | #[cfg(test)] 190 | mod tests { 191 | use super::*; 192 | use crate::cbor::DagCborCodec; 193 | use crate::codec_impl::IpldCodec; 194 | use crate::ipld; 195 | use crate::ipld::Ipld; 196 | use crate::multihash::Code; 197 | use crate::store::DefaultParams; 198 | use fnv::FnvHashSet; 199 | 200 | type IpldBlock = Block; 201 | 202 | #[test] 203 | fn test_references() { 204 | let b1 = IpldBlock::encode(IpldCodec::Raw, Code::Blake3_256, &ipld!(&b"cid1"[..])).unwrap(); 205 | let b2 = IpldBlock::encode(IpldCodec::DagJson, Code::Blake3_256, &ipld!("cid2")).unwrap(); 206 | let b3 = IpldBlock::encode( 207 | IpldCodec::DagPb, 208 | Code::Blake3_256, 209 | &ipld!({ 210 | "Data": &b"data"[..], 211 | "Links": Ipld::List(vec![]), 212 | }), 213 | ) 214 | .unwrap(); 215 | 216 | let payload = ipld!({ 217 | "cid1": &b1.cid, 218 | "cid2": { "other": true, "cid2": { "cid2": &b2.cid }}, 219 | "cid3": [[ &b3.cid, &b1.cid ]], 220 | }); 221 | let block = IpldBlock::encode(IpldCodec::DagCbor, Code::Blake3_256, &payload).unwrap(); 222 | let payload2 = block.decode::().unwrap(); 223 | assert_eq!(payload, payload2); 224 | 225 | let mut refs = FnvHashSet::default(); 226 | payload2.references(&mut refs); 227 | assert_eq!(refs.len(), 3); 228 | assert!(refs.contains(&b1.cid)); 229 | assert!(refs.contains(&b2.cid)); 230 | assert!(refs.contains(&b3.cid)); 231 | } 232 | 233 | #[test] 234 | fn test_transmute() { 235 | let b1 = IpldBlock::encode(DagCborCodec, Code::Blake3_256, &42).unwrap(); 236 | assert_eq!(b1.cid.codec(), 0x71); 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /core/tests/serde_serializer.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "serde-codec")] 2 | 3 | extern crate alloc; 4 | 5 | use alloc::collections::BTreeMap; 6 | use core::convert::TryFrom; 7 | 8 | use serde::{ser, Serialize}; 9 | use serde_bytes::ByteBuf; 10 | 11 | use libipld_core::cid::Cid; 12 | use libipld_core::ipld::Ipld; 13 | use libipld_core::serde::to_ipld; 14 | 15 | fn assert_serialized(input: T, ipld: Ipld) 16 | where 17 | T: ser::Serialize, 18 | { 19 | let serialized = to_ipld(input).unwrap(); 20 | assert_eq!(serialized, ipld); 21 | } 22 | 23 | #[test] 24 | #[allow(clippy::let_unit_value)] 25 | fn ipld_serializer_unit() { 26 | let unit = (); 27 | let serialized = to_ipld(unit); 28 | assert!(serialized.is_err()); 29 | } 30 | 31 | #[test] 32 | fn ipld_serializer_unit_struct() { 33 | #[derive(Clone, Debug, Serialize, PartialEq)] 34 | struct UnitStruct; 35 | 36 | let unit_struct = UnitStruct; 37 | let serialized = to_ipld(unit_struct); 38 | assert!(serialized.is_err()); 39 | } 40 | 41 | #[test] 42 | fn ipld_serializer_bool() { 43 | let bool = false; 44 | let ipld = Ipld::Bool(bool); 45 | assert_serialized(bool, ipld); 46 | } 47 | 48 | #[test] 49 | fn ipld_serializer_u8() { 50 | let integer = 34u8; 51 | let ipld = Ipld::Integer(integer.into()); 52 | assert_serialized(integer, ipld); 53 | } 54 | 55 | #[test] 56 | fn ipld_serializer_u16() { 57 | let integer = 345u16; 58 | let ipld = Ipld::Integer(integer.into()); 59 | assert_serialized(integer, ipld); 60 | } 61 | 62 | #[test] 63 | fn ipld_serializer_u32() { 64 | let integer = 345678u32; 65 | let ipld = Ipld::Integer(integer.into()); 66 | assert_serialized(integer, ipld); 67 | } 68 | 69 | #[test] 70 | fn ipld_serializer_u64() { 71 | let integer = 34567890123u64; 72 | let ipld = Ipld::Integer(integer.into()); 73 | assert_serialized(integer, ipld); 74 | } 75 | 76 | #[test] 77 | fn ipld_serializer_i8() { 78 | let integer = -23i8; 79 | let ipld = Ipld::Integer(integer.into()); 80 | assert_serialized(integer, ipld); 81 | } 82 | 83 | #[test] 84 | fn ipld_serializer_i16() { 85 | let integer = 2345i16; 86 | let ipld = Ipld::Integer(integer.into()); 87 | assert_serialized(integer, ipld); 88 | } 89 | 90 | #[test] 91 | fn ipld_serializer_i32() { 92 | let integer = 234567i32; 93 | let ipld = Ipld::Integer(integer.into()); 94 | assert_serialized(integer, ipld); 95 | } 96 | 97 | #[test] 98 | fn ipld_serializer_i64() { 99 | let integer = 2345678901i64; 100 | let ipld = Ipld::Integer(integer.into()); 101 | assert_serialized(integer, ipld); 102 | } 103 | 104 | #[test] 105 | fn ipld_serializer_i128() { 106 | let integer = 34567890123467890123i128; 107 | let ipld = Ipld::Integer(integer); 108 | assert_serialized(integer, ipld); 109 | } 110 | 111 | #[test] 112 | fn ipld_serializer_f32() { 113 | let float = 7.3f32; 114 | let ipld = Ipld::Float(float.into()); 115 | assert_serialized(float, ipld); 116 | } 117 | 118 | #[test] 119 | fn ipld_serializer_f64() { 120 | let float = 427.8f64; 121 | let ipld = Ipld::Float(float); 122 | assert_serialized(float, ipld); 123 | } 124 | 125 | #[test] 126 | fn ipld_serializer_char() { 127 | let char = 'x'; 128 | let ipld = Ipld::String(char.to_string()); 129 | assert_serialized(char, ipld); 130 | } 131 | 132 | #[test] 133 | fn ipld_serializer_str() { 134 | let str: &str = "hello"; 135 | let ipld = Ipld::String(str.to_string()); 136 | assert_serialized(str, ipld); 137 | } 138 | 139 | #[test] 140 | fn ipld_serializer_bytes() { 141 | let bytes = vec![0x68, 0x65, 0x6c, 0x6c, 0x6f]; 142 | let ipld = Ipld::Bytes(bytes.clone()); 143 | assert_serialized(ByteBuf::from(bytes), ipld); 144 | } 145 | 146 | #[test] 147 | fn ipld_serializer_list() { 148 | let list = vec![0x68, 0x65, 0x6c, 0x6c, 0x6f]; 149 | let ipld = Ipld::List(vec![ 150 | Ipld::Integer(0x68), 151 | Ipld::Integer(0x65), 152 | Ipld::Integer(0x6c), 153 | Ipld::Integer(0x6c), 154 | Ipld::Integer(0x6f), 155 | ]); 156 | assert_serialized(list, ipld); 157 | } 158 | 159 | #[test] 160 | fn ipld_serializer_tuple() { 161 | let tuple = (true, "hello".to_string()); 162 | let ipld = Ipld::List(vec![Ipld::Bool(tuple.0), Ipld::String(tuple.1.clone())]); 163 | assert_serialized(tuple, ipld); 164 | } 165 | 166 | #[test] 167 | fn ipld_serializer_tuple_struct() { 168 | #[derive(Clone, Debug, Serialize, PartialEq)] 169 | struct TupleStruct(u8, bool); 170 | 171 | let tuple_struct = TupleStruct(82, true); 172 | let ipld = Ipld::List(vec![Ipld::Integer(82), Ipld::Bool(true)]); 173 | assert_serialized(tuple_struct, ipld); 174 | } 175 | 176 | #[test] 177 | fn ipld_serializer_map() { 178 | let map = BTreeMap::from([("hello".to_string(), true), ("world!".to_string(), false)]); 179 | let ipld = Ipld::Map(BTreeMap::from([ 180 | ("hello".to_string(), Ipld::Bool(true)), 181 | ("world!".to_string(), Ipld::Bool(false)), 182 | ])); 183 | assert_serialized(map, ipld); 184 | } 185 | 186 | /// A CID is deserialized through a newtype struct. 187 | #[test] 188 | fn ipld_serializer_cid() { 189 | let cid = Cid::try_from("bafkreie74tgmnxqwojhtumgh5dzfj46gi4mynlfr7dmm7duwzyvnpw7h7m").unwrap(); 190 | let ipld = Ipld::Link(cid); 191 | assert_serialized(cid, ipld); 192 | } 193 | 194 | #[test] 195 | fn ipld_serializer_newtype_struct() { 196 | #[derive(Clone, Debug, Serialize, PartialEq)] 197 | struct Wrapped(u8); 198 | 199 | let newtype_struct = Wrapped(3); 200 | let ipld = Ipld::Integer(3); 201 | assert_serialized(newtype_struct, ipld); 202 | } 203 | 204 | /// An additional test, just to make sure that wrapped CIDs also work. 205 | #[test] 206 | fn ipld_serializer_newtype_struct_cid() { 207 | #[derive(Clone, Debug, Serialize, PartialEq)] 208 | struct Wrapped(Cid); 209 | 210 | let cid = Cid::try_from("bafkreie74tgmnxqwojhtumgh5dzfj46gi4mynlfr7dmm7duwzyvnpw7h7m").unwrap(); 211 | let newtype_struct = Wrapped(cid); 212 | let ipld = Ipld::Link(cid); 213 | assert_serialized(newtype_struct, ipld); 214 | } 215 | 216 | #[test] 217 | fn ipld_serializer_option() { 218 | let option_some: Option = Some(58u8); 219 | let option_none: Option = None; 220 | let ipld_some = Ipld::Integer(58); 221 | let ipld_none = Ipld::Null; 222 | assert_serialized(option_some, ipld_some); 223 | assert_serialized(option_none, ipld_none); 224 | } 225 | 226 | #[test] 227 | fn ipld_serializer_enum() { 228 | #[derive(Clone, Debug, Serialize, PartialEq)] 229 | enum MyEnum { 230 | One, 231 | Two(u8), 232 | Three { value: bool }, 233 | } 234 | 235 | let enum_one = MyEnum::One; 236 | let ipld_one = Ipld::String("One".into()); 237 | assert_serialized(enum_one, ipld_one); 238 | 239 | let enum_two = MyEnum::Two(4); 240 | let ipld_two = Ipld::Map(BTreeMap::from([("Two".into(), Ipld::Integer(4))])); 241 | assert_serialized(enum_two, ipld_two); 242 | 243 | let enum_three = MyEnum::Three { value: true }; 244 | let ipld_three = Ipld::Map(BTreeMap::from([( 245 | "Three".into(), 246 | Ipld::Map(BTreeMap::from([("value".into(), Ipld::Bool(true))])), 247 | )])); 248 | assert_serialized(enum_three, ipld_three); 249 | } 250 | 251 | #[test] 252 | fn ipld_serializer_struct() { 253 | #[derive(Clone, Debug, Serialize, PartialEq)] 254 | struct MyStruct { 255 | hello: u8, 256 | world: bool, 257 | } 258 | 259 | let my_struct = MyStruct { 260 | hello: 91, 261 | world: false, 262 | }; 263 | let ipld = Ipld::Map(BTreeMap::from([ 264 | ("hello".into(), Ipld::Integer(my_struct.hello.into())), 265 | ("world".into(), Ipld::Bool(my_struct.world)), 266 | ])); 267 | assert_serialized(my_struct, ipld); 268 | } 269 | -------------------------------------------------------------------------------- /src/codec_impl.rs: -------------------------------------------------------------------------------- 1 | //! IPLD Codecs. 2 | #[cfg(feature = "dag-cbor")] 3 | use crate::cbor::DagCborCodec; 4 | use crate::cid::Cid; 5 | use crate::codec::{Codec, Decode, Encode, References}; 6 | use crate::error::{Result, UnsupportedCodec}; 7 | use crate::ipld::Ipld; 8 | #[cfg(feature = "dag-json")] 9 | use crate::json::DagJsonCodec; 10 | #[cfg(feature = "dag-pb")] 11 | use crate::pb::DagPbCodec; 12 | use crate::raw::RawCodec; 13 | use core::convert::TryFrom; 14 | use std::io::{Read, Seek, Write}; 15 | 16 | /// Default codecs. 17 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 18 | pub enum IpldCodec { 19 | /// Raw codec. 20 | Raw, 21 | /// Cbor codec. 22 | #[cfg(feature = "dag-cbor")] 23 | DagCbor, 24 | /// Json codec. 25 | #[cfg(feature = "dag-json")] 26 | DagJson, 27 | /// Protobuf codec. 28 | #[cfg(feature = "dag-pb")] 29 | DagPb, 30 | } 31 | 32 | impl TryFrom for IpldCodec { 33 | type Error = UnsupportedCodec; 34 | 35 | fn try_from(ccode: u64) -> core::result::Result { 36 | Ok(match ccode { 37 | 0x55 => Self::Raw, 38 | #[cfg(feature = "dag-cbor")] 39 | 0x71 => Self::DagCbor, 40 | #[cfg(feature = "dag-json")] 41 | 0x0129 => Self::DagJson, 42 | #[cfg(feature = "dag-pb")] 43 | 0x70 => Self::DagPb, 44 | _ => return Err(UnsupportedCodec(ccode)), 45 | }) 46 | } 47 | } 48 | 49 | impl From for u64 { 50 | fn from(mc: IpldCodec) -> Self { 51 | match mc { 52 | IpldCodec::Raw => 0x55, 53 | #[cfg(feature = "dag-cbor")] 54 | IpldCodec::DagCbor => 0x71, 55 | #[cfg(feature = "dag-json")] 56 | IpldCodec::DagJson => 0x0129, 57 | #[cfg(feature = "dag-pb")] 58 | IpldCodec::DagPb => 0x70, 59 | } 60 | } 61 | } 62 | 63 | impl From for IpldCodec { 64 | fn from(_: RawCodec) -> Self { 65 | Self::Raw 66 | } 67 | } 68 | 69 | #[cfg(feature = "dag-cbor")] 70 | impl From for IpldCodec { 71 | fn from(_: DagCborCodec) -> Self { 72 | Self::DagCbor 73 | } 74 | } 75 | 76 | #[cfg(feature = "dag-cbor")] 77 | impl From for DagCborCodec { 78 | fn from(_: IpldCodec) -> Self { 79 | Self 80 | } 81 | } 82 | 83 | #[cfg(feature = "dag-json")] 84 | impl From for IpldCodec { 85 | fn from(_: DagJsonCodec) -> Self { 86 | Self::DagJson 87 | } 88 | } 89 | 90 | #[cfg(feature = "dag-json")] 91 | impl From for DagJsonCodec { 92 | fn from(_: IpldCodec) -> Self { 93 | Self 94 | } 95 | } 96 | 97 | #[cfg(feature = "dag-pb")] 98 | impl From for IpldCodec { 99 | fn from(_: DagPbCodec) -> Self { 100 | Self::DagPb 101 | } 102 | } 103 | 104 | #[cfg(feature = "dag-pb")] 105 | impl From for DagPbCodec { 106 | fn from(_: IpldCodec) -> Self { 107 | Self 108 | } 109 | } 110 | 111 | impl Codec for IpldCodec {} 112 | 113 | impl Encode for Ipld { 114 | fn encode(&self, c: IpldCodec, w: &mut W) -> Result<()> { 115 | match c { 116 | IpldCodec::Raw => self.encode(RawCodec, w)?, 117 | #[cfg(feature = "dag-cbor")] 118 | IpldCodec::DagCbor => self.encode(DagCborCodec, w)?, 119 | #[cfg(feature = "dag-json")] 120 | IpldCodec::DagJson => self.encode(DagJsonCodec, w)?, 121 | #[cfg(feature = "dag-pb")] 122 | IpldCodec::DagPb => self.encode(DagPbCodec, w)?, 123 | }; 124 | Ok(()) 125 | } 126 | } 127 | 128 | impl Decode for Ipld { 129 | fn decode(c: IpldCodec, r: &mut R) -> Result { 130 | Ok(match c { 131 | IpldCodec::Raw => Self::decode(RawCodec, r)?, 132 | #[cfg(feature = "dag-cbor")] 133 | IpldCodec::DagCbor => Self::decode(DagCborCodec, r)?, 134 | #[cfg(feature = "dag-json")] 135 | IpldCodec::DagJson => Self::decode(DagJsonCodec, r)?, 136 | #[cfg(feature = "dag-pb")] 137 | IpldCodec::DagPb => Self::decode(DagPbCodec, r)?, 138 | }) 139 | } 140 | } 141 | 142 | impl References for Ipld { 143 | fn references>( 144 | c: IpldCodec, 145 | r: &mut R, 146 | set: &mut E, 147 | ) -> Result<()> { 148 | match c { 149 | IpldCodec::Raw => >::references(RawCodec, r, set)?, 150 | #[cfg(feature = "dag-cbor")] 151 | IpldCodec::DagCbor => { 152 | >::references(DagCborCodec, r, set)? 153 | } 154 | #[cfg(feature = "dag-json")] 155 | IpldCodec::DagJson => { 156 | >::references(DagJsonCodec, r, set)? 157 | } 158 | #[cfg(feature = "dag-pb")] 159 | IpldCodec::DagPb => >::references(DagPbCodec, r, set)?, 160 | }; 161 | Ok(()) 162 | } 163 | } 164 | 165 | #[cfg(test)] 166 | mod tests { 167 | use super::*; 168 | 169 | #[test] 170 | fn raw_encode() { 171 | let data = Ipld::Bytes([0x22, 0x33, 0x44].to_vec()); 172 | let result = IpldCodec::Raw.encode(&data).unwrap(); 173 | assert_eq!(result, vec![0x22, 0x33, 0x44]); 174 | } 175 | 176 | #[test] 177 | fn raw_decode() { 178 | let data = [0x22, 0x33, 0x44]; 179 | let result: Ipld = IpldCodec::Raw.decode(&data).unwrap(); 180 | assert_eq!(result, Ipld::Bytes(data.to_vec())); 181 | } 182 | 183 | #[cfg(feature = "dag-cbor")] 184 | #[test] 185 | fn dag_cbor_encode() { 186 | let data = Ipld::Bytes([0x22, 0x33, 0x44].to_vec()); 187 | let result = IpldCodec::DagCbor.encode(&data).unwrap(); 188 | assert_eq!(result, vec![0x43, 0x22, 0x33, 0x44]); 189 | } 190 | 191 | #[cfg(feature = "dag-cbor")] 192 | #[test] 193 | fn dag_cbor_decode() { 194 | let data = [0x43, 0x22, 0x33, 0x44]; 195 | let result: Ipld = IpldCodec::DagCbor.decode(&data).unwrap(); 196 | assert_eq!(result, Ipld::Bytes(vec![0x22, 0x33, 0x44])); 197 | } 198 | 199 | #[cfg(feature = "dag-json")] 200 | #[test] 201 | fn dag_json_encode() { 202 | let data = Ipld::Bool(true); 203 | let result = String::from_utf8(IpldCodec::DagJson.encode(&data).unwrap().to_vec()).unwrap(); 204 | assert_eq!(result, "true"); 205 | } 206 | 207 | #[cfg(feature = "dag-json")] 208 | #[test] 209 | fn dag_json_decode() { 210 | let data = b"true"; 211 | let result: Ipld = IpldCodec::DagJson.decode(data).unwrap(); 212 | assert_eq!(result, Ipld::Bool(true)); 213 | } 214 | 215 | #[cfg(feature = "dag-pb")] 216 | #[test] 217 | fn dag_pb_encode() { 218 | let mut data_map = std::collections::BTreeMap::::new(); 219 | data_map.insert("Data".to_string(), Ipld::Bytes(b"data".to_vec())); 220 | data_map.insert("Links".to_string(), Ipld::List(vec![])); 221 | 222 | let data = Ipld::Map(data_map); 223 | let result = IpldCodec::DagPb.encode(&data).unwrap(); 224 | assert_eq!(result, vec![0x0a, 0x04, 0x64, 0x61, 0x74, 0x61]); 225 | } 226 | 227 | #[cfg(feature = "dag-pb")] 228 | #[test] 229 | fn dag_pb_decode() { 230 | let mut data_map = std::collections::BTreeMap::::new(); 231 | data_map.insert("Data".to_string(), Ipld::Bytes(b"data".to_vec())); 232 | data_map.insert("Links".to_string(), Ipld::List(vec![])); 233 | let expected = Ipld::Map(data_map); 234 | 235 | let data = [0x0a, 0x04, 0x64, 0x61, 0x74, 0x61]; 236 | let result: Ipld = IpldCodec::DagPb.decode(&data).unwrap(); 237 | assert_eq!(result, expected); 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /dag-json/src/codec.rs: -------------------------------------------------------------------------------- 1 | use core::convert::TryFrom; 2 | use libipld_core::cid::Cid; 3 | use libipld_core::ipld::Ipld; 4 | use libipld_core::multibase::Base; 5 | use serde::de::Error as SerdeError; 6 | use serde::{de, ser, Deserialize, Serialize}; 7 | use serde_json::ser::Serializer; 8 | use serde_json::Error; 9 | use std::collections::BTreeMap; 10 | use std::fmt; 11 | use std::io::{Read, Write}; 12 | 13 | const RESERVED_KEY: &str = "/"; 14 | const BYTES_KEY: &str = "bytes"; 15 | 16 | pub fn encode(ipld: &Ipld, writer: &mut W) -> Result<(), Error> { 17 | let mut ser = Serializer::new(writer); 18 | serialize(ipld, &mut ser)?; 19 | Ok(()) 20 | } 21 | 22 | pub fn decode(r: &mut R) -> Result { 23 | let mut de = serde_json::Deserializer::from_reader(r); 24 | deserialize(&mut de) 25 | } 26 | 27 | fn serialize(ipld: &Ipld, ser: S) -> Result { 28 | match &ipld { 29 | Ipld::Null => ser.serialize_none(), 30 | Ipld::Bool(bool) => ser.serialize_bool(*bool), 31 | Ipld::Integer(i128) => ser.serialize_i128(*i128), 32 | Ipld::Float(f64) => ser.serialize_f64(*f64), 33 | Ipld::String(string) => ser.serialize_str(string), 34 | Ipld::Bytes(bytes) => { 35 | let base_encoded = Base::Base64.encode(bytes); 36 | let byteskv = BTreeMap::from([("bytes", &base_encoded)]); 37 | let slashkv = BTreeMap::from([("/", byteskv)]); 38 | ser.collect_map(slashkv) 39 | } 40 | Ipld::List(list) => { 41 | let wrapped = list.iter().map(Wrapper); 42 | ser.collect_seq(wrapped) 43 | } 44 | Ipld::Map(map) => { 45 | let wrapped = map.iter().map(|(key, ipld)| (key, Wrapper(ipld))); 46 | ser.collect_map(wrapped) 47 | } 48 | Ipld::Link(link) => { 49 | let mut map = BTreeMap::new(); 50 | map.insert("/", link.to_string()); 51 | 52 | ser.collect_map(map) 53 | } 54 | } 55 | } 56 | 57 | fn deserialize<'de, D: de::Deserializer<'de>>(deserializer: D) -> Result { 58 | // Sadly such a PhantomData hack is needed 59 | deserializer.deserialize_any(JsonVisitor) 60 | } 61 | 62 | // Needed for `collect_seq` and `collect_map` in Seserializer 63 | struct Wrapper<'a>(&'a Ipld); 64 | 65 | impl<'a> Serialize for Wrapper<'a> { 66 | fn serialize(&self, serializer: S) -> Result 67 | where 68 | S: ser::Serializer, 69 | { 70 | serialize(self.0, serializer) 71 | } 72 | } 73 | 74 | // serde deserializer visitor that is used by Deseraliazer to decode 75 | // json into IPLD. 76 | struct JsonVisitor; 77 | impl<'de> de::Visitor<'de> for JsonVisitor { 78 | type Value = Ipld; 79 | 80 | fn expecting(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 81 | fmt.write_str("any valid JSON value") 82 | } 83 | 84 | fn visit_str(self, value: &str) -> Result 85 | where 86 | E: de::Error, 87 | { 88 | self.visit_string(String::from(value)) 89 | } 90 | 91 | fn visit_string(self, value: String) -> Result 92 | where 93 | E: de::Error, 94 | { 95 | Ok(Ipld::String(value)) 96 | } 97 | fn visit_bytes(self, v: &[u8]) -> Result 98 | where 99 | E: de::Error, 100 | { 101 | self.visit_byte_buf(v.to_owned()) 102 | } 103 | 104 | fn visit_byte_buf(self, v: Vec) -> Result 105 | where 106 | E: de::Error, 107 | { 108 | Ok(Ipld::Bytes(v)) 109 | } 110 | 111 | fn visit_u64(self, v: u64) -> Result 112 | where 113 | E: de::Error, 114 | { 115 | Ok(Ipld::Integer(v.into())) 116 | } 117 | 118 | fn visit_i64(self, v: i64) -> Result 119 | where 120 | E: de::Error, 121 | { 122 | Ok(Ipld::Integer(v.into())) 123 | } 124 | 125 | fn visit_i128(self, v: i128) -> Result 126 | where 127 | E: de::Error, 128 | { 129 | Ok(Ipld::Integer(v)) 130 | } 131 | 132 | fn visit_bool(self, v: bool) -> Result 133 | where 134 | E: de::Error, 135 | { 136 | Ok(Ipld::Bool(v)) 137 | } 138 | 139 | fn visit_none(self) -> Result 140 | where 141 | E: de::Error, 142 | { 143 | self.visit_unit() 144 | } 145 | 146 | fn visit_unit(self) -> Result 147 | where 148 | E: de::Error, 149 | { 150 | Ok(Ipld::Null) 151 | } 152 | 153 | fn visit_seq(self, mut visitor: V) -> Result 154 | where 155 | V: de::SeqAccess<'de>, 156 | { 157 | let mut vec: Vec = Vec::new(); 158 | 159 | while let Some(elem) = visitor.next_element()? { 160 | vec.push(elem); 161 | } 162 | 163 | let unwrapped = vec.into_iter().map(|WrapperOwned(ipld)| ipld).collect(); 164 | Ok(Ipld::List(unwrapped)) 165 | } 166 | 167 | fn visit_map(self, mut visitor: V) -> Result 168 | where 169 | V: de::MapAccess<'de>, 170 | { 171 | let mut values: Vec<(String, WrapperOwned)> = Vec::new(); 172 | 173 | while let Some((key, value)) = visitor.next_entry()? { 174 | values.push((key, value)); 175 | } 176 | 177 | // JSON Object represents an IPLD Link if it is a slash, followed by a string 178 | // (`{ "/": "...." }`) therefore we validate if that is the case here. 179 | if let Some((key, WrapperOwned(Ipld::String(value)))) = values.first() { 180 | if key == RESERVED_KEY && values.len() == 1 { 181 | let cid = Cid::try_from(value.clone()).map_err(SerdeError::custom)?; 182 | return Ok(Ipld::Link(cid)); 183 | } 184 | } 185 | // JSON Object represents IPLD bytes if it is a slash, followed by an object which contains 186 | // only a single key called "bytes", where the value is a string. 187 | if let Some((key, WrapperOwned(Ipld::Map(map)))) = values.first() { 188 | // Object with a slash 189 | if key == RESERVED_KEY && values.len() == 1 { 190 | if let Some((bytes_key, Ipld::String(bytes_value))) = map.iter().next() { 191 | if bytes_key == BYTES_KEY && values.len() == 1 { 192 | let decoded_bytes = Base::Base64.decode(bytes_value).map_err(|_| { 193 | SerdeError::custom("bytes kind must be base-64 encoded") 194 | })?; 195 | return Ok(Ipld::Bytes(decoded_bytes)); 196 | } 197 | } 198 | } 199 | } 200 | 201 | let mut unwrapped = BTreeMap::new(); 202 | for (key, WrapperOwned(value)) in values { 203 | let prev_value = unwrapped.insert(key, value); 204 | if prev_value.is_some() { 205 | return Err(SerdeError::custom("duplicate map key".to_string())); 206 | } 207 | } 208 | Ok(Ipld::Map(unwrapped)) 209 | } 210 | 211 | fn visit_f64(self, v: f64) -> Result 212 | where 213 | E: de::Error, 214 | { 215 | Ok(Ipld::Float(v)) 216 | } 217 | } 218 | 219 | // Needed for `visit_seq` and `visit_map` in Deserializer 220 | /// We cannot directly implement `serde::Deserializer` for `Ipld` as it is a remote type. 221 | /// Instead wrap it into a newtype struct and implement `serde::Deserialize` for that one. 222 | /// All the deserializer does is calling the `deserialize()` function we defined which returns 223 | /// an unwrapped `Ipld` instance. Wrap that `Ipld` instance in `Wrapper` and return it. 224 | /// Users of this wrapper will then unwrap it again so that they can return the expected `Ipld` 225 | /// instance. 226 | struct WrapperOwned(Ipld); 227 | 228 | impl<'de> Deserialize<'de> for WrapperOwned { 229 | fn deserialize(deserializer: D) -> Result 230 | where 231 | D: de::Deserializer<'de>, 232 | { 233 | let deserialized = deserialize(deserializer); 234 | // Better version of Ok(Wrapper(deserialized.unwrap())) 235 | deserialized.map(Self) 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /core/src/ipld.rs: -------------------------------------------------------------------------------- 1 | //! Ipld representation. 2 | use alloc::{ 3 | borrow::ToOwned, 4 | boxed::Box, 5 | collections::BTreeMap, 6 | string::{String, ToString}, 7 | vec, 8 | vec::Vec, 9 | }; 10 | use core::fmt; 11 | 12 | use crate::cid::Cid; 13 | use crate::error::TypeError; 14 | 15 | /// Ipld 16 | #[derive(Clone, PartialEq)] 17 | pub enum Ipld { 18 | /// Represents the absence of a value or the value undefined. 19 | Null, 20 | /// Represents a boolean value. 21 | Bool(bool), 22 | /// Represents an integer. 23 | Integer(i128), 24 | /// Represents a floating point value. 25 | Float(f64), 26 | /// Represents an UTF-8 string. 27 | String(String), 28 | /// Represents a sequence of bytes. 29 | Bytes(Vec), 30 | /// Represents a list. 31 | List(Vec), 32 | /// Represents a map of strings. 33 | Map(BTreeMap), 34 | /// Represents a map of integers. 35 | Link(Cid), 36 | } 37 | 38 | impl fmt::Debug for Ipld { 39 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 40 | if f.alternate() { 41 | match self { 42 | Self::Null => write!(f, "Null"), 43 | Self::Bool(b) => write!(f, "Bool({:?})", b), 44 | Self::Integer(i) => write!(f, "Integer({:?})", i), 45 | Self::Float(i) => write!(f, "Float({:?})", i), 46 | Self::String(s) => write!(f, "String({:?})", s), 47 | Self::Bytes(b) => write!(f, "Bytes({:?})", b), 48 | Self::List(l) => write!(f, "List({:#?})", l), 49 | Self::Map(m) => write!(f, "Map({:#?})", m), 50 | Self::Link(cid) => write!(f, "Link({})", cid), 51 | } 52 | } else { 53 | match self { 54 | Self::Null => write!(f, "null"), 55 | Self::Bool(b) => write!(f, "{:?}", b), 56 | Self::Integer(i) => write!(f, "{:?}", i), 57 | Self::Float(i) => write!(f, "{:?}", i), 58 | Self::String(s) => write!(f, "{:?}", s), 59 | Self::Bytes(b) => write!(f, "{:?}", b), 60 | Self::List(l) => write!(f, "{:?}", l), 61 | Self::Map(m) => write!(f, "{:?}", m), 62 | Self::Link(cid) => write!(f, "{}", cid), 63 | } 64 | } 65 | } 66 | } 67 | 68 | /// An index into ipld 69 | pub enum IpldIndex<'a> { 70 | /// An index into an ipld list. 71 | List(usize), 72 | /// An owned index into an ipld map. 73 | Map(String), 74 | /// An index into an ipld map. 75 | MapRef(&'a str), 76 | } 77 | 78 | impl<'a> From for IpldIndex<'a> { 79 | fn from(index: usize) -> Self { 80 | Self::List(index) 81 | } 82 | } 83 | 84 | impl<'a> From for IpldIndex<'a> { 85 | fn from(key: String) -> Self { 86 | Self::Map(key) 87 | } 88 | } 89 | 90 | impl<'a> From<&'a str> for IpldIndex<'a> { 91 | fn from(key: &'a str) -> Self { 92 | Self::MapRef(key) 93 | } 94 | } 95 | 96 | impl Ipld { 97 | /// Destructs an ipld list or map 98 | pub fn take<'a, T: Into>>(mut self, index: T) -> Result { 99 | let index = index.into(); 100 | let ipld = match &mut self { 101 | Ipld::List(ref mut l) => match index { 102 | IpldIndex::List(i) => Some(i), 103 | IpldIndex::Map(ref key) => key.parse().ok(), 104 | IpldIndex::MapRef(key) => key.parse().ok(), 105 | } 106 | .map(|i| { 107 | if i < l.len() { 108 | Some(l.swap_remove(i)) 109 | } else { 110 | None 111 | } 112 | }), 113 | Ipld::Map(ref mut m) => match index { 114 | IpldIndex::Map(ref key) => Some(m.remove(key)), 115 | IpldIndex::MapRef(key) => Some(m.remove(key)), 116 | IpldIndex::List(i) => Some(m.remove(&i.to_string())), 117 | }, 118 | _ => None, 119 | }; 120 | ipld.unwrap_or_default() 121 | .ok_or_else(|| TypeError::new(index, self)) 122 | } 123 | 124 | /// Indexes into an ipld list or map. 125 | pub fn get<'a, T: Into>>(&self, index: T) -> Result<&Self, TypeError> { 126 | let index = index.into(); 127 | let ipld = match self { 128 | Ipld::List(l) => match index { 129 | IpldIndex::List(i) => Some(i), 130 | IpldIndex::Map(ref key) => key.parse().ok(), 131 | IpldIndex::MapRef(key) => key.parse().ok(), 132 | } 133 | .map(|i| l.get(i)), 134 | Ipld::Map(m) => match index { 135 | IpldIndex::Map(ref key) => Some(m.get(key)), 136 | IpldIndex::MapRef(key) => Some(m.get(key)), 137 | IpldIndex::List(i) => Some(m.get(&i.to_string())), 138 | }, 139 | _ => None, 140 | }; 141 | ipld.unwrap_or_default() 142 | .ok_or_else(|| TypeError::new(index, self)) 143 | } 144 | 145 | /// Returns an iterator. 146 | pub fn iter(&self) -> IpldIter<'_> { 147 | IpldIter { 148 | stack: vec![Box::new(vec![self].into_iter())], 149 | } 150 | } 151 | 152 | /// Returns the references to other blocks. 153 | pub fn references>(&self, set: &mut E) { 154 | for ipld in self.iter() { 155 | if let Ipld::Link(cid) = ipld { 156 | set.extend(core::iter::once(cid.to_owned())); 157 | } 158 | } 159 | } 160 | } 161 | 162 | /// Ipld iterator. 163 | pub struct IpldIter<'a> { 164 | stack: Vec + 'a>>, 165 | } 166 | 167 | impl<'a> Iterator for IpldIter<'a> { 168 | type Item = &'a Ipld; 169 | 170 | fn next(&mut self) -> Option { 171 | loop { 172 | if let Some(iter) = self.stack.last_mut() { 173 | if let Some(ipld) = iter.next() { 174 | match ipld { 175 | Ipld::List(list) => { 176 | self.stack.push(Box::new(list.iter())); 177 | } 178 | Ipld::Map(map) => { 179 | self.stack.push(Box::new(map.values())); 180 | } 181 | _ => {} 182 | } 183 | return Some(ipld); 184 | } else { 185 | self.stack.pop(); 186 | } 187 | } else { 188 | return None; 189 | } 190 | } 191 | } 192 | } 193 | 194 | #[cfg(test)] 195 | mod tests { 196 | use super::*; 197 | use crate::cid::Cid; 198 | use crate::multihash::{Code, MultihashDigest}; 199 | 200 | #[test] 201 | fn test_ipld_bool_from() { 202 | assert_eq!(Ipld::Bool(true), Ipld::from(true)); 203 | assert_eq!(Ipld::Bool(false), Ipld::from(false)); 204 | } 205 | 206 | #[test] 207 | fn test_ipld_integer_from() { 208 | assert_eq!(Ipld::Integer(1), Ipld::from(1i8)); 209 | assert_eq!(Ipld::Integer(1), Ipld::from(1i16)); 210 | assert_eq!(Ipld::Integer(1), Ipld::from(1i32)); 211 | assert_eq!(Ipld::Integer(1), Ipld::from(1i64)); 212 | assert_eq!(Ipld::Integer(1), Ipld::from(1i128)); 213 | 214 | //assert_eq!(Ipld::Integer(1), 1u8.to_ipld().to_owned()); 215 | assert_eq!(Ipld::Integer(1), Ipld::from(1u16)); 216 | assert_eq!(Ipld::Integer(1), Ipld::from(1u32)); 217 | assert_eq!(Ipld::Integer(1), Ipld::from(1u64)); 218 | } 219 | 220 | #[test] 221 | fn test_ipld_float_from() { 222 | assert_eq!(Ipld::Float(1.0), Ipld::from(1.0f32)); 223 | assert_eq!(Ipld::Float(1.0), Ipld::from(1.0f64)); 224 | } 225 | 226 | #[test] 227 | fn test_ipld_string_from() { 228 | assert_eq!(Ipld::String("a string".into()), Ipld::from("a string")); 229 | assert_eq!( 230 | Ipld::String("a string".into()), 231 | Ipld::from("a string".to_string()) 232 | ); 233 | } 234 | 235 | #[test] 236 | fn test_ipld_bytes_from() { 237 | assert_eq!( 238 | Ipld::Bytes(vec![0, 1, 2, 3]), 239 | Ipld::from(&[0u8, 1u8, 2u8, 3u8][..]) 240 | ); 241 | assert_eq!( 242 | Ipld::Bytes(vec![0, 1, 2, 3]), 243 | Ipld::from(vec![0u8, 1u8, 2u8, 3u8]) 244 | ); 245 | } 246 | 247 | #[test] 248 | fn test_ipld_link_from() { 249 | let data = vec![0, 1, 2, 3]; 250 | let hash = Code::Blake3_256.digest(&data); 251 | let cid = Cid::new_v1(0x55, hash); 252 | assert_eq!(Ipld::Link(cid), Ipld::from(cid)); 253 | } 254 | 255 | #[test] 256 | fn test_take() { 257 | let ipld = Ipld::List(vec![Ipld::Integer(0), Ipld::Integer(1), Ipld::Integer(2)]); 258 | assert_eq!(ipld.clone().take(0).unwrap(), Ipld::Integer(0)); 259 | assert_eq!(ipld.clone().take(1).unwrap(), Ipld::Integer(1)); 260 | assert_eq!(ipld.take(2).unwrap(), Ipld::Integer(2)); 261 | 262 | let mut map = BTreeMap::new(); 263 | map.insert("a".to_string(), Ipld::Integer(0)); 264 | map.insert("b".to_string(), Ipld::Integer(1)); 265 | map.insert("c".to_string(), Ipld::Integer(2)); 266 | let ipld = Ipld::Map(map); 267 | assert_eq!(ipld.take("a").unwrap(), Ipld::Integer(0)); 268 | } 269 | 270 | #[test] 271 | fn test_get() { 272 | let ipld = Ipld::List(vec![Ipld::Integer(0), Ipld::Integer(1), Ipld::Integer(2)]); 273 | assert_eq!(ipld.get(0).unwrap(), &Ipld::Integer(0)); 274 | assert_eq!(ipld.get(1).unwrap(), &Ipld::Integer(1)); 275 | assert_eq!(ipld.get(2).unwrap(), &Ipld::Integer(2)); 276 | 277 | let mut map = BTreeMap::new(); 278 | map.insert("a".to_string(), Ipld::Integer(0)); 279 | map.insert("b".to_string(), Ipld::Integer(1)); 280 | map.insert("c".to_string(), Ipld::Integer(2)); 281 | let ipld = Ipld::Map(map); 282 | assert_eq!(ipld.get("a").unwrap(), &Ipld::Integer(0)); 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | https://www.apache.org/licenses/LICENSE-2.0 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | https://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `ipld!` macro. 2 | /// Construct an `Ipld` from a literal. 3 | /// 4 | /// ```edition2018 5 | /// # use libipld_macro::ipld; 6 | /// # 7 | /// let value = ipld!({ 8 | /// "code": 200, 9 | /// "success": true, 10 | /// "payload": { 11 | /// "features": [ 12 | /// "serde", 13 | /// "json" 14 | /// ] 15 | /// } 16 | /// }); 17 | /// ``` 18 | /// 19 | /// Variables or expressions can be interpolated into the JSON literal. Any type 20 | /// interpolated into an array element or object value must implement Serde's 21 | /// `Serialize` trait, while any type interpolated into a object key must 22 | /// implement `Into`. If the `Serialize` implementation of the 23 | /// interpolated type decides to fail, or if the interpolated type contains a 24 | /// map with non-string keys, the `json!` macro will panic. 25 | /// 26 | /// ```edition2018 27 | /// # use libipld_macro::ipld; 28 | /// # 29 | /// let code = 200; 30 | /// let features = vec!["serde", "json"]; 31 | /// 32 | /// let value = ipld!({ 33 | /// "code": code, 34 | /// "success": code == 200, 35 | /// "payload": { 36 | /// features[0]: features[1] 37 | /// } 38 | /// }); 39 | /// ``` 40 | /// 41 | /// Trailing commas are allowed inside both arrays and objects. 42 | /// 43 | /// ```edition2018 44 | /// # use libipld_macro::ipld; 45 | /// # 46 | /// let value = ipld!([ 47 | /// "notice", 48 | /// "the", 49 | /// "trailing", 50 | /// "comma -->", 51 | /// ]); 52 | /// ``` 53 | pub use libipld_core::ipld::Ipld; 54 | 55 | #[macro_export(local_inner_macros)] 56 | macro_rules! ipld { 57 | // Hide distracting implementation details from the generated rustdoc. 58 | ($($ipld:tt)+) => { 59 | ipld_internal!($($ipld)+) 60 | }; 61 | } 62 | 63 | #[macro_export(local_inner_macros)] 64 | #[doc(hidden)] 65 | macro_rules! ipld_internal { 66 | ////////////////////////////////////////////////////////////////////////// 67 | // TT muncher for parsing the inside of an array [...]. Produces a vec![...] 68 | // of the elements. 69 | // 70 | // Must be invoked as: ipld_internal!(@array [] $($tt)*) 71 | ////////////////////////////////////////////////////////////////////////// 72 | 73 | // Done with trailing comma. 74 | (@array [$($elems:expr,)*]) => { 75 | ipld_internal_vec![$($elems,)*] 76 | }; 77 | 78 | // Done without trailing comma. 79 | (@array [$($elems:expr),*]) => { 80 | ipld_internal_vec![$($elems),*] 81 | }; 82 | 83 | // Next element is `null`. 84 | (@array [$($elems:expr,)*] null $($rest:tt)*) => { 85 | ipld_internal!(@array [$($elems,)* ipld_internal!(null)] $($rest)*) 86 | }; 87 | 88 | // Next element is `true`. 89 | (@array [$($elems:expr,)*] true $($rest:tt)*) => { 90 | ipld_internal!(@array [$($elems,)* ipld_internal!(true)] $($rest)*) 91 | }; 92 | 93 | // Next element is `false`. 94 | (@array [$($elems:expr,)*] false $($rest:tt)*) => { 95 | ipld_internal!(@array [$($elems,)* ipld_internal!(false)] $($rest)*) 96 | }; 97 | 98 | // Next element is an array. 99 | (@array [$($elems:expr,)*] [$($array:tt)*] $($rest:tt)*) => { 100 | ipld_internal!(@array [$($elems,)* ipld_internal!([$($array)*])] $($rest)*) 101 | }; 102 | 103 | // Next element is a map. 104 | (@array [$($elems:expr,)*] {$($map:tt)*} $($rest:tt)*) => { 105 | ipld_internal!(@array [$($elems,)* ipld_internal!({$($map)*})] $($rest)*) 106 | }; 107 | 108 | // Next element is an expression followed by comma. 109 | (@array [$($elems:expr,)*] $next:expr, $($rest:tt)*) => { 110 | ipld_internal!(@array [$($elems,)* ipld_internal!($next),] $($rest)*) 111 | }; 112 | 113 | // Last element is an expression with no trailing comma. 114 | (@array [$($elems:expr,)*] $last:expr) => { 115 | ipld_internal!(@array [$($elems,)* ipld_internal!($last)]) 116 | }; 117 | 118 | // Comma after the most recent element. 119 | (@array [$($elems:expr),*] , $($rest:tt)*) => { 120 | ipld_internal!(@array [$($elems,)*] $($rest)*) 121 | }; 122 | 123 | // Unexpected token after most recent element. 124 | (@array [$($elems:expr),*] $unexpected:tt $($rest:tt)*) => { 125 | ipld_unexpected!($unexpected) 126 | }; 127 | 128 | ////////////////////////////////////////////////////////////////////////// 129 | // TT muncher for parsing the inside of an object {...}. Each entry is 130 | // inserted into the given map variable. 131 | // 132 | // Must be invoked as: json_internal!(@object $map () ($($tt)*) ($($tt)*)) 133 | // 134 | // We require two copies of the input tokens so that we can match on one 135 | // copy and trigger errors on the other copy. 136 | ////////////////////////////////////////////////////////////////////////// 137 | 138 | // Done. 139 | (@object $object:ident () () ()) => {}; 140 | 141 | // Insert the current entry followed by trailing comma. 142 | (@object $object:ident [$($key:tt)+] ($value:expr) , $($rest:tt)*) => { 143 | let _ = $object.insert(($($key)+).into(), $value); 144 | ipld_internal!(@object $object () ($($rest)*) ($($rest)*)); 145 | }; 146 | 147 | // Current entry followed by unexpected token. 148 | (@object $object:ident [$($key:tt)+] ($value:expr) $unexpected:tt $($rest:tt)*) => { 149 | ipld_unexpected!($unexpected); 150 | }; 151 | 152 | // Insert the last entry without trailing comma. 153 | (@object $object:ident [$($key:tt)+] ($value:expr)) => { 154 | let _ = $object.insert(($($key)+).into(), $value); 155 | }; 156 | 157 | // Next value is `null`. 158 | (@object $object:ident ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => { 159 | ipld_internal!(@object $object [$($key)+] (ipld_internal!(null)) $($rest)*); 160 | }; 161 | 162 | // Next value is `true`. 163 | (@object $object:ident ($($key:tt)+) (: true $($rest:tt)*) $copy:tt) => { 164 | ipld_internal!(@object $object [$($key)+] (ipld_internal!(true)) $($rest)*); 165 | }; 166 | 167 | // Next value is `false`. 168 | (@object $object:ident ($($key:tt)+) (: false $($rest:tt)*) $copy:tt) => { 169 | ipld_internal!(@object $object [$($key)+] (ipld_internal!(false)) $($rest)*); 170 | }; 171 | 172 | // Next value is an array. 173 | (@object $object:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => { 174 | ipld_internal!(@object $object [$($key)+] (ipld_internal!([$($array)*])) $($rest)*); 175 | }; 176 | 177 | // Next value is a map. 178 | (@object $object:ident ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => { 179 | ipld_internal!(@object $object [$($key)+] (ipld_internal!({$($map)*})) $($rest)*); 180 | }; 181 | 182 | // Next value is an expression followed by comma. 183 | (@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => { 184 | ipld_internal!(@object $object [$($key)+] (ipld_internal!($value)) , $($rest)*); 185 | }; 186 | 187 | // Last value is an expression with no trailing comma. 188 | (@object $object:ident ($($key:tt)+) (: $value:expr) $copy:tt) => { 189 | ipld_internal!(@object $object [$($key)+] (ipld_internal!($value))); 190 | }; 191 | 192 | // Missing value for last entry. Trigger a reasonable error message. 193 | (@object $object:ident ($($key:tt)+) (:) $copy:tt) => { 194 | // "unexpected end of macro invocation" 195 | ipld_internal!(); 196 | }; 197 | 198 | // Missing colon and value for last entry. Trigger a reasonable error 199 | // message. 200 | (@object $object:ident ($($key:tt)+) () $copy:tt) => { 201 | // "unexpected end of macro invocation" 202 | ipld_internal!(); 203 | }; 204 | 205 | // Misplaced colon. Trigger a reasonable error message. 206 | (@object $object:ident () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => { 207 | // Takes no arguments so "no rules expected the token `:`". 208 | ipld_unexpected!($colon); 209 | }; 210 | 211 | // Found a comma inside a key. Trigger a reasonable error message. 212 | (@object $object:ident ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => { 213 | // Takes no arguments so "no rules expected the token `,`". 214 | ipld_unexpected!($comma); 215 | }; 216 | 217 | // Key is fully parenthesized. This avoids clippy double_parens false 218 | // positives because the parenthesization may be necessary here. 219 | (@object $object:ident () (($key:expr) : $($rest:tt)*) $copy:tt) => { 220 | ipld_internal!(@object $object ($key) (: $($rest)*) (: $($rest)*)); 221 | }; 222 | 223 | // Munch a token into the current key. 224 | (@object $object:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => { 225 | ipld_internal!(@object $object ($($key)* $tt) ($($rest)*) ($($rest)*)); 226 | }; 227 | 228 | ////////////////////////////////////////////////////////////////////////// 229 | // The main implementation. 230 | // 231 | // Must be invoked as: json_internal!($($json)+) 232 | ////////////////////////////////////////////////////////////////////////// 233 | 234 | (null) => { 235 | $crate::Ipld::Null 236 | }; 237 | 238 | (true) => { 239 | $crate::Ipld::Bool(true) 240 | }; 241 | 242 | (false) => { 243 | $crate::Ipld::Bool(false) 244 | }; 245 | 246 | ([]) => { 247 | $crate::Ipld::List(ipld_internal_vec![]) 248 | }; 249 | 250 | ([ $($tt:tt)+ ]) => { 251 | $crate::Ipld::List(ipld_internal!(@array [] $($tt)+)) 252 | }; 253 | 254 | ({}) => { 255 | $crate::Ipld::Map(std::collections::BTreeMap::new()) 256 | }; 257 | 258 | ({ $($tt:tt)+ }) => { 259 | $crate::Ipld::Map({ 260 | let mut object = std::collections::BTreeMap::new(); 261 | ipld_internal!(@object object () ($($tt)+) ($($tt)+)); 262 | object 263 | }) 264 | }; 265 | 266 | // Any Serialize type: numbers, strings, struct literals, variables etc. 267 | // Must be below every other rule. 268 | ($other:expr) => { 269 | { 270 | $crate::Ipld::from($other) 271 | } 272 | }; 273 | } 274 | 275 | // The json_internal macro above cannot invoke vec directly because it uses 276 | // local_inner_macros. A vec invocation there would resolve to $crate::vec. 277 | // Instead invoke vec here outside of local_inner_macros. 278 | #[macro_export] 279 | #[doc(hidden)] 280 | macro_rules! ipld_internal_vec { 281 | ($($content:tt)*) => { 282 | vec![$($content)*] 283 | }; 284 | } 285 | 286 | #[macro_export] 287 | #[doc(hidden)] 288 | macro_rules! ipld_unexpected { 289 | () => {}; 290 | } 291 | 292 | #[cfg(test)] 293 | mod tests { 294 | use super::*; 295 | use libipld_core::cid::Cid; 296 | use libipld_core::multihash::{Code, MultihashDigest}; 297 | 298 | #[test] 299 | fn test_macro() { 300 | let _: Ipld = ipld!(null); 301 | let _: Ipld = ipld!(true); 302 | let _: Ipld = ipld!(false); 303 | let _: Ipld = ipld!(1); 304 | let _: Ipld = ipld!(1.0); 305 | let a: Ipld = ipld!("string"); 306 | let _: Ipld = ipld!([]); 307 | let _: Ipld = ipld!([1, 2, 3]); 308 | let _: Ipld = ipld!({}); 309 | let _: Ipld = ipld!({ 310 | "bye": null, 311 | "numbers": [1, 2, 3], 312 | "a": a, 313 | }); 314 | let mh = Code::Blake3_256.digest(&b"cid"[..]); 315 | let _: Ipld = ipld!(Cid::new_v1(0, mh)); 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /core/src/convert.rs: -------------------------------------------------------------------------------- 1 | //! Conversion to and from ipld. 2 | use alloc::{ 3 | borrow::ToOwned, 4 | boxed::Box, 5 | collections::BTreeMap, 6 | string::{String, ToString}, 7 | vec::Vec, 8 | }; 9 | 10 | use crate::{cid::Cid, ipld::Ipld}; 11 | 12 | #[cfg(feature = "std")] 13 | use crate::error::{Error, TypeError, TypeErrorType}; 14 | 15 | #[cfg(feature = "std")] 16 | impl TryFrom for () { 17 | type Error = Error; 18 | 19 | fn try_from(ipld: Ipld) -> Result { 20 | match ipld { 21 | Ipld::Null => Ok(()), 22 | _ => Err(TypeError { 23 | expected: TypeErrorType::Null, 24 | found: ipld.into(), 25 | } 26 | .into()), 27 | } 28 | } 29 | } 30 | 31 | #[cfg(feature = "std")] 32 | macro_rules! derive_try_from_ipld_option { 33 | ($enum:ident, $ty:ty) => { 34 | impl TryFrom for Option<$ty> { 35 | type Error = Error; 36 | 37 | fn try_from(ipld: Ipld) -> Result { 38 | match ipld { 39 | Ipld::Null => Ok(None), 40 | Ipld::$enum(value) => Ok(Some(value.try_into()?)), 41 | _ => Err(TypeError { 42 | expected: TypeErrorType::$enum, 43 | found: ipld.into(), 44 | } 45 | .into()), 46 | } 47 | } 48 | } 49 | }; 50 | } 51 | 52 | #[cfg(feature = "std")] 53 | macro_rules! derive_try_from_ipld { 54 | ($enum:ident, $ty:ty) => { 55 | impl TryFrom for $ty { 56 | type Error = Error; 57 | 58 | fn try_from(ipld: Ipld) -> Result { 59 | match ipld { 60 | Ipld::$enum(value) => Ok(value.try_into()?), 61 | _ => Err(TypeError { 62 | expected: TypeErrorType::$enum, 63 | found: ipld.into(), 64 | } 65 | .into()), 66 | } 67 | } 68 | } 69 | }; 70 | } 71 | 72 | macro_rules! derive_into_ipld_prim { 73 | ($enum:ident, $ty:ty, $fn:ident) => { 74 | impl From<$ty> for Ipld { 75 | fn from(t: $ty) -> Self { 76 | Ipld::$enum(t.$fn() as _) 77 | } 78 | } 79 | }; 80 | } 81 | 82 | macro_rules! derive_into_ipld { 83 | ($enum:ident, $ty:ty, $($fn:ident),*) => { 84 | impl From<$ty> for Ipld { 85 | fn from(t: $ty) -> Self { 86 | Ipld::$enum(t$(.$fn())*) 87 | } 88 | } 89 | }; 90 | } 91 | 92 | derive_into_ipld!(Bool, bool, clone); 93 | derive_into_ipld_prim!(Integer, i8, clone); 94 | derive_into_ipld_prim!(Integer, i16, clone); 95 | derive_into_ipld_prim!(Integer, i32, clone); 96 | derive_into_ipld_prim!(Integer, i64, clone); 97 | derive_into_ipld_prim!(Integer, i128, clone); 98 | derive_into_ipld_prim!(Integer, isize, clone); 99 | derive_into_ipld_prim!(Integer, u8, clone); 100 | derive_into_ipld_prim!(Integer, u16, clone); 101 | derive_into_ipld_prim!(Integer, u32, clone); 102 | derive_into_ipld_prim!(Integer, u64, clone); 103 | derive_into_ipld_prim!(Integer, usize, clone); 104 | derive_into_ipld_prim!(Float, f32, clone); 105 | derive_into_ipld_prim!(Float, f64, clone); 106 | derive_into_ipld!(String, String, into); 107 | derive_into_ipld!(String, &str, to_string); 108 | derive_into_ipld!(Bytes, Box<[u8]>, into_vec); 109 | derive_into_ipld!(Bytes, Vec, into); 110 | derive_into_ipld!(Bytes, &[u8], to_vec); 111 | derive_into_ipld!(List, Vec, into); 112 | derive_into_ipld!(Map, BTreeMap, to_owned); 113 | derive_into_ipld!(Link, Cid, clone); 114 | derive_into_ipld!(Link, &Cid, to_owned); 115 | 116 | #[cfg(feature = "std")] 117 | derive_try_from_ipld!(Bool, bool); 118 | #[cfg(feature = "std")] 119 | derive_try_from_ipld!(Integer, i8); 120 | #[cfg(feature = "std")] 121 | derive_try_from_ipld!(Integer, i16); 122 | #[cfg(feature = "std")] 123 | derive_try_from_ipld!(Integer, i32); 124 | #[cfg(feature = "std")] 125 | derive_try_from_ipld!(Integer, i64); 126 | #[cfg(feature = "std")] 127 | derive_try_from_ipld!(Integer, i128); 128 | #[cfg(feature = "std")] 129 | derive_try_from_ipld!(Integer, isize); 130 | #[cfg(feature = "std")] 131 | derive_try_from_ipld!(Integer, u8); 132 | #[cfg(feature = "std")] 133 | derive_try_from_ipld!(Integer, u16); 134 | #[cfg(feature = "std")] 135 | derive_try_from_ipld!(Integer, u32); 136 | #[cfg(feature = "std")] 137 | derive_try_from_ipld!(Integer, u64); 138 | #[cfg(feature = "std")] 139 | derive_try_from_ipld!(Integer, u128); 140 | #[cfg(feature = "std")] 141 | derive_try_from_ipld!(Integer, usize); 142 | 143 | //derive_from_ipld!(Float, f32); // User explicit conversion is prefered. Would implicitly lossily convert from f64. 144 | 145 | #[cfg(feature = "std")] 146 | derive_try_from_ipld!(Float, f64); 147 | #[cfg(feature = "std")] 148 | derive_try_from_ipld!(String, String); 149 | #[cfg(feature = "std")] 150 | derive_try_from_ipld!(Bytes, Vec); 151 | #[cfg(feature = "std")] 152 | derive_try_from_ipld!(List, Vec); 153 | #[cfg(feature = "std")] 154 | derive_try_from_ipld!(Map, BTreeMap); 155 | #[cfg(feature = "std")] 156 | derive_try_from_ipld!(Link, Cid); 157 | 158 | #[cfg(feature = "std")] 159 | derive_try_from_ipld_option!(Bool, bool); 160 | #[cfg(feature = "std")] 161 | derive_try_from_ipld_option!(Integer, i8); 162 | #[cfg(feature = "std")] 163 | derive_try_from_ipld_option!(Integer, i16); 164 | #[cfg(feature = "std")] 165 | derive_try_from_ipld_option!(Integer, i32); 166 | #[cfg(feature = "std")] 167 | derive_try_from_ipld_option!(Integer, i64); 168 | #[cfg(feature = "std")] 169 | derive_try_from_ipld_option!(Integer, i128); 170 | #[cfg(feature = "std")] 171 | derive_try_from_ipld_option!(Integer, isize); 172 | #[cfg(feature = "std")] 173 | derive_try_from_ipld_option!(Integer, u8); 174 | #[cfg(feature = "std")] 175 | derive_try_from_ipld_option!(Integer, u16); 176 | #[cfg(feature = "std")] 177 | derive_try_from_ipld_option!(Integer, u32); 178 | #[cfg(feature = "std")] 179 | derive_try_from_ipld_option!(Integer, u64); 180 | #[cfg(feature = "std")] 181 | derive_try_from_ipld_option!(Integer, u128); 182 | #[cfg(feature = "std")] 183 | derive_try_from_ipld_option!(Integer, usize); 184 | 185 | //derive_from_ipld_option!(Float, f32); // User explicit conversion is prefered. Would implicitly lossily convert from f64. 186 | 187 | #[cfg(feature = "std")] 188 | derive_try_from_ipld_option!(Float, f64); 189 | #[cfg(feature = "std")] 190 | derive_try_from_ipld_option!(String, String); 191 | #[cfg(feature = "std")] 192 | derive_try_from_ipld_option!(Bytes, Vec); 193 | #[cfg(feature = "std")] 194 | derive_try_from_ipld_option!(List, Vec); 195 | #[cfg(feature = "std")] 196 | derive_try_from_ipld_option!(Map, BTreeMap); 197 | #[cfg(feature = "std")] 198 | derive_try_from_ipld_option!(Link, Cid); 199 | 200 | #[cfg(test)] 201 | mod tests { 202 | use alloc::collections::BTreeMap; 203 | 204 | use cid::Cid; 205 | 206 | use crate::ipld::Ipld; 207 | 208 | #[test] 209 | #[should_panic] 210 | fn try_into_wrong_type() { 211 | let _boolean: bool = Ipld::Integer(u8::MAX as i128).try_into().unwrap(); 212 | } 213 | 214 | #[test] 215 | #[should_panic] 216 | fn try_into_wrong_range() { 217 | let int: u128 = Ipld::Integer(-1i128).try_into().unwrap(); 218 | assert_eq!(int, u128::MIN); 219 | } 220 | 221 | #[test] 222 | fn try_into_bool() { 223 | let boolean: bool = Ipld::Bool(true).try_into().unwrap(); 224 | assert!(boolean); 225 | 226 | let boolean: Option = Ipld::Null.try_into().unwrap(); 227 | assert_eq!(boolean, Option::None) 228 | } 229 | 230 | #[test] 231 | fn try_into_ints() { 232 | let int: u8 = Ipld::Integer(u8::MAX as i128).try_into().unwrap(); 233 | assert_eq!(int, u8::MAX); 234 | 235 | let int: u16 = Ipld::Integer(u16::MAX as i128).try_into().unwrap(); 236 | assert_eq!(int, u16::MAX); 237 | 238 | let int: u32 = Ipld::Integer(u32::MAX as i128).try_into().unwrap(); 239 | assert_eq!(int, u32::MAX); 240 | 241 | let int: u64 = Ipld::Integer(u64::MAX as i128).try_into().unwrap(); 242 | assert_eq!(int, u64::MAX); 243 | 244 | let int: usize = Ipld::Integer(usize::MAX as i128).try_into().unwrap(); 245 | assert_eq!(int, usize::MAX); 246 | 247 | let int: u128 = Ipld::Integer(i128::MAX).try_into().unwrap(); 248 | assert_eq!(int, i128::MAX as u128); 249 | 250 | let int: i8 = Ipld::Integer(i8::MIN as i128).try_into().unwrap(); 251 | assert_eq!(int, i8::MIN); 252 | 253 | let int: i16 = Ipld::Integer(i16::MIN as i128).try_into().unwrap(); 254 | assert_eq!(int, i16::MIN); 255 | 256 | let int: i32 = Ipld::Integer(i32::MIN as i128).try_into().unwrap(); 257 | assert_eq!(int, i32::MIN); 258 | 259 | let int: i64 = Ipld::Integer(i64::MIN as i128).try_into().unwrap(); 260 | assert_eq!(int, i64::MIN); 261 | 262 | let int: isize = Ipld::Integer(isize::MIN as i128).try_into().unwrap(); 263 | assert_eq!(int, isize::MIN); 264 | 265 | let int: i128 = Ipld::Integer(i128::MIN).try_into().unwrap(); 266 | assert_eq!(int, i128::MIN); 267 | 268 | let int: Option = Ipld::Null.try_into().unwrap(); 269 | assert_eq!(int, Option::None) 270 | } 271 | 272 | #[test] 273 | fn try_into_floats() { 274 | /* let float: f32 = Ipld::Float(f32::MAX as f64).try_into().unwrap(); 275 | assert_eq!(float, f32::MAX); */ 276 | 277 | let float: f64 = Ipld::Float(f64::MAX).try_into().unwrap(); 278 | assert_eq!(float, f64::MAX); 279 | 280 | let float: Option = Ipld::Null.try_into().unwrap(); 281 | assert_eq!(float, Option::None) 282 | } 283 | 284 | #[test] 285 | fn try_into_string() { 286 | let lyrics: String = "I'm blue babedi babeda".into(); 287 | let string: String = Ipld::String(lyrics.clone()).try_into().unwrap(); 288 | assert_eq!(string, lyrics); 289 | 290 | let option: Option = Ipld::Null.try_into().unwrap(); 291 | assert_eq!(option, Option::None) 292 | } 293 | 294 | #[test] 295 | fn try_into_vec() { 296 | let data = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; 297 | let bytes: Vec = Ipld::Bytes(data.clone()).try_into().unwrap(); 298 | assert_eq!(bytes, data); 299 | 300 | let option: Option> = Ipld::Null.try_into().unwrap(); 301 | assert_eq!(option, Option::None) 302 | } 303 | 304 | #[test] 305 | fn try_into_list() { 306 | let ints = vec![Ipld::Integer(0), Ipld::Integer(1), Ipld::Integer(2)]; 307 | let list: Vec = Ipld::List(ints.clone()).try_into().unwrap(); 308 | assert_eq!(ints, list); 309 | 310 | let option: Option> = Ipld::Null.try_into().unwrap(); 311 | assert_eq!(option, Option::None) 312 | } 313 | 314 | #[test] 315 | fn try_into_map() { 316 | let mut numbs = BTreeMap::new(); 317 | numbs.insert("zero".into(), Ipld::Integer(0)); 318 | numbs.insert("one".into(), Ipld::Integer(1)); 319 | numbs.insert("two".into(), Ipld::Integer(2)); 320 | let map: BTreeMap = Ipld::Map(numbs.clone()).try_into().unwrap(); 321 | assert_eq!(numbs, map); 322 | 323 | let option: Option> = Ipld::Null.try_into().unwrap(); 324 | assert_eq!(option, Option::None) 325 | } 326 | 327 | #[test] 328 | fn try_into_cid() { 329 | let cid = Cid::default(); 330 | let link: Cid = Ipld::Link(cid).try_into().unwrap(); 331 | assert_eq!(cid, link); 332 | 333 | let option: Option = Ipld::Null.try_into().unwrap(); 334 | assert_eq!(option, Option::None) 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /dag-cbor-derive/src/parse.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::*; 2 | use crate::attr::{Attrs, DeriveAttr, FieldAttr}; 3 | use quote::quote; 4 | use syn::parse::Parse; 5 | use syn::spanned::Spanned; 6 | use synstructure::{BindingInfo, Structure, VariantInfo}; 7 | 8 | pub fn parse(s: &Structure) -> SchemaType { 9 | match &s.ast().data { 10 | syn::Data::Struct(_) => SchemaType::Struct(parse_struct( 11 | &s.variants()[0], 12 | Some(s.ast().generics.clone()), 13 | )), 14 | syn::Data::Enum(_) => SchemaType::Union(parse_union(s)), 15 | syn::Data::Union(_) => unimplemented!(), 16 | } 17 | } 18 | 19 | fn parse_attrs(ast: &[syn::Attribute]) -> Vec { 20 | let mut derive_attrs = Vec::with_capacity(ast.len()); 21 | for attr in ast { 22 | let attrs: Result, _> = syn::parse2(attr.tokens.clone()); 23 | if let Ok(attrs) = attrs { 24 | for attr in attrs.attrs { 25 | derive_attrs.push(attr); 26 | } 27 | } 28 | } 29 | derive_attrs 30 | } 31 | 32 | fn parse_struct_repr(ast: &[syn::Attribute]) -> Option { 33 | let attrs = parse_attrs::(ast); 34 | let mut repr = None; 35 | for DeriveAttr::Repr(attr) in attrs { 36 | repr = Some(match attr.value.value().as_str() { 37 | "map" => StructRepr::Map, 38 | "tuple" => StructRepr::Tuple, 39 | "value" => StructRepr::Value, 40 | "null" => StructRepr::Null, 41 | repr => panic!("unknown struct representation {}", repr), 42 | }) 43 | } 44 | repr 45 | } 46 | 47 | fn parse_union_repr(ast: &[syn::Attribute]) -> UnionRepr { 48 | let attrs = parse_attrs::(ast); 49 | let mut repr = None; 50 | for DeriveAttr::Repr(attr) in attrs { 51 | repr = Some(match attr.value.value().as_str() { 52 | "keyed" => UnionRepr::Keyed, 53 | "kinded" => UnionRepr::Kinded, 54 | "string" => UnionRepr::String, 55 | "int" => UnionRepr::Int, 56 | "int-tuple" => UnionRepr::IntTuple, 57 | repr => panic!("unknown enum representation {}", repr), 58 | }) 59 | } 60 | repr.unwrap_or(UnionRepr::Keyed) 61 | } 62 | 63 | fn parse_struct(v: &VariantInfo, generics: Option) -> Struct { 64 | let repr = parse_struct_repr(v.ast().attrs); 65 | let mut fields: Vec<_> = v 66 | .bindings() 67 | .iter() 68 | .enumerate() 69 | .map(|(i, binding)| parse_field(i, binding)) 70 | .collect(); 71 | let repr = repr.unwrap_or_else(|| match &v.ast().fields { 72 | syn::Fields::Named(_) => StructRepr::Map, 73 | syn::Fields::Unnamed(_) => StructRepr::Tuple, 74 | syn::Fields::Unit => StructRepr::Null, 75 | }); 76 | if repr == StructRepr::Map { 77 | fields.sort_by(|f1, f2| match (&f1.name, &f2.name) { 78 | (syn::Member::Named(ident1), syn::Member::Named(ident2)) => { 79 | ident1.to_string().cmp(&ident2.to_string()) 80 | } 81 | (syn::Member::Unnamed(index1), syn::Member::Unnamed(index2)) => { 82 | index1.index.cmp(&index2.index) 83 | } 84 | _ => unreachable!(), 85 | }); 86 | } 87 | Struct { 88 | name: v.ast().ident.clone(), 89 | generics, 90 | rename: None, 91 | fields, 92 | repr, 93 | pat: TokenStreamEq(v.pat()), 94 | construct: TokenStreamEq(v.construct(|_, i| { 95 | let binding = &v.bindings()[i]; 96 | quote!(#binding) 97 | })), 98 | } 99 | } 100 | 101 | fn parse_union(s: &Structure) -> Union { 102 | let repr = parse_union_repr(&s.ast().attrs); 103 | Union { 104 | name: s.ast().ident.clone(), 105 | generics: s.ast().generics.clone(), 106 | variants: s 107 | .variants() 108 | .iter() 109 | .map(|v| { 110 | let mut s = parse_struct(v, None); 111 | for attr in parse_attrs::(v.ast().attrs) { 112 | if let FieldAttr::Rename(attr) = attr { 113 | s.rename = Some(attr.value.value()); 114 | } 115 | } 116 | s 117 | }) 118 | .collect(), 119 | repr, 120 | } 121 | } 122 | 123 | fn parse_field(i: usize, b: &BindingInfo) -> StructField { 124 | let mut field = StructField { 125 | name: match b.ast().ident.as_ref() { 126 | Some(ident) => syn::Member::Named(ident.clone()), 127 | None => syn::Member::Unnamed(syn::Index { 128 | index: i as _, 129 | span: b.ast().ty.span(), 130 | }), 131 | }, 132 | rename: None, 133 | default: None, 134 | binding: b.binding.clone(), 135 | }; 136 | for attr in parse_attrs::(&b.ast().attrs) { 137 | match attr { 138 | FieldAttr::Rename(attr) => field.rename = Some(attr.value.value()), 139 | FieldAttr::Default(attr) => field.default = Some(attr.value), 140 | } 141 | } 142 | field 143 | } 144 | 145 | #[cfg(test)] 146 | pub mod tests { 147 | use super::*; 148 | use proc_macro2::TokenStream; 149 | use quote::{format_ident, quote}; 150 | 151 | macro_rules! format_index { 152 | ($i:expr) => { 153 | syn::Index { 154 | index: $i as _, 155 | span: proc_macro2::Span::call_site(), 156 | } 157 | }; 158 | } 159 | 160 | pub fn ast(ts: TokenStream) -> SchemaType { 161 | let d = syn::parse2(ts).unwrap(); 162 | let s = Structure::new(&d); 163 | parse(&s) 164 | } 165 | 166 | #[test] 167 | fn test_struct_repr_map() { 168 | let ast = ast(quote! { 169 | #[derive(DagCbor)] 170 | #[ipld(repr = "map")] 171 | struct Map { 172 | #[ipld(rename = "other", default = false)] 173 | field: bool, 174 | } 175 | }); 176 | 177 | assert_eq!( 178 | ast, 179 | SchemaType::Struct(Struct { 180 | name: format_ident!("Map"), 181 | generics: Some(Default::default()), 182 | rename: None, 183 | fields: vec![StructField { 184 | name: syn::Member::Named(format_ident!("field")), 185 | rename: Some("other".to_string()), 186 | default: Some(syn::parse2(quote!(false)).unwrap()), 187 | binding: format_ident!("__binding_0"), 188 | }], 189 | repr: StructRepr::Map, 190 | pat: TokenStreamEq(quote! { Map { field: ref __binding_0, }}), 191 | construct: TokenStreamEq(quote! { Map { field: __binding_0, }}), 192 | }) 193 | ); 194 | } 195 | 196 | #[test] 197 | fn test_struct_repr_tuple() { 198 | let ast = ast(quote! { 199 | #[derive(DagCbor)] 200 | #[ipld(repr = "tuple")] 201 | struct Tuple(bool); 202 | }); 203 | 204 | assert_eq!( 205 | ast, 206 | SchemaType::Struct(Struct { 207 | name: format_ident!("Tuple"), 208 | generics: Some(Default::default()), 209 | rename: None, 210 | fields: vec![StructField { 211 | name: syn::Member::Unnamed(format_index!(0)), 212 | rename: None, 213 | default: None, 214 | binding: format_ident!("__binding_0"), 215 | }], 216 | repr: StructRepr::Tuple, 217 | pat: TokenStreamEq(quote! { Tuple(ref __binding_0,) }), 218 | construct: TokenStreamEq(quote! { Tuple(__binding_0,) }), 219 | }) 220 | ); 221 | } 222 | 223 | #[test] 224 | fn test_struct_repr_default() { 225 | let ast = ast(quote! { 226 | #[derive(DagCbor)] 227 | struct Map; 228 | }); 229 | 230 | assert_eq!( 231 | ast, 232 | SchemaType::Struct(Struct { 233 | name: format_ident!("Map"), 234 | generics: Some(Default::default()), 235 | rename: None, 236 | fields: Default::default(), 237 | repr: StructRepr::Null, 238 | pat: TokenStreamEq(quote!(Map)), 239 | construct: TokenStreamEq(quote!(Map)), 240 | }) 241 | ); 242 | } 243 | 244 | #[test] 245 | fn test_union_repr_default() { 246 | let ast = ast(quote! { 247 | #[derive(DagCbor)] 248 | enum Union { 249 | #[ipld(rename = "unit")] 250 | Unit, 251 | Tuple(bool), 252 | Struct { value: bool }, 253 | } 254 | }); 255 | 256 | assert_eq!( 257 | ast, 258 | SchemaType::Union(Union { 259 | name: format_ident!("Union"), 260 | generics: Default::default(), 261 | variants: vec![ 262 | Struct { 263 | name: format_ident!("Unit"), 264 | generics: None, 265 | rename: Some("unit".into()), 266 | fields: vec![], 267 | repr: StructRepr::Null, 268 | pat: TokenStreamEq(quote!(Union::Unit)), 269 | construct: TokenStreamEq(quote!(Union::Unit)), 270 | }, 271 | Struct { 272 | name: format_ident!("Tuple"), 273 | generics: None, 274 | rename: None, 275 | fields: vec![StructField { 276 | name: syn::Member::Unnamed(format_index!(0)), 277 | rename: None, 278 | default: None, 279 | binding: format_ident!("__binding_0"), 280 | }], 281 | repr: StructRepr::Tuple, 282 | pat: TokenStreamEq(quote! { Union::Tuple(ref __binding_0,) }), 283 | construct: TokenStreamEq(quote! { Union::Tuple(__binding_0,) }), 284 | }, 285 | Struct { 286 | name: format_ident!("Struct"), 287 | generics: None, 288 | rename: None, 289 | fields: vec![StructField { 290 | name: syn::Member::Named(format_ident!("value")), 291 | rename: None, 292 | default: None, 293 | binding: format_ident!("__binding_0"), 294 | }], 295 | repr: StructRepr::Map, 296 | pat: TokenStreamEq(quote! { Union::Struct { value: ref __binding_0, } }), 297 | construct: TokenStreamEq(quote! { Union::Struct { value: __binding_0, } }), 298 | } 299 | ], 300 | repr: UnionRepr::Keyed, 301 | }) 302 | ); 303 | } 304 | 305 | #[test] 306 | fn test_enum_repr_string() { 307 | let ast = ast(quote! { 308 | #[derive(DagCbor)] 309 | #[ipld(repr = "string")] 310 | enum Enum { 311 | #[ipld(rename = "test")] 312 | Variant, 313 | } 314 | }); 315 | 316 | assert_eq!( 317 | ast, 318 | SchemaType::Union(Union { 319 | name: format_ident!("Enum"), 320 | generics: Default::default(), 321 | variants: vec![Struct { 322 | name: format_ident!("Variant"), 323 | generics: None, 324 | rename: Some("test".into()), 325 | fields: vec![], 326 | repr: StructRepr::Null, 327 | pat: TokenStreamEq(quote!(Enum::Variant)), 328 | construct: TokenStreamEq(quote!(Enum::Variant)), 329 | }], 330 | repr: UnionRepr::String, 331 | }) 332 | ); 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /dag-cbor/src/encode.rs: -------------------------------------------------------------------------------- 1 | //! CBOR encoder. 2 | 3 | use std::cmp::Ordering; 4 | use std::collections::BTreeMap; 5 | use std::io::Write; 6 | use std::iter::FromIterator; 7 | use std::ops::Deref; 8 | use std::sync::Arc; 9 | 10 | use byteorder::{BigEndian, ByteOrder}; 11 | use libipld_core::cid::Cid; 12 | use libipld_core::codec::Encode; 13 | use libipld_core::error::Result; 14 | use libipld_core::ipld::Ipld; 15 | 16 | use crate::cbor::{MajorKind, FALSE, TRUE}; 17 | use crate::error::NumberOutOfRange; 18 | use crate::DagCborCodec as DagCbor; 19 | 20 | /// Writes a null byte to a cbor encoded byte stream. 21 | pub fn write_null(w: &mut W) -> Result<()> { 22 | w.write_all(&[0xf6])?; 23 | Ok(()) 24 | } 25 | 26 | /// Writes a u8 to a cbor encoded byte stream. 27 | pub fn write_u8(w: &mut W, major: MajorKind, value: u8) -> Result<()> { 28 | let major = major as u8; 29 | if value <= 0x17 { 30 | let buf = [major << 5 | value]; 31 | w.write_all(&buf)?; 32 | } else { 33 | let buf = [major << 5 | 24, value]; 34 | w.write_all(&buf)?; 35 | } 36 | Ok(()) 37 | } 38 | 39 | /// Writes a u16 to a cbor encoded byte stream. 40 | pub fn write_u16(w: &mut W, major: MajorKind, value: u16) -> Result<()> { 41 | if value <= u16::from(u8::max_value()) { 42 | write_u8(w, major, value as u8)?; 43 | } else { 44 | let mut buf = [(major as u8) << 5 | 25, 0, 0]; 45 | BigEndian::write_u16(&mut buf[1..], value); 46 | w.write_all(&buf)?; 47 | } 48 | Ok(()) 49 | } 50 | 51 | /// Writes a u32 to a cbor encoded byte stream. 52 | pub fn write_u32(w: &mut W, major: MajorKind, value: u32) -> Result<()> { 53 | if value <= u32::from(u16::max_value()) { 54 | write_u16(w, major, value as u16)?; 55 | } else { 56 | let mut buf = [(major as u8) << 5 | 26, 0, 0, 0, 0]; 57 | BigEndian::write_u32(&mut buf[1..], value); 58 | w.write_all(&buf)?; 59 | } 60 | Ok(()) 61 | } 62 | 63 | /// Writes a u64 to a cbor encoded byte stream. 64 | pub fn write_u64(w: &mut W, major: MajorKind, value: u64) -> Result<()> { 65 | if value <= u64::from(u32::max_value()) { 66 | write_u32(w, major, value as u32)?; 67 | } else { 68 | let mut buf = [(major as u8) << 5 | 27, 0, 0, 0, 0, 0, 0, 0, 0]; 69 | BigEndian::write_u64(&mut buf[1..], value); 70 | w.write_all(&buf)?; 71 | } 72 | Ok(()) 73 | } 74 | 75 | /// Writes a tag to a cbor encoded byte stream. 76 | pub fn write_tag(w: &mut W, tag: u64) -> Result<()> { 77 | write_u64(w, MajorKind::Tag, tag) 78 | } 79 | 80 | impl Encode for bool { 81 | fn encode(&self, _: DagCbor, w: &mut W) -> Result<()> { 82 | let buf = if *self { [TRUE.into()] } else { [FALSE.into()] }; 83 | w.write_all(&buf)?; 84 | Ok(()) 85 | } 86 | } 87 | 88 | impl Encode for u8 { 89 | fn encode(&self, _: DagCbor, w: &mut W) -> Result<()> { 90 | write_u8(w, MajorKind::UnsignedInt, *self) 91 | } 92 | } 93 | 94 | impl Encode for u16 { 95 | fn encode(&self, _: DagCbor, w: &mut W) -> Result<()> { 96 | write_u16(w, MajorKind::UnsignedInt, *self) 97 | } 98 | } 99 | 100 | impl Encode for u32 { 101 | fn encode(&self, _: DagCbor, w: &mut W) -> Result<()> { 102 | write_u32(w, MajorKind::UnsignedInt, *self) 103 | } 104 | } 105 | 106 | impl Encode for u64 { 107 | fn encode(&self, _: DagCbor, w: &mut W) -> Result<()> { 108 | write_u64(w, MajorKind::UnsignedInt, *self) 109 | } 110 | } 111 | 112 | impl Encode for i8 { 113 | fn encode(&self, c: DagCbor, w: &mut W) -> Result<()> { 114 | if self.is_negative() { 115 | write_u8(w, MajorKind::NegativeInt, -(*self + 1) as u8) 116 | } else { 117 | (*self as u8).encode(c, w) 118 | } 119 | } 120 | } 121 | 122 | impl Encode for i16 { 123 | fn encode(&self, c: DagCbor, w: &mut W) -> Result<()> { 124 | if self.is_negative() { 125 | write_u16(w, MajorKind::NegativeInt, -(*self + 1) as u16) 126 | } else { 127 | (*self as u16).encode(c, w) 128 | } 129 | } 130 | } 131 | 132 | impl Encode for i32 { 133 | fn encode(&self, c: DagCbor, w: &mut W) -> Result<()> { 134 | if self.is_negative() { 135 | write_u32(w, MajorKind::NegativeInt, -(*self + 1) as u32) 136 | } else { 137 | (*self as u32).encode(c, w) 138 | } 139 | } 140 | } 141 | 142 | impl Encode for i64 { 143 | fn encode(&self, c: DagCbor, w: &mut W) -> Result<()> { 144 | if self.is_negative() { 145 | write_u64(w, MajorKind::NegativeInt, -(*self + 1) as u64) 146 | } else { 147 | (*self as u64).encode(c, w) 148 | } 149 | } 150 | } 151 | 152 | macro_rules! impl_nonzero { 153 | ($($t:ty),*) => { 154 | $( 155 | impl Encode for $t { 156 | fn encode(&self, c: DagCbor, w: &mut W) -> Result<()> { 157 | self.get().encode(c, w) 158 | } 159 | } 160 | )* 161 | }; 162 | } 163 | 164 | impl_nonzero!( 165 | std::num::NonZeroU8, 166 | std::num::NonZeroU16, 167 | std::num::NonZeroU32, 168 | std::num::NonZeroU64, 169 | std::num::NonZeroI8, 170 | std::num::NonZeroI16, 171 | std::num::NonZeroI32, 172 | std::num::NonZeroI64, 173 | std::num::NonZeroI128 174 | ); 175 | 176 | impl Encode for f32 { 177 | fn encode(&self, c: DagCbor, w: &mut W) -> Result<()> { 178 | // IPLD maximally encodes floats. 179 | f64::from(*self).encode(c, w) 180 | } 181 | } 182 | 183 | impl Encode for f64 { 184 | fn encode(&self, _: DagCbor, w: &mut W) -> Result<()> { 185 | // IPLD forbids nan, infinities, etc. 186 | if !self.is_finite() { 187 | return Err(NumberOutOfRange::new::().into()); 188 | } 189 | let mut buf = [0xfb, 0, 0, 0, 0, 0, 0, 0, 0]; 190 | BigEndian::write_f64(&mut buf[1..], *self); 191 | w.write_all(&buf)?; 192 | Ok(()) 193 | } 194 | } 195 | 196 | impl Encode for [u8] { 197 | fn encode(&self, _: DagCbor, w: &mut W) -> Result<()> { 198 | write_u64(w, MajorKind::ByteString, self.len() as u64)?; 199 | w.write_all(self)?; 200 | Ok(()) 201 | } 202 | } 203 | 204 | impl Encode for Box<[u8]> { 205 | fn encode(&self, c: DagCbor, w: &mut W) -> Result<()> { 206 | self[..].encode(c, w) 207 | } 208 | } 209 | 210 | impl Encode for str { 211 | fn encode(&self, _: DagCbor, w: &mut W) -> Result<()> { 212 | write_u64(w, MajorKind::TextString, self.len() as u64)?; 213 | w.write_all(self.as_bytes())?; 214 | Ok(()) 215 | } 216 | } 217 | 218 | impl Encode for String { 219 | fn encode(&self, c: DagCbor, w: &mut W) -> Result<()> { 220 | self.as_str().encode(c, w) 221 | } 222 | } 223 | 224 | impl Encode for i128 { 225 | fn encode(&self, _: DagCbor, w: &mut W) -> Result<()> { 226 | if *self < 0 { 227 | if -(*self + 1) > u64::max_value() as i128 { 228 | return Err(NumberOutOfRange::new::().into()); 229 | } 230 | write_u64(w, MajorKind::NegativeInt, -(*self + 1) as u64)?; 231 | } else { 232 | if *self > u64::max_value() as i128 { 233 | return Err(NumberOutOfRange::new::().into()); 234 | } 235 | write_u64(w, MajorKind::UnsignedInt, *self as u64)?; 236 | } 237 | Ok(()) 238 | } 239 | } 240 | 241 | impl Encode for Cid { 242 | fn encode(&self, _: DagCbor, w: &mut W) -> Result<()> { 243 | write_tag(w, 42)?; 244 | // insert zero byte per https://github.com/ipld/specs/blob/master/block-layer/codecs/dag-cbor.md#links 245 | // TODO: don't allocate 246 | let buf = self.to_bytes(); 247 | let len = buf.len(); 248 | write_u64(w, MajorKind::ByteString, len as u64 + 1)?; 249 | w.write_all(&[0])?; 250 | w.write_all(&buf[..len])?; 251 | Ok(()) 252 | } 253 | } 254 | 255 | impl> Encode for Option { 256 | fn encode(&self, c: DagCbor, w: &mut W) -> Result<()> { 257 | if let Some(value) = self { 258 | value.encode(c, w)?; 259 | } else { 260 | write_null(w)?; 261 | } 262 | Ok(()) 263 | } 264 | } 265 | 266 | impl> Encode for Vec { 267 | fn encode(&self, c: DagCbor, w: &mut W) -> Result<()> { 268 | write_u64(w, MajorKind::Array, self.len() as u64)?; 269 | for value in self { 270 | value.encode(c, w)?; 271 | } 272 | Ok(()) 273 | } 274 | } 275 | 276 | impl + 'static> Encode for BTreeMap { 277 | fn encode(&self, c: DagCbor, w: &mut W) -> Result<()> { 278 | write_u64(w, MajorKind::Map, self.len() as u64)?; 279 | // CBOR RFC-7049 specifies a canonical sort order, where keys are sorted by length first. 280 | // This was later revised with RFC-8949, but we need to stick to the original order to stay 281 | // compatible with existing data. 282 | let mut cbor_order = Vec::from_iter(self); 283 | cbor_order.sort_unstable_by(|&(key_a, _), &(key_b, _)| { 284 | match key_a.len().cmp(&key_b.len()) { 285 | Ordering::Greater => Ordering::Greater, 286 | Ordering::Less => Ordering::Less, 287 | Ordering::Equal => key_a.cmp(key_b), 288 | } 289 | }); 290 | for (k, v) in cbor_order { 291 | k.encode(c, w)?; 292 | v.encode(c, w)?; 293 | } 294 | Ok(()) 295 | } 296 | } 297 | 298 | impl Encode for Ipld { 299 | fn encode(&self, c: DagCbor, w: &mut W) -> Result<()> { 300 | match self { 301 | Self::Null => write_null(w), 302 | Self::Bool(b) => b.encode(c, w), 303 | Self::Integer(i) => i.encode(c, w), 304 | Self::Float(f) => f.encode(c, w), 305 | Self::Bytes(b) => b.as_slice().encode(c, w), 306 | Self::String(s) => s.encode(c, w), 307 | Self::List(l) => l.encode(c, w), 308 | Self::Map(m) => m.encode(c, w), 309 | Self::Link(cid) => cid.encode(c, w), 310 | } 311 | } 312 | } 313 | 314 | impl> Encode for Arc { 315 | fn encode(&self, c: DagCbor, w: &mut W) -> Result<()> { 316 | self.deref().encode(c, w) 317 | } 318 | } 319 | 320 | impl Encode for () { 321 | fn encode(&self, _c: DagCbor, w: &mut W) -> Result<()> { 322 | write_u8(w, MajorKind::Array, 0)?; 323 | Ok(()) 324 | } 325 | } 326 | 327 | impl> Encode for (A,) { 328 | fn encode(&self, c: DagCbor, w: &mut W) -> Result<()> { 329 | write_u8(w, MajorKind::Array, 1)?; 330 | self.0.encode(c, w)?; 331 | Ok(()) 332 | } 333 | } 334 | 335 | impl, B: Encode> Encode for (A, B) { 336 | fn encode(&self, c: DagCbor, w: &mut W) -> Result<()> { 337 | write_u8(w, MajorKind::Array, 2)?; 338 | self.0.encode(c, w)?; 339 | self.1.encode(c, w)?; 340 | Ok(()) 341 | } 342 | } 343 | 344 | impl, B: Encode, C: Encode> Encode for (A, B, C) { 345 | fn encode(&self, c: DagCbor, w: &mut W) -> Result<()> { 346 | write_u8(w, MajorKind::Array, 3)?; 347 | self.0.encode(c, w)?; 348 | self.1.encode(c, w)?; 349 | self.2.encode(c, w)?; 350 | Ok(()) 351 | } 352 | } 353 | 354 | impl, B: Encode, C: Encode, D: Encode> Encode 355 | for (A, B, C, D) 356 | { 357 | fn encode(&self, c: DagCbor, w: &mut W) -> Result<()> { 358 | write_u8(w, MajorKind::Array, 4)?; 359 | self.0.encode(c, w)?; 360 | self.1.encode(c, w)?; 361 | self.2.encode(c, w)?; 362 | self.3.encode(c, w)?; 363 | Ok(()) 364 | } 365 | } 366 | -------------------------------------------------------------------------------- /dag-pb/src/codec.rs: -------------------------------------------------------------------------------- 1 | use core::convert::{TryFrom, TryInto}; 2 | use std::collections::BTreeMap; 3 | 4 | use bytes::Bytes; 5 | use libipld_core::cid::Cid; 6 | use libipld_core::error::{Result, TypeError, TypeErrorType}; 7 | use libipld_core::ipld::Ipld; 8 | use quick_protobuf::sizeofs::{sizeof_len, sizeof_varint}; 9 | use quick_protobuf::{BytesReader, MessageRead, MessageWrite, Writer, WriterBackend}; 10 | 11 | /// A protobuf ipld link. 12 | #[derive(Debug, PartialEq, Eq, Clone)] 13 | pub struct PbLink { 14 | /// Content identifier. 15 | pub cid: Cid, 16 | /// Name of the link. 17 | pub name: Option, 18 | /// Size of the data. 19 | pub size: Option, 20 | } 21 | 22 | /// A protobuf ipld node. 23 | #[derive(Debug, PartialEq, Eq, Clone, Default)] 24 | pub struct PbNode { 25 | /// List of protobuf ipld links. 26 | pub links: Vec, 27 | /// Binary data blob. 28 | pub data: Option, 29 | } 30 | 31 | #[derive(Debug, PartialEq, Eq, Clone, Default)] 32 | pub(crate) struct PbNodeRef<'a> { 33 | links: Vec, 34 | data: Option<&'a [u8]>, 35 | } 36 | 37 | impl PbNode { 38 | pub(crate) fn links(bytes: Bytes, links: &mut impl Extend) -> Result<()> { 39 | let node = PbNode::from_bytes(bytes)?; 40 | for link in node.links { 41 | links.extend(Some(link.cid)); 42 | } 43 | Ok(()) 44 | } 45 | 46 | /// Deserializes a `PbNode` from bytes. 47 | pub fn from_bytes(buf: Bytes) -> Result { 48 | let mut reader = BytesReader::from_bytes(&buf); 49 | let node = PbNodeRef::from_reader(&mut reader, &buf)?; 50 | let data = node.data.map(|d| buf.slice_ref(d)); 51 | 52 | Ok(PbNode { 53 | links: node.links, 54 | data, 55 | }) 56 | } 57 | 58 | /// Serializes a `PbNode` to bytes. 59 | pub fn into_bytes(mut self) -> Box<[u8]> { 60 | // Links must be strictly sorted by name before encoding, leaving stable 61 | // ordering where the names are the same (or absent). 62 | self.links.sort_by(|a, b| { 63 | let a = a.name.as_ref().map(|s| s.as_bytes()).unwrap_or(&[][..]); 64 | let b = b.name.as_ref().map(|s| s.as_bytes()).unwrap_or(&[][..]); 65 | a.cmp(b) 66 | }); 67 | 68 | let mut buf = Vec::with_capacity(self.get_size()); 69 | let mut writer = Writer::new(&mut buf); 70 | self.write_message(&mut writer) 71 | .expect("protobuf to be valid"); 72 | buf.into_boxed_slice() 73 | } 74 | } 75 | 76 | impl PbNodeRef<'_> { 77 | /// Serializes a `PbNode` to bytes. 78 | pub fn into_bytes(mut self) -> Box<[u8]> { 79 | // Links must be strictly sorted by name before encoding, leaving stable 80 | // ordering where the names are the same (or absent). 81 | self.links.sort_by(|a, b| { 82 | let a = a.name.as_ref().map(|s| s.as_bytes()).unwrap_or(&[][..]); 83 | let b = b.name.as_ref().map(|s| s.as_bytes()).unwrap_or(&[][..]); 84 | a.cmp(b) 85 | }); 86 | 87 | let mut buf = Vec::with_capacity(self.get_size()); 88 | let mut writer = Writer::new(&mut buf); 89 | self.write_message(&mut writer) 90 | .expect("protobuf to be valid"); 91 | buf.into_boxed_slice() 92 | } 93 | } 94 | 95 | impl From for Ipld { 96 | fn from(node: PbNode) -> Self { 97 | let mut map = BTreeMap::::new(); 98 | let links = node 99 | .links 100 | .into_iter() 101 | .map(|link| link.into()) 102 | .collect::>(); 103 | map.insert("Links".to_string(), links.into()); 104 | if let Some(data) = node.data { 105 | map.insert("Data".to_string(), Ipld::Bytes(data.to_vec())); 106 | } 107 | map.into() 108 | } 109 | } 110 | 111 | impl From for Ipld { 112 | fn from(link: PbLink) -> Self { 113 | let mut map = BTreeMap::::new(); 114 | map.insert("Hash".to_string(), link.cid.into()); 115 | 116 | if let Some(name) = link.name { 117 | map.insert("Name".to_string(), name.into()); 118 | } 119 | if let Some(size) = link.size { 120 | map.insert("Tsize".to_string(), size.into()); 121 | } 122 | map.into() 123 | } 124 | } 125 | 126 | impl<'a> TryFrom<&'a Ipld> for PbNodeRef<'a> { 127 | type Error = TypeError; 128 | 129 | fn try_from(ipld: &'a Ipld) -> core::result::Result { 130 | let mut node = PbNodeRef::default(); 131 | 132 | match ipld.get("Links")? { 133 | Ipld::List(links) => { 134 | let mut prev_name = "".to_string(); 135 | for link in links.iter() { 136 | match link { 137 | Ipld::Map(_) => { 138 | let pb_link: PbLink = link.try_into()?; 139 | // Make sure the links are sorted correctly. 140 | if let Some(ref name) = pb_link.name { 141 | if name.as_bytes() < prev_name.as_bytes() { 142 | // This error message isn't ideal, but the important thing is 143 | // that it errors. 144 | return Err(TypeError::new(TypeErrorType::Link, ipld)); 145 | } 146 | prev_name = name.clone() 147 | } 148 | node.links.push(pb_link) 149 | } 150 | ipld => return Err(TypeError::new(TypeErrorType::Link, ipld)), 151 | } 152 | } 153 | } 154 | ipld => return Err(TypeError::new(TypeErrorType::List, ipld)), 155 | } 156 | 157 | match ipld.get("Data") { 158 | Ok(Ipld::Bytes(data)) => node.data = Some(&data[..]), 159 | Ok(ipld) => return Err(TypeError::new(TypeErrorType::Bytes, ipld)), 160 | _ => (), 161 | } 162 | 163 | Ok(node) 164 | } 165 | } 166 | 167 | impl TryFrom<&Ipld> for PbLink { 168 | type Error = TypeError; 169 | 170 | fn try_from(ipld: &Ipld) -> core::result::Result { 171 | if let Ipld::Map(map) = ipld { 172 | let mut cid = None; 173 | let mut name = None; 174 | let mut size = None; 175 | for (key, value) in map { 176 | match key.as_str() { 177 | "Hash" => { 178 | cid = if let Ipld::Link(cid) = value { 179 | Some(*cid) 180 | } else { 181 | return Err(TypeError::new(TypeErrorType::Link, ipld)); 182 | }; 183 | } 184 | "Name" => { 185 | name = if let Ipld::String(name) = value { 186 | Some(name.clone()) 187 | } else { 188 | return Err(TypeError::new(TypeErrorType::String, ipld)); 189 | } 190 | } 191 | "Tsize" => { 192 | size = if let Ipld::Integer(size) = value { 193 | Some( 194 | u64::try_from(*size) 195 | .map_err(|_| TypeError::new(TypeErrorType::Integer, value))?, 196 | ) 197 | } else { 198 | return Err(TypeError::new(TypeErrorType::Integer, ipld)); 199 | } 200 | } 201 | _ => { 202 | return Err(TypeError::new( 203 | TypeErrorType::Key("Hash, Name or Tsize".to_string()), 204 | TypeErrorType::Key(key.to_string()), 205 | )); 206 | } 207 | } 208 | } 209 | 210 | // Name and size are optional, CID is not. 211 | match cid { 212 | Some(cid) => Ok(PbLink { cid, name, size }), 213 | None => Err(TypeError::new(TypeErrorType::Key("Hash".to_string()), ipld)), 214 | } 215 | } else { 216 | Err(TypeError::new(TypeErrorType::Map, ipld)) 217 | } 218 | } 219 | } 220 | 221 | impl<'a> MessageRead<'a> for PbLink { 222 | fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> quick_protobuf::Result { 223 | let mut cid = None; 224 | let mut name = None; 225 | let mut size = None; 226 | 227 | while !r.is_eof() { 228 | match r.next_tag(bytes) { 229 | Ok(10) => { 230 | let bytes = r.read_bytes(bytes)?; 231 | cid = Some( 232 | Cid::try_from(bytes) 233 | .map_err(|e| quick_protobuf::Error::Message(e.to_string()))?, 234 | ); 235 | } 236 | Ok(18) => name = Some(r.read_string(bytes)?.to_string()), 237 | Ok(24) => size = Some(r.read_uint64(bytes)?), 238 | Ok(_) => { 239 | return Err(quick_protobuf::Error::Message( 240 | "unexpected bytes".to_string(), 241 | )) 242 | } 243 | Err(e) => return Err(e), 244 | } 245 | } 246 | Ok(PbLink { 247 | cid: cid.ok_or_else(|| quick_protobuf::Error::Message("missing Hash".into()))?, 248 | name, 249 | size, 250 | }) 251 | } 252 | } 253 | 254 | impl MessageWrite for PbLink { 255 | fn get_size(&self) -> usize { 256 | let mut size = 0; 257 | let l = self.cid.encoded_len(); 258 | size += 1 + sizeof_len(l); 259 | 260 | if let Some(ref name) = self.name { 261 | size += 1 + sizeof_len(name.as_bytes().len()); 262 | } 263 | 264 | if let Some(tsize) = self.size { 265 | size += 1 + sizeof_varint(tsize); 266 | } 267 | size 268 | } 269 | 270 | fn write_message(&self, w: &mut Writer) -> quick_protobuf::Result<()> { 271 | let bytes = self.cid.to_bytes(); 272 | w.write_with_tag(10, |w| w.write_bytes(&bytes))?; 273 | 274 | if let Some(ref name) = self.name { 275 | w.write_with_tag(18, |w| w.write_string(name))?; 276 | } 277 | if let Some(size) = self.size { 278 | w.write_with_tag(24, |w| w.write_uint64(size))?; 279 | } 280 | Ok(()) 281 | } 282 | } 283 | 284 | impl<'a> MessageRead<'a> for PbNodeRef<'a> { 285 | fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> quick_protobuf::Result { 286 | let mut msg = Self::default(); 287 | let mut links_before_data = false; 288 | while !r.is_eof() { 289 | match r.next_tag(bytes) { 290 | Ok(18) => { 291 | // Links and data might be in any order, but they may not be interleaved. 292 | if links_before_data { 293 | return Err(quick_protobuf::Error::Message( 294 | "duplicate Links section".to_string(), 295 | )); 296 | } 297 | msg.links.push(r.read_message::(bytes)?) 298 | } 299 | Ok(10) => { 300 | msg.data = Some(r.read_bytes(bytes)?); 301 | if !msg.links.is_empty() { 302 | links_before_data = true 303 | } 304 | } 305 | Ok(_) => { 306 | return Err(quick_protobuf::Error::Message( 307 | "unexpected bytes".to_string(), 308 | )) 309 | } 310 | Err(e) => return Err(e), 311 | } 312 | } 313 | Ok(msg) 314 | } 315 | } 316 | 317 | impl MessageWrite for PbNode { 318 | fn get_size(&self) -> usize { 319 | let mut size = 0; 320 | if let Some(ref data) = self.data { 321 | size += 1 + sizeof_len(data.len()); 322 | } 323 | 324 | size += self 325 | .links 326 | .iter() 327 | .map(|s| 1 + sizeof_len((s).get_size())) 328 | .sum::(); 329 | 330 | size 331 | } 332 | 333 | fn write_message(&self, w: &mut Writer) -> quick_protobuf::Result<()> { 334 | for s in &self.links { 335 | w.write_with_tag(18, |w| w.write_message(s))?; 336 | } 337 | 338 | if let Some(ref data) = self.data { 339 | w.write_with_tag(10, |w| w.write_bytes(data))?; 340 | } 341 | 342 | Ok(()) 343 | } 344 | } 345 | 346 | impl MessageWrite for PbNodeRef<'_> { 347 | fn get_size(&self) -> usize { 348 | let mut size = 0; 349 | if let Some(data) = self.data { 350 | size += 1 + sizeof_len(data.len()); 351 | } 352 | 353 | size += self 354 | .links 355 | .iter() 356 | .map(|s| 1 + sizeof_len((s).get_size())) 357 | .sum::(); 358 | 359 | size 360 | } 361 | 362 | fn write_message(&self, w: &mut Writer) -> quick_protobuf::Result<()> { 363 | for s in &self.links { 364 | w.write_with_tag(18, |w| w.write_message(s))?; 365 | } 366 | 367 | if let Some(data) = self.data { 368 | w.write_with_tag(10, |w| w.write_bytes(data))?; 369 | } 370 | 371 | Ok(()) 372 | } 373 | } 374 | --------------------------------------------------------------------------------