Documentation
use serde::{Deserialize, Serialize};

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Patch<T> {
    #[serde(rename = "unset")]
    Unset,
    #[serde(rename = "set")]
    Set(T),
}

impl<T> Default for Patch<T> {
    fn default() -> Self {
        Self::Unset
    }
}

impl<T> Clone for Patch<T>
where
    T: Clone,
{
    fn clone(&self) -> Self {
        match self {
            Self::Unset => Self::Unset,
            Self::Set(v) => Self::Set(v.clone()),
        }
    }
}

impl<T> From<T> for Patch<T> {
    fn from(v: T) -> Self {
        Self::Set(v)
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct State<T> {
    #[serde(rename = "desired")]
    pub desired: Option<T>,
    #[serde(rename = "reported")]
    pub reported: Option<T>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct DeltaState<T> {
    #[serde(rename = "desired")]
    pub desired: Option<T>,
    #[serde(rename = "reported")]
    pub reported: Option<T>,
    #[serde(rename = "delta")]
    pub delta: Option<T>,
}

/// A request state document has the following format:
/// - **state** — Updates affect only the fields specified. Typically, you'll
///   use either the desired or the reported property, but not both in the same
///   request.
/// - **desired** — The state properties and values requested to be updated in
///   the device.
/// - **reported** — The state properties and values reported by the device.
/// - **clientToken** — If used, you can match the request and corresponding
///   response by the client token.
/// - **version** — If used, the Device Shadow service processes the update only
///   if the specified version matches the latest version it has.
#[derive(Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct Request<'a, T> {
    pub state: State<T>,
    #[serde(rename = "clientToken")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub client_token: Option<&'a str>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub version: Option<i64>,
}

/// Response accepted state documents have the following format:
/// - **state**
///     - reported — Present only if a thing reported any data in the reported
///       section and contains only fields that were in the request state
///       document.
///     - desired — Present only if a device reported any data in the desired
///       section and contains only fields that were in the request state
///       document.
///     - delta — Present only if the desired data differs from the shadow's
///       current reported data.
/// - **metadata** — Contains the timestamps for each attribute in the desired
///   and reported sections so that you can determine when the state was
///   updated.
/// - **timestamp** — The Epoch date and time the response was generated by AWS
///   IoT.
/// - **clientToken** — Present only if a client token was used when publishing
///   valid JSON to the /update topic.
/// - **version** — The current version of the document for the device's shadow
///   shared in AWS IoT. It is increased by one over the previous version of the
///   document.
#[derive(Debug, Serialize, Deserialize)]
pub struct AcceptedResponse<'a, T> {
    pub state: DeltaState<T>,
    pub timestamp: u64,
    #[serde(rename = "clientToken")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub client_token: Option<&'a str>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub version: Option<i64>,
}

/// Response accepted state documents have the following format:
/// - **state**
/// - **metadata** — Contains the timestamps for each attribute in the desired
///   and reported sections so that you can determine when the state was
///   updated.
/// - **timestamp** — The Epoch date and time the response was generated by AWS
///   IoT.
/// - **clientToken** — Present only if a client token was used when publishing
///   valid JSON to the /update topic.
/// - **version** — The current version of the document for the device's shadow
///   shared in AWS IoT. It is increased by one over the previous version of the
///   document.
#[derive(Debug, Serialize, Deserialize)]
pub struct DeltaResponse<'a, T> {
    pub state: Option<T>,
    pub timestamp: u64,
    #[serde(rename = "clientToken")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub client_token: Option<&'a str>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub version: Option<i64>,
}

/// An error response document has the following format:
/// - **code** — An HTTP response code that indicates the type of error.
/// - **message** — A text message that provides additional information.
/// - **timestamp** — The date and time the response was generated by AWS IoT.
///   This property is not present in all error response documents.
/// - **clientToken** — Present only if a client token was used in the published
///   message.
#[derive(Debug, Deserialize)]
pub struct ErrorResponse<'a> {
    pub code: u16,
    pub message: &'a str,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub timestamp: Option<u64>,
    #[serde(rename = "clientToken")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub client_token: Option<&'a str>,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
    struct Test {
        field: bool,
    }

    #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
    struct TestMap(heapless::LinearMap<heapless::String<3>, Patch<Test>, 5>);

    #[test]
    fn deserialize_patch_unset() {
        let payload = "\"unset\"";

        let (patch, _) = serde_json_core::from_str::<Patch<()>>(payload).unwrap();
        assert_eq!(patch, Patch::Unset);
    }

    #[test]
    fn serialize_map_patch_delta() {
        let payload = "{\"1\":{\"set\":{\"field\":true}}}";

        let mut exp_map = TestMap(heapless::LinearMap::default());
        exp_map
            .0
            .insert(
                heapless::String::from("1"),
                Patch::Set(Test { field: true }),
            )
            .unwrap();

        let patch = serde_json_core::to_string::<_, 512>(&exp_map).unwrap();
        assert_eq!(patch.as_str(), payload);
    }

    #[test]
    fn deserialize_map_patch_delta() {
        let payload = "{\"1\":{\"set\":{\"field\":true}}}";

        let mut exp_map = TestMap(heapless::LinearMap::default());
        exp_map
            .0
            .insert(
                heapless::String::from("1"),
                Patch::Set(Test { field: true }),
            )
            .unwrap();

        let (patch, _) = serde_json_core::from_str::<TestMap>(payload).unwrap();
        assert_eq!(patch, exp_map);
    }

    #[test]
    fn deserialize_map_patch_missing() {
        let payload = "{}";

        let exp_map = TestMap(heapless::LinearMap::default());

        let (patch, _) = serde_json_core::from_str::<TestMap>(payload).unwrap();
        assert_eq!(patch, exp_map);
    }

    #[test]
    fn deserialize_map_patch_unset() {
        let payload = "{\"1\":\"unset\"}";

        let mut exp_map = TestMap(heapless::LinearMap::default());
        exp_map
            .0
            .insert(heapless::String::from("1"), Patch::Unset)
            .unwrap();

        let (patch, _) = serde_json_core::from_str::<TestMap>(payload).unwrap();
        assert_eq!(patch, exp_map);
    }
}