konnektoren_core/commands/
parse.rs

1use super::{ChallengeCommand, Command, CommandError, GameCommand};
2use crate::challenges::{ChallengeResult, CustomChallengeResult};
3use serde_json::Value;
4use wasm_bindgen::prelude::*;
5
6impl From<CommandError> for JsValue {
7    fn from(error: CommandError) -> Self {
8        JsValue::from_str(&error.to_string())
9    }
10}
11
12impl TryFrom<JsValue> for Command {
13    type Error = CommandError;
14
15    fn try_from(value: JsValue) -> Result<Self, Self::Error> {
16        let obj: Value = serde_wasm_bindgen::from_value(value)
17            .map_err(|e| CommandError::ParseError(e.to_string()))?;
18        Command::try_from(obj)
19    }
20}
21
22impl TryFrom<Value> for Command {
23    type Error = CommandError;
24
25    fn try_from(obj: Value) -> Result<Self, Self::Error> {
26        match obj.get("type").and_then(|v| v.as_str()) {
27            Some("Game") => Ok(Command::Game(GameCommand::try_from(obj)?)),
28            Some("Challenge") => Ok(Command::Challenge(ChallengeCommand::try_from(obj)?)),
29            Some(unknown_type) => Err(CommandError::UnknownCommandType(unknown_type.to_string())),
30            None => Err(CommandError::MissingData),
31        }
32    }
33}
34
35impl TryFrom<Value> for GameCommand {
36    type Error = CommandError;
37
38    fn try_from(value: Value) -> Result<Self, Self::Error> {
39        match value.get("action").and_then(|v| v.as_str()) {
40            Some("NextChallenge") => Ok(GameCommand::NextChallenge),
41            Some("PreviousChallenge") => Ok(GameCommand::PreviousChallenge),
42            Some(unknown_action) => {
43                Err(CommandError::UnknownCommandType(unknown_action.to_string()))
44            }
45            None => Err(CommandError::MissingData),
46        }
47    }
48}
49
50impl TryFrom<Value> for ChallengeCommand {
51    type Error = CommandError;
52
53    fn try_from(value: Value) -> Result<Self, Self::Error> {
54        match value.get("action").and_then(|v| v.as_str()) {
55            Some("NextTask") => Ok(ChallengeCommand::NextTask),
56            Some("PreviousTask") => Ok(ChallengeCommand::PreviousTask),
57            Some("SolveOption") => {
58                let option_index = value
59                    .get("optionIndex")
60                    .ok_or(CommandError::MissingData)?
61                    .as_u64()
62                    .ok_or_else(|| {
63                        CommandError::InvalidData("optionIndex must be a number".to_string())
64                    })?;
65                Ok(ChallengeCommand::SolveOption(option_index as usize))
66            }
67            Some("Finish") => {
68                let result = value.get("result").ok_or(CommandError::MissingData)?;
69                let result: CustomChallengeResult = serde_json::from_value(result.clone())
70                    .map_err(|e| CommandError::ParseError(e.to_string()))?;
71                Ok(ChallengeCommand::Finish(Some(ChallengeResult::Custom(
72                    result.clone(),
73                ))))
74            }
75            Some(unknown_action) => {
76                Err(CommandError::UnknownCommandType(unknown_action.to_string()))
77            }
78            None => Err(CommandError::MissingData),
79        }
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use crate::challenges::ChallengeResult;
87
88    #[test]
89    fn test_parse_game_command() {
90        let json = r#"{"type":"Game","action":"NextChallenge"}"#;
91        let value: serde_json::Value = serde_json::from_str(json).unwrap();
92        let command = Command::try_from(value).unwrap();
93        assert_eq!(command, Command::Game(GameCommand::NextChallenge));
94    }
95
96    #[test]
97    fn test_parse_challenge_command() {
98        let json = r#"{"type":"Challenge","action":"NextTask"}"#;
99        let value: serde_json::Value = serde_json::from_str(json).unwrap();
100        let command = Command::try_from(value).unwrap();
101        assert_eq!(command, Command::Challenge(ChallengeCommand::NextTask));
102    }
103
104    #[test]
105    fn test_parse_challenge_command_with_option() {
106        let json = r#"{"type":"Challenge","action":"SolveOption","optionIndex":0}"#;
107        let value: serde_json::Value = serde_json::from_str(json).unwrap();
108        let command = Command::try_from(value).unwrap();
109        assert_eq!(
110            command,
111            Command::Challenge(ChallengeCommand::SolveOption(0))
112        );
113    }
114
115    #[test]
116    fn test_parse_challenge_command_with_result() {
117        let json = r#"{"type":"Challenge","action":"Finish","result":{"id":"123","performance":0.0,"data":{}}}"#;
118        let value: serde_json::Value = serde_json::from_str(json).unwrap();
119        let command = Command::try_from(value).unwrap();
120        assert_eq!(
121            command,
122            Command::Challenge(ChallengeCommand::Finish(Some(ChallengeResult::Custom(
123                CustomChallengeResult {
124                    id: "123".to_string(),
125                    performance: 0.0,
126                    data: serde_json::json!({}),
127                }
128            ))))
129        );
130    }
131
132    #[test]
133    fn test_parse_unknown_command() {
134        let json = r#"{"type":"Unknown","action":"NextTask"}"#;
135        let value: serde_json::Value = serde_json::from_str(json).unwrap();
136        let result = Command::try_from(value);
137        assert!(result.is_err());
138        assert_eq!(
139            result.unwrap_err(),
140            CommandError::UnknownCommandType("Unknown".to_string())
141        );
142    }
143
144    #[test]
145    fn test_parse_missing_data() {
146        let json = r#"{"type":"Challenge"}"#;
147        let value: serde_json::Value = serde_json::from_str(json).unwrap();
148        let result = Command::try_from(value);
149        assert!(result.is_err());
150        assert_eq!(result.unwrap_err(), CommandError::MissingData);
151    }
152
153    #[test]
154    fn test_parse_missing_option_index() {
155        let json = r#"{"type":"Challenge","action":"SolveOption"}"#;
156        let value: serde_json::Value = serde_json::from_str(json).unwrap();
157        let result = Command::try_from(value);
158        assert!(result.is_err());
159        assert_eq!(result.unwrap_err(), CommandError::MissingData);
160    }
161
162    #[test]
163    fn test_parse_invalid_json() {
164        let json = r#"{"type":"Challenge","action":"SolveOption","optionIndex":"invalid"}"#;
165        let value: serde_json::Value = serde_json::from_str(json).unwrap();
166        let result = Command::try_from(value);
167        assert!(result.is_err());
168        assert!(matches!(result.unwrap_err(), CommandError::InvalidData(_)));
169    }
170
171    #[cfg(target_arch = "wasm32")]
172    mod wasm_tests {
173        use super::*;
174        use wasm_bindgen_test::*;
175
176        #[wasm_bindgen_test]
177        fn test_parse_game_event_js() {
178            let json = r#"{"type":"Game", "action":"NextChallenge"}"#;
179            let value: JsValue = js_sys::JSON::parse(json).unwrap();
180            let command = Command::try_from(value).unwrap();
181            assert_eq!(command, Command::Game(GameCommand::NextChallenge));
182        }
183
184        #[wasm_bindgen_test]
185        fn test_parse_challenge_event_js() {
186            let json = r#"{"type":"Challenge", "action":"NextTask"}"#;
187            let value: JsValue = js_sys::JSON::parse(json).unwrap();
188            let command = Command::try_from(value).unwrap();
189            assert_eq!(command, Command::Challenge(ChallengeCommand::NextTask));
190        }
191
192        #[wasm_bindgen_test]
193        fn test_parse_challenge_event_with_option_js() {
194            let json = r#"{"type":"Challenge", "action":"SolveOption", "optionIndex":0}"#;
195            let value: JsValue = js_sys::JSON::parse(json).unwrap();
196            let command = Command::try_from(value).unwrap();
197            assert_eq!(
198                command,
199                Command::Challenge(ChallengeCommand::SolveOption(0))
200            );
201        }
202
203        #[wasm_bindgen_test]
204        fn test_parse_challenge_event_with_result_js() {
205            let json = r#"{"type":"Challenge", "action":"Finish", "result":{"id":"123","performance":0.0,"data":{}}}"#;
206            let value: JsValue = js_sys::JSON::parse(json).unwrap();
207            let command = Command::try_from(value).unwrap();
208            assert_eq!(
209                command,
210                Command::Challenge(ChallengeCommand::Finish(Some(ChallengeResult::Custom(
211                    CustomChallengeResult {
212                        id: "123".to_string(),
213                        performance: 0.0,
214                        data: serde_json::json!({}),
215                    }
216                ))))
217            );
218        }
219
220        #[wasm_bindgen_test]
221        fn test_parse_unknown_command_js() {
222            let json = r#"{"type":"Unknown", "action":"NextTask"}"#;
223            let value: JsValue = js_sys::JSON::parse(json).unwrap();
224            let result = Command::try_from(value);
225            assert!(result.is_err());
226            assert_eq!(
227                result.unwrap_err(),
228                CommandError::UnknownCommandType("Unknown".to_string())
229            );
230        }
231
232        #[wasm_bindgen_test]
233        fn test_parse_missing_data_js() {
234            let json = r#"{"type":"Challenge"}"#;
235            let value: JsValue = js_sys::JSON::parse(json).unwrap();
236            let result = Command::try_from(value);
237            assert!(result.is_err());
238            assert_eq!(result.unwrap_err(), CommandError::MissingData);
239        }
240
241        #[wasm_bindgen_test]
242        fn test_parse_missing_option_index_js() {
243            let json = r#"{"type":"Challenge", "action":"SolveOption"}"#;
244            let value: JsValue = js_sys::JSON::parse(json).unwrap();
245            let result = Command::try_from(value);
246            assert!(result.is_err());
247            assert_eq!(result.unwrap_err(), CommandError::MissingData);
248        }
249    }
250}