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
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
/// This module contains structures to deserialize responses from arangoDB
/// server via HTTP request, as well as convenient functions to deserialize
/// `Response`.
///
/// For response with `error` and `code` fields indicating the whether the
/// request is succesful, use `Response` to abstract over request status
/// and data of concerns.
///
/// For response storing all information in `result` filed, use
/// `ArangoResult`.
use std::ops::Deref;

use log::trace;
use serde::{
    de::{self, DeserializeOwned, Deserializer},
    Deserialize,
};
use serde_json::value::Value;

use crate::{ArangoError, ClientError};

use super::aql::QueryStats;

/// There are different type of json object when requests to arangoDB
/// server is accepted or not. Here provides an abstraction for
/// response of success and failure.
///
/// When ArangoDB server response error code, then an error would be cast.
pub(crate) fn deserialize_response<T>(text: &str) -> Result<T, ClientError>
where
    T: DeserializeOwned,
{
    let response: Response<T> = serde_json::from_str(text)?;
    Ok(Into::<Result<T, ArangoError>>::into(response)?)
}

/// An enum to divide into successful and failed response.
///
/// Request to server can failed at application level, like insufficient
/// permission, database not found and etc. Response from arangoDB can tell
/// whether the query succeeded and why if it failed.
///
/// The function of this enum is almost the same as
/// Result, except that it's used to deserialize from
/// server response.
#[derive(Debug)]
pub(crate) enum Response<T> {
    Ok(T),
    Err(ArangoError),
}

impl<T> Into<Result<T, ArangoError>> for Response<T> {
    fn into(self) -> Result<T, ArangoError> {
        match self {
            Response::Ok(success) => Ok(success),
            Response::Err(err) => Err(err),
        }
    }
}

impl<'de, T> Deserialize<'de> for Response<T>
where
    T: Deserialize<'de>,
{
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let map = serde_json::Map::deserialize(deserializer)?;
        trace!("Deserialize normal Response: {:?}", map);
        let error = map
            .get("error")
            .map_or_else(|| Ok(false), Deserialize::deserialize)
            .map_err(de::Error::custom)?;
        let rest = Value::Object(map);

        if error {
            ArangoError::deserialize(rest)
                .map(Response::Err)
                .map_err(de::Error::custom)
        } else {
            T::deserialize(rest)
                .map(Response::Ok)
                .map_err(de::Error::custom)
        }
    }
}

/// Helper struct to deserialize json result that store
/// information in "result" field.
#[derive(Deserialize, Debug)]
pub(crate) struct ArangoResult<T> {
    #[serde(rename = "result")]
    result: T,
}

impl<T> ArangoResult<T> {
    pub fn unwrap(self) -> T {
        self.result
    }
}

impl<T> Deref for ArangoResult<T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        &self.result
    }
}

#[derive(Deserialize, Debug)]
pub struct Cursor<T> {
    /// the total number of result documents available
    ///
    /// only available if the query was executed with the count attribute
    /// set
    pub count: Option<usize>,
    /// a boolean flag indicating whether the query result was served from
    /// the query cache or not.
    ///
    /// If the query result is served from the query cache, the extra
    /// return attribute will not contain any stats sub-attribute
    /// and no profile sub-attribute.,
    pub cached: bool,
    /// A boolean indicator whether there are more results available for
    /// the cursor on the server
    #[serde(rename = "hasMore")]
    pub more: bool,

    /// (anonymous json object): an array of result documents (might be
    /// empty if query has no results)
    pub result: Vec<T>,
    ///  id of temporary cursor created on the server
    pub id: Option<String>,

    /// an optional JSON object with extra information about the query
    /// result contained in its stats sub-attribute. For
    /// data-modification queries, the extra.stats sub-attribute
    /// will contain the number of
    /// modified documents and the number of documents that could
    /// not be modified due to an error if ignoreErrors query
    /// option is specified.
    pub extra: Option<Extra>,
}

#[derive(Deserialize, Debug)]
pub struct Extra {
    // TODO
    stats: Option<QueryStats>,
    // TODO
    warnings: Option<Vec<Value>>,
}

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

    #[derive(Debug, Deserialize)]
    pub struct CollectionResponse {
        pub id: String,
        pub name: String,
        pub status: u8,
        pub r#type: u8,
        #[serde(rename = "isSystem")]
        pub is_system: bool,
    }

    #[test]
    fn response() {
        let text = "{\"id\":\"9947\",\"name\":\"relation\",\"status\":2,\"type\":3,\"isSystem\": \
                    false,\"globallyUniqueId\":\"hD260BE2A30F9/9947\"}";
        let result = serde_json::from_str::<Response<CollectionResponse>>(text);
        assert_eq!(result.is_ok(), true, "failed: {:?}", result);

        let text = "{\"error\":false,\"code\":412,\"id\":\"9947\",\"name\":\"relation\",\"status\"\
                    :2,\"type\":3,\"isSystem\": false,\"globallyUniqueId\":\"hD260BE2A30F9/9947\"}";
        let result = serde_json::from_str::<Response<CollectionResponse>>(text);
        assert_eq!(result.is_ok(), true, "failed: {:?}", result);

        let text = "{\"error\":true,\"code\":412,\"errorMessage\":\"error\",\"errorNum\":1200}";
        let result = serde_json::from_str::<Response<CollectionResponse>>(text);
        assert_eq!(result.is_ok(), true, "failed: {:?}", result);
        let response = Into::<Result<_, _>>::into(result.unwrap());

        assert_eq!(
            response.is_err(),
            true,
            "response should be error: {:?}",
            response
        );
    }
}