196 lines
4.9 KiB
Markdown
196 lines
4.9 KiB
Markdown
# 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
|
|
|
|
```rust
|
|
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<T>
|
|
```rust
|
|
#[index]
|
|
pub title: Option<String>,
|
|
```
|
|
Generates: `IndexKey { name: "title", value: <string_value> }` (only if Some)
|
|
|
|
### BTreeMap<String, String>
|
|
```rust
|
|
#[index]
|
|
pub tags: BTreeMap<String, String>,
|
|
```
|
|
Generates: `IndexKey { name: "tags:tag", value: "key=value" }` for each entry
|
|
|
|
### Vec<T>
|
|
```rust
|
|
#[index]
|
|
pub items: Vec<String>,
|
|
```
|
|
Generates: `IndexKey { name: "items:item", value: "0:value" }` for each item
|
|
|
|
### OffsetDateTime
|
|
```rust
|
|
#[index]
|
|
pub start_time: OffsetDateTime,
|
|
```
|
|
Generates: `IndexKey { name: "start_time", value: "2025-10-20" }` (date only)
|
|
|
|
### Enums and Other Types
|
|
```rust
|
|
#[index]
|
|
pub status: EventStatus,
|
|
```
|
|
Generates: `IndexKey { name: "status", value: "Debug(status)" }` (using Debug format)
|
|
|
|
## Complete Example
|
|
|
|
```rust
|
|
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:
|
|
```rust
|
|
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
|
|
|
|
```rust
|
|
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")]`)
|