commit
fa4e847e72
@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
/Cargo.lock
|
@ -0,0 +1,20 @@
|
|||||||
|
[workspace]
|
||||||
|
members = [".", "derive"]
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "rusty-value"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies.rusty-value-derive]
|
||||||
|
path = "./derive"
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
derive = ["rusty-value-derive"]
|
||||||
|
|
||||||
|
[dev-dependencies.rusty-value-derive]
|
||||||
|
path = "./derive"
|
@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "rusty-value-derive"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
proc-macro2 = "1.0.46"
|
||||||
|
quote = "1.0.21"
|
||||||
|
syn = "1.0.101"
|
@ -0,0 +1,84 @@
|
|||||||
|
use proc_macro::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
|
use syn::{parse_macro_input, DataStruct, DeriveInput, FieldsNamed, FieldsUnnamed};
|
||||||
|
|
||||||
|
#[proc_macro_derive(RustyValue)]
|
||||||
|
pub fn derive_value(input: TokenStream) -> TokenStream {
|
||||||
|
derive(parse_macro_input!(input as DeriveInput))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn derive(input: DeriveInput) -> TokenStream {
|
||||||
|
match &input.data {
|
||||||
|
syn::Data::Struct(s) => derive_struct(&input, s),
|
||||||
|
syn::Data::Enum(_) => todo!(),
|
||||||
|
syn::Data::Union(_) => panic!("unions are currently unsupported"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn derive_struct(input: &DeriveInput, struct_data: &DataStruct) -> TokenStream {
|
||||||
|
let ident = &input.ident;
|
||||||
|
let name = ident.to_string();
|
||||||
|
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||||
|
|
||||||
|
match &struct_data.fields {
|
||||||
|
syn::Fields::Named(FieldsNamed { named, .. }) => {
|
||||||
|
let field_idents = named.iter().map(|f| f.ident.as_ref()).collect::<Vec<_>>();
|
||||||
|
let field_names = named
|
||||||
|
.iter()
|
||||||
|
.map(|f| f.ident.as_ref().unwrap().to_string())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let field_count = named.len();
|
||||||
|
|
||||||
|
TokenStream::from(quote! {
|
||||||
|
impl #impl_generics rusty_value::RustyValue for #ident #ty_generics #where_clause {
|
||||||
|
fn into_rusty_value(self) -> rusty_value::Value {
|
||||||
|
use rusty_value::*;
|
||||||
|
let mut values = std::collections::HashMap::with_capacity(#field_count);
|
||||||
|
|
||||||
|
#(
|
||||||
|
values.insert(#field_names.to_string(), self.#field_idents.into_rusty_value());
|
||||||
|
)*
|
||||||
|
|
||||||
|
Value::Struct(Struct{
|
||||||
|
name: #name.to_string(),
|
||||||
|
fields: StructFields::Named(values),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
syn::Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => {
|
||||||
|
let field_indices = unnamed
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, _)| syn::Index::from(i))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let field_count = unnamed.len();
|
||||||
|
|
||||||
|
TokenStream::from(quote! {
|
||||||
|
impl #impl_generics rusty_value::RustyValue for #ident #ty_generics #where_clause {
|
||||||
|
fn into_rusty_value(self) -> rusty_value::Value {
|
||||||
|
use rusty_value::*;
|
||||||
|
let mut values = Vec::with_capacity(#field_count);
|
||||||
|
|
||||||
|
#(
|
||||||
|
values.push(self.#field_indices.into_rusty_value());
|
||||||
|
)*
|
||||||
|
|
||||||
|
Value::Struct(Struct{
|
||||||
|
name: #name.to_string(),
|
||||||
|
fields: StructFields::Unnamed(values),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
syn::Fields::Unit => TokenStream::from(quote! {
|
||||||
|
impl #impl_generics rusty_value::RustyValue for #ident #ty_generics #where_clause {
|
||||||
|
fn into_rusty_value(self) -> rusty_value::Value {
|
||||||
|
Value::Unit(#name.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
pub(crate) mod value;
|
||||||
|
pub(crate) mod value_trait;
|
||||||
|
pub use value::*;
|
||||||
|
pub use value_trait::*;
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
|
#[cfg(feature = "derive")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
|
||||||
|
pub use rusty_value_derive::*;
|
@ -0,0 +1,71 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum Value {
|
||||||
|
Primitive(Primitive),
|
||||||
|
Struct(Struct),
|
||||||
|
Enum(Enum),
|
||||||
|
Map(HashMap<HashablePrimitive, Value>),
|
||||||
|
List(Vec<Value>),
|
||||||
|
Unit(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct Enum {
|
||||||
|
pub name: String,
|
||||||
|
pub variant: String,
|
||||||
|
pub value: Box<Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct Struct {
|
||||||
|
pub name: String,
|
||||||
|
pub fields: StructFields,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum StructFields {
|
||||||
|
Named(HashMap<String, Value>),
|
||||||
|
Unnamed(Vec<Value>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, PartialOrd)]
|
||||||
|
pub enum Primitive {
|
||||||
|
Integer(Integer),
|
||||||
|
Float(Float),
|
||||||
|
String(String),
|
||||||
|
Char(char),
|
||||||
|
Bool(bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
|
pub enum Integer {
|
||||||
|
U8(u8),
|
||||||
|
I8(i8),
|
||||||
|
U16(u16),
|
||||||
|
I16(i16),
|
||||||
|
U32(u32),
|
||||||
|
I32(i32),
|
||||||
|
U64(u64),
|
||||||
|
I64(i64),
|
||||||
|
U128(u128),
|
||||||
|
I128(i128),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, PartialOrd)]
|
||||||
|
pub enum Float {
|
||||||
|
F32(f32),
|
||||||
|
F64(f64),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum HashableValue {
|
||||||
|
Primitive(HashablePrimitive),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
|
pub enum HashablePrimitive {
|
||||||
|
Integer(Integer),
|
||||||
|
String(String),
|
||||||
|
Char(char),
|
||||||
|
Bool(bool),
|
||||||
|
}
|
@ -0,0 +1,101 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::{HashablePrimitive, Primitive, Value};
|
||||||
|
|
||||||
|
pub trait RustyValue {
|
||||||
|
fn into_rusty_value(self) -> Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RustyValue for u8 {
|
||||||
|
fn into_rusty_value(self) -> Value {
|
||||||
|
Value::Primitive(Primitive::Integer(crate::Integer::U8(self)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RustyValue for i8 {
|
||||||
|
fn into_rusty_value(self) -> Value {
|
||||||
|
Value::Primitive(Primitive::Integer(crate::Integer::I8(self)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RustyValue for u16 {
|
||||||
|
fn into_rusty_value(self) -> Value {
|
||||||
|
Value::Primitive(Primitive::Integer(crate::Integer::U16(self)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RustyValue for i16 {
|
||||||
|
fn into_rusty_value(self) -> Value {
|
||||||
|
Value::Primitive(Primitive::Integer(crate::Integer::I16(self)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RustyValue for u32 {
|
||||||
|
fn into_rusty_value(self) -> Value {
|
||||||
|
Value::Primitive(Primitive::Integer(crate::Integer::U32(self)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RustyValue for i32 {
|
||||||
|
fn into_rusty_value(self) -> Value {
|
||||||
|
Value::Primitive(Primitive::Integer(crate::Integer::I32(self)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RustyValue for u64 {
|
||||||
|
fn into_rusty_value(self) -> Value {
|
||||||
|
Value::Primitive(Primitive::Integer(crate::Integer::U64(self)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RustyValue for i64 {
|
||||||
|
fn into_rusty_value(self) -> Value {
|
||||||
|
Value::Primitive(Primitive::Integer(crate::Integer::I64(self)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RustyValue for u128 {
|
||||||
|
fn into_rusty_value(self) -> Value {
|
||||||
|
Value::Primitive(Primitive::Integer(crate::Integer::U128(self)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RustyValue for i128 {
|
||||||
|
fn into_rusty_value(self) -> Value {
|
||||||
|
Value::Primitive(Primitive::Integer(crate::Integer::I128(self)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RustyValue for String {
|
||||||
|
fn into_rusty_value(self) -> Value {
|
||||||
|
Value::Primitive(Primitive::String(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RustyValue for bool {
|
||||||
|
fn into_rusty_value(self) -> Value {
|
||||||
|
Value::Primitive(Primitive::Bool(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: RustyValue> RustyValue for Vec<R> {
|
||||||
|
fn into_rusty_value(self) -> Value {
|
||||||
|
let value_vec = self
|
||||||
|
.into_iter()
|
||||||
|
.map(|v| v.into_rusty_value())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
Value::List(value_vec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: RustyValue> RustyValue for HashMap<String, R> {
|
||||||
|
fn into_rusty_value(self) -> Value {
|
||||||
|
let map = self
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, v)| (HashablePrimitive::String(k), v.into_rusty_value()))
|
||||||
|
.collect::<HashMap<_, _>>();
|
||||||
|
|
||||||
|
Value::Map(map)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
use rusty_value::{RustyValue, StructFields, Value};
|
||||||
|
use rusty_value_derive::*;
|
||||||
|
|
||||||
|
#[derive(RustyValue)]
|
||||||
|
struct TestStructNamed {
|
||||||
|
foo: String,
|
||||||
|
bar: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_handles_named_fields() {
|
||||||
|
let test_struct = TestStructNamed {
|
||||||
|
foo: String::from("Hello World"),
|
||||||
|
bar: 12,
|
||||||
|
};
|
||||||
|
let value = test_struct.into_rusty_value();
|
||||||
|
dbg!(&value);
|
||||||
|
|
||||||
|
if let Value::Struct(s) = value {
|
||||||
|
assert_eq!(&s.name, "TestStructNamed");
|
||||||
|
|
||||||
|
if let StructFields::Named(fields) = s.fields {
|
||||||
|
assert_eq!(fields.len(), 2);
|
||||||
|
} else {
|
||||||
|
panic!("Struct wasn't serialized as named struct")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Struct wasn't serialized as struct");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(RustyValue)]
|
||||||
|
struct TestStructUnnamed(String, u64);
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_handles_unnamed_fields() {
|
||||||
|
let test_struct = TestStructUnnamed(String::from("Hello World"), 12);
|
||||||
|
let value = test_struct.into_rusty_value();
|
||||||
|
dbg!(&value);
|
||||||
|
|
||||||
|
if let Value::Struct(s) = value {
|
||||||
|
assert_eq!(&s.name, "TestStructUnnamed");
|
||||||
|
|
||||||
|
if let StructFields::Unnamed(fields) = s.fields {
|
||||||
|
assert_eq!(fields.len(), 2);
|
||||||
|
} else {
|
||||||
|
panic!("Struct wasn't serialized as unnamed struct")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Struct wasn't serialized as struct");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(RustyValue)]
|
||||||
|
struct TestStructUnit;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_handles_unit_structs() {
|
||||||
|
let test_struct = TestStructUnit;
|
||||||
|
let value = test_struct.into_rusty_value();
|
||||||
|
dbg!(&value);
|
||||||
|
|
||||||
|
if let Value::Unit(s) = value {
|
||||||
|
assert_eq!(&s, "TestStructUnit");
|
||||||
|
} else {
|
||||||
|
panic!("Struct wasn't serialized as struct");
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue