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

Impl from collections for RpcValue #6

Merged
18 changes: 15 additions & 3 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,26 @@ env:

jobs:
build:
strategy:
matrix:
toolchain:
- channel: stable
cargo_args: --verbose --all-targets
- channel: nightly
cargo_args: --verbose --all-targets --all-features

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- name: Setup toolchain
run: >
rustup update ${{ matrix.toolchain.channel }} &&
rustup default ${{ matrix.toolchain.channel }} &&
rustup component add clippy
- name: Clippy
run: cargo clippy --verbose --all-targets --all-features
run: cargo clippy ${{ matrix.toolchain.cargo_args }}
- name: Build
run: cargo build --verbose --all-features
run: cargo build ${{ matrix.toolchain.cargo_args }}
- name: Run tests
run: cargo test --verbose --all-features -- --test-threads=1
run: cargo test ${{ matrix.toolchain.cargo_args }} -- --test-threads=1
7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,12 @@ chrono = "0.4.38"
hex = "0.4.3"
clap = { version = "4.5.7", features = ["derive"] }

[features]

# Enables feature `min_specialization` of nightly compiler and provides
Copy link
Contributor

Choose a reason for hiding this comment

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

I which production project can we use unstable rust?

Copy link
Contributor Author

@j4r0u53k j4r0u53k Jul 6, 2024

Choose a reason for hiding this comment

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

It's entirely up to consumers of the lib whether they want to enable this feature or not. By default it's disabled.

The purpose of this feature is only to provide optimalization for converting a collection of RpcValues into RpcValue, where a user code decides to handle all the conversions in the uniform way - call .into() on any convertible data without special treatment of Collection<RpcValue> case.

Copy link
Contributor

Choose a reason for hiding this comment

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

We have only one consumer currently - us. So this code is effectively dead. We can keep it until min_specialization will be stabilized, hopefully there will be some external consumers this time. My concern was just to know, how can be this feature utilized now.

Copy link
Contributor Author

@j4r0u53k j4r0u53k Jul 7, 2024

Choose a reason for hiding this comment

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

Whether or not there are currently any external consumers shouldn't be the concern here. Anybody can start a project at any time utilizing the feature if they consider it meaningful.

The min_specialization has been around for a while and it's highly demanded. I don't think it will get removed. However, it might not get stabilized for another couple of years, which would mean we should either give up on this feature completely, or at least leave it there as an option, disabled by default, which is what I propose.

Copy link
Contributor

Choose a reason for hiding this comment

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

Well, we can keep it and revisit it when specialization will be stabilized even if it is far from finished rust-lang/rust#31844.

Copy link
Contributor

Choose a reason for hiding this comment

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

I still wonder, why specialization generates necessity of macro used on lines rpcvalue.rs:208 - rpcvalue.rs:222, which wasn't needed before. My naive understanding is, that just special cases should be introduced, but the generic impl should remain the same.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The cfg macros inside the impls are necessary because of the default keyword/marker in the definition of the generic implementations of from methods, which can appear only if the compiler feature is enabled.

# special `impl From<Collection<RpcValue>> for RpcValue` to just move
# the data without iterating through collections.
specialization = []

[[bin]]
name = "cp2cp"
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![cfg_attr(feature = "specialization", feature(min_specialization))]

pub mod chainpack;
pub mod cpon;
pub mod datetime;
Expand Down
214 changes: 200 additions & 14 deletions src/rpcvalue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,15 +192,157 @@ impl<Tz: chrono::TimeZone> From<chrono::DateTime<Tz>> for Value {
}
}

impl<T: Into<Value>> From<T> for RpcValue {
fn from(value: T) -> Self {
macro_rules! impl_from_type_for_rpcvalue {
($from:ty) => {
impl From<$from> for RpcValue {
fn from(value: $from) -> Self {
$crate::RpcValue {
meta: None,
value: value.into(),
}
}
}
};
}

impl_from_type_for_rpcvalue!(());
impl_from_type_for_rpcvalue!(bool);
impl_from_type_for_rpcvalue!(&str);
impl_from_type_for_rpcvalue!(String);
impl_from_type_for_rpcvalue!(&String);
impl_from_type_for_rpcvalue!(&[u8]);
impl_from_type_for_rpcvalue!(i32);
impl_from_type_for_rpcvalue!(i64);
impl_from_type_for_rpcvalue!(isize);
impl_from_type_for_rpcvalue!(u32);
impl_from_type_for_rpcvalue!(u64);
impl_from_type_for_rpcvalue!(f64);
impl_from_type_for_rpcvalue!(Decimal);
impl_from_type_for_rpcvalue!(DateTime);
impl_from_type_for_rpcvalue!(chrono::NaiveDateTime);

impl<Tz: chrono::TimeZone> From<chrono::DateTime<Tz>> for RpcValue {
fn from(value: chrono::DateTime<Tz>) -> Self {
RpcValue {
meta: None,
value: value.into(),
}
}
}

impl From<Vec<u8>> for RpcValue {
fn from(val: Vec<u8>) -> Self {
RpcValue {
meta: None,
value: Value::Blob(Box::new(val))
}
}
}

#[cfg(feature = "specialization")]
mod with_specialization {
use super::{
from_vec_rpcvalue_for_rpcvalue,
from_map_rpcvalue_for_rpcvalue,
from_imap_rpcvalue_for_rpcvalue
};
use crate::RpcValue;
use std::collections::BTreeMap;
use super::{List,Map,IMap};

impl<T: Into<RpcValue>> From<Vec<T>> for RpcValue {
default fn from(value: Vec<T>) -> Self {
from_vec_rpcvalue_for_rpcvalue(value)
}
}
impl<T: Into<RpcValue>> From<BTreeMap<String, T>> for RpcValue {
default fn from(value: BTreeMap<String, T>) -> Self {
from_map_rpcvalue_for_rpcvalue(value)
}
}
impl<T: Into<RpcValue>> From<BTreeMap<i32, T>> for RpcValue {
default fn from(value: BTreeMap<i32, T>) -> Self {
from_imap_rpcvalue_for_rpcvalue(value)
}
}

// Specializations of `impl From<Collection<RpcValue>> for RpcValue`
// for List, Map and IMap to just move the value instead of iterating
// through the collections.
impl_from_type_for_rpcvalue!(List);
impl_from_type_for_rpcvalue!(Map);
impl_from_type_for_rpcvalue!(IMap);
}


#[cfg(not(feature = "specialization"))]
mod without_specialization {
use super::{
from_vec_rpcvalue_for_rpcvalue,
from_map_rpcvalue_for_rpcvalue,
from_imap_rpcvalue_for_rpcvalue}
;
use crate::RpcValue;
use std::collections::BTreeMap;


impl<T: Into<RpcValue>> From<Vec<T>> for RpcValue {
fn from(value: Vec<T>) -> Self {
from_vec_rpcvalue_for_rpcvalue(value)
}
}

impl<T: Into<RpcValue>> From<BTreeMap<String, T>> for RpcValue {
fn from(value: BTreeMap<String, T>) -> Self {
from_map_rpcvalue_for_rpcvalue(value)
}
}

impl<T: Into<RpcValue>> From<BTreeMap<i32, T>> for RpcValue {
fn from(value: BTreeMap<i32, T>) -> Self {
from_imap_rpcvalue_for_rpcvalue(value)
}
}
}

fn from_vec_rpcvalue_for_rpcvalue<T: Into<RpcValue>>(value: Vec<T>) -> RpcValue {
RpcValue {
meta: None,
value: Value::List(Box::new(
value
.into_iter()
.map(Into::into)
.collect::<Vec<_>>()
))
}
}

fn from_map_rpcvalue_for_rpcvalue<T: Into<RpcValue>>(value: BTreeMap<String, T>) -> RpcValue {
RpcValue {
meta: None,
value: Value::Map(Box::new(
value
.into_iter()
.map(|(k, v)| (k, v.into()))
.collect::<BTreeMap<_,_>>()
))
}
}

fn from_imap_rpcvalue_for_rpcvalue<T: Into<RpcValue>>(value: BTreeMap<i32, T>) -> RpcValue {
RpcValue {
meta: None,
value: Value::IMap(Box::new(
value
.into_iter()
.map(|(k, v)| (k, v.into()))
.collect::<BTreeMap<_,_>>()
))
}
}



fn format_err_try_from(expected_type: &str, actual_type: &str) -> String {
format!("Expected type `{expected_type}`, got `{actual_type}`")
}
Expand Down Expand Up @@ -1072,18 +1214,20 @@ mod test {
assert_eq!(rrv.try_into(), Ok(&vec1));
assert_eq!(rv.try_into(), Ok(vec1));

let mut m: Map = BTreeMap::new();
m.insert("foo".to_string(), RpcValue::from(123));
m.insert("bar".to_string(), RpcValue::from("foo"));
let m = [
("foo".to_string(), RpcValue::from(123)),
("bar".to_string(), RpcValue::from("foo"))
].into_iter().collect::<Map>();
let rv = RpcValue::from(m.clone());
assert_eq!(rv.as_map(), &m);
let rrv = &rv;
assert_eq!(rrv.try_into(), Ok(&m));
assert_eq!(rv.try_into(), Ok(m));

let mut m: BTreeMap<i32, RpcValue> = BTreeMap::new();
m.insert(1, RpcValue::from(123));
m.insert(2, RpcValue::from("foo"));
let m = [
(1, RpcValue::from(123)),
(2, RpcValue::from("foo"))
].into_iter().collect::<IMap>();
let rv = RpcValue::from(m.clone());
assert_eq!(rv.as_imap(), &m);
let rrv = &rv;
Expand All @@ -1096,22 +1240,64 @@ mod test {
assert_eq!(rrv.try_into(), Ok(vec1.clone()));
assert_eq!(rv.try_into(), Ok(vec1));

let mut m = BTreeMap::new();
m.insert("foo".to_owned(), 123_i32);
m.insert("bar".to_owned(), 456_i32);
let m = [
("foo".to_owned(), 123_i32),
("bar".to_owned(), 456_i32)
].into_iter().collect::<BTreeMap<_,_>>();
let rv = RpcValue::from(
m.iter().map(|(k, &v)| (k.clone(), v.into())).collect::<Map>());
let rrv = &rv;
assert_eq!(rrv.try_into(), Ok(m.clone()));
assert_eq!(rv.try_into(), Ok(m));

let mut m = BTreeMap::new();
m.insert(1, std::f64::consts::E);
m.insert(7, std::f64::consts::PI);
let m = [
(1, std::f64::consts::E),
(7, std::f64::consts::PI)
].into_iter().collect::<BTreeMap<_,_>>();
let rv = RpcValue::from(
m.iter().map(|(&k, &v)| (k, v.into())).collect::<IMap>());
let rrv = &rv;
assert_eq!(rrv.try_into(), Ok(m.clone()));
assert_eq!(rv.try_into(), Ok(m));


// Collection -> RpcValue -> Collection

let v = vec![RpcValue::from(1i32), RpcValue::from(2i32)];
assert_eq!(Ok(v.clone()), RpcValue::from(v).try_into());

let v = vec![1i32, 2i32];
assert_eq!(Ok(v.clone()), RpcValue::from(v).try_into());

let v = [
"x",
"yy",
"zzz",
].into_iter().map(String::from).collect::<Vec<_>>();
assert_eq!(Ok(v.clone()), RpcValue::from(v).try_into());

let v = [
("xxx".to_owned(), RpcValue::from(1_i32)),
("yyy".to_owned(), RpcValue::from(2_i32)),
].into_iter().collect::<BTreeMap<_,_>>();
assert_eq!(Ok(v.clone()), RpcValue::from(v).try_into());

let v = [
("xxx".to_owned(), 1_i32),
("yyy".to_owned(), 2_i32),
].into_iter().collect::<BTreeMap<_,_>>();
assert_eq!(Ok(v.clone()), RpcValue::from(v).try_into());

let v = [
(1, RpcValue::from(1_i32)),
(2, RpcValue::from(2_i32)),
].into_iter().collect::<BTreeMap<_,_>>();
assert_eq!(Ok(v.clone()), RpcValue::from(v).try_into());

let v = [
(1, 1_i32),
(2, 2_i32),
].into_iter().collect::<BTreeMap<_,_>>();
assert_eq!(Ok(v.clone()), RpcValue::from(v).try_into());
}
}
Loading
Loading