use crate::domain::Domain;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Profile {
pub id: String,
pub name: String,
pub description: String,
pub budget_mb: u64,
pub domains: Vec<Domain>,
pub include_external: bool,
}
impl Profile {
#[must_use]
pub fn survival() -> Self {
Self {
id: "survival".into(),
name: "Survival Kit".into(),
description: "Essential survival, medical, and practical knowledge. Fits on a phone."
.into(),
budget_mb: 2_000,
domains: vec![
Domain::Medicine,
Domain::Survival,
Domain::Agriculture,
Domain::Construction,
Domain::Repair,
Domain::Communication,
],
include_external: true,
}
}
#[must_use]
pub fn homesteader() -> Self {
Self {
id: "homesteader".into(),
name: "Homesteader".into(),
description:
"Self-sufficiency: farming, building, repair, medicine, plus science fundamentals."
.into(),
budget_mb: 5_000,
domains: vec![
Domain::Medicine,
Domain::Survival,
Domain::Agriculture,
Domain::Construction,
Domain::Repair,
Domain::Communication,
Domain::Chemistry,
Domain::Biology,
Domain::EarthScience,
Domain::Mathematics,
],
include_external: true,
}
}
#[must_use]
pub fn developer() -> Self {
Self {
id: "developer".into(),
name: "Developer".into(),
description: "Programming references, algorithms, systems knowledge, math.".into(),
budget_mb: 3_000,
domains: vec![
Domain::Computing,
Domain::Mathematics,
Domain::Statistics,
Domain::Physics,
],
include_external: false,
}
}
#[must_use]
pub fn educator() -> Self {
Self {
id: "educator".into(),
name: "Educator".into(),
description: "Broad knowledge base for teaching and learning across all domains."
.into(),
budget_mb: 8_000,
domains: Domain::all().to_vec(),
include_external: true,
}
}
#[must_use]
pub fn full() -> Self {
Self {
id: "full".into(),
name: "Complete Knowledge Base".into(),
description: "All domains, all sources. The full foundation of knowing.".into(),
budget_mb: 10_000,
domains: Domain::all().to_vec(),
include_external: true,
}
}
#[must_use]
pub fn all_profiles() -> Vec<Self> {
vec![
Self::survival(),
Self::homesteader(),
Self::developer(),
Self::educator(),
Self::full(),
]
}
}
impl std::fmt::Display for Profile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} — {}GB, {} domains",
self.name,
self.budget_mb / 1_000,
self.domains.len()
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn profile_survival() {
let p = Profile::survival();
assert_eq!(p.id, "survival");
assert!(p.budget_mb <= 2_000);
assert!(p.domains.contains(&Domain::Medicine));
}
#[test]
fn profile_developer() {
let p = Profile::developer();
assert!(p.domains.contains(&Domain::Computing));
assert!(!p.include_external);
}
#[test]
fn profile_full_has_all_domains() {
let p = Profile::full();
assert_eq!(p.domains.len(), Domain::all().len());
}
#[test]
fn all_profiles() {
let profiles = Profile::all_profiles();
assert_eq!(profiles.len(), 5);
}
#[test]
fn profile_display() {
let p = Profile::survival();
let s = p.to_string();
assert!(s.contains("Survival Kit"));
assert!(s.contains("2GB"));
}
#[test]
fn profile_serde_roundtrip() {
let p = Profile::survival();
let json = serde_json::to_string(&p).unwrap();
let decoded: Profile = serde_json::from_str(&json).unwrap();
assert_eq!(p.id, decoded.id);
assert_eq!(p.budget_mb, decoded.budget_mb);
}
}