Skip to content

Commit

Permalink
feat!: Add a zero-deps example
Browse files Browse the repository at this point in the history
  • Loading branch information
fasterthanlime committed Sep 12, 2024
1 parent 3459513 commit d296162
Show file tree
Hide file tree
Showing 10 changed files with 277 additions and 39 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
/target
target
.DS_Store
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
[workspace]
resolver = "2"
members = ["merde", "merde_json", "merde_time", "merde_core"]
exclude = ["zerodeps-example"]
10 changes: 10 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
check:
#!/bin/bash -eux
cargo check --example simple --no-default-features --features=json
cargo run --example simple --features=core,json
cargo test -F full
cargo check --no-default-features
cargo hack --feature-powerset check

pushd zerodeps-example
cargo check
cargo check --features=merde
cargo tree --prefix none --no-dedupe | grep -v compact_str
cargo tree --prefix none --no-dedupe --features=merde | grep compact_str
popd
5 changes: 3 additions & 2 deletions merde/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ merde_json = { version = "3.0.0", path = "../merde_json", optional = true }
merde_time = { version = "3.0.0", path = "../merde_time", optional = true }

[features]
default = ["core"]
full = ["json", "time", "core"]
default = ["core", "deserialize"]
full = ["core", "deserialize", "json", "time"]
deserialize = ["core"]
core = ["dep:merde_core"]
json = ["dep:merde_json"]
time = ["dep:merde_time"]
106 changes: 106 additions & 0 deletions merde/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,3 +389,109 @@ fn main() {
```

You can of course make your own newtype wrappers to control how a field gets deserialized.

## Conditional compilation

(As of merde 3.1), you never need to add `cfg` gates to conditionally invoke the `merde::derive!`
macro, because, with default features disabled, `merde` has zero dependencies.

There's two main ways to be conservative with the amount of generated code / the amount of
dependencies pulled with merde.

### Approach 1: "core" by default, "deserialize" on demand

Your manifest could look like this:

```toml
# in `Cargo.toml`

[dependencies]
merde = { version = "3.1", default-features = false, features = ["core"] }
```

And then you'd be able to use merde-provided types, like `CowStr`:

```rust
use merde::CowStr;

#[derive(Debug)]
struct Person<'s> {
name: CowStr<'s>,
age: u8, // sorry 256-year-olds
}

merde::derive! {
impl (ValueDeserialize, JsonSerialize, IntoStatic) for Person<'s> { name, age }
}
```

And the `impl` blocks for `ValueDeserialize`, and `JsonSerialize` wouldn't actually
be generated unless crates downstream of yours enable `merde/deserialize` or `merde/json`.

### Approach 2: zero-deps

If your manifest looks more like this:

```toml
# in `Cargo.toml`

[dependencies]
merde = { version = "3.1", default-features = false }

[features]
default = []
merde = ["merde/core"]
```

...with no `merde` features enabled by default at all, then you have to stay
away from merde types, or use substitutes, for example, you could switch
`CowStr<'s>` with `std::borrow::Cow<'s, str>` and get largely the same API:

```rust
#[cfg(feature = "merde")]
use merde::CowStr;

#[cfg(not(feature = "merde"))]
pub type CowStr<'s> = std::borrow::Cow<'s, str>;

#[derive(Debug)]
pub struct Person<'s> {
name: CowStr<'s>,
age: u8, // sorry 256-year-olds
}

merde::derive! {
impl (ValueDeserialize, JsonSerialize, IntoStatic) for Person<'s> { name, age }
}
```

(But not the same ABI! Careful if you use this in conjunction with something
like [rubicon](https://github.com/bearcove/rubicon)).

With that configuration, users of your crate will only have to pay for downloading
`merde` and evaluating a few `derive!` macros which will produce empty token trees —
no extra dependencies, barely any extra build time.

See `zerodeps-example` in the [merde repository](https://github.com/bearcove/merde)
for a demonstration:

```shell
❯ cargo tree --prefix none
zerodeps-example v0.1.0 (/Users/amos/bearcove/merde/zerodeps-example)
merde v3.0.0 (/Users/amos/bearcove/merde/merde)
```

```shell
❯ cargo tree --prefix none --features 'merde'
zerodeps-example v0.1.0 (/Users/amos/bearcove/merde/zerodeps-example)
merde v3.0.0 (/Users/amos/bearcove/merde/merde)
merde_core v3.0.0 (/Users/amos/bearcove/merde/merde_core)
compact_str v0.8.0
castaway v0.2.3
rustversion v1.0.17 (proc-macro)
cfg-if v1.0.0
itoa v1.0.11
rustversion v1.0.17 (proc-macro)
ryu v1.0.18
static_assertions v1.1.0
```
19 changes: 16 additions & 3 deletions merde/examples/simple.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
#[cfg(feature = "core")]
use merde::CowStr;
use merde::json::JsonSerialize;

#[cfg(not(feature = "core"))]
type CowStr<'s> = std::borrow::Cow<'s, str>;

#[cfg(all(feature = "core", feature = "json"))]
fn main() {
use merde::json::JsonSerialize;

let input = r#"
{
"name": "John Doe",
Expand Down Expand Up @@ -30,7 +36,15 @@ fn main() {
assert_eq!(person, person2);
}

#[allow(dead_code)]
#[cfg(not(all(feature = "core", feature = "json")))]
fn main() {
eprintln!("Well if the `core` feature is not enabled,");
eprintln!("we can't call `from_str_via_value` and stuff,");
eprintln!("but this still serves as an example that");
eprintln!("you can keep your `merde::derive!` in place,");
eprintln!("they'll just not generate any code.");
}

#[derive(Debug, PartialEq, Eq)]
struct Address<'s> {
street: CowStr<'s>,
Expand All @@ -48,7 +62,6 @@ merde::derive! {
}
}

#[allow(dead_code)]
#[derive(Debug, PartialEq, Eq)]
struct Person<'s> {
name: CowStr<'s>,
Expand Down
70 changes: 37 additions & 33 deletions merde/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub use merde_core::*;
#[macro_export]
macro_rules! impl_value_deserialize {
($struct_name:ident <$lifetime:lifetime> { $($field:ident),+ }) => {
#[cfg(feature = "deserialize")]
#[automatically_derived]
impl<$lifetime> $crate::ValueDeserialize<$lifetime> for $struct_name<$lifetime>
{
Expand Down Expand Up @@ -49,6 +50,7 @@ macro_rules! impl_value_deserialize {
};

($struct_name:ident { $($field:ident),+ }) => {
#[cfg(feature = "deserialize")]
#[automatically_derived]
impl<'s> $crate::ValueDeserialize<'s> for $struct_name
{
Expand Down Expand Up @@ -83,6 +85,7 @@ macro_rules! impl_value_deserialize {
#[macro_export]
macro_rules! impl_into_static {
($struct_name:ident <$lifetime:lifetime> { $($field:ident),+ }) => {
#[cfg(feature = "core")]
#[automatically_derived]
impl<$lifetime> $crate::IntoStatic for $struct_name<$lifetime> {
type Output = $struct_name<'static>;
Expand All @@ -99,6 +102,7 @@ macro_rules! impl_into_static {
};

($struct_name:ident { $($field:ident),+ }) => {
#[cfg(feature = "core")]
#[automatically_derived]
impl $crate::IntoStatic for $struct_name {
type Output = $struct_name;
Expand All @@ -115,7 +119,7 @@ macro_rules! impl_into_static {
#[macro_export]
macro_rules! impl_json_serialize {
($struct_name:ident < $lifetime:lifetime > { $($field:ident),+ }) => {
#[cfg(feature = "json")]
#[cfg(all(feature = "core", feature = "json"))]
#[automatically_derived]
impl<$lifetime> $crate::json::JsonSerialize for $struct_name<$lifetime> {
fn json_serialize(&self, serializer: &mut $crate::json::JsonSerializer) {
Expand All @@ -131,7 +135,7 @@ macro_rules! impl_json_serialize {
};

($struct_name:ident { $($field:ident),+ }) => {
#[cfg(feature = "json")]
#[cfg(all(feature = "core", feature = "json"))]
#[automatically_derived]
impl $crate::json::JsonSerialize for $struct_name {
fn json_serialize(&self, serializer: &mut $crate::json::JsonSerializer) {
Expand All @@ -147,6 +151,37 @@ macro_rules! impl_json_serialize {
};
}

#[doc(hidden)]
#[macro_export]
macro_rules! impl_trait {
// borrowed
(@impl JsonSerialize, $struct_name:ident <$lifetime:lifetime> { $($field:ident),+ }) => {
$crate::impl_json_serialize!($struct_name <$lifetime> { $($field),+ });
};
// owned
(@impl JsonSerialize, $struct_name:ident { $($field:ident),+ }) => {
$crate::impl_json_serialize!($struct_name { $($field),+ });
};

// with lifetime param
(@impl ValueDeserialize, $struct_name:ident <$lifetime:lifetime> { $($field:ident),+ }) => {
$crate::impl_value_deserialize!($struct_name <$lifetime> { $($field),+ });
};
// l
(@impl ValueDeserialize, $struct_name:ident { $($field:ident),+ }) => {
$crate::impl_value_deserialize!($struct_name { $($field),+ });
};

// with lifetime param
(@impl IntoStatic, $struct_name:ident <$lifetime:lifetime> { $($field:ident),+ }) => {
$crate::impl_into_static!($struct_name <$lifetime> { $($field),+ });
};
// without lifetime param
(@impl IntoStatic, $struct_name:ident { $($field:ident),+ }) => {
$crate::impl_into_static!($struct_name { $($field),+ });
};
}

/// Derives the specified traits for a struct.
///
/// This macro can be used to generate implementations of [`JsonSerialize`], [`ValueDeserialize`],
Expand Down Expand Up @@ -213,37 +248,6 @@ macro_rules! derive {
(@step1 { } $struct_name:ident $fields:tt) => {};
}

#[doc(hidden)]
#[macro_export]
macro_rules! impl_trait {
// borrowed
(@impl JsonSerialize, $struct_name:ident <$lifetime:lifetime> { $($field:ident),+ }) => {
$crate::impl_json_serialize!($struct_name <$lifetime> { $($field),+ });
};
// owned
(@impl JsonSerialize, $struct_name:ident { $($field:ident),+ }) => {
$crate::impl_json_serialize!($struct_name { $($field),+ });
};

// with lifetime param
(@impl ValueDeserialize, $struct_name:ident <$lifetime:lifetime> { $($field:ident),+ }) => {
$crate::impl_value_deserialize!($struct_name <$lifetime> { $($field),+ });
};
// l
(@impl ValueDeserialize, $struct_name:ident { $($field:ident),+ }) => {
$crate::impl_value_deserialize!($struct_name { $($field),+ });
};

// with lifetime param
(@impl IntoStatic, $struct_name:ident <$lifetime:lifetime> { $($field:ident),+ }) => {
$crate::impl_into_static!($struct_name <$lifetime> { $($field),+ });
};
// without lifetime param
(@impl IntoStatic, $struct_name:ident { $($field:ident),+ }) => {
$crate::impl_into_static!($struct_name { $($field),+ });
};
}

#[cfg(test)]
#[cfg(feature = "json")]
mod json_tests {
Expand Down
77 changes: 77 additions & 0 deletions zerodeps-example/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit d296162

Please sign in to comment.