From 5257c6f3401b1f06a8836372cbf055b563ffd831 Mon Sep 17 00:00:00 2001 From: Fedor Tolstonozhenko Date: Sun, 29 Sep 2024 21:05:10 +0200 Subject: [PATCH] Flatten Package structure Remove nested container struct for Round/Themes/Questions --- crates/opensi-core/src/lib.rs | 116 ++++++++++------------- crates/opensi-core/src/serde_utils.rs | 109 +++++++++++++++++++++ crates/opensi-editor/src/package_tab.rs | 6 +- crates/opensi-editor/src/package_tree.rs | 8 +- 4 files changed, 166 insertions(+), 73 deletions(-) create mode 100644 crates/opensi-core/src/serde_utils.rs diff --git a/crates/opensi-core/src/lib.rs b/crates/opensi-core/src/lib.rs index 46411ef..ad78b61 100644 --- a/crates/opensi-core/src/lib.rs +++ b/crates/opensi-core/src/lib.rs @@ -11,6 +11,9 @@ use std::{fs::File, io, io::Read}; use zip::write::FileOptions; use zip::{CompressionMethod, ZipArchive, ZipWriter}; +pub mod serde_utils; +use serde_utils::*; + #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] #[serde(rename = "package")] pub struct Package { @@ -36,7 +39,8 @@ pub struct Package { // elements pub info: Info, - pub rounds: Rounds, + #[serde(deserialize_with = "unwrap_list", serialize_with = "wrap_round_list")] + pub rounds: Vec, pub tags: Option>, // resources @@ -47,37 +51,37 @@ pub struct Package { impl Package { /// Get [`Round`] by index. pub fn get_round(&self, index: usize) -> Option<&Round> { - self.rounds.rounds.get(index) + self.rounds.get(index) } /// Get mutable [`Round`] by index. pub fn get_round_mut(&mut self, index: usize) -> Option<&mut Round> { - self.rounds.rounds.get_mut(index) + self.rounds.get_mut(index) } /// Remove [`Round`] by index and return it. pub fn remove_round(&mut self, index: usize) -> Option { - if index >= self.rounds.rounds.len() { + if index >= self.rounds.len() { return None; } - self.rounds.rounds.remove(index).into() + self.rounds.remove(index).into() } /// Push a new [`Round`] to the end of the package and /// return a reference to it. pub fn push_round(&mut self, round: Round) -> &mut Round { - self.rounds.rounds.push(round); - self.rounds.rounds.last_mut().unwrap() + self.rounds.push(round); + self.rounds.last_mut().unwrap() } /// Insert a new [`Round`] at position and return a /// reference to it. pub fn insert_round(&mut self, index: usize, round: Round) -> Option<&mut Round> { - if index > self.rounds.rounds.len() { + if index > self.rounds.len() { return None; } - self.rounds.rounds.insert(index, round); - Some(&mut self.rounds.rounds[index]) + self.rounds.insert(index, round); + Some(&mut self.rounds[index]) } /// Clone a [`Round`], push it afterwards and return @@ -95,29 +99,29 @@ impl Package { /// Get [`Theme`] in [`Round`] by indices. pub fn get_theme(&self, round_index: usize, index: usize) -> Option<&Theme> { - self.get_round(round_index).and_then(|round| round.themes.themes.get(index)) + self.get_round(round_index).and_then(|round| round.themes.get(index)) } /// Get mutable [`Theme`] in [`Round`] by indices. pub fn get_theme_mut(&mut self, round_index: usize, index: usize) -> Option<&mut Theme> { - self.get_round_mut(round_index).and_then(|round| round.themes.themes.get_mut(index)) + self.get_round_mut(round_index).and_then(|round| round.themes.get_mut(index)) } /// Remove [`Theme`] in [`Round`] by indices. pub fn remove_theme(&mut self, round_index: usize, index: usize) -> Option { let round = self.get_round_mut(round_index)?; - if index >= round.themes.themes.len() { + if index >= round.themes.len() { return None; } - round.themes.themes.remove(index).into() + round.themes.remove(index).into() } /// Push a new [`Theme`] to the end of the [`Round`] and /// return a reference to it. pub fn push_theme(&mut self, round_index: usize, theme: Theme) -> Option<&mut Theme> { let round = self.get_round_mut(round_index)?; - round.themes.themes.push(theme); - round.themes.themes.last_mut().unwrap().into() + round.themes.push(theme); + round.themes.last_mut().unwrap().into() } /// Insert a new [`Theme`] at position and return a @@ -129,11 +133,11 @@ impl Package { theme: Theme, ) -> Option<&mut Theme> { let round = self.get_round_mut(round_index)?; - if index > round.themes.themes.len() { + if index > round.themes.len() { return None; } - round.themes.themes.insert(index, theme); - Some(&mut round.themes.themes[index]) + round.themes.insert(index, theme); + Some(&mut round.themes[index]) } /// Clone a [`Theme`], push it afterwards and return @@ -158,8 +162,7 @@ impl Package { theme_index: usize, index: usize, ) -> Option<&Question> { - self.get_theme(round_index, theme_index) - .and_then(|theme| theme.questions.questions.get(index)) + self.get_theme(round_index, theme_index).and_then(|theme| theme.questions.get(index)) } /// Get mutable [`Question`] in [`Theme`] in [`Round`] by indices. @@ -170,7 +173,7 @@ impl Package { index: usize, ) -> Option<&mut Question> { self.get_theme_mut(round_index, theme_index) - .and_then(|theme| theme.questions.questions.get_mut(index)) + .and_then(|theme| theme.questions.get_mut(index)) } /// Remove [`Question`] in [`Theme`] in [`Round`] by indices. @@ -181,10 +184,10 @@ impl Package { index: usize, ) -> Option { let theme = self.get_theme_mut(round_index, theme_index)?; - if index >= theme.questions.questions.len() { + if index >= theme.questions.len() { return None; } - theme.questions.questions.remove(index).into() + theme.questions.remove(index).into() } /// Push a new [`Question`] to the end of the [`Theme`] in [`Round`] @@ -196,8 +199,8 @@ impl Package { question: Question, ) -> Option<&mut Question> { let theme = self.get_theme_mut(round_index, theme_index)?; - theme.questions.questions.push(question); - theme.questions.questions.last_mut().unwrap().into() + theme.questions.push(question); + theme.questions.last_mut().unwrap().into() } /// Insert a new [`Question`] at position and return a @@ -210,11 +213,11 @@ impl Package { question: Question, ) -> Option<&mut Question> { let theme = self.get_theme_mut(round_index, theme_index)?; - if index > theme.questions.questions.len() { + if index > theme.questions.len() { return None; } - theme.questions.questions.insert(index, question); - Some(&mut theme.questions.questions[index]) + theme.questions.insert(index, question); + Some(&mut theme.questions[index]) } /// Clone a [`question`], push it afterwards and return @@ -255,7 +258,7 @@ impl Package { let Some(theme) = self.get_theme(round_index, theme_index) else { return 100; }; - let questions = &theme.questions.questions; + let questions = &theme.questions; let mut iter = questions.iter().rev(); match (iter.next(), iter.next()) { (Some(last), Some(prev)) => { @@ -330,12 +333,7 @@ pub struct Round { pub variant: Option, #[serde(rename = "@info")] pub info: Option, - pub themes: Themes, -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] -pub struct Themes { - #[serde(rename = "theme")] + #[serde(deserialize_with = "unwrap_list", serialize_with = "wrap_theme_list")] pub themes: Vec, } @@ -343,7 +341,8 @@ pub struct Themes { pub struct Theme { #[serde(rename = "@name")] pub name: String, - pub questions: Questions, + #[serde(deserialize_with = "unwrap_list", serialize_with = "wrap_question_list")] + pub questions: Vec, #[serde(rename = "@info")] pub info: Option, } @@ -358,17 +357,20 @@ pub struct Questions { pub struct Question { #[serde(rename = "@price")] pub price: usize, - pub scenario: Scenario, - pub right: Right, - pub wrong: Option, + #[serde(deserialize_with = "unwrap_list", serialize_with = "wrap_atom_list")] + pub scenario: Vec, + #[serde(deserialize_with = "unwrap_list", serialize_with = "wrap_answer_list")] + pub right: Vec, + #[serde(deserialize_with = "unwrap_option_list", serialize_with = "wrap_option_answer_list", default)] + pub wrong: Option>, #[serde(rename = "type")] - pub variant: Option, + pub question_type: Option, #[serde(rename = "@info")] pub info: Option, } #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] -pub struct Variant { +pub struct QuestionType { #[serde(rename = "@name")] pub name: String, #[serde(rename = "param")] @@ -376,35 +378,17 @@ pub struct Variant { } #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] -pub struct Param { - #[serde(rename = "@name")] - pub name: String, +pub struct Answer { #[serde(rename = "$value")] - pub body: String, -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] -pub struct Scenario { - #[serde(rename = "atom")] - pub atoms: Vec, -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] -pub struct Right { - #[serde(rename = "answer")] - pub answers: Vec, -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] -pub struct Wrong { - #[serde(rename = "answer")] - pub answers: Vec, + pub body: Option, } #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] -pub struct Answer { +pub struct Param { + #[serde(rename = "@name")] + pub name: String, #[serde(rename = "$value")] - pub body: Option, + pub body: String, } #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] diff --git a/crates/opensi-core/src/serde_utils.rs b/crates/opensi-core/src/serde_utils.rs new file mode 100644 index 0000000..82c1ea5 --- /dev/null +++ b/crates/opensi-core/src/serde_utils.rs @@ -0,0 +1,109 @@ +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use crate::{Answer, Atom, Question, Round, Theme}; + +// Generic function to deserialize List structures +pub fn unwrap_list<'de, T, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, + T: Deserialize<'de> + Default, +{ + #[derive(Deserialize)] + struct List { + #[serde(rename = "$value")] + element: Vec, + } + Ok(List::deserialize(deserializer)?.element) +} + +pub fn unwrap_option_list<'de, T, D>(deserializer: D) -> Result>, D::Error> +where + D: Deserializer<'de>, + T: Deserialize<'de> + Default, +{ + #[derive(Serialize, Deserialize)] + struct List { + // #[serde(default)] + #[serde(rename = "$value")] + element: Option>, + } + + Ok(List::deserialize(deserializer)?.element) +} + +pub fn wrap_round_list(rounds: &Vec, serializer: S) -> Result +where + S: Serializer, +{ + #[derive(Serialize)] + struct List<'a> { + round: &'a Vec, + } + + let list = List { round: rounds }; + list.serialize(serializer) +} + +pub fn wrap_theme_list(themes: &Vec, serializer: S) -> Result +where + S: Serializer, +{ + #[derive(Serialize)] + struct List<'a> { + theme: &'a Vec, + } + + let list = List { theme: themes }; + list.serialize(serializer) +} + +pub fn wrap_question_list(questions: &Vec, serializer: S) -> Result +where + S: Serializer, +{ + #[derive(Serialize)] + struct List<'a> { + question: &'a Vec, + } + + let list = List { question: questions }; + list.serialize(serializer) +} + +pub fn wrap_atom_list(atoms: &Vec, serializer: S) -> Result +where + S: Serializer, +{ + #[derive(Serialize)] + struct List<'a> { + atom: &'a Vec, + } + + let list = List { atom: atoms }; + list.serialize(serializer) +} + +pub fn wrap_answer_list(answers: &Vec, serializer: S) -> Result +where + S: Serializer, +{ + #[derive(Serialize)] + struct List<'a> { + answer: &'a Vec, + } + + let list = List { answer: answers }; + list.serialize(serializer) +} + +pub fn wrap_option_answer_list(answers: &Option>, serializer: S) -> Result +where + S: Serializer, +{ + #[derive(Serialize)] + struct List<'a> { + answer: &'a Option>, + } + + let list = List { answer: answers }; + list.serialize(serializer) +} \ No newline at end of file diff --git a/crates/opensi-editor/src/package_tab.rs b/crates/opensi-editor/src/package_tab.rs index 2f71a78..dce1738 100644 --- a/crates/opensi-editor/src/package_tab.rs +++ b/crates/opensi-editor/src/package_tab.rs @@ -93,10 +93,10 @@ fn package_rounds(package: &mut Package, selected: &mut Option, ui: frame.content_ui.with_layout(egui::Layout::top_down(egui::Align::Center), |ui| { unselectable_heading(&round.name, ui); ui.separator(); - let theme_names = if round.themes.themes.is_empty() { + let theme_names = if round.themes.is_empty() { "Пусто".to_string() } else { - round.themes.themes.iter().map(|theme| &theme.name).join(", ") + round.themes.iter().map(|theme| &theme.name).join(", ") }; unselectable_label(egui::RichText::new(theme_names).italics(), ui); }); @@ -126,7 +126,7 @@ fn package_rounds(package: &mut Package, selected: &mut Option, ui: .with_cross_align(egui::Align::Center), ) .body(|mut body| { - for index in 0..package.rounds.rounds.len() { + for index in 0..package.rounds.len() { body.row((button_size + 4.0) * 3.0, |mut row| { row.col(|ui| { let Some(round) = package.get_round_mut(index) else { diff --git a/crates/opensi-editor/src/package_tree.rs b/crates/opensi-editor/src/package_tree.rs index ecc0b08..e07747d 100644 --- a/crates/opensi-editor/src/package_tree.rs +++ b/crates/opensi-editor/src/package_tree.rs @@ -194,10 +194,10 @@ fn tree_node_ui<'a>( let Some(node) = node else { ui.push_id(format!("package-tree"), |ui| { - if package.rounds.rounds.is_empty() { + if package.rounds.is_empty() { ui.weak("Нет раундов"); } else { - for index in 0..package.rounds.rounds.len() { + for index in 0..package.rounds.len() { tree_node_ui(package, Some(PackageNode::Round { index }), selected, ui); } } @@ -224,7 +224,7 @@ fn tree_node_ui<'a>( .body(|ui| { for theme_index in 0..package .get_round(index) - .map(|round| round.themes.themes.len()) + .map(|round| round.themes.len()) .unwrap_or_default() { tree_node_ui( @@ -246,7 +246,7 @@ fn tree_node_ui<'a>( .body(|ui| { for question_index in 0..package .get_theme(round_index, index) - .map(|theme| theme.questions.questions.len()) + .map(|theme| theme.questions.len()) .unwrap_or_default() { tree_node_ui(