use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use crate::Content;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
#[non_exhaustive]
pub enum Question {
Noul(Noul),
Choice(Choice),
Score(Score),
}
impl From<Noul> for Question {
fn from(question: Noul) -> Self {
Question::Noul(question)
}
}
impl From<Choice> for Question {
fn from(question: Choice) -> Self {
Question::Choice(question)
}
}
impl From<Score> for Question {
fn from(question: Score) -> Self {
Question::Score(question)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Noul {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub instructions: Option<Content>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub criteria: Option<NoulCriteria>,
}
impl Noul {
pub fn new(instructions: impl Into<Content>) -> Self {
Noul { instructions: Some(instructions.into()), criteria: None }
}
#[must_use]
pub fn yes(mut self, meaning: impl Into<Content>) -> Self {
self.criteria.get_or_insert_with(NoulCriteria::default).yes = Some(meaning.into());
self
}
#[must_use]
pub fn no(mut self, meaning: impl Into<Content>) -> Self {
self.criteria.get_or_insert_with(NoulCriteria::default).no = Some(meaning.into());
self
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct NoulCriteria {
#[serde(rename = "true", default, skip_serializing_if = "Option::is_none")]
pub yes: Option<Content>,
#[serde(rename = "false", default, skip_serializing_if = "Option::is_none")]
pub no: Option<Content>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Choice {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub instructions: Option<Content>,
pub criteria: IndexMap<String, Option<Content>>,
}
impl Choice {
pub fn new<N, D>(instructions: impl Into<Content>, options: impl IntoIterator<Item = (N, D)>) -> Self
where
N: Into<String>,
D: Into<Content>,
{
let criteria = options.into_iter().map(|(name, description)| (name.into(), Some(description.into()))).collect();
Choice { instructions: Some(instructions.into()), criteria }
}
pub fn labels<N: Into<String>>(instructions: impl Into<Content>, names: impl IntoIterator<Item = N>) -> Self {
let criteria = names.into_iter().map(|name| (name.into(), None)).collect();
Choice { instructions: Some(instructions.into()), criteria }
}
#[must_use]
pub fn option(mut self, name: impl Into<String>, description: impl Into<Content>) -> Self {
self.criteria.insert(name.into(), Some(description.into()));
self
}
#[must_use]
pub fn label(mut self, name: impl Into<String>) -> Self {
self.criteria.insert(name.into(), None);
self
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Score {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub instructions: Option<Content>,
pub criteria: Vec<Content>,
}
impl Score {
pub fn new<L: Into<Content>>(instructions: impl Into<Content>, levels: impl IntoIterator<Item = L>) -> Self {
Score { instructions: Some(instructions.into()), criteria: levels.into_iter().map(Into::into).collect() }
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Questions(IndexMap<String, Question>);
impl Questions {
pub fn new() -> Self {
Questions::default()
}
#[must_use]
pub fn with(mut self, id: impl Into<String>, question: impl Into<Question>) -> Self {
self.insert(id, question);
self
}
pub fn insert(&mut self, id: impl Into<String>, question: impl Into<Question>) -> Option<Question> {
self.0.insert(id.into(), question.into())
}
pub fn get(&self, id: &str) -> Option<&Question> {
self.0.get(id)
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn ids(&self) -> impl Iterator<Item = &str> {
self.0.keys().map(String::as_str)
}
pub fn iter(&self) -> indexmap::map::Iter<'_, String, Question> {
self.0.iter()
}
pub fn as_map(&self) -> &IndexMap<String, Question> {
&self.0
}
}
impl From<IndexMap<String, Question>> for Questions {
fn from(questions: IndexMap<String, Question>) -> Self {
Questions(questions)
}
}
impl From<Questions> for IndexMap<String, Question> {
fn from(questions: Questions) -> Self {
questions.0
}
}
impl<I: Into<String>, Q: Into<Question>> FromIterator<(I, Q)> for Questions {
fn from_iter<T: IntoIterator<Item = (I, Q)>>(questions: T) -> Self {
Questions(questions.into_iter().map(|(id, question)| (id.into(), question.into())).collect())
}
}
impl<I: Into<String>, Q: Into<Question>> Extend<(I, Q)> for Questions {
fn extend<T: IntoIterator<Item = (I, Q)>>(&mut self, questions: T) {
self.0.extend(questions.into_iter().map(|(id, question)| (id.into(), question.into())));
}
}
impl IntoIterator for Questions {
type Item = (String, Question);
type IntoIter = indexmap::map::IntoIter<String, Question>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<'a> IntoIterator for &'a Questions {
type Item = (&'a String, &'a Question);
type IntoIter = indexmap::map::Iter<'a, String, Question>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}