Files
osiris/docs/DERIVE_MACRO.md
Timur Gordon 097360ad12 first commit
2025-10-20 22:24:25 +02:00

4.9 KiB

OSIRIS Derive Macro

The #[derive(DeriveObject)] macro automatically implements the Object trait for your structs, generating index keys based on fields marked with #[index].

Usage

use osiris::{BaseData, DeriveObject};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;

#[derive(Debug, Clone, Serialize, Deserialize, DeriveObject)]
pub struct Note {
    pub base_data: BaseData,
    
    #[index]
    pub title: Option<String>,
    
    pub content: Option<String>,
    
    #[index]
    pub tags: BTreeMap<String, String>,
}

What Gets Generated

The derive macro automatically implements:

  1. object_type() - Returns the struct name as a string
  2. base_data() - Returns a reference to base_data
  3. base_data_mut() - Returns a mutable reference to base_data
  4. index_keys() - Generates index keys for all #[index] fields
  5. indexed_fields() - Returns a list of indexed field names

Supported Field Types

Option

#[index]
pub title: Option<String>,

Generates: IndexKey { name: "title", value: <string_value> } (only if Some)

BTreeMap<String, String>

#[index]
pub tags: BTreeMap<String, String>,

Generates: IndexKey { name: "tags:tag", value: "key=value" } for each entry

Vec

#[index]
pub items: Vec<String>,

Generates: IndexKey { name: "items:item", value: "0:value" } for each item

OffsetDateTime

#[index]
pub start_time: OffsetDateTime,

Generates: IndexKey { name: "start_time", value: "2025-10-20" } (date only)

Enums and Other Types

#[index]
pub status: EventStatus,

Generates: IndexKey { name: "status", value: "Debug(status)" } (using Debug format)

Complete Example

use osiris::{BaseData, DeriveObject};
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum EventStatus {
    Draft,
    Published,
    Cancelled,
}

#[derive(Debug, Clone, Serialize, Deserialize, DeriveObject)]
pub struct Event {
    pub base_data: BaseData,
    
    #[index]
    pub title: String,
    
    pub description: Option<String>,
    
    #[index]
    #[serde(with = "time::serde::timestamp")]
    pub start_time: OffsetDateTime,
    
    #[index]
    pub location: Option<String>,
    
    #[index]
    pub status: EventStatus,
    
    pub all_day: bool,
    
    #[index]
    pub category: Option<String>,
}

impl Event {
    pub fn new(ns: String, title: impl ToString) -> Self {
        let now = OffsetDateTime::now_utc();
        Self {
            base_data: BaseData::new(ns),
            title: title.to_string(),
            description: None,
            start_time: now,
            location: None,
            status: EventStatus::Draft,
            all_day: false,
            category: None,
        }
    }
}

Generated Index Keys

For the Event example above with:

  • title = "Team Meeting"
  • start_time = 2025-10-20T10:00:00Z
  • location = Some("Room 101")
  • status = EventStatus::Published
  • category = Some("work")

The generated index keys would be:

vec![
    IndexKey { name: "mime", value: "application/json" }, // from base_data
    IndexKey { name: "title", value: "Team Meeting" },
    IndexKey { name: "start_time", value: "2025-10-20" },
    IndexKey { name: "location", value: "Room 101" },
    IndexKey { name: "status", value: "Published" },
    IndexKey { name: "category", value: "work" },
]

HeroDB Storage

These index keys are stored in HeroDB as:

idx:events:title:Team Meeting          → {event_id}
idx:events:start_time:2025-10-20       → {event_id}
idx:events:location:Room 101           → {event_id}
idx:events:status:Published            → {event_id}
idx:events:category:work               → {event_id}

Querying by Index

use osiris::store::GenericStore;

let store = GenericStore::new(client);

// Get all events on a specific date
let ids = store.get_ids_by_index("events", "start_time", "2025-10-20").await?;

// Get all published events
let ids = store.get_ids_by_index("events", "status", "Published").await?;

// Get all events in a category
let ids = store.get_ids_by_index("events", "category", "work").await?;

Requirements

  1. Must have base_data field: The struct must have a field named base_data of type BaseData
  2. Must derive standard traits: Debug, Clone, Serialize, Deserialize
  3. Fields marked with #[index]: Only fields with the #[index] attribute will be indexed

Limitations

  • The macro currently uses Debug formatting for enums and complex types
  • BTreeMap indexing assumes String keys and values
  • Vec indexing uses numeric indices (may not be ideal for all use cases)

Future Enhancements

  • Custom index key formatters via attributes
  • Support for nested struct indexing
  • Conditional indexing (e.g., #[index(if = "is_published")])
  • Custom index names (e.g., #[index(name = "custom_name")])