use std::collections::HashMap;
use schemars::JsonSchema;
use serde::de::{Error as _, IgnoredAny, MapAccess, Visitor};
use serde::{Deserialize, Deserializer, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, JsonSchema)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum Question {
Noul(NoulQuestion),
Choice(ChoiceQuestion),
Score(ScoreQuestion),
}
#[derive(Deserialize)]
#[serde(field_identifier, rename_all = "lowercase")]
enum QuestionField {
Type,
Instructions,
Criteria,
#[serde(other)]
Other,
}
#[derive(Clone, Copy, Deserialize)]
#[serde(rename_all = "lowercase")]
enum QuestionTag {
Noul,
Choice,
Score,
}
impl<'de> Deserialize<'de> for Question {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct QuestionVisitor;
impl<'de> Visitor<'de> for QuestionVisitor {
type Value = Question;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("a tagged question object")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut tag: Option<QuestionTag> = None;
let mut instructions: Option<Instructions> = None;
let mut early_criteria: Option<serde_json::Value> = None;
let mut noul_criteria: Option<Option<NoulCriteria>> = None;
let mut choice_criteria: Option<HashMap<String, Option<String>>> = None;
let mut score_criteria: Option<Vec<String>> = None;
while let Some(key) = map.next_key::<QuestionField>()? {
match key {
QuestionField::Type => {
if tag.is_some() {
return Err(A::Error::duplicate_field("type"));
}
tag = Some(map.next_value()?);
}
QuestionField::Instructions => {
if instructions.is_some() {
return Err(A::Error::duplicate_field("instructions"));
}
instructions = Some(map.next_value()?);
}
QuestionField::Criteria => {
let already_buffered = early_criteria.is_some()
|| noul_criteria.is_some()
|| choice_criteria.is_some()
|| score_criteria.is_some();
if already_buffered {
return Err(A::Error::duplicate_field("criteria"));
}
match tag {
None => early_criteria = Some(map.next_value()?),
Some(QuestionTag::Noul) => {
noul_criteria = Some(map.next_value::<Option<NoulCriteria>>()?)
}
Some(QuestionTag::Choice) => {
choice_criteria = Some(map.next_value()?)
}
Some(QuestionTag::Score) => {
score_criteria = Some(map.next_value()?)
}
}
}
QuestionField::Other => {
let _ = map.next_value::<IgnoredAny>()?;
}
}
}
let tag = tag.ok_or_else(|| A::Error::missing_field("type"))?;
let instructions =
instructions.ok_or_else(|| A::Error::missing_field("instructions"))?;
match tag {
QuestionTag::Noul => Ok(Question::Noul(NoulQuestion {
instructions,
criteria: match noul_criteria {
Some(criteria) => criteria,
None => convert_criteria::<Option<NoulCriteria>, _>(early_criteria)?
.flatten(),
},
})),
QuestionTag::Choice => Ok(Question::Choice(ChoiceQuestion {
instructions,
criteria: match choice_criteria {
Some(criteria) => criteria,
None => convert_criteria(early_criteria)?
.ok_or_else(|| A::Error::missing_field("criteria"))?,
},
})),
QuestionTag::Score => Ok(Question::Score(ScoreQuestion {
instructions,
criteria: match score_criteria {
Some(criteria) => criteria,
None => convert_criteria(early_criteria)?
.ok_or_else(|| A::Error::missing_field("criteria"))?,
},
})),
}
}
}
deserializer.deserialize_map(QuestionVisitor)
}
}
fn convert_criteria<T, E>(early: Option<serde_json::Value>) -> Result<Option<T>, E>
where
T: serde::de::DeserializeOwned,
E: serde::de::Error,
{
early
.map(serde_json::from_value)
.transpose()
.map_err(E::custom)
}
pub type Instructions = serde_json::Value;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct NoulQuestion {
pub instructions: Instructions,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub criteria: Option<NoulCriteria>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct NoulCriteria {
#[serde(rename = "true")]
pub r#true: String,
#[serde(rename = "false")]
pub r#false: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct ChoiceQuestion {
pub instructions: Instructions,
pub criteria: HashMap<String, Option<String>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct ScoreQuestion {
pub instructions: Instructions,
pub criteria: Vec<String>,
}