Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add DAG-JOSE implementation #177

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ fnv = "1.0.7"
libipld-cbor = { version = "0.16.0", path = "dag-cbor", optional = true }
libipld-cbor-derive = { version = "0.16.0", path = "dag-cbor-derive", optional = true }
libipld-core = { version = "0.16.0", path = "core" }
libipld-jose = { version = "0.16.0", path = "dag-jose", optional = true }
libipld-json = { version = "0.16.0", path = "dag-json", optional = true }
libipld-macro = { version = "0.16.0", path = "macro" }
libipld-pb = { version = "0.16.0", path = "dag-pb", optional = true }
Expand All @@ -33,6 +34,7 @@ multihash = "0.18.0"
[features]
default = ["dag-cbor", "dag-json", "dag-pb", "derive"]
dag-cbor = ["libipld-cbor"]
dag-jose = ["libipld-jose"]
dag-json = ["libipld-json"]
dag-pb = ["libipld-pb"]
derive = ["libipld-cbor-derive"]
Expand All @@ -44,10 +46,12 @@ members = [
"core",
"dag-cbor",
"dag-cbor-derive",
"dag-jose",
"dag-json",
"dag-pb",
"macro",
"dag-cbor-derive/examples/renamed-package",
"dag-cbor-derive/examples/internal-package",
]

[profile.release]
Expand Down
1 change: 1 addition & 0 deletions dag-cbor-derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ repository = "https://github.com/ipfs-rust/rust-ipld"
proc-macro = true

[dependencies]
anyhow = "1.0.68"
proc-macro-crate = "1.1.0"
proc-macro2 = "1.0.27"
quote = "1.0.9"
Expand Down
6 changes: 3 additions & 3 deletions dag-cbor-derive/examples/basic.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use libipld::cbor::DagCborCodec;
use libipld::cbor::{Bytes, DagCborCodec};
use libipld::codec::assert_roundtrip;
use libipld::{ipld, DagCbor, Ipld};
use std::collections::BTreeMap;
Expand All @@ -9,7 +9,7 @@ struct NamedStruct {
integer: u32,
float: f64,
string: String,
bytes: Vec<u8>,
bytes: Bytes,
list: Vec<Ipld>,
map: BTreeMap<String, Ipld>,
//link: Cid,
Expand Down Expand Up @@ -44,7 +44,7 @@ fn main() {
"integer": 0,
"float": 0.0,
"string": "",
"bytes": [],
"bytes": vec![].into_boxed_slice(),
"list": [],
"map": {},
}),
Expand Down
13 changes: 13 additions & 0 deletions dag-cbor-derive/examples/internal-package/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "internal-package"
version = "0.1.0"
edition = "2021"
publish = false

[package.metadata.release]
release = false

[dependencies]
libipld-core = { path = "../../../core"}
libipld-cbor = { path = "../../../dag-cbor"}
libipld-cbor-derive = { path = "../../../dag-cbor-derive"}
12 changes: 12 additions & 0 deletions dag-cbor-derive/examples/internal-package/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//! The purpose of this example is to test whether the derive compiles if the libipld package was
//! imported from within this repo as libipld_core
use libipld_cbor;
use libipld_cbor_derive::DagCborInternal;

#[derive(Clone, DagCborInternal, Debug, Default, PartialEq)]
struct NamedStruct {
boolean: bool,
integer: u32,
float: f64,
string: String,
}
40 changes: 24 additions & 16 deletions dag-cbor-derive/src/gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,49 +4,57 @@ use crate::ast::*;
use proc_macro2::TokenStream;
use quote::quote;

pub fn gen_encode(ast: &SchemaType, libipld: &syn::Ident) -> TokenStream {
pub fn gen_encode(
ast: &SchemaType,
libipld_core: &TokenStream,
libipld_cbor: &TokenStream,
) -> TokenStream {
let (ident, generics, body) = match ast {
SchemaType::Struct(s) => (&s.name, s.generics.as_ref().unwrap(), gen_encode_struct(s)),
SchemaType::Union(u) => (&u.name, &u.generics, gen_encode_union(u)),
};
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let trait_name = quote!(#libipld::codec::Encode<#libipld::cbor::DagCborCodec>);
let trait_name = quote!(#libipld_core::codec::Encode<#libipld_cbor::DagCborCodec>);

quote! {
impl #impl_generics #trait_name for #ident #ty_generics #where_clause {
fn encode<W: std::io::Write>(
&self,
c: #libipld::cbor::DagCborCodec,
c: #libipld_cbor::DagCborCodec,
w: &mut W,
) -> #libipld::Result<()> {
use #libipld::codec::Encode;
use #libipld::cbor::cbor::MajorKind;
use #libipld::cbor::encode::{write_null, write_u8, write_u64};
) -> #libipld_core::error::Result<()> {
use #libipld_core::codec::Encode;
use #libipld_cbor::cbor::MajorKind;
use #libipld_cbor::encode::{write_null, write_u8, write_u64};
#body
}
}
}
}

pub fn gen_decode(ast: &SchemaType, libipld: &syn::Ident) -> TokenStream {
pub fn gen_decode(
ast: &SchemaType,
libipld_core: &TokenStream,
libipld_cbor: &TokenStream,
) -> TokenStream {
let (ident, generics, body) = match ast {
SchemaType::Struct(s) => (&s.name, s.generics.as_ref().unwrap(), gen_decode_struct(s)),
SchemaType::Union(u) => (&u.name, &u.generics, gen_decode_union(u)),
};
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let trait_name = quote!(#libipld::codec::Decode<#libipld::cbor::DagCborCodec>);
let trait_name = quote!(#libipld_core::codec::Decode<#libipld_cbor::DagCborCodec>);

quote! {
impl #impl_generics #trait_name for #ident #ty_generics #where_clause {
fn decode<R: std::io::Read + std::io::Seek>(
c: #libipld::cbor::DagCborCodec,
c: #libipld_cbor::DagCborCodec,
r: &mut R,
) -> #libipld::Result<Self> {
use #libipld::cbor::cbor::{MajorKind, NULL};
use #libipld::cbor::decode::{read_uint, read_major};
use #libipld::cbor::error::{LengthOutOfRange, MissingKey, UnexpectedCode, UnexpectedKey};
use #libipld::codec::Decode;
use #libipld::error::Result;
) -> #libipld_core::error::Result<Self> {
use #libipld_cbor::cbor::{MajorKind, NULL};
use #libipld_cbor::decode::{read_uint, read_major};
use #libipld_cbor::error::{LengthOutOfRange, MissingKey, UnexpectedCode, UnexpectedKey};
use #libipld_core::codec::Decode;
use #libipld_core::error::Result;
use std::io::SeekFrom;
#body
}
Expand Down
42 changes: 35 additions & 7 deletions dag-cbor-derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,47 @@
#![deny(warnings)]

use anyhow::anyhow;
use proc_macro2::{Span, TokenStream};
use proc_macro_crate::{crate_name, FoundCrate};
use quote::quote;
use quote::{quote, ToTokens};
use synstructure::{decl_derive, Structure};

decl_derive!([DagCbor, attributes(ipld)] => dag_cbor_derive);

decl_derive!([DagCborInternal, attributes(ipld)] => dag_cbor_derive_internal);

mod ast;
mod attr;
mod gen;
mod parse;

// Entry point for the DagCbor derive macro
fn dag_cbor_derive(s: Structure) -> TokenStream {
let libipld = match use_crate("libipld") {
let libipld_core = match use_crate("libipld") {
Ok(ident) => ident,
Err(error) => return error,
};
let libipld_cbor = quote!(#libipld_core::cbor);
let ast = parse::parse(&s);
let encode = gen::gen_encode(&ast, &libipld_core, &libipld_cbor);
let decode = gen::gen_decode(&ast, &libipld_core, &libipld_cbor);
quote! {
#encode
#decode
}
}

// Entry point for the DagCborCrate derive macro
// This variant of the macro may be used within libipld itself
// as it uses the API exposed by the sub-crates within the workspace directly
// instead of the API exposed by the top level libipld crate.
fn dag_cbor_derive_internal(s: Structure) -> TokenStream {
let libipld_core = quote!(libipld_core);
let libipld_cbor = quote!(libipld_cbor);

let ast = parse::parse(&s);
let encode = gen::gen_encode(&ast, &libipld);
let decode = gen::gen_decode(&ast, &libipld);
let encode = gen::gen_encode(&ast, &libipld_core, &libipld_cbor);
let decode = gen::gen_decode(&ast, &libipld_core, &libipld_cbor);
quote! {
#encode
#decode
Expand All @@ -28,10 +52,14 @@ fn dag_cbor_derive(s: Structure) -> TokenStream {
///
/// This works even if the crate was renamed in the `Cargo.toml` file. If the crate is not a
/// dependency, it will lead to a compile-time error.
fn use_crate(name: &str) -> Result<syn::Ident, TokenStream> {
fn use_crate(name: &str) -> Result<TokenStream, TokenStream> {
match crate_name(name) {
Ok(FoundCrate::Name(n)) => Ok(syn::Ident::new(&n, Span::call_site())),
Ok(FoundCrate::Itself) => Ok(syn::Ident::new("crate", Span::call_site())),
Ok(FoundCrate::Name(n)) => Ok(syn::Ident::new(&n, Span::call_site()).to_token_stream()),
Ok(FoundCrate::Itself) => Err(syn::Error::new(
Span::call_site(),
anyhow!("unsupported use of dag-cbor-derive macro from within libipld crate"),
)
.to_compile_error()),
Err(err) => Err(syn::Error::new(Span::call_site(), err).to_compile_error()),
}
}
Expand Down
5 changes: 5 additions & 0 deletions dag-cbor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ pub trait DagCbor: Encode<DagCborCodec> + Decode<DagCborCodec> {}

impl<T: Encode<DagCborCodec> + Decode<DagCborCodec>> DagCbor for T {}

/// Bytes is sequence of byte values.
///
/// Implements Encode and Decode to/from CBOR byte strings
pub type Bytes = Box<[u8]>;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little confused as to why this is needed. It seems like Vec is covered. Also why Box<[u8]> and not Vec.

Copy link
Author

@nathanielc nathanielc Jan 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It stems from here https://github.com/ipld/libipld/blob/master/dag-cbor/src/decode.rs#L297 and here https://github.com/ipld/libipld/blob/master/dag-cbor/src/decode.rs#L321

Using a Vec<u8> creates an IPLD list of u8 values, where using Box<[u8]> creates an IPLD byte sequence.

We could change the cbor Decode implementations but this was the smallest change I could make to existing code. Additionally we could try and specialize the implementation but that required Rust nightly.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LIttle odd that they didn't handle Vec as a byte string, but is there a reason we need a ByteString here, and an array of u8 isn't sufficient. A large number of libraries work on Vec or bytes::Bytes, so to use this, they'll have to be regularly calling into_boxed_slice() to work with the types specified here.

dag-pb actually uses bytes::Bytes. Oddly, it also returns a Box<[u8]> for the payload here https://github.com/ipld/libipld/blob/master/dag-pb/src/codec.rs#L78 rather than the Vec or a Bytes structure.


#[cfg(test)]
mod tests {
use super::*;
Expand Down
28 changes: 28 additions & 0 deletions dag-jose/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[package]
name = "libipld-jose"
version = "0.16.0"
authors = ["Nathaniel Cook <nvcook42@gmail.com>"]
edition = "2021"
license = "MIT OR Apache-2.0"
description = "ipld dag-json codec"
repository = "https://github.com/ipfs-rust/rust-ipld"

[dependencies]
anyhow = "1.0.68"
base64-url = "1.4.13"
libipld-core = { version = "0.16.0", path = "../core" }
libipld-cbor = { version = "0.16.0", path = "../dag-cbor" }
libipld-cbor-derive = { version = "0.16.0", path = "../dag-cbor-derive" }
# TODO gate behind the dag-json feature flag
libipld-json = { version = "0.16.0", path = "../dag-json" }
multihash = "0.18.0"
thiserror = "1.0.38"

[dev-dependencies]
libipld-json = { version = "0.16.0", path = "../dag-json" }
libipld-macro = { version = "0.16.0", path = "../macro" }
assert-json-diff = "2.0.2"
hex = "0.4.3"
once_cell = "1.17.0"
serde_json = "1.0.91"
testmark = {git = "https://github.com/bsundsrud/rust-testmark" }
Loading