diff --git a/.gitignore b/.gitignore index 9b8b0ec0..bebd1879 100755 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ target/ Cargo.lock .idea/ /tester_canyon_sql/ -canyon_tester/ \ No newline at end of file +canyon_tester/ +macro_utils.rs \ No newline at end of file diff --git a/canyon_macros/Cargo.toml b/canyon_macros/Cargo.toml index 560c94d6..6f6d7a78 100755 --- a/canyon_macros/Cargo.toml +++ b/canyon_macros/Cargo.toml @@ -4,9 +4,11 @@ version = "0.1.0" edition = "2018" [dependencies] -syn = "1.0" -quote = "1.0" -proc-macro2 = "1.0" +syn = { version = "1.0.86", features = ["full"] } +quote = "1.0.9" +proc-macro2 = "1.0.27" +futures = "0.3.21" +canyon_observer = { path = "../canyon_observer" } [lib] -proc-macro = true \ No newline at end of file +proc-macro = true diff --git a/canyon_macros/src/canyon_macro.rs b/canyon_macros/src/canyon_macro.rs new file mode 100644 index 00000000..81f24d47 --- /dev/null +++ b/canyon_macros/src/canyon_macro.rs @@ -0,0 +1,59 @@ +/// Provides helpers to build the #[canyon] procedural macro like attribute + +use proc_macro2::TokenStream; +use syn::Block; +use quote::quote; + +use canyon_observer::CANYON_REGISTER; + +/// Creates a TokenScream that is used to load the data generated at compile-time +/// by the `CanyonManaged` macros again on the Canyon register but +pub fn _wire_data_on_canyon_register(canyon_manager_tokens: &mut Vec) { + let mut identifiers = String::new(); + + unsafe { + for element in &CANYON_REGISTER { + identifiers.push_str(element.as_str()); + identifiers.push(','); + } + } + + let tokens = quote! { + use canyon_sql::canyon_observer::{ + CANYON_REGISTER, + CREDENTIALS, + credentials::DatabaseCredentials + }; + + unsafe { CREDENTIALS = Some(DatabaseCredentials::new()); } + unsafe { println!("CREDENTIALS MACRO IN: {:?}", CREDENTIALS); } + unsafe { CANYON_REGISTER = #identifiers + .split(',') + .map(str::to_string) + .collect(); + // TODO Delete (or just pick without it) the last elemement + // from the new assignation + // CANYON_REGISTER.pop_back(); + } + unsafe { println!("Register status IN: {:?}", CANYON_REGISTER) }; + }; + + canyon_manager_tokens.push(tokens); +} + +/// Generates the TokenStream that has the code written by the user +/// in the `fn main()` +pub fn _user_body_builder(func_body: Box, macro_tokens: &mut Vec) { + // Gets a Vec with all the staments in the body of the fn + let function_statements = func_body.stmts; + + for stmt in function_statements { + let quote = quote! {#stmt}; + let quoterino: TokenStream = quote + .to_string() + .parse() + .unwrap(); + + macro_tokens.push(quoterino) + } +} \ No newline at end of file diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index 2ae1ba60..97a7021b 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -1,11 +1,96 @@ extern crate proc_macro; -use proc_macro2::Ident; +use proc_macro::TokenStream as CompilerTokenStream; +use proc_macro2::{Ident, TokenStream}; use quote::quote; use syn::{ - DeriveInput, Fields, Visibility + DeriveInput, Fields, Visibility, parse_macro_input, ItemFn, Type }; + +mod canyon_macro; + +use canyon_macro::{_user_body_builder, _wire_data_on_canyon_register}; +use canyon_observer::CANYON_REGISTER; + + +/// Macro for handling the entry point to the program. +/// +/// Avoids the user to write the tokio attribute and +/// the async modifier to the main fn +/// TODO Check for the _meta attribute metadata when necessary +#[proc_macro_attribute] +pub fn canyon(_meta: CompilerTokenStream, input: CompilerTokenStream) -> CompilerTokenStream { + // get the function this attribute is attached to + let func = parse_macro_input!(input as ItemFn); + let sign = func.sig; + let body = func.block; + + // The code written by the Canyon Manager + let mut canyon_manager_tokens: Vec = Vec::new(); + // Builds the code that Canyon needs in it's initialization + _wire_data_on_canyon_register(&mut canyon_manager_tokens); + + // The code written by the user + let mut macro_tokens: Vec = Vec::new(); + // Builds the code that represents the user written code + _user_body_builder(body, &mut macro_tokens); + + + let tok = quote! { + use canyon_sql::tokio; + #[tokio::main] + async #sign { + { + #(#canyon_manager_tokens)* + } + + #(#macro_tokens)* + } + }; + + tok.into() +} + + +/// Takes data from the struct annotated with macro to fill the Canyon Register +/// where lives the data that Canyon needs to work in `managed mode` +#[proc_macro_attribute] +pub fn canyon_managed(_meta: CompilerTokenStream, input: CompilerTokenStream) -> CompilerTokenStream { + let ast: DeriveInput = syn::parse(input).unwrap(); + let (vis, ty, generics) = (&ast.vis, &ast.ident, &ast.generics); + let fields = fields_with_types( + match ast.data { + syn::Data::Struct(ref s) => &s.fields, + _ => panic!("Field names can only be derived for structs"), + } + ); + + // Notifies the observer that an observable must be registered on the system + // In other words, adds the data of the structure to the Canyon Register + unsafe { CANYON_REGISTER.push(ty.to_string()); } + println!("Observable <{}> added to the register", ty.to_string()); + + + let struct_fields = fields.iter().map(|(_vis, ident, ty)| { + quote! { + #vis #ident: #ty + } + }); + + let (_impl_generics, ty_generics, _where_clause) = + generics.split_for_impl(); + + let tokens = quote! { + pub struct #ty <#ty_generics> { + #(#struct_fields),* + } + }; + + tokens.into() +} + + /// Allows the implementors to auto-derive de `crud-operations` trait, which defines the methods /// that will perform the database communication and that will query against the db. #[proc_macro_derive(CanyonCRUD)] @@ -18,6 +103,7 @@ pub fn crud_operations(input: proc_macro::TokenStream) -> proc_macro::TokenStrea impl_crud_operations_trait_for_struct(&ast) } + fn impl_crud_operations_trait_for_struct(ast: &syn::DeriveInput) -> proc_macro::TokenStream { let ty = &ast.ident; let tokens = quote! { @@ -31,10 +117,15 @@ fn impl_crud_operations_trait_for_struct(ast: &syn::DeriveInput) -> proc_macro:: #[proc_macro_derive(CanyonMapper)] pub fn implement_row_mapper_for_type(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + // Gets the data from the AST let ast: DeriveInput = syn::parse(input).unwrap(); let (vis, ty, generics) = (&ast.vis, &ast.ident, &ast.generics); + + // Retrieves the table name automatically from the Struct identifier + // or from the TODO: #table_name = 'user_defined_db_table_name' let table_name: String = database_table_name_from_struct(ty); + // Recoves the identifiers of the struct's members let fields = filter_fields( match ast.data { syn::Data::Struct(ref s) => &s.fields, @@ -42,13 +133,8 @@ pub fn implement_row_mapper_for_type(input: proc_macro::TokenStream) -> proc_mac } ); - let names_const_fields_str = fields.iter().map(|(_vis, ident)| { - let ident_name = ident.to_string(); - quote! { - #ident_name - } - }); - + // Creates the TokenStream for wire the column names into the + // Canyon RowMapper let field_names_for_row_mapper = fields.iter().map(|(_vis, ident)| { let ident_name = ident.to_string(); quote! { @@ -57,53 +143,29 @@ pub fn implement_row_mapper_for_type(input: proc_macro::TokenStream) -> proc_mac } }); + // Get the generics identifiers let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); let tokens = quote! { - use canyon_sql::{ - self, crud::CrudOperations, mapper::RowMapper, - async_trait::*, - }; - use canyon_sql::tokio_postgres::Row; - impl #impl_generics #ty #ty_generics #where_clause { - // Find all + // Find all // Select registers by columns not enabled yet #vis async fn find_all() -> Vec<#ty> { - #ty::__find_all(#table_name, &[]) + <#ty as CrudOperations<#ty>>::__find_all(#table_name, &[]) .await .as_response::<#ty>() } // Find by ID #vis async fn find_by_id(id: i32) -> #ty { - #ty::__find_by_id(#table_name, id) + <#ty as CrudOperations<#ty>>::__find_by_id(#table_name, id) .await .as_response::<#ty>()[0].clone() } - fn get_field_names() -> Vec { - let mut vec = Vec::new(); - - let field_names = stringify!( - #(#names_const_fields_str),* - ).split(",") - .collect::>() - .into_iter() - .for_each( |field_name| - vec.push( - field_name - .replace('"', "") - .replace(' ', "") - .to_string() - ) - ); - vec - } - } impl RowMapper for #ty { @@ -129,6 +191,19 @@ fn filter_fields(fields: &Fields) -> Vec<(Visibility, Ident)> { } +fn fields_with_types(fields: &Fields) -> Vec<(Visibility, Ident, Type)> { + fields + .iter() + .map(|field| + (field.vis.clone(), + field.ident.as_ref().unwrap().clone(), + field.ty.clone() + ) + ) + .collect::>() +} + + /// Parses a syn::Identifier to get a snake case database name from the type identifier fn database_table_name_from_struct(ty: &Ident) -> String { diff --git a/canyon_observer/Cargo.toml b/canyon_observer/Cargo.toml new file mode 100644 index 00000000..d67333fb --- /dev/null +++ b/canyon_observer/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "canyon_observer" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] \ No newline at end of file diff --git a/canyon_observer/src/credentials.rs b/canyon_observer/src/credentials.rs new file mode 100644 index 00000000..2dc783f2 --- /dev/null +++ b/canyon_observer/src/credentials.rs @@ -0,0 +1,65 @@ +/// This crate will replace the action of retrieve the database credentials +/// in order to only wire the once time to the entire program's lifetime + +use std::{fs, collections::HashMap}; + +/// Manages to retrieve the credentials to the desired database connection from an +/// handcoded `Secrets.toml` file, located at the root of the project. +#[derive(Clone, Debug)] +pub struct DatabaseCredentials { + pub username: String, + pub password: String, + pub db_name: String, +} + +impl DatabaseCredentials{ + + pub fn new() -> Self { + + let parsed_credentials = DatabaseCredentials::credentials_parser(); + + Self { + username: parsed_credentials.get("username").unwrap().to_owned(), + password: parsed_credentials.get("password").unwrap().to_owned(), + db_name: parsed_credentials.get("db_name").unwrap().to_owned() + } + } + + pub fn credentials_parser() -> HashMap { + + const FILE_NAME: &str = "Secrets.toml"; + let mut credentials_mapper: HashMap<_, _> = HashMap::new(); + + let secrets_file = fs::read_to_string(FILE_NAME) + .expect( // TODO Convert this to a custom error + &(format!( + "\n\nNo file --> {} <-- founded on the root of this project.", FILE_NAME + ) + "\nPlease, ensure that you created one .toml file with the necesary" + + " properties needed in order to connect to the database.\n\n") + ); + + let secrets_file_splitted = secrets_file + .split_terminator("\n"); + + for entry in secrets_file_splitted { + let cleaned_entry = + entry + .split_ascii_whitespace() + .filter( + |x| x != &"=" + ); + + let mut pair = Vec::new(); + cleaned_entry.for_each( + |elem| pair.push(elem.to_string()) + ); + + let attr = pair.get(0).unwrap(); + let value = pair.get(1).unwrap(); + + credentials_mapper.insert(attr.to_owned(), value.to_owned()); + } + + credentials_mapper + } +} \ No newline at end of file diff --git a/canyon_observer/src/lib.rs b/canyon_observer/src/lib.rs new file mode 100644 index 00000000..561121b6 --- /dev/null +++ b/canyon_observer/src/lib.rs @@ -0,0 +1,32 @@ +pub mod credentials; + +use credentials::DatabaseCredentials; + + +/// Holds the data needed by Canyon when the host +/// application it's running. +/// +/// Takes care about provide a namespace where retrieve the +/// databse credentials in only one place +/// +/// Also takes care about track what data structures Canyon +/// should be managing +pub static mut CANYON_REGISTER: Vec = Vec::new(); + +pub static mut CREDENTIALS: Option = None; + +/// Provides a prodecural way of manipulate the internal Canyon dat +///! Warning #[UNIMPLEMENTED] +pub trait CanyonManager { + /// Register into the CANYON_REGISTER namaspace data about a structure that should + /// be completly managed by Canyon + fn register_entity(entity_identifier: &'static str) { + unsafe {CANYON_REGISTER.push(entity_identifier.to_string())}; + } + + /// Shows the data owned by the CANYON_REGISTER data structure + ///! This should be only enabled in development stage + fn print_managed_structures() { + unsafe {println!("Managed data: {:?}", CANYON_REGISTER);} + } +} diff --git a/canyon_sql/Cargo.toml b/canyon_sql/Cargo.toml index df1b9002..180567a6 100755 --- a/canyon_sql/Cargo.toml +++ b/canyon_sql/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "canyon_sql" version = "0.4.0" -edition = "2018" +edition = "2021" [dependencies] tokio-postgres = { version = "0.7.2" } @@ -10,9 +10,11 @@ toml = { version = "0.5.8" } # Forbidden Rust dark arts tokio = { version = "1.9.0", features = ["full"] } async-trait = { version = "0.1.50" } +futures = "0.3.21" # Serialization/Deserialization serde_json = "1.0.59" serde = "1.0.90" -canyon_macros = { path = "../canyon_macros" } \ No newline at end of file +canyon_macros = { path = "../canyon_macros" } +canyon_observer = { path = "../canyon_observer" } \ No newline at end of file diff --git a/canyon_sql/src/connector.rs b/canyon_sql/src/connector.rs index 120a1d7a..2fa81d2e 100755 --- a/canyon_sql/src/connector.rs +++ b/canyon_sql/src/connector.rs @@ -1,5 +1,5 @@ use tokio_postgres::{Client, Connection, Error, NoTls, Socket, tls::NoTlsStream}; -use std::{fs, collections::HashMap}; +use std::{fs, collections::HashMap, marker::PhantomData}; /// Manages to retrieve the credentials to the desired database connection from an /// handcoded `Secrets.toml` file, located at the root of the project. @@ -64,14 +64,18 @@ impl DatabaseCredentials{ /// Creates a new connection with a database, returning the necessary tools /// to query the created link. /// TODO: Explain how to use this struct independently from CRUD trait -pub struct DatabaseConnection{ +pub struct DatabaseConnection<'a> { pub client: Client, pub connection: Connection, + pub phantom: &'a PhantomData> } -impl DatabaseConnection{ +unsafe impl Send for DatabaseConnection<'_> {} +unsafe impl Sync for DatabaseConnection<'_> {} - pub async fn new() -> Result { +impl<'a> DatabaseConnection<'a> { + + pub async fn new() -> Result, Error> { let credentials = DatabaseCredentials::new(); @@ -89,6 +93,7 @@ impl DatabaseConnection{ Ok(Self { client: new_client, connection: new_connection, + phantom: &PhantomData }) } } diff --git a/canyon_sql/src/crud.rs b/canyon_sql/src/crud.rs index 9c63ff20..ccb3e594 100755 --- a/canyon_sql/src/crud.rs +++ b/canyon_sql/src/crud.rs @@ -1,7 +1,5 @@ use std::fmt::Debug; - use async_trait::async_trait; - use tokio_postgres::{ToStatement, types::ToSql}; use crate::{connector::DatabaseConnection, results::DatabaseResult}; @@ -80,3 +78,4 @@ pub trait CrudOperations: Transaction { } } + \ No newline at end of file diff --git a/canyon_sql/src/lib.rs b/canyon_sql/src/lib.rs index 58fcfc7e..db5b0d78 100755 --- a/canyon_sql/src/lib.rs +++ b/canyon_sql/src/lib.rs @@ -2,14 +2,37 @@ pub use tokio; pub use async_trait; pub use tokio_postgres; +// pub use canyon_observer; // Core mods mod connector; +mod results; + +// Core public mods pub mod crud; pub mod mapper; -mod results; // Macros crate pub extern crate canyon_macros; +pub use canyon_observer; + +/// This reexports allows the users to import all the available +/// `Canyon-SQL` features in a single statement like: +/// +/// `use canyon_sql::*` +/// +/// and avoids polluting the macros with imports. +/// +/// The decision of reexports all this crates was made because the macros +/// was importing this ones already, but if two structures was defined on the +/// same file, the imported names into it collinding, avoiding let the user +/// to have multiple structs in only one file. +/// +/// This particular feature (or decision) will be opened for revision +/// 'cause it's not definitive to let this forever pub use canyon_macros::*; +pub use crud::*; +pub use mapper::*; +pub use async_trait::*; +pub use tokio_postgres::Row; \ No newline at end of file