use core::panic; use std::str::FromStr; use serde::{Deserialize, Deserializer, Serialize}; use serde_yaml::Value; use crate::kyujitai::{Kyujitai, self}; #[derive(Serialize, Deserialize)] pub struct Challenge { pub japanese: Vec>>, pub english: Option>, pub song: Option, pub youtube: Option, pub spotify: Option, pub translation: Option, pub suggester: Option, pub date: chrono::NaiveDate, } #[derive(Serialize, Deserialize)] pub struct Song { pub japanese: Option, pub english: Option, } #[derive(Serialize)] pub struct ChallengeWord { pub dictionary: Option, pub pos: Option, pub text: Option>, } impl<'de> Deserialize<'de> for ChallengeWord { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let value = Deserialize::deserialize(deserializer)?; let map = if let Value::Mapping(map) = value { map } else { return Err(serde::de::Error::invalid_type( serde::de::Unexpected::Other("not a string or map"), &"a string or map", )); }; let text = map.get("text").map(|value| match value { Value::String(string) => vec![Furigana::new( string.clone(), None, )], Value::Sequence(sequence) => sequence .iter() .map(|value| match value { Value::String(kanji) => Furigana::new( kanji.to_owned(), None, ), Value::Mapping(mapping) => { let (kanji, furigana) = mapping.iter().next().unwrap(); Furigana::new( kanji.as_str().unwrap().to_owned(), Some(furigana.as_str().unwrap().to_owned()) ) } _ => panic!(), }) .collect(), _ => panic!(), }); let dictionary = match map.get("dictionary") { Some(value) => match value { Value::Null => None, Value::String(dictionary) => Some(if !dictionary.starts_with("http") { format!("https://jisho.org/word/{dictionary}") } else { dictionary.to_owned() }), _ => panic!("dictionary must be string!"), }, None => text.as_ref().map(|furigana| furigana.iter().map(|segment| segment.kanji.clone()).collect()), }; let pos: Option = map .get("pos") .map(|value| value.as_str().unwrap().parse().unwrap()); Ok(ChallengeWord { dictionary, pos, text, }) } } #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum PartOfSpeech { Noun, Adjective, Verb, Adverb, Particle, Phrase, } #[derive(Debug, PartialEq, Eq)] pub struct ParsePartOfSpeechError; impl FromStr for PartOfSpeech { type Err = ParsePartOfSpeechError; fn from_str(s: &str) -> Result { use PartOfSpeech::*; Ok(match s { "noun" => Noun, "adjective" => Adjective, "verb" => Verb, "adverb" => Adverb, "particle" => Particle, "phrase" => Phrase, _ => return Err(ParsePartOfSpeechError), }) } } #[derive(Serialize, Deserialize)] pub struct Furigana { pub kanji: String, pub kyujitai: Option, pub furigana: Option, } impl Furigana { fn new(kanji: String, furigana: Option) -> Self { Self { kyujitai: match kanji.to_kyujitai() { kyujitai if kyujitai.eq(&kanji) => None, kyujitai => Some(kyujitai), }, kanji, furigana, } } } #[derive(Serialize, Deserialize)] pub struct Translation { pub author: Option, pub site: TranslationSite, } #[derive(Serialize, Deserialize)] pub struct TranslationSite { pub name: String, pub link: String, }