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}