generated from phundrak/rust-poem-openapi-template
feat: rename crates from gejdr-crud to georm
This commit renames gejdr-crud to georm for an easier name to remember in case I publish `georm` and `georm-macros`. This commit extracts the `Georm` (formerly `Crud`) trait from `gejdr-core` to its dedicated crate `georm` on which gejdr-core now depends. Currently writing tests
This commit is contained in:
parent
915bd8387e
commit
857b1d98d0
31
Cargo.lock
generated
31
Cargo.lock
generated
@ -965,7 +965,7 @@ name = "gejdr-core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"gejdr-macros",
|
||||
"georm",
|
||||
"serde",
|
||||
"sqlx",
|
||||
"tracing",
|
||||
@ -973,17 +973,6 @@ dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gejdr-macros"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"deluxe",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"sqlx",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
@ -994,6 +983,24 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "georm"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"georm-macros",
|
||||
"sqlx",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "georm-macros"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"deluxe",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.15"
|
||||
|
@ -4,6 +4,7 @@ members = [
|
||||
"gejdr-core",
|
||||
"gejdr-bot",
|
||||
"gejdr-backend",
|
||||
"gejdr-macros"
|
||||
"georm-macros",
|
||||
"georm"
|
||||
]
|
||||
resolver = "2"
|
||||
|
@ -9,7 +9,7 @@ serde = "1.0.215"
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = { version = "0.3.18", features = ["fmt", "std", "env-filter", "registry", "json", "tracing-log"] }
|
||||
uuid = { version = "1.11.0", features = ["v4", "serde"] }
|
||||
gejdr-macros = { path = "../gejdr-macros" }
|
||||
georm = { path = "../georm" }
|
||||
|
||||
[dependencies.sqlx]
|
||||
version = "0.8.3"
|
||||
|
@ -0,0 +1,5 @@
|
||||
DROP TABLE IF EXISTS book_genres;
|
||||
DROP TABLE IF EXISTS books;
|
||||
DROP TABLE IF EXISTS genres;
|
||||
DROP TABLE IF EXISTS authors;
|
||||
DROP SCHEMA IF EXISTS tests;
|
@ -0,0 +1,26 @@
|
||||
CREATE SCHEMA IF NOT EXISTS tests;
|
||||
|
||||
CREATE TABLE tests.authors (
|
||||
author_id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(100) NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE tests.books (
|
||||
id SERIAL PRIMARY KEY,
|
||||
title VARCHAR(100) NOT NULL,
|
||||
author_id INT NOT NULL,
|
||||
FOREIGN KEY (author_id) REFERENCES tests.authors(author_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE tests.genres (
|
||||
genre_id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(100) NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE tests.book_genres (
|
||||
book_id INT NOT NULL,
|
||||
genre_id INT NOT NULL,
|
||||
PRIMARY KEY (book_id, genre_id),
|
||||
FOREIGN KEY (book_id) REFERENCES tests.books(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (genre_id) REFERENCES tests.genres(genre_id) ON DELETE CASCADE
|
||||
);
|
@ -1,4 +1,4 @@
|
||||
use super::Crud;
|
||||
use georm::Georm;
|
||||
use sqlx::PgPool;
|
||||
|
||||
type Timestampz = chrono::DateTime<chrono::Utc>;
|
||||
@ -23,10 +23,10 @@ impl RemoteUser {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize, Debug, PartialEq, Eq, Default, Clone, Crud)]
|
||||
#[crud(table = "users")]
|
||||
#[derive(serde::Deserialize, serde::Serialize, Debug, PartialEq, Eq, Default, Clone, Georm)]
|
||||
#[georm(table = "users")]
|
||||
pub struct User {
|
||||
#[crud(id)]
|
||||
#[georm(id)]
|
||||
pub id: String,
|
||||
pub username: String,
|
||||
pub email: Option<String>,
|
||||
|
@ -1,76 +1 @@
|
||||
pub mod accounts;
|
||||
pub use gejdr_macros::Crud;
|
||||
|
||||
pub trait Crud<Id> {
|
||||
/// Find the entiy in the database based on its identifier.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns any error Postgres may have encountered
|
||||
fn find(
|
||||
pool: &sqlx::PgPool,
|
||||
id: &Id,
|
||||
) -> impl std::future::Future<Output = sqlx::Result<Option<Self>>> + Send
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Create the entity in the database.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns any error Postgres may have encountered
|
||||
fn create(
|
||||
&self,
|
||||
pool: &sqlx::PgPool,
|
||||
) -> impl std::future::Future<Output = sqlx::Result<Self>> + Send
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Update an entity with a matching identifier in the database.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns any error Postgres may have encountered
|
||||
fn update(
|
||||
&self,
|
||||
pool: &sqlx::PgPool,
|
||||
) -> impl std::future::Future<Output = sqlx::Result<Self>> + Send
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Update an entity with a matching identifier in the database if
|
||||
/// it exists, create it otherwise.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns any error Postgres may have encountered
|
||||
fn create_or_update(
|
||||
&self,
|
||||
pool: &sqlx::PgPool,
|
||||
) -> impl std::future::Future<Output = sqlx::Result<Self>> + Send
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Delete the entity from the database if it exists.
|
||||
///
|
||||
/// # Returns
|
||||
/// Returns the amount of rows affected by the deletion.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns any error Postgres may have encountered
|
||||
fn delete(
|
||||
&self,
|
||||
pool: &sqlx::PgPool,
|
||||
) -> impl std::future::Future<Output = sqlx::Result<u64>> + Send;
|
||||
|
||||
/// Delete any entity with the identifier `id`.
|
||||
///
|
||||
/// # Returns
|
||||
/// Returns the amount of rows affected by the deletion.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns any error Postgres may have encountered
|
||||
fn delete_by_id(
|
||||
pool: &sqlx::PgPool,
|
||||
id: &Id,
|
||||
) -> impl std::future::Future<Output = sqlx::Result<u64>> + Send;
|
||||
|
||||
/// Returns the identifier of the entity.
|
||||
fn get_id(&self) -> &Id;
|
||||
}
|
||||
|
@ -1,18 +0,0 @@
|
||||
[package]
|
||||
name = "gejdr-macros"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
deluxe = "0.5.0"
|
||||
proc-macro2 = "1.0.93"
|
||||
quote = "1.0.38"
|
||||
syn = "2.0.96"
|
||||
|
||||
[dependencies.sqlx]
|
||||
version = "0.8.3"
|
||||
default-features = false
|
||||
features = ["postgres", "uuid", "chrono", "migrate", "runtime-tokio", "macros"]
|
23
georm-macros/Cargo.toml
Normal file
23
georm-macros/Cargo.toml
Normal file
@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "georm-macros"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["Lucien Cartier-Tilet <lucien@phundrak.com>"]
|
||||
description = "Macros for Georm, the small, opiniated SQLx ORM for PostgreSQL. Not intended to be used directly."
|
||||
homepage = "https://labs.phundrak.com/phundrak/gejdr-rs"
|
||||
repository = "https://labs.phundrak.com/phundrak/gejdr-rs"
|
||||
license = "MIT OR GPL-3.0-or-later"
|
||||
keywords = ["sqlx", "orm", "postgres", "postgresql", "database", "async"]
|
||||
categories = ["database"]
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
deluxe = "0.5.0"
|
||||
proc-macro2 = "1.0.93"
|
||||
quote = "1.0.38"
|
||||
syn = "2.0.96"
|
||||
|
||||
[lints.rust]
|
||||
unsafe_code = "forbid"
|
@ -1,8 +1,9 @@
|
||||
use quote::quote;
|
||||
use std::fmt::{self, Display};
|
||||
|
||||
#[derive(deluxe::ExtractAttributes)]
|
||||
#[deluxe(attributes(crud))]
|
||||
pub struct CrudStructAttributes {
|
||||
#[deluxe(attributes(georm))]
|
||||
pub struct GeormStructAttributes {
|
||||
pub table: String,
|
||||
#[deluxe(default = Vec::new())]
|
||||
pub one_to_many: Vec<O2MRelationship>,
|
||||
@ -44,7 +45,7 @@ pub struct M2MLink {
|
||||
pub to: String,
|
||||
}
|
||||
|
||||
//#[crud(
|
||||
//#[georm(
|
||||
// table = "users",
|
||||
// many_to_many = [
|
||||
// {
|
||||
@ -128,8 +129,8 @@ WHERE local.{} = $1
|
||||
}
|
||||
|
||||
#[derive(deluxe::ExtractAttributes, Clone)]
|
||||
#[deluxe(attributes(crud))]
|
||||
struct CrudFieldAttributes {
|
||||
#[deluxe(attributes(georm))]
|
||||
struct GeormFieldAttributes {
|
||||
#[deluxe(default = false)]
|
||||
pub id: bool,
|
||||
#[deluxe(default = None)]
|
||||
@ -138,7 +139,7 @@ struct CrudFieldAttributes {
|
||||
pub relation: Option<O2ORelationship>,
|
||||
}
|
||||
|
||||
// #[crud(
|
||||
// #[georm(
|
||||
// table = "profileId",
|
||||
// one_to_one = { name = profile, id = "id", entity = Profile, nullable }
|
||||
// )]
|
||||
@ -154,7 +155,7 @@ pub struct O2ORelationship {
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CrudField {
|
||||
pub struct GeormField {
|
||||
pub ident: syn::Ident,
|
||||
pub field: syn::Field,
|
||||
pub ty: syn::Type,
|
||||
@ -163,11 +164,11 @@ pub struct CrudField {
|
||||
pub relation: Option<O2ORelationship>,
|
||||
}
|
||||
|
||||
impl CrudField {
|
||||
impl GeormField {
|
||||
pub fn new(field: &mut syn::Field) -> Self {
|
||||
let ident = field.clone().ident.unwrap();
|
||||
let ty = field.clone().ty;
|
||||
let attrs: CrudFieldAttributes =
|
||||
let attrs: GeormFieldAttributes =
|
||||
deluxe::extract_attributes(field).expect("Could not extract attributes from field");
|
||||
Self {
|
||||
ident: ident.clone(),
|
||||
@ -180,8 +181,20 @@ impl CrudField {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&CrudField> for proc_macro2::TokenStream {
|
||||
fn from(value: &CrudField) -> Self {
|
||||
impl Display for GeormField {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let default_column = self.ident.to_string();
|
||||
if self.column == default_column {
|
||||
write!(f, "{}", self.column)
|
||||
} else {
|
||||
write!(f, "{} as {}", self.column, default_column)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl From<&GeormField> for proc_macro2::TokenStream {
|
||||
fn from(value: &GeormField) -> Self {
|
||||
let Some(relation) = value.relation.clone() else {
|
||||
return quote! {};
|
||||
};
|
@ -1,13 +1,13 @@
|
||||
use ir::CrudField;
|
||||
use ir::GeormField;
|
||||
use quote::quote;
|
||||
|
||||
mod ir;
|
||||
mod relationships;
|
||||
mod trait_implementation;
|
||||
|
||||
fn extract_crud_field_attrs(
|
||||
fn extract_georm_field_attrs(
|
||||
ast: &mut syn::DeriveInput,
|
||||
) -> deluxe::Result<(Vec<CrudField>, CrudField)> {
|
||||
) -> deluxe::Result<(Vec<GeormField>, GeormField)> {
|
||||
let syn::Data::Struct(s) = &mut ast.data else {
|
||||
return Err(syn::Error::new_spanned(
|
||||
ast,
|
||||
@ -18,9 +18,9 @@ fn extract_crud_field_attrs(
|
||||
.fields
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|mut field| CrudField::new(&mut field))
|
||||
.collect::<Vec<CrudField>>();
|
||||
let identifiers: Vec<CrudField> = fields
|
||||
.map(|mut field| GeormField::new(&mut field))
|
||||
.collect::<Vec<GeormField>>();
|
||||
let identifiers: Vec<GeormField> = fields
|
||||
.clone()
|
||||
.into_iter()
|
||||
.filter(|field| field.id)
|
||||
@ -42,13 +42,13 @@ fn extract_crud_field_attrs(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn crud_derive_macro2(
|
||||
pub fn georm_derive_macro2(
|
||||
item: proc_macro2::TokenStream,
|
||||
) -> deluxe::Result<proc_macro2::TokenStream> {
|
||||
let mut ast: syn::DeriveInput = syn::parse2(item).expect("Failed to parse input");
|
||||
let struct_attrs: ir::CrudStructAttributes =
|
||||
let struct_attrs: ir::GeormStructAttributes =
|
||||
deluxe::extract_attributes(&mut ast).expect("Could not extract attributes from struct");
|
||||
let (fields, id) = extract_crud_field_attrs(&mut ast)?;
|
||||
let (fields, id) = extract_georm_field_attrs(&mut ast)?;
|
||||
let trait_impl = trait_implementation::derive_trait(&ast, &struct_attrs.table, &fields, &id);
|
||||
let relationships = relationships::derive_relationships(&ast, &struct_attrs, &fields, &id);
|
||||
let code = quote! {
|
@ -1,8 +1,8 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::crud::ir::M2MRelationshipComplete;
|
||||
use crate::georm::ir::M2MRelationshipComplete;
|
||||
|
||||
use super::ir::CrudField;
|
||||
use super::ir::GeormField;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
@ -30,9 +30,9 @@ where
|
||||
|
||||
pub fn derive_relationships(
|
||||
ast: &syn::DeriveInput,
|
||||
struct_attrs: &super::ir::CrudStructAttributes,
|
||||
fields: &[CrudField],
|
||||
id: &CrudField,
|
||||
struct_attrs: &super::ir::GeormStructAttributes,
|
||||
fields: &[GeormField],
|
||||
id: &GeormField,
|
||||
) -> TokenStream {
|
||||
let struct_name = &ast.ident;
|
||||
let one_to_one = derive(fields, |field| field.relation.is_none());
|
@ -1,8 +1,25 @@
|
||||
use super::ir::CrudField;
|
||||
use super::ir::GeormField;
|
||||
use quote::quote;
|
||||
|
||||
fn generate_find_query(table: &str, id: &CrudField) -> proc_macro2::TokenStream {
|
||||
let find_string = format!("SELECT * FROM {} WHERE {} = $1", table, id.column);
|
||||
fn aliased_columns(fields: &[GeormField]) -> String {
|
||||
fields.iter().map(std::string::ToString::to_string).collect::<Vec<String>>().join(", ")
|
||||
}
|
||||
|
||||
|
||||
fn generate_find_query(
|
||||
table: &str,
|
||||
id: &GeormField,
|
||||
fields: &[GeormField],
|
||||
) -> proc_macro2::TokenStream {
|
||||
let select_columns = fields
|
||||
.iter()
|
||||
.map(std::string::ToString::to_string)
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
let find_string = format!(
|
||||
"SELECT {select_columns} FROM {table} WHERE {} = $1",
|
||||
id.column
|
||||
);
|
||||
let ty = &id.ty;
|
||||
quote! {
|
||||
async fn find(pool: &::sqlx::PgPool, id: &#ty) -> ::sqlx::Result<Option<Self>> {
|
||||
@ -13,11 +30,11 @@ fn generate_find_query(table: &str, id: &CrudField) -> proc_macro2::TokenStream
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_create_query(table: &str, fields: &[CrudField]) -> proc_macro2::TokenStream {
|
||||
fn generate_create_query(table: &str, fields: &[GeormField]) -> proc_macro2::TokenStream {
|
||||
let inputs: Vec<String> = (1..=fields.len()).map(|num| format!("${num}")).collect();
|
||||
let return_columns = aliased_columns(fields);
|
||||
let create_string = format!(
|
||||
"INSERT INTO {} ({}) VALUES ({}) RETURNING *",
|
||||
table,
|
||||
"INSERT INTO {table} ({}) VALUES ({}) RETURNING {return_columns}",
|
||||
fields
|
||||
.iter()
|
||||
.map(|v| v.column.clone())
|
||||
@ -41,10 +58,11 @@ fn generate_create_query(table: &str, fields: &[CrudField]) -> proc_macro2::Toke
|
||||
|
||||
fn generate_update_query(
|
||||
table: &str,
|
||||
fields: &[CrudField],
|
||||
id: &CrudField,
|
||||
fields: &[GeormField],
|
||||
id: &GeormField,
|
||||
) -> proc_macro2::TokenStream {
|
||||
let mut fields: Vec<&CrudField> = fields.iter().filter(|f| !f.id).collect();
|
||||
let return_columns = aliased_columns(fields);
|
||||
let mut fields: Vec<&GeormField> = fields.iter().filter(|f| !f.id).collect();
|
||||
let update_columns = fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
@ -52,9 +70,7 @@ fn generate_update_query(
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
let update_string = format!(
|
||||
"UPDATE {} SET {} WHERE {} = ${} RETURNING *",
|
||||
table,
|
||||
update_columns,
|
||||
"UPDATE {table} SET {update_columns} WHERE {} = ${} RETURNING {return_columns}",
|
||||
id.column,
|
||||
fields.len() + 1
|
||||
);
|
||||
@ -73,7 +89,7 @@ fn generate_update_query(
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_delete_query(table: &str, id: &CrudField) -> proc_macro2::TokenStream {
|
||||
fn generate_delete_query(table: &str, id: &GeormField) -> proc_macro2::TokenStream {
|
||||
let delete_string = format!("DELETE FROM {} WHERE {} = $1", table, id.column);
|
||||
let ty = &id.ty;
|
||||
|
||||
@ -87,16 +103,12 @@ fn generate_delete_query(table: &str, id: &CrudField) -> proc_macro2::TokenStrea
|
||||
}
|
||||
|
||||
async fn delete(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<u64> {
|
||||
let rows_affected = ::sqlx::query!(#delete_string, self.get_id())
|
||||
.execute(pool)
|
||||
.await?
|
||||
.rows_affected();
|
||||
Ok(rows_affected)
|
||||
Self::delete_by_id(pool, self.get_id()).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_get_id(id: &CrudField) -> proc_macro2::TokenStream {
|
||||
fn generate_get_id(id: &GeormField) -> proc_macro2::TokenStream {
|
||||
let ident = &id.ident;
|
||||
let ty = &id.ty;
|
||||
quote! {
|
||||
@ -109,8 +121,8 @@ fn generate_get_id(id: &CrudField) -> proc_macro2::TokenStream {
|
||||
pub fn derive_trait(
|
||||
ast: &syn::DeriveInput,
|
||||
table: &str,
|
||||
fields: &[CrudField],
|
||||
id: &CrudField,
|
||||
fields: &[GeormField],
|
||||
id: &GeormField,
|
||||
) -> proc_macro2::TokenStream {
|
||||
let ty = &id.ty;
|
||||
let id_ident = &id.ident;
|
||||
@ -121,18 +133,15 @@ pub fn derive_trait(
|
||||
|
||||
// generate
|
||||
let get_id = generate_get_id(id);
|
||||
let find_query = generate_find_query(table, id);
|
||||
let find_query = generate_find_query(table, id, fields);
|
||||
let create_query = generate_create_query(table, fields);
|
||||
let update_query = generate_update_query(table, fields, id);
|
||||
let delete_query = generate_delete_query(table, id);
|
||||
quote! {
|
||||
impl #impl_generics Crud<#ty> for #ident #type_generics #where_clause {
|
||||
impl #impl_generics Georm<#ty> for #ident #type_generics #where_clause {
|
||||
#get_id
|
||||
|
||||
#find_query
|
||||
|
||||
#create_query
|
||||
|
||||
#update_query
|
||||
|
||||
async fn create_or_update(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<Self> {
|
@ -5,9 +5,9 @@
|
||||
#![allow(clippy::unused_async)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
//! Create ``SQLx`` CRUD code for a struct in Postgres.
|
||||
//! Creates ORM functionality for ``SQLx`` with `PostgreSQL`.
|
||||
//!
|
||||
//! This crate provides the trait implementation `Crud` which
|
||||
//! This crate provides the trait implementation `Georm` which
|
||||
//! generates the following ``SQLx`` queries:
|
||||
//! - find an entity by id
|
||||
//!
|
||||
@ -25,7 +25,7 @@
|
||||
//! - update an entity or create it if it does not already exist in
|
||||
//! the database
|
||||
//!
|
||||
//! This macro relies on the trait `Crud` found in the `gejdr-core`
|
||||
//! This macro relies on the trait `Georm` found in the `gejdr-core`
|
||||
//! crate.
|
||||
//!
|
||||
//! To use this macro, you need to add it to the derives of the
|
||||
@ -33,25 +33,25 @@
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! Add `#[crud(table = "my_table_name")]` atop of the structure,
|
||||
//! after the `Crud` derive.
|
||||
//! Add `#[georm(table = "my_table_name")]` atop of the structure,
|
||||
//! after the `Georm` derive.
|
||||
//!
|
||||
//! ## Entity Identifier
|
||||
//! You will also need to add `#[crud(id)]` atop of the field of your
|
||||
//! You will also need to add `#[georm(id)]` atop of the field of your
|
||||
//! struct that will be used as the identifier of your entity.
|
||||
//!
|
||||
//! ## Column Name
|
||||
//! If the name of a field does not match the name of its related
|
||||
//! column, you can use `#[crud(column = "...")]` to specify the
|
||||
//! column, you can use `#[georm(column = "...")]` to specify the
|
||||
//! correct value.
|
||||
//!
|
||||
//! ```ignore
|
||||
//! #[derive(Crud)]
|
||||
//! #[crud(table = "users")]
|
||||
//! #[derive(Georm)]
|
||||
//! #[georm(table = "users")]
|
||||
//! pub struct User {
|
||||
//! #[crud(id)]
|
||||
//! #[georm(id)]
|
||||
//! id: String,
|
||||
//! #[crud(column = "name")]
|
||||
//! #[georm(column = "name")]
|
||||
//! username: String,
|
||||
//! created_at: Timestampz,
|
||||
//! last_updated: Timestampz,
|
||||
@ -65,7 +65,7 @@
|
||||
//! # Limitations
|
||||
//! ## ID
|
||||
//! For now, only one identifier is supported. It does not have to be
|
||||
//! a primary key, but it is strongly encouraged to use GeJDR’s Crud
|
||||
//! a primary key, but it is strongly encouraged to use GeJDR’s Georm
|
||||
//! ID on a unique and non-null column of your database schema.
|
||||
//!
|
||||
//! ## Database type
|
||||
@ -75,15 +75,15 @@
|
||||
//! Otherwise, pull requests to add additional syntaxes are most
|
||||
//! welcome.
|
||||
|
||||
mod crud;
|
||||
use crud::crud_derive_macro2;
|
||||
mod georm;
|
||||
use georm::georm_derive_macro2;
|
||||
|
||||
/// Generates CRUD code for Sqlx for a struct.
|
||||
/// Generates GEORM code for Sqlx for a struct.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// May panic if errors arise while parsing and generating code.
|
||||
#[proc_macro_derive(Crud, attributes(crud))]
|
||||
pub fn crud_derive_macro(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
crud_derive_macro2(item.into()).unwrap().into()
|
||||
#[proc_macro_derive(Georm, attributes(georm))]
|
||||
pub fn georm_derive_macro(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
georm_derive_macro2(item.into()).unwrap().into()
|
||||
}
|
22
georm/Cargo.toml
Normal file
22
georm/Cargo.toml
Normal file
@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "georm"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["Lucien Cartier-Tilet <lucien@phundrak.com>"]
|
||||
description = "A small, opiniated ORM for SQLx and PostgreSQL"
|
||||
homepage = "https://labs.phundrak.com/phundrak/gejdr-rs"
|
||||
repository = "https://labs.phundrak.com/phundrak/gejdr-rs"
|
||||
license = "MIT OR GPL-3.0-or-later"
|
||||
keywords = ["sqlx", "orm", "postgres", "postgresql", "database", "async"]
|
||||
categories = ["database"]
|
||||
|
||||
[dependencies]
|
||||
georm-macros = { path = "../georm-macros" }
|
||||
|
||||
[dependencies.sqlx]
|
||||
version = "0.8.3"
|
||||
default-features = false
|
||||
features = ["postgres", "runtime-tokio", "macros", "migrate"]
|
||||
|
||||
[lints.rust]
|
||||
unsafe_code = "forbid"
|
1
georm/README.md
Normal file
1
georm/README.md
Normal file
@ -0,0 +1 @@
|
||||
# A small, opiniated ORM for SQLx with PostgreSQL
|
82
georm/src/lib.rs
Normal file
82
georm/src/lib.rs
Normal file
@ -0,0 +1,82 @@
|
||||
#![deny(clippy::all)]
|
||||
#![deny(clippy::pedantic)]
|
||||
#![deny(clippy::nursery)]
|
||||
#![allow(clippy::module_name_repetitions)]
|
||||
#![allow(clippy::unused_async)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
pub use georm_macros::Georm;
|
||||
|
||||
pub trait Georm<Id> {
|
||||
/// Find the entiy in the database based on its identifier.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns any error Postgres may have encountered
|
||||
fn find(
|
||||
pool: &sqlx::PgPool,
|
||||
id: &Id,
|
||||
) -> impl std::future::Future<Output = sqlx::Result<Option<Self>>> + Send
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Create the entity in the database.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns any error Postgres may have encountered
|
||||
fn create(
|
||||
&self,
|
||||
pool: &sqlx::PgPool,
|
||||
) -> impl std::future::Future<Output = sqlx::Result<Self>> + Send
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Update an entity with a matching identifier in the database.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns any error Postgres may have encountered
|
||||
fn update(
|
||||
&self,
|
||||
pool: &sqlx::PgPool,
|
||||
) -> impl std::future::Future<Output = sqlx::Result<Self>> + Send
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Update an entity with a matching identifier in the database if
|
||||
/// it exists, create it otherwise.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns any error Postgres may have encountered
|
||||
fn create_or_update(
|
||||
&self,
|
||||
pool: &sqlx::PgPool,
|
||||
) -> impl std::future::Future<Output = sqlx::Result<Self>> + Send
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Delete the entity from the database if it exists.
|
||||
///
|
||||
/// # Returns
|
||||
/// Returns the amount of rows affected by the deletion.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns any error Postgres may have encountered
|
||||
fn delete(
|
||||
&self,
|
||||
pool: &sqlx::PgPool,
|
||||
) -> impl std::future::Future<Output = sqlx::Result<u64>> + Send;
|
||||
|
||||
/// Delete any entity with the identifier `id`.
|
||||
///
|
||||
/// # Returns
|
||||
/// Returns the amount of rows affected by the deletion.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns any error Postgres may have encountered
|
||||
fn delete_by_id(
|
||||
pool: &sqlx::PgPool,
|
||||
id: &Id,
|
||||
) -> impl std::future::Future<Output = sqlx::Result<u64>> + Send;
|
||||
|
||||
/// Returns the identifier of the entity.
|
||||
fn get_id(&self) -> &Id;
|
||||
}
|
0
georm/tests/fixtures/simple_struct.sql
vendored
Normal file
0
georm/tests/fixtures/simple_struct.sql
vendored
Normal file
9
georm/tests/simple_struct.rs
Normal file
9
georm/tests/simple_struct.rs
Normal file
@ -0,0 +1,9 @@
|
||||
use georm::Georm;
|
||||
|
||||
#[derive(Debug, Georm)]
|
||||
#[georm(table = "tests.authors")]
|
||||
struct Author {
|
||||
#[georm(column = "author_id", id)]
|
||||
id: i32,
|
||||
name: String
|
||||
}
|
Loading…
Reference in New Issue
Block a user