squad-quest/src/quest/mod.rs
2ndbeam d61011f5ea feat: CLI quest CRUD
- Quest creation
- Quest list retrieving
- Quest update
- Quest deletion
2025-11-29 14:40:23 +03:00

180 lines
4.6 KiB
Rust

//! Text-based quests and user solutions for them
pub mod error;
use std::{fs, io::Write, path::PathBuf};
use serde::{ Serialize, Deserialize };
use error::QuestError;
/// Difficulty of the quest
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
pub enum QuestDifficulty {
/// Easy quest
Easy,
/// Normal quest
Normal,
/// Hard quest
Hard,
/// Special case of hard quests. Also is a default value for enum
Secret
}
impl Default for QuestDifficulty {
fn default() -> Self {
QuestDifficulty::Secret
}
}
fn default_name() -> String {
"Slay the dragon".to_string()
}
fn default_description() -> String {
"Just do it in any way".to_string()
}
fn default_answer() -> String {
"Attachment should show that the dragon was slain".to_string()
}
/// Quest struct
#[derive(Serialize, Deserialize, Debug, PartialEq)]
#[serde(default)]
pub struct Quest {
/// Quest identifier
pub id: u16,
/// Difficulty of this quest
pub difficulty: QuestDifficulty,
/// Reward for the quest
pub reward: u32,
/// Visible quest name
pub name: String,
/// Visible quest description
pub description: String,
/// Quest answer, available for admins
pub answer: String,
}
impl Default for Quest {
fn default() -> Self {
Quest {
id: u16::default(),
difficulty: QuestDifficulty::default(),
reward: u32::default(),
name: default_name(),
description: default_description(),
answer: default_answer()
}
}
}
impl Quest {
/// Parse quest TOML or return error
///
/// # Examples
/// ```rust
/// use squad_quest::quest::{Quest,error::QuestError};
/// # fn main() {
/// # let _ = wrapper();
/// # }
///
/// # fn wrapper() -> Result<(), QuestError> {
/// let path = "quests/0.toml".into();
///
/// let quest = Quest::load(path)?;
/// #
/// # Ok(())
/// # }
/// ```
pub fn load(path: PathBuf) -> Result<Self, QuestError> {
match std::fs::read_to_string(path) {
Ok(string) => {
match toml::from_str::<Quest>(&string) {
Ok(quest) => Ok(quest),
Err(error) => Err(QuestError::TomlDeserializeError(error))
}
},
Err(error) => Err(QuestError::IoError(error))
}
}
/// Check if given file is a quest, then delete it or raise an error.
/// If file is not a quest, raises [QuestError::TomlDeserializeError]
///
/// # Examples
/// ```rust
/// use squad_quest::quest::{Quest,error::QuestError};
///
/// let path = "quests/0.toml".into();
///
/// if let Err(error) = Quest::delete(path) {
/// // handle the error
/// }
/// ```
pub fn delete(path: PathBuf) -> Result<(), QuestError> {
match Quest::load(path.clone()) {
Ok(_) => {
if let Err(error) = fs::remove_file(path) {
return Err(QuestError::IoError(error));
}
Ok(())
},
Err(error) => Err(error)
}
}
/// Save quest to given folder in TOML format.
/// File will be saved as `{id}.toml`.
/// If file exists, this method will override it.
///
/// # Examples
/// ```rust
/// # fn main() {
/// use squad_quest::quest::{Quest,error::QuestError};
/// use std::path::PathBuf;
///
/// let quest = Quest::default();
///
/// let path: PathBuf = "quests".into();
/// # let path2 = path.clone();
///
/// if let Err(error) = quest.save(path) {
/// // handle the error
/// }
/// # let filename = format!("{}.toml", quest.id);
/// # let _ = Quest::delete(path2.with_file_name(filename));
/// # }
/// ```
pub fn save(&self, path: PathBuf) -> Result<(), QuestError> {
let filename = format!("{}.toml", self.id);
let mut full_path = path;
full_path.push(filename);
let str = match toml::to_string_pretty(&self) {
Ok(string) => string,
Err(error) => {
return Err(QuestError::TomlSerializeError(error));
}
};
let mut file = match fs::File::create(full_path) {
Ok(f) => f,
Err(error) => {
return Err(QuestError::IoError(error));
}
};
if let Err(error) = file.write_all(str.as_bytes()) {
return Err(QuestError::IoError(error));
}
Ok(())
}
}