Skip to content

Commit

Permalink
feat: full implementation for first release
Browse files Browse the repository at this point in the history
  • Loading branch information
cdbrkfxrpt committed Jul 29, 2024
1 parent e44e5ad commit eb2a306
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 20 deletions.
2 changes: 1 addition & 1 deletion .envrc
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env bash

export CARGO_FEATURES="serde"
CARGO_FEATURES="serde"

if ! has nix_direnv_version || ! nix_direnv_version 3.0.4; then
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.4/direnvrc" "sha256-DzlYZ33mWF/Gs8DDeyjr8mnVmQGx7ASYqA5WlxwvBG4="
Expand Down
24 changes: 24 additions & 0 deletions Cargo.lock

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

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ exclude = [".github"]
authors = ["Florian Eich <[email protected]>"]

[dependencies]
serde = { version = "1.0.204", optional = true }
serde = { version = "1.0.204", features = ["derive"], optional = true }
thiserror = "1.0.63"

[features]
Expand All @@ -24,4 +24,5 @@ serde = ["dep:serde"]
[dev-dependencies]
pretty_assertions = "1.4.0"
proptest = "1.5.0"
serde_json = "1.0.120"
test-strategy = "0.4.0"
2 changes: 1 addition & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
rust-bin.nightly.latest.rustfmt
];

cargoTestExtraArgs = "--workspace";
cargoExtraArgs = "--all-features";
};

cargoArtifacts = craneLib.buildDepsOnly commonArgs;
Expand Down
99 changes: 98 additions & 1 deletion src/maybe_multiple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,51 @@

use crate::Multiple;

#[derive(Debug)]
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(untagged)
)]
pub enum MaybeMultiple<T> {
None,
Some(T),
Multiple(Multiple<T>),
}

////////////////////////////////////////////////////////////////////////////////
// Type implementations
////////////////////////////////////////////////////////////////////////////////

impl<T> MaybeMultiple<T> {
#[must_use = "to assert that this doesn't have a value, wrap this in an `assert!()` instead"]
#[inline]
pub fn is_none(&self) -> bool {
matches!(self, Self::None)
}

#[must_use = "to assert that this has a value, wrap this in an `assert!()` instead"]
#[inline]
pub fn is_some(&self) -> bool {
matches!(self, Self::Some(_))
}

#[must_use = "to assert that this has a value, wrap this in an `assert!()` instead"]
#[inline]
pub fn is_multiple(&self) -> bool {
matches!(self, Self::Multiple(_))
}

#[inline]
pub fn from_vec(v: Vec<T>) -> Self {
Self::from(v)
}
}

////////////////////////////////////////////////////////////////////////////////
// Trait implementations
////////////////////////////////////////////////////////////////////////////////

impl<T> Default for MaybeMultiple<T> {
fn default() -> Self {
Self::None
Expand Down Expand Up @@ -50,3 +78,72 @@ impl<T> From<MaybeMultiple<T>> for Vec<T> {
}
}
}

////////////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////////////

#[cfg(test)]
mod tests {
use super::MaybeMultiple;
use pretty_assertions::assert_eq;
use proptest::collection::size_range;
use test_strategy::proptest;

#[proptest]
fn proptest_conversions(v: Vec<u8>) {
match v.len() {
0 => assert_eq!(MaybeMultiple::from_vec(v), MaybeMultiple::None),
1 => {
let e = v[0];
assert_eq!(MaybeMultiple::from(v), MaybeMultiple::Some(e));
}
_ => assert_eq!(
MaybeMultiple::from(v.clone()),
MaybeMultiple::Multiple(v.try_into().unwrap())
),
}
}

#[test]
fn serialize_deserialize_none() {
assert_eq!(
serde_json::to_string(&MaybeMultiple::<u8>::None).unwrap(),
"null"
);
assert_eq!(
serde_json::from_str::<MaybeMultiple<u8>>("null").unwrap(),
MaybeMultiple::None
);
}

#[proptest]
fn serialize_deserialize_some(val: u8) {
let maybe_multiple = MaybeMultiple::Some(val);
assert_eq!(
serde_json::to_string(&maybe_multiple).unwrap(),
serde_json::to_string(&val).unwrap()
);

let val_str = serde_json::to_string(&val).unwrap();
assert_eq!(
serde_json::from_str::<MaybeMultiple<_>>(&val_str).unwrap(),
maybe_multiple
);
}

#[proptest]
fn serialize_deserialize_multiple(#[any(size_range(2..128).lift())] vec: Vec<u8>) {
let maybe_multiple = MaybeMultiple::from_vec(vec.clone());
assert_eq!(
serde_json::to_string(&maybe_multiple).unwrap(),
serde_json::to_string(&vec).unwrap()
);

let vec_str = serde_json::to_string(&vec).unwrap();
assert_eq!(
serde_json::from_str::<MaybeMultiple<_>>(&vec_str).unwrap(),
maybe_multiple
);
}
}
105 changes: 89 additions & 16 deletions src/multiple.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,40 @@
// Copyright 2024 bmc::labs GmbH. All rights reserved.

use thiserror::Error;
#[cfg(feature = "serde")]
use serde::ser::SerializeSeq;

#[derive(Debug, PartialEq, Eq, Error)]
#[error("could not convert from Vec: Vec contains fewer than 2 elements")]
pub struct VecSizeError;
use thiserror::Error;

#[derive(Debug)]
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize),
serde(try_from = "Vec<T>")
)]
pub struct Multiple<T> {
head: [T; 2],
tail: Vec<T>,
}

////////////////////////////////////////////////////////////////////////////////
// Type implementations
////////////////////////////////////////////////////////////////////////////////

impl<T> Multiple<T> {
#[allow(clippy::len_without_is_empty)] // this struct can never be empty by definition
pub fn len(&self) -> usize {
self.tail.len() + 2
}
}

////////////////////////////////////////////////////////////////////////////////
// Trait implementations
////////////////////////////////////////////////////////////////////////////////

#[derive(Debug, PartialEq, Eq, Error)]
#[error("could not convert from Vec: Vec contains fewer than 2 elements")]
pub struct VecSizeError;

impl<T> TryFrom<Vec<T>> for Multiple<T> {
type Error = VecSizeError;

Expand All @@ -37,33 +60,83 @@ impl<T> From<Multiple<T>> for Vec<T> {
}
}

impl<T> PartialEq for Multiple<T>
where
T: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.head == other.head && self.tail == other.tail
}
}

impl<T> Eq for Multiple<T> where T: Eq {}

#[cfg(feature = "serde")]
impl<T> serde::Serialize for Multiple<T>
where
T: serde::Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut seq = serializer.serialize_seq(Some(self.len()))?;
seq.serialize_element(&self.head[0])?;
seq.serialize_element(&self.head[1])?;
for t in &self.tail {
seq.serialize_element(t)?;
}
seq.end()
}
}

////////////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////////////

#[cfg(test)]
mod tests {
use super::{Multiple, VecSizeError};
use pretty_assertions::assert_eq;
use proptest::collection::size_range;
use test_strategy::proptest;

#[test]
fn manual_conversions() {
let v: Vec<u8> = vec![];
assert_eq!(Multiple::try_from(v).unwrap_err(), VecSizeError);
let vec: Vec<u8> = vec![];
assert_eq!(Multiple::try_from(vec).unwrap_err(), VecSizeError);

let v: Vec<u8> = vec![42];
assert_eq!(Multiple::try_from(v).unwrap_err(), VecSizeError);
let vec: Vec<u8> = vec![42];
assert_eq!(Multiple::try_from(vec).unwrap_err(), VecSizeError);

let v: Vec<u8> = vec![42, 43];
assert_eq!(Vec::from(Multiple::try_from(v.clone()).unwrap()), v);
let vec: Vec<u8> = vec![42, 43];
assert_eq!(Vec::from(Multiple::try_from(vec.clone()).unwrap()), vec);

let v: Vec<u8> = vec![42, 43, 44];
assert_eq!(Vec::from(Multiple::try_from(v.clone()).unwrap()), v);
let vec: Vec<u8> = vec![42, 43, 44];
assert_eq!(Vec::from(Multiple::try_from(vec.clone()).unwrap()), vec);
}

#[proptest]
fn proptest_conversions(v: Vec<u8>) {
if v.len() < 2 {
assert_eq!(Multiple::try_from(v).unwrap_err(), VecSizeError);
fn proptest_conversions(vec: Vec<u8>) {
if vec.len() < 2 {
assert_eq!(Multiple::try_from(vec).unwrap_err(), VecSizeError);
} else {
assert_eq!(Vec::from(Multiple::try_from(v.clone()).unwrap()), v);
assert_eq!(Vec::from(Multiple::try_from(vec.clone()).unwrap()), vec);
}
}

#[proptest]
fn serialize_deserialize(#[any(size_range(2..128).lift())] vec: Vec<u8>) {
let multiple = Multiple::try_from(vec.clone()).unwrap();
assert_eq!(
serde_json::to_string(&multiple).unwrap(),
serde_json::to_string(&vec).unwrap()
);

let vec_str = serde_json::to_string(&vec).unwrap();
assert_eq!(
serde_json::from_str::<Multiple<_>>(&vec_str).unwrap(),
multiple
);
}
}

0 comments on commit eb2a306

Please sign in to comment.