diff --git a/Cargo.toml b/Cargo.toml index 81687b7..3ece959 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ license = "Apache-2.0" name = "scaffolding-core" readme = "README.md" repository = "https://github.com/dsietz/scaffolding-core" -version = "0.0.2" +version = "0.0.3" [badges] maintenance = {status = "experimental"} @@ -26,7 +26,7 @@ path = "src/lib.rs" [dependencies] chrono = "0.4.35" -scaffolding-macros = {path = "./scaffolding-macros", version = "0.0.2"} +scaffolding-macros = {path = "./scaffolding-macros", version = "0.0.3"} [dependencies.uuid] features = ["v4"] diff --git a/scaffolding-macros/Cargo.toml b/scaffolding-macros/Cargo.toml index bd7a8dd..07a56be 100644 --- a/scaffolding-macros/Cargo.toml +++ b/scaffolding-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "scaffolding-macros" -version = "0.0.2" +version = "0.0.3" authors = ["dsietz "] edition = "2021" readme = "README.md" diff --git a/scaffolding-macros/src/lib.rs b/scaffolding-macros/src/lib.rs index 1dc899e..336e775 100644 --- a/scaffolding-macros/src/lib.rs +++ b/scaffolding-macros/src/lib.rs @@ -3,10 +3,19 @@ use quote::quote; use syn::parse::{Parse, ParseStream, Parser, Result}; use syn::{parse, parse_macro_input, punctuated::Punctuated, ItemStruct, LitStr, Token}; +static METADATA: &str = "metadata"; + #[proc_macro_attribute] -pub fn as_entity(args: TokenStream, input: TokenStream) -> TokenStream { +pub fn scaffolding_entity(args: TokenStream, input: TokenStream) -> TokenStream { + // println!("attr: \"{}\"", args.to_string()); + let mut item_struct = parse_macro_input!(input as ItemStruct); - let _ = parse_macro_input!(args as parse::Nothing); + let attrs = parse_macro_input!(args as Args) + .vars + .iter() + .map(|a| a.value()) + .collect::>(); + // println!("metadata: {:?}", attrs.contains(&"metadata".to_string())); if let syn::Fields::Named(ref mut fields) = item_struct.fields { // The unique identifier of the object @@ -39,6 +48,18 @@ pub fn as_entity(args: TokenStream, input: TokenStream) -> TokenStream { .parse2(quote! { expired_dtm: i64 }) .unwrap(), ); + + match attrs.contains(&METADATA.to_string()) { + true => { + // The metadata handler + fields.named.push( + syn::Field::parse_named + .parse2(quote! { metadata: BTreeMap }) + .unwrap(), + ); + } + false => {} + } } return quote! { @@ -47,6 +68,26 @@ pub fn as_entity(args: TokenStream, input: TokenStream) -> TokenStream { .into(); } +// #[proc_macro_attribute] +// pub fn scaffolding_metadata(args: TokenStream, input: TokenStream) -> TokenStream { +// let mut item_struct = parse_macro_input!(input as ItemStruct); +// let _ = parse_macro_input!(args as parse::Nothing); + +// if let syn::Fields::Named(ref mut fields) = item_struct.fields { +// // The unique identifier of the object +// fields.named.push( +// syn::Field::parse_named +// .parse2(quote! { metadata: BTreeMap::new() }) +// .unwrap(), +// ); +// } + +// return quote! { +// #item_struct +// } +// .into(); +// } + #[derive(Debug)] struct Args { pub vars: Vec, diff --git a/scaffolding-macros/tests/macro_test.rs b/scaffolding-macros/tests/macro_test.rs index 3bb7fe8..d6a1222 100644 --- a/scaffolding-macros/tests/macro_test.rs +++ b/scaffolding-macros/tests/macro_test.rs @@ -4,8 +4,9 @@ extern crate scaffolding_macros; #[cfg(test)] mod tests { use scaffolding_macros::*; + use std::collections::BTreeMap; - #[as_entity] + #[scaffolding_entity("metadata")] #[derive(Debug, Clone)] struct MyEntity { b: bool, @@ -19,6 +20,7 @@ mod tests { modified_dtm: 1711281509, inactive_dtm: 1711281509, expired_dtm: 1711281509, + metadata: BTreeMap::new(), b: true, }; println!("struct is {:?}", entity); diff --git a/src/defaults.rs b/src/defaults.rs new file mode 100644 index 0000000..1c0425c --- /dev/null +++ b/src/defaults.rs @@ -0,0 +1,117 @@ +//! The defaults module provides the methods for creating deafult values +//! for the Scaffolding common attributes + +use chrono::{DateTime, Duration, Months, Utc}; +use uuid::Uuid; + +/// generates a uuid v4 value +/// +/// ```rust +/// use scaffolding_core::defaults::*; +/// +/// assert_eq!(id().len(), "54324f57-9e6b-4142-b68d-1d4c86572d0a".len()); +/// ``` +pub fn id() -> String { + Uuid::new_v4().to_string() +} + +/// adds x days to the timestamp +/// +/// ```rust +/// use scaffolding_core::defaults::*; +/// +/// assert_eq!(add_days(1711295319, 1), 1711381719); +/// ``` +pub fn add_days(dtm: i64, days: i64) -> i64 { + let dt = DateTime::from_timestamp(dtm, 0).unwrap() + Duration::try_days(days).unwrap(); + dt.timestamp() +} + +/// adds x months to the timestamp +/// +/// ```rust +/// use scaffolding_core::defaults::*; +/// +/// assert_eq!(add_months(1711295319, 1), 1713973719); +/// ``` +pub fn add_months(dtm: i64, months: u32) -> i64 { + let dt = DateTime::from_timestamp(dtm, 0).unwrap() + Months::new(months); + dt.timestamp() +} + +/// adds x years to the timestamp +/// +/// ```rust +/// use scaffolding_core::defaults::*; +/// +/// assert_eq!(add_years(1711295319, 1), 1742831319); +/// ``` +pub fn add_years(dtm: i64, years: u32) -> i64 { + let dt = DateTime::from_timestamp(dtm, 0).unwrap() + Months::new(years * 12); + dt.timestamp() +} + +/// provided the default unix epoch time (UTC) as seconds +/// for the timestamp: 9999-12-31 23:59:59 +/// +/// ```rust +/// use scaffolding_core::defaults::*; +/// +/// assert_eq!(never(), 253402261199); +/// ``` +pub fn never() -> i64 { + 253402261199 +} + +/// generate the current unix epoch time (UTC) as seconds +/// +/// ```rust +/// use chrono::Utc; +/// use scaffolding_core::defaults::*; +/// +/// assert_eq!(now(), Utc::now().timestamp()); +/// ``` +pub fn now() -> i64 { + Utc::now().timestamp() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_id() { + assert_eq!(id().len(), "54324f57-9e6b-4142-b68d-1d4c86572d0a".len()); + } + + #[test] + fn test_add_days() { + assert_eq!(add_days(1711295319, 1), 1711381719); + } + + #[test] + fn test_add_months() { + assert_eq!(add_months(1711295319, 1), 1713973719); + // test for a leap year + // 2023-1-29 +1 = 2023-2-28 + assert_eq!(add_months(1674993600, 1), 1677585600); + } + + #[test] + fn test_add_years() { + assert_eq!(add_years(1711295319, 1), 1742831319); + // test for a leap year + // 2024-2-29 +1 = 2025-2-28 + assert_eq!(add_years(1709208000, 1), 1740744000); + } + + #[test] + fn test_never() { + assert_eq!(never(), 253402261199); + } + + #[test] + fn test_now() { + assert_eq!(now(), Utc::now().timestamp()); + } +} diff --git a/src/lib.rs b/src/lib.rs index 9e916cc..d8483bd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,16 +8,16 @@ //! `extend` a class - both data structure and behavior. //! //! ## Scaffolding Concept -//! I. A class that `extends` the "Scaffolding class" should inherate all the "parent" data structure and behavior, +//! 1. A class that `extends` the "Scaffolding class" should inherate all the "parent" data structure and behavior, //! as well as append the "child" specific data structure and behavior -//! II. The developer should have the flexibility to adopt the default "parent" characteristics or overwrite them as desired. -//! III. There are common class attributes that are required in order to manage it using CRUD +//! 2. The developer should have the flexibility to adopt the default "parent" characteristics or overwrite them as desired. +//! 3. There are common class attributes that are required in order to manage it using CRUD //! + `id` - The unique identifier of the object. //! + `created_dtm` - The unix epoch (UTC) representation of when the object was created //! + `modified_dtm` - The unix epoch (UTC) representation of when the object was last updated //! + `inactive_dtm` - The unix epoch (UTC) representation of when the object was/will be considered obsolete //! + `expired_dtm` - The unix epoch (UTC) representation of when the object was/will be ready for deletion -//! IV. There is common class behaviors that are required in order to manage it using CRUD +//! 4. There is common class behaviors that are required in order to manage it using CRUD //! + The `id` is not optional. It must be either provided or automatically generated during instantiation. //! This can be done by calling the `Scaffolding` trait's `id()` method //! + The `created_dtm` is not optional. It must be either provided or automatically generated during instantiation. @@ -34,10 +34,10 @@ //! ```rust //! extern crate scaffolding_core; //! -//! use scaffolding_core::*; +//! use scaffolding_core::{defaults, Scaffolding}; //! use scaffolding_macros::*; //! -//! #[as_entity] +//! #[scaffolding_entity] //! #[derive(Debug, Clone, Scaffolding)] //! struct MyEntity { //! b: bool, @@ -46,11 +46,11 @@ //! impl MyEntity { //! fn new(arg: bool) -> Self { //! Self { -//! id: ::id(), -//! created_dtm: ::now(), -//! modified_dtm: ::now(), -//! inactive_dtm: ::add_months(::now(), 12), -//! expired_dtm: ::add_years(::now(), 3), +//! id: defaults::id(), +//! created_dtm: defaults::now(), +//! modified_dtm: defaults::now(), +//! inactive_dtm: defaults::add_months(defaults::now(), 12), +//! expired_dtm: defaults::add_years(defaults::now(), 3), //! b: arg, //! } //! } @@ -70,42 +70,7 @@ //! assert_eq!(entity.my_func(), "my function"); //! ``` -use chrono::{DateTime, Duration, Months, Utc}; -use uuid::Uuid; - /// The core behavior of an Scaffolding object -pub trait Scaffolding { - /// generates a uuid v4 value - fn id() -> String { - Uuid::new_v4().to_string() - } - - /// adds x days to the timestamp - fn add_days(dtm: i64, days: i64) -> i64 { - let dt = DateTime::from_timestamp(dtm, 0).unwrap() + Duration::try_days(days).unwrap(); - dt.timestamp() - } - - /// adds x months to the timestamp - fn add_months(dtm: i64, months: u32) -> i64 { - let dt = DateTime::from_timestamp(dtm, 0).unwrap() + Months::new(months); - dt.timestamp() - } - - /// adds x years to the timestamp - fn add_years(dtm: i64, years: u32) -> i64 { - let dt = DateTime::from_timestamp(dtm, 0).unwrap() + Months::new(years * 12); - dt.timestamp() - } - - /// provided the default unix epoch time (UTC) as seconds - /// for the timestamp: 9999-12-31 23:59:59 - fn never() -> i64 { - 253402261199 - } +pub trait Scaffolding {} - /// generate the current unix epoch time (UTC) as seconds - fn now() -> i64 { - Utc::now().timestamp() - } -} +pub mod defaults; diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 7f23346..d75bae5 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -1,13 +1,15 @@ +// #[macro_use] extern crate scaffolding_core; extern crate scaffolding_macros; #[cfg(test)] mod tests { use chrono::Utc; - use scaffolding_core::*; + use scaffolding_core::{defaults, Scaffolding}; use scaffolding_macros::*; + use std::collections::BTreeMap; - #[as_entity] + #[scaffolding_entity("metadata")] #[derive(Debug, Clone, Scaffolding)] struct MyEntity { b: bool, @@ -17,13 +19,14 @@ mod tests { impl MyEntity { fn new(arg: bool) -> Self { Self { - id: ::id(), - created_dtm: ::now(), - modified_dtm: ::now(), - inactive_dtm: ::add_days(::now(), 90), - expired_dtm: ::add_years(::now(), 3), + id: defaults::id(), + created_dtm: defaults::now(), + modified_dtm: defaults::now(), + inactive_dtm: defaults::add_days(defaults::now(), 90), + expired_dtm: defaults::add_years(defaults::now(), 3), + metadata: BTreeMap::new(), b: arg, - n: ::never(), + n: defaults::never(), } } @@ -32,13 +35,6 @@ mod tests { } } - // #[test] - // fn test_entity_hello() { - // let mut entity = MyEntity::new(true); - // entity.hello(); - // assert_eq!(entity.my_func(), "my function"); - // } - #[test] fn test_entity_new() { let now = Utc::now().timestamp();