headless_lms_models/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
/*!
Functions and structs for interacting with the database.
Each submodule corresponds to a database table.
*/
// we always use --document-private-items, so this warning is moot
#![allow(rustdoc::private_intra_doc_links)]
pub mod certificate_configuration_to_requirements;
pub mod certificate_configurations;
pub mod certificate_fonts;
pub mod chapters;
pub mod chatbot_configurations;
pub mod chatbot_conversation_messages;
pub mod chatbot_conversations;
pub mod chatbot_page_sync_statuses;
pub mod code_giveaway_codes;
pub mod code_giveaways;
pub mod course_background_question_answers;
pub mod course_background_questions;
pub mod course_custom_privacy_policy_checkbox_texts;
pub mod course_exams;
pub mod course_instance_enrollments;
pub mod course_instances;
pub mod course_language_groups;
pub mod course_module_completion_registered_to_study_registries;
pub mod course_module_completions;
pub mod course_modules;
pub mod courses;
pub mod email_deliveries;
pub mod email_templates;
pub mod ended_processed_exams;
pub mod exams;
pub mod exercise_language_groups;
pub mod exercise_repositories;
pub mod exercise_reset_logs;
pub mod exercise_service_info;
pub mod exercise_services;
pub mod exercise_slide_submissions;
pub mod exercise_slides;
pub mod exercise_task_gradings;
pub mod exercise_task_regrading_submissions;
pub mod exercise_task_submissions;
pub mod exercise_tasks;
pub mod exercises;
pub mod feedback;
pub mod file_uploads;
pub mod flagged_answers;
pub mod generated_certificates;
pub mod glossary;
pub mod join_code_uses;
pub mod library;
pub mod marketing_consents;
pub mod material_references;
pub mod offered_answers_to_peer_review_temporary;
pub mod open_university_registration_links;
pub mod organizations;
pub mod other_domain_to_course_redirections;
pub mod page_audio_files;
pub mod page_history;
pub mod page_language_groups;
pub mod page_visit_datum;
pub mod page_visit_datum_daily_visit_hashing_keys;
pub mod page_visit_datum_summary_by_courses;
pub mod page_visit_datum_summary_by_courses_countries;
pub mod page_visit_datum_summary_by_courses_device_types;
pub mod page_visit_datum_summary_by_pages;
pub mod pages;
pub mod partner_block;
pub mod peer_or_self_review_configs;
pub mod peer_or_self_review_question_submissions;
pub mod peer_or_self_review_questions;
pub mod peer_or_self_review_submissions;
pub mod peer_review_queue_entries;
pub mod pending_roles;
pub mod playground_examples;
pub mod privacy_link;
pub mod proposed_block_edits;
pub mod proposed_page_edits;
pub mod regradings;
pub mod rejected_exercise_slide_submissions;
pub mod repository_exercises;
pub mod research_forms;
pub mod roles;
pub mod student_countries;
pub mod study_registry_registrars;
pub mod suspected_cheaters;
pub mod teacher_grading_decisions;
pub mod url_redirections;
pub mod user_course_instance_exercise_service_variables;
pub mod user_course_settings;
pub mod user_details;
pub mod user_exercise_slide_states;
pub mod user_exercise_states;
pub mod user_exercise_task_states;
pub mod user_research_consents;
pub mod users;
pub mod error;
pub mod prelude;
#[cfg(test)]
pub mod test_helper;
use futures::future::BoxFuture;
use url::Url;
use uuid::Uuid;
pub use self::error::{ModelError, ModelErrorType, ModelResult};
use crate::prelude::*;
#[macro_use]
extern crate tracing;
/**
Helper struct to use with functions that insert data into the database.
## Examples
### Usage when inserting to a database
By calling `.into_uuid()` function implemented by `PKeyPolicy<Uuid>`, this enum can be used with
SQLX queries while letting the caller dictate how the primary key should be decided.
```no_run
# use headless_lms_models::{ModelResult, PKeyPolicy};
# use uuid::Uuid;
# use sqlx::PgConnection;
async fn insert(
conn: &mut PgConnection,
pkey_policy: PKeyPolicy<Uuid>,
) -> ModelResult<Uuid> {
let res = sqlx::query!(
"INSERT INTO organizations (id) VALUES ($1) RETURNING id",
pkey_policy.into_uuid(),
)
.fetch_one(conn)
.await?;
Ok(res.id)
}
# async fn random_function(conn: &mut PgConnection) -> ModelResult<()> {
// Insert using generated id.
let foo_1_id = insert(conn, PKeyPolicy::Generate).await.unwrap();
// Insert using fixed id.
let uuid = Uuid::parse_str("8fce44cf-738e-4fc9-8d8e-47c350fd3a7f").unwrap();
let foo_2_id = insert(conn, PKeyPolicy::Fixed(uuid)).await.unwrap();
assert_eq!(foo_2_id, uuid);
# Ok(())
# }
```
### Usage in a higher-order function.
When `PKeyPolicy` is used with a higher-order function, an arbitrary struct can be provided
instead. The data can be mapped further by calling the `.map()` or `.map_ref()` methods.
```no_run
# use headless_lms_models::{ModelResult, PKeyPolicy};
# use uuid::Uuid;
# use sqlx::PgConnection;
# mod foos {
# use headless_lms_models::{ModelResult, PKeyPolicy};
# use uuid::Uuid;
# use sqlx::PgConnection;
# pub async fn insert(conn: &mut PgConnection, pkey_policy: PKeyPolicy<Uuid>) -> ModelResult<()> {
# Ok(())
# }
# }
# mod bars {
# use headless_lms_models::{ModelResult, PKeyPolicy};
# use uuid::Uuid;
# use sqlx::PgConnection;
# pub async fn insert(conn: &mut PgConnection, pkey_policy: PKeyPolicy<Uuid>) -> ModelResult<()> {
# Ok(())
# }
# }
struct FooBar {
foo: Uuid,
bar: Uuid,
}
async fn multiple_inserts(
conn: &mut PgConnection,
pkey_policy: PKeyPolicy<FooBar>,
) -> ModelResult<()> {
foos::insert(conn, pkey_policy.map_ref(|x| x.foo)).await?;
bars::insert(conn, pkey_policy.map_ref(|x| x.bar)).await?;
Ok(())
}
# async fn some_function(conn: &mut PgConnection) {
// Insert using generated ids.
assert!(multiple_inserts(conn, PKeyPolicy::Generate).await.is_ok());
// Insert using fixed ids.
let foobar = FooBar {
foo: Uuid::parse_str("52760668-cc9d-4144-9226-d2aacb83bea9").unwrap(),
bar: Uuid::parse_str("ce9bd0cd-0e66-4522-a1b4-52a9347a115c").unwrap(),
};
assert!(multiple_inserts(conn, PKeyPolicy::Fixed(foobar)).await.is_ok());
# }
```
*/
pub enum PKeyPolicy<T> {
/// Ids will be generated based on the associated data. Usually only used in
/// local test environments where reproducible database states are desired.
Fixed(T),
/// Ids will be generated on the database level. This should be the default
/// behavior.
Generate,
}
impl<T> PKeyPolicy<T> {
/// Gets reference to the fixed data, if there are any.
pub fn fixed(&self) -> Option<&T> {
match self {
PKeyPolicy::Fixed(t) => Some(t),
PKeyPolicy::Generate => None,
}
}
/// Maps `PKeyPolicy<T>` to `PKeyPolicy<U>` by applying a function to the contained value.
pub fn map<U, F>(self, f: F) -> PKeyPolicy<U>
where
F: FnOnce(T) -> U,
{
match self {
PKeyPolicy::Fixed(x) => PKeyPolicy::Fixed(f(x)),
PKeyPolicy::Generate => PKeyPolicy::Generate,
}
}
/// Maps a reference of contained data in `Fixed(T)` to `PKeyPolicy<U>` by applying a function
/// to the contained value. This is useful whenever a referenced value can be used instead of
/// having to consume the original value.
pub fn map_ref<U, F>(&self, f: F) -> PKeyPolicy<U>
where
F: FnOnce(&T) -> U,
{
match self {
PKeyPolicy::Fixed(x) => PKeyPolicy::Fixed(f(x)),
PKeyPolicy::Generate => PKeyPolicy::Generate,
}
}
}
impl PKeyPolicy<Uuid> {
/// Maps into the contained `Uuid` value or generates a new one.
pub fn into_uuid(self) -> Uuid {
match self {
PKeyPolicy::Fixed(uuid) => uuid,
PKeyPolicy::Generate => Uuid::new_v4(),
}
}
}
/// Many database tables are related to either a course or an exam
#[derive(Clone, Copy)]
pub enum CourseOrExamId {
Course(Uuid),
Exam(Uuid),
}
impl CourseOrExamId {
pub fn from(course_id: Option<Uuid>, exam_id: Option<Uuid>) -> ModelResult<Self> {
match (course_id, exam_id) {
(Some(course_id), None) => Ok(Self::Course(course_id)),
(None, Some(exam_id)) => Ok(Self::Exam(exam_id)),
(Some(_), Some(_)) => Err(ModelError::new(
ModelErrorType::Generic,
"Database row had both a course id and an exam id".to_string(),
None,
)),
(None, None) => Err(ModelError::new(
ModelErrorType::Generic,
"Database row did not have a course id or an exam id".to_string(),
None,
)),
}
}
pub fn to_course_and_exam_ids(self) -> (Option<Uuid>, Option<Uuid>) {
match self {
Self::Course(instance_id) => (Some(instance_id), None),
Self::Exam(exam_id) => (None, Some(exam_id)),
}
}
pub fn exam_id(self) -> Option<Uuid> {
if let CourseOrExamId::Exam(id) = self {
Some(id)
} else {
None
}
}
}
/// A "trait alias" so this `for<'a>` ... string doesn't need to be repeated everywhere
/// Arguments:
/// `Url`: The URL that the request is sent to (the exercise service's endpoint)
/// `&str`: Exercise type/service slug
/// `Option<Value>`: The Json for the request, for example the private spec in a public spec request
pub trait SpecFetcher:
for<'a> Fn(
Url,
&'a str,
Option<&'a serde_json::Value>,
) -> BoxFuture<'a, ModelResult<serde_json::Value>>
{
}
impl<
T: for<'a> Fn(
Url,
&'a str,
Option<&'a serde_json::Value>,
) -> BoxFuture<'a, ModelResult<serde_json::Value>>,
> SpecFetcher for T
{
}