Skip to main content

dynamodb_facade/
test_fixtures.rs

1//! Shared domain types for doc examples and integration tests.
2//!
3//! This module defines an **Online Learning Platform** stored in a single
4//! DynamoDB table (mono-table pattern). It provides concrete attribute
5//! definitions, table/index definitions, and item types that are referenced
6//! throughout the crate's documentation examples.
7//!
8//! # Domain overview
9//!
10//! All entities live in `PlatformTable` (composite key `PK` + `SK`):
11//!
12//! | Entity | PK | SK |
13//! |---|---|---|
14//! | [`PlatformConfig`] | `"PLATFORM_CONFIG"` | `"PLATFORM_CONFIG"` |
15//! | [`User`] | `"USER#<uuid>"` | `"USER"` |
16//! | [`Enrollment`] | `"USER#<uuid>"` | `"ENROLL#<uuid>"` |
17//!
18//! GSIs:
19//! - [`TypeIndex`] — partition key `_TYPE`; query all items of a given type
20//! - [`EmailIndex`] — partition key `email`; look up any item by email
21
22use serde::{Deserialize, Serialize};
23
24use super::{Item, KeyId, NumberAttribute, StringAttribute};
25
26// ---------------------------------------------------------------------------
27// Attribute definitions
28// ---------------------------------------------------------------------------
29
30crate::attribute_definitions! {
31    /// Partition key for the platform mono-table.
32    PK { "PK": StringAttribute }
33
34    /// Sort key for the platform mono-table.
35    SK { "SK": StringAttribute }
36
37    /// Item type discriminator (single-table design).
38    ItemType { "_TYPE": StringAttribute }
39
40    /// TTL attribute for expiring items.
41    Expiration { "expiration_timestamp": NumberAttribute }
42
43    /// Email attribute, used as a GSI partition key.
44    Email { "email": StringAttribute }
45}
46
47// ---------------------------------------------------------------------------
48// Table definition
49// ---------------------------------------------------------------------------
50
51crate::table_definitions! {
52    /// The platform mono-table with composite key (PK + SK).
53    PlatformTable {
54        type PartitionKey = PK;
55        type SortKey = SK;
56        fn table_name() -> String {
57            std::env::var("TABLE_NAME").unwrap_or_else(|_| "platform".to_owned())
58        }
59    }
60}
61
62// ---------------------------------------------------------------------------
63// Index definitions
64// ---------------------------------------------------------------------------
65
66crate::index_definitions! {
67    /// GSI on item type — query all items of a given type.
68    #[table = PlatformTable]
69    TypeIndex {
70        type PartitionKey = ItemType;
71        fn index_name() -> String { "iType".to_owned() }
72    }
73
74    /// GSI on email — look up any item by email address.
75    #[table = PlatformTable]
76    EmailIndex {
77        type PartitionKey = Email;
78        fn index_name() -> String { "iEmail".to_owned() }
79    }
80}
81
82// ---------------------------------------------------------------------------
83// Item types
84// ---------------------------------------------------------------------------
85
86/// Platform-wide configuration (singleton item).
87///
88/// Stored at `PK = "PLATFORM_CONFIG"`, `SK = "PLATFORM_CONFIG"`.
89/// Accessed via `PlatformConfig::get(client, KeyId::NONE)`.
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct PlatformConfig {
92    pub max_enrollments: u32,
93    pub maintenance_mode: bool,
94}
95
96crate::dynamodb_item! {
97    #[table = PlatformTable]
98    PlatformConfig {
99        #[partition_key]
100        PK { const VALUE: &'static str = "PLATFORM_CONFIG"; }
101        #[sort_key]
102        SK { const VALUE: &'static str = "PLATFORM_CONFIG"; }
103        ItemType { const VALUE: &'static str = "PLATFORM_CONFIG"; }
104    }
105}
106
107/// A registered user on the platform.
108///
109/// Stored at `PK = "USER#<id>"`, `SK = "USER"`.
110/// Accessed via `User::get(client, KeyId::pk(user_id))`.
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct User {
113    pub id: String,
114    pub name: String,
115    pub email: String,
116    pub role: String,
117}
118
119crate::dynamodb_item! {
120    #[table = PlatformTable]
121    User {
122        #[partition_key]
123        PK {
124            fn attribute_id(&self) -> &'id str { &self.id }
125            fn attribute_value(id) -> String { format!("USER#{id}") }
126        }
127        #[sort_key]
128        SK { const VALUE: &'static str = "USER"; }
129        ItemType { const VALUE: &'static str = "USER"; }
130        #[marker_only]
131        Email {
132            fn attribute_id(&self) -> &'id str { &self.email }
133            fn attribute_value(id) -> String { id.to_owned() }
134        }
135    }
136}
137
138/// A user's enrollment in a course.
139///
140/// Stored at `PK = "USER#<user_id>"`, `SK = "ENROLL#<course_id>"`.
141/// Accessed via `Enrollment::get(client, KeyId::pk(user_id).sk(course_id))`.
142#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct Enrollment {
144    pub user_id: String,
145    pub course_id: String,
146    pub enrolled_at: u64,
147    pub progress: f64,
148}
149
150crate::dynamodb_item! {
151    #[table = PlatformTable]
152    Enrollment {
153        #[partition_key]
154        PK {
155            fn attribute_id(&self) -> &'id str { &self.user_id }
156            fn attribute_value(id) -> String { format!("USER#{id}") }
157        }
158        #[sort_key]
159        SK {
160            fn attribute_id(&self) -> &'id str { &self.course_id }
161            fn attribute_value(id) -> String { format!("ENROLL#{id}") }
162        }
163        ItemType { const VALUE: &'static str = "ENROLLMENT"; }
164    }
165}
166
167// ---------------------------------------------------------------------------
168// Helper constructors for doc examples
169// ---------------------------------------------------------------------------
170
171/// Returns a sample [`PlatformConfig`] for use in doc examples.
172pub fn sample_config() -> PlatformConfig {
173    PlatformConfig {
174        max_enrollments: 10,
175        maintenance_mode: false,
176    }
177}
178
179/// Returns a sample [`User`] for use in doc examples.
180pub fn sample_user() -> User {
181    User {
182        id: "user-1".to_owned(),
183        name: "Alice".to_owned(),
184        email: "alice@example.com".to_owned(),
185        role: "student".to_owned(),
186    }
187}
188
189/// Returns a sample [`Enrollment`] for use in doc examples.
190pub fn sample_enrollment() -> Enrollment {
191    Enrollment {
192        user_id: "user-1".to_owned(),
193        course_id: "course-42".to_owned(),
194        enrolled_at: 1_700_000_000,
195        progress: 0.0,
196    }
197}
198
199/// Returns a [`KeyId`] for the sample user.
200pub fn sample_user_key_id() -> KeyId<&'static str, super::NoId> {
201    KeyId::pk("user-1")
202}
203
204/// Returns a [`KeyId`] for the sample enrollment.
205pub fn sample_enrollment_key_id() -> KeyId<&'static str, &'static str> {
206    KeyId::pk("user-1").sk("course-42")
207}
208
209/// Returns a sample [`Item<PlatformTable>`] representing the sample user.
210pub fn sample_user_item() -> Item<PlatformTable> {
211    use super::DynamoDBItem;
212    sample_user().to_item()
213}