1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
use self::super::super::super::constraints::{SudokuBoard9x9ConciseLength, SudokuString};
use serde::de::{Deserializer, Deserialize, Error as DeserializerError};
use serde::ser::{SerializeStruct, Serializer, Serialize};
use std::str::FromStr;
use std::borrow::Cow;


/// The message sent to and from the client on acquisiion/submission of solved board
///
/// Consult [`doc/sudoku.md`](../doc/sudoku/)
#[derive_FromForm]
#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub struct BoardMessage {
    /// ID of the full original board
    pub board_id: i32,

    /// The skeleton to be solved by the user
    pub board_skeleton: String,

    /// The solved board from the skeleton, to be present only on board submit
    pub solved_board: Option<String>,
}

#[derive(Deserialize)]
struct BoardMessageData<'s> {
    board_id: i32,
    board_skeleton: Cow<'s, str>,
    solved_board: Option<Cow<'s, str>>,
}

impl Serialize for BoardMessage {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        let mut s = serializer.serialize_struct("BoardMessage", 2 + self.solved_board.is_some() as usize)?;
        s.serialize_field("board_id", &self.board_id)?;
        s.serialize_field("board_skeleton", &self.board_skeleton)?;
        if let Some(ref sb) = self.solved_board {
            s.serialize_field("solved_board", &sb)?;
        }
        s.end()
    }
}

impl<'de> Deserialize<'de> for BoardMessage {
    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        let data = BoardMessageData::deserialize(deserializer)?;

        if data.board_id <= 0 {
            Err(DeserializerError::custom("id nonpositive"))?;
        }
        if let Err(e) = SudokuString::<SudokuBoard9x9ConciseLength>::from_str(&data.board_skeleton) {
            Err(board_error::<D>(e, "board skeleton"))?;
        }
        if let Some(sb) = data.solved_board.as_ref() {
            if let Err(e) = SudokuString::<SudokuBoard9x9ConciseLength>::from_str(sb) {
                Err(board_error::<D>(e, "solved board"))?;
            }
        }

        Ok(BoardMessage {
            board_id: data.board_id,
            board_skeleton: data.board_skeleton.into(),
            solved_board: data.solved_board.map(Into::into),
        })
    }
}

fn board_error<'de, D: Deserializer<'de>>(e: Option<Option<usize>>, name: &'static str) -> D::Error {
    match e {
        Some(Some(s)) => DeserializerError::custom(format_args!("{} of invalid length {}", name, s)),
        Some(None) => DeserializerError::custom(format_args!("{} contained non-sudoku character", name)),
        None => DeserializerError::custom(format_args!("couldn't decode {} string", name)),
    }
}