first commit

This commit is contained in:
Timur Gordon
2025-10-20 22:24:25 +02:00
commit 097360ad12
48 changed files with 6712 additions and 0 deletions

12
osiris_derive/Cargo.toml Normal file
View File

@@ -0,0 +1,12 @@
[package]
name = "osiris_derive"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true
[dependencies]
syn = { version = "2.0", features = ["full", "extra-traits"] }
quote = "1.0"
proc-macro2 = "1.0"

202
osiris_derive/src/lib.rs Normal file
View File

@@ -0,0 +1,202 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Fields, Type};
/// Derive macro for the Object trait
///
/// Automatically implements `index_keys()` and `indexed_fields()` based on fields marked with #[index]
///
/// # Example
///
/// ```rust
/// #[derive(Object)]
/// pub struct Note {
/// pub base_data: BaseData,
///
/// #[index]
/// pub title: Option<String>,
///
/// pub content: Option<String>,
///
/// #[index]
/// pub tags: BTreeMap<String, String>,
/// }
/// ```
#[proc_macro_derive(Object, attributes(index))]
pub fn derive_object(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let generics = &input.generics;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
// Extract fields with #[index] attribute
let indexed_fields = match &input.data {
Data::Struct(data) => match &data.fields {
Fields::Named(fields) => {
fields.named.iter().filter_map(|field| {
let has_index = field.attrs.iter().any(|attr| {
attr.path().is_ident("index")
});
if has_index {
let field_name = field.ident.as_ref()?;
let field_type = &field.ty;
Some((field_name.clone(), field_type.clone()))
} else {
None
}
}).collect::<Vec<_>>()
}
_ => vec![],
},
_ => vec![],
};
// Generate index_keys() implementation
let index_keys_impl = generate_index_keys(&indexed_fields);
// Generate indexed_fields() implementation
let field_names: Vec<_> = indexed_fields.iter()
.map(|(name, _)| name.to_string())
.collect();
// Always use ::osiris for external usage
// When used inside the osiris crate's src/, the compiler will resolve it correctly
let crate_path = quote! { ::osiris };
let expanded = quote! {
impl #impl_generics #crate_path::Object for #name #ty_generics #where_clause {
fn object_type() -> &'static str {
stringify!(#name)
}
fn base_data(&self) -> &#crate_path::BaseData {
&self.base_data
}
fn base_data_mut(&mut self) -> &mut #crate_path::BaseData {
&mut self.base_data
}
fn index_keys(&self) -> Vec<#crate_path::IndexKey> {
let mut keys = Vec::new();
// Index from base_data
if let Some(mime) = &self.base_data.mime {
keys.push(#crate_path::IndexKey::new("mime", mime));
}
#index_keys_impl
keys
}
fn indexed_fields() -> Vec<&'static str> {
vec![#(#field_names),*]
}
}
};
TokenStream::from(expanded)
}
fn generate_index_keys(fields: &[(syn::Ident, Type)]) -> proc_macro2::TokenStream {
let mut implementations = Vec::new();
// Always use ::osiris
let crate_path = quote! { ::osiris };
for (field_name, field_type) in fields {
let field_name_str = field_name.to_string();
// Check if it's an Option type
if is_option_type(field_type) {
implementations.push(quote! {
if let Some(value) = &self.#field_name {
keys.push(#crate_path::IndexKey::new(#field_name_str, value));
}
});
}
// Check if it's a BTreeMap (for tags)
else if is_btreemap_type(field_type) {
implementations.push(quote! {
for (key, value) in &self.#field_name {
keys.push(#crate_path::IndexKey {
name: concat!(#field_name_str, ":tag"),
value: format!("{}={}", key, value),
});
}
});
}
// Check if it's a Vec
else if is_vec_type(field_type) {
implementations.push(quote! {
for (idx, value) in self.#field_name.iter().enumerate() {
keys.push(#crate_path::IndexKey {
name: concat!(#field_name_str, ":item"),
value: format!("{}:{}", idx, value),
});
}
});
}
// For OffsetDateTime, index as date string
else if is_offsetdatetime_type(field_type) {
implementations.push(quote! {
{
let date_str = self.#field_name.date().to_string();
keys.push(#crate_path::IndexKey::new(#field_name_str, date_str));
}
});
}
// For enums or other types, convert to string
else {
implementations.push(quote! {
{
let value_str = format!("{:?}", &self.#field_name);
keys.push(#crate_path::IndexKey::new(#field_name_str, value_str));
}
});
}
}
quote! {
#(#implementations)*
}
}
fn is_option_type(ty: &Type) -> bool {
if let Type::Path(type_path) = ty {
if let Some(segment) = type_path.path.segments.last() {
return segment.ident == "Option";
}
}
false
}
fn is_btreemap_type(ty: &Type) -> bool {
if let Type::Path(type_path) = ty {
if let Some(segment) = type_path.path.segments.last() {
return segment.ident == "BTreeMap";
}
}
false
}
fn is_vec_type(ty: &Type) -> bool {
if let Type::Path(type_path) = ty {
if let Some(segment) = type_path.path.segments.last() {
return segment.ident == "Vec";
}
}
false
}
fn is_offsetdatetime_type(ty: &Type) -> bool {
if let Type::Path(type_path) = ty {
if let Some(segment) = type_path.path.segments.last() {
return segment.ident == "OffsetDateTime";
}
}
false
}