Skip to content

Commit

Permalink
FEAT: init project
Browse files Browse the repository at this point in the history
  • Loading branch information
wuyuedefeng committed Sep 14, 2023
0 parents commit e62ea0b
Show file tree
Hide file tree
Showing 39 changed files with 3,261 additions and 0 deletions.
30 changes: 30 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Rust

on:
push:
branches: [ next, next ]
pull_request:
branches: [ next ]

env:
CARGO_TERM_COLOR: always

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

- name: Build
run: cargo build --features sqlite --verbose

- name: Run tests with sqlite features
run: cargo test --features sqlite --verbose

- name: Run tests with mysql features
run: cargo test --features mysql --verbose

- name: Run tests with postgres features
run: cargo test --features postgres --verbose
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/target
/Cargo.lock

# Added by cargo
#
# already existing elements were commented out

#/target
#/Cargo.lock
9 changes: 9 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"editor.formatOnSave": true,
"editor.formatOnType": true,
"[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer",
"editor.formatOnSave": true
},
"rust-analyzer.linkedProjects": ["./Cargo.toml"]
}
66 changes: 66 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
[workspace]
members = [".", "arel-macros", "./example"]

[workspace.dependencies]
log = "0.4"

uuid = "1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
bytes = { version = "1", features = ["serde"] }
chrono = { version = "0.4", features = ["serde"] }

# =============================================================================================

[package]
name = "arel"
version = "0.3.0"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "a sql orm base sqlx"
authors = ["sanmu <578595193@qq.com>"]
homepage = "https://github.com/rust-china/arel"
categories = ["database"]
keywords = ["async", "orm", "sqlite", "mysql", "postgres"]

[features]
default = ["with-json", "with-chrono"]
sqlite = ["sqlx/sqlite"]
mysql = ["sqlx/mysql"]
postgres = ["sqlx/postgres"]
runtime-tokio-native-tls = ["sqlx/runtime-tokio-native-tls"]
runtime-tokio-rustls = ["sqlx/runtime-tokio-rustls"]
runtime-tokio = ["sqlx/runtime-tokio"]
runtime-async-std-native-tls = ["sqlx/runtime-async-std-native-tls"]
runtime-async-std-rustls = ["sqlx/runtime-async-std-rustls"]
runtime-async-std = ["sqlx/runtime-async-std"]
tls-native-tls = ["sqlx/tls-native-tls"]
tls-rustls = ["sqlx/tls-rustls"]

with-json = ["serde_json", "bytes/serde", "chrono?/serde", "uuid?/serde", "sqlx/json"]
with-chrono = ["chrono", "sqlx/chrono"]

[package.metadata.docs.rs]
features = ["sqlite"]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dev-dependencies]
arel = { path = ".", features = ["runtime-tokio", "tls-rustls"] }
tokio = { version = "1", features = ["full"] }

[dependencies]
once_cell = "1.18"
async-trait = "0.1"
anyhow = "1.0"
thiserror = "1.0"
regex = "1.9"

bytes = { version = "1", features = ["serde"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1", optional = true }
chrono = { version = "0.4", features = ["serde"], optional = true }
uuid = { version = "1", optional = true }
sqlx = { version = "0.7" }

# arel-macros = { version = "0.3.0" }
arel-macros = { path = "./arel-macros" }
16 changes: 16 additions & 0 deletions arel-macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "arel-macros"
version = "0.3.0"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "arel macros"
authors = ["sanmu <578595193@qq.com>"]

[lib]
proc-macro = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
syn = { version = "2", features = ["extra-traits", "full"] }
proc-macro2 = { version = "1" }
quote = { version = "1" }
43 changes: 43 additions & 0 deletions arel-macros/src/arel/arel_trait.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// fn _table_name() -> &'static str;
pub(crate) fn impl_table_name(input: &crate::ItemInput) -> syn::Result<proc_macro2::TokenStream> {
let mut ret_token_stream = proc_macro2::TokenStream::new();
if let Some((table_name, _)) = input.get_args_path_value(vec![], "table_name", None)? {
ret_token_stream.extend(quote::quote!(
fn _table_name() -> &'static str {
#table_name
}
));
}
if ret_token_stream.is_empty() {
Err(syn::Error::new_spanned(&input.input, r#"Please set arel(table_name = "xxx")"#))
} else {
Ok(ret_token_stream)
}
}
// fn _primary_keys() -> Vec<&'static str>
pub(crate) fn impl_primary_keys(input: &crate::ItemInput) -> syn::Result<proc_macro2::TokenStream> {
let fields = input.struct_fields()?;
let mut primary_keys: Vec<String> = vec![];
for field in fields.iter() {
if let Some(_) = crate::ItemInput::get_field_path_value(field, vec!["arel"], "primary_key", None)? {
if let Some(field_ident) = &field.ident {
if let Some((rename, _)) = crate::ItemInput::get_field_path_value(field, vec!["arel"], "rename", None)? {
primary_keys.push(rename);
} else {
primary_keys.push(field_ident.to_string().trim_start_matches("r#").to_string());
}
}
}
}

let mut ret_token_stream = proc_macro2::TokenStream::new();
if primary_keys.len() > 0 {
ret_token_stream.extend(quote::quote!(
fn _primary_keys() -> Vec<&'static str> {
vec![#(#primary_keys),*]
}
))
}

Ok(ret_token_stream)
}
118 changes: 118 additions & 0 deletions arel-macros/src/arel/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
mod arel_trait;

use proc_macro::TokenStream;
use quote::ToTokens;
use syn::parse::Parser;

pub fn create_arel(args: TokenStream, input: TokenStream) -> TokenStream {
let input = crate::ItemInput {
args: if args.is_empty() {
None
} else {
Some(syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated.parse(args).unwrap())
},
input: syn::parse_macro_input!(input as syn::Item),
};

match do_expand(&input) {
Ok(token_stream) => token_stream.into(),
Err(e) => {
let mut ret_token_stream = e.to_compile_error();
ret_token_stream.extend(input.input.to_token_stream());
ret_token_stream.into()
}
}
}

fn do_expand(input: &crate::ItemInput) -> syn::Result<proc_macro2::TokenStream> {
match &input.input {
syn::Item::Struct(_) => (),
_ => return Err(syn::Error::new_spanned(&input.input, "arel only allow use on struct type")),
}

let model_name_ident = input.ident()?;
let mut model_fields = vec![];
for field in input.struct_fields()?.iter() {
let mut new_field = field.clone();
new_field.attrs = vec![];
model_fields.push(new_field);
}

let arel_trait_impl_table_name = arel_trait::impl_table_name(input)?;
let arel_trait_impl_primary_keys = arel_trait::impl_primary_keys(input)?;
let impl_trait_sqlx_from_row = impl_trait_sqlx_from_row(input)?;

let generics = input.generics()?;
let (impl_generics, type_generics, where_clause) = generics.split_for_impl();

let vis = input.vis()?;
Ok(quote::quote!(
#[derive(Clone, Debug, Default, PartialEq)]
#vis struct #model_name_ident #generics {
#(#model_fields),*
}

impl #impl_generics arel::SuperArel for #model_name_ident #type_generics #where_clause {
#arel_trait_impl_table_name
#arel_trait_impl_primary_keys
}

#impl_trait_sqlx_from_row
))
}

// impl<'r> arel::sqlx::FromRow<'r, arel::db::DatabaseRow> for User {
// fn from_row(row: &'r arel::db::DatabaseRow) -> Result<Self, sqlx::Error> {
// let mut model = Self::default();
// model.id = <i32 as arel::ArelAttributeFromRow>::from_row(row, "id")?;
// model.name = <String as arel::ArelAttributeFromRow>::from_row(row, "name")?;
// model.age = <Option<i32> as arel::ArelAttributeFromRow>::from_row(row, "age")?;
// model.gender = <Gender as arel::ArelAttributeFromRow>::from_row(row, "gender")?;
// model.r#type = <String as arel::ArelAttributeFromRow>::from_row(row, "type")?;
// model.address = <Option<String> as arel::ArelAttributeFromRow>::from_row(row, "address")?;
// model.expired_at = <Option<chrono::DateTime<chrono::FixedOffset>> as arel::ArelAttributeFromRow>::from_row(row, "expired_at")?;
// Ok(model)
// }
// }
fn impl_trait_sqlx_from_row(input: &crate::ItemInput) -> syn::Result<proc_macro2::TokenStream> {
let struct_ident = input.ident()?;
let fields = input.struct_fields()?;

let mut build_assign_clauses = vec![];
for field in fields.iter() {
let mut new_field = field.clone();
new_field.attrs = vec![];

let ident = &field.ident;
let r#type = &field.ty;

let field_name = {
// arel(rename="x")
if let Some((rename, _)) = crate::ItemInput::get_field_path_value(field, vec!["arel"], "rename", None)? {
rename
} else {
match ident {
Some(ident) => ident.to_string().trim_start_matches("r#").to_string(),
_ => return Err(syn::Error::new_spanned(field, "Field name can not Blank!")), //不可达
}
}
};
build_assign_clauses.push(quote::quote!(
model.#ident = <#r#type as arel::ArelAttributeFromRow>::from_row(&row, #field_name)?;
));
}

let mut generics = input.generics()?.clone();
generics.params.push(syn::parse_quote!('_r));
let (impl_generics, _, _) = generics.split_for_impl();
let (_, type_generics, where_clause) = input.generics()?.split_for_impl();
Ok(quote::quote!(
impl #impl_generics arel::sqlx::FromRow<'_r, arel::db::DatabaseRow> for #struct_ident #type_generics #where_clause {
fn from_row(row: &'_r arel::db::DatabaseRow) -> arel::sqlx::Result<Self, arel::sqlx::Error> {
let mut model = Self::default();
#(#build_assign_clauses)*
Ok(model)
}
}
))
}
37 changes: 37 additions & 0 deletions arel-macros/src/inputs/derive_input.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
pub struct DeriveInput {
pub args: Option<syn::punctuated::Punctuated<syn::Meta, syn::Token![,]>>,
/**
* input: syn::parse_macro_input!(input as syn::DeriveInput)
* */
pub input: syn::DeriveInput,
}

impl DeriveInput {
pub fn fields(&self) -> syn::Result<&syn::punctuated::Punctuated<syn::Field, syn::Token![,]>> {
if let syn::Data::Struct(syn::DataStruct {
fields: syn::Fields::Named(syn::FieldsNamed { named, .. }),
..
}) = &self.input.data
{
Ok(named)
} else {
Err(syn::Error::new_spanned(&self.input, "Must call on struct"))
}
}
}

impl DeriveInput {
pub fn get_field_path_value(field: &syn::Field, root_attr_paths: Vec<&str>, attr_path: &str, allowed_path_names: Option<Vec<&str>>) -> syn::Result<Option<(String, Option<syn::Lit>)>> {
let metas = field.attrs.iter().map(|f| &f.meta).collect::<Vec<&syn::Meta>>();
Self::get_path_value_from_metas(metas, root_attr_path, attr_path, allowed_path_names)
}
pub fn get_path_value_from_metas(metas: Vec<&syn::Meta>, root_attr_paths: Vec<&str>, attr_path: &str, allowed_path_names: Option<Vec<&str>>) -> syn::Result<Option<(String, Option<syn::Lit>)>> {
for meta in metas {
match super::get_path_value_from_meta(meta, Some(root_attr_path), attr_path, allowed_path_names.clone())? {
Some(v) => return Ok(Some(v)),
None => continue,
}
}
Ok(None)
}
}

0 comments on commit e62ea0b

Please sign in to comment.