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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
use hyper;
use serde_json;
use std;
use url;

use ErrorResponse;

/// Principal error type.
///
/// An `Error` specifies an error originating from or propagated by the couchdb
/// crate.
///
/// Error variants with a value of the type `Option<ErrorResponse>` signify
/// error responses from the CouchDB server. The value for these variants is
/// `None` if the request causing the error was a HEAD request, and `Some`
/// otherwise. This reflects how the server returns no detailed error
/// information for HEAD requests.
///
#[derive(Debug)]
pub enum Error {
    /// The database path is in an invalid format.
    BadDatabasePath(BadPathKind),

    /// The design document path is in an invalid format.
    BadDesignDocumentPath(BadPathKind),

    /// The document path is in an invalid format.
    BadDocumentPath(BadPathKind),

    // The MD5 hash is in an invalid format.
    #[doc(hidden)]
    BadMd5Hash,

    /// The client request is invalid.
    BadRequest(Option<ErrorResponse>),

    /// The revision is in an invalid format.
    BadRevision,

    /// The UUID is in an invalid format.
    BadUuid,

    /// The view path is in an invalid format.
    BadViewPath(BadPathKind),

    /// The database already exists.
    DatabaseExists(Option<ErrorResponse>),

    // JSON-decoding error.
    #[doc(hidden)]
    Decode(DecodeErrorKind),

    /// The client request conflicts with an existing document.
    DocumentConflict(Option<ErrorResponse>),

    // JSON-encoding error.
    #[doc(hidden)]
    Encode(EncodeErrorKind),

    /// An internal server error occurred.
    InternalServerError(Option<ErrorResponse>),

    // The string has an invalid percent-encoding.
    #[doc(hidden)]
    InvalidPercentEncoding {
        encoding: String,
    },

    // I/O error with a compile-time description.
    #[doc(hidden)]
    Io {
        cause: std::io::Error,
        description: &'static str,
    },

    // The CouchDB server responded without a Content-Type header.
    #[doc(hidden)]
    NoContentTypeHeader {
        expected: &'static str,
    },

    /// The resource does not exist.
    ///
    /// In case of a HEAD request, the response value is `None` (because the
    /// server doesn't send response content for HEAD requests). For all other
    /// request methods, the response value is `Some`.
    ///
    NotFound(Option<ErrorResponse>),

    // Channel-receiver error with a compile-time description and thread-join
    // error.
    #[doc(hidden)]
    ReceiveFromThread {
        cause: std::sync::mpsc::RecvError,
        description: &'static str,
    },

    // HTTP-transport error.
    #[doc(hidden)]
    Transport(TransportKind),

    /// The client is unauthorized to carry out the operation.
    ///
    /// In case of a HEAD request, the response value is `None` (because the
    /// server doesn't send response content for HEAD requests). For all other
    /// request methods, the response value is `Some`.
    ///
    Unauthorized(Option<ErrorResponse>),

    // The CouchDB server responded with a Content-Type header that the client
    // didn't expect.
    #[doc(hidden)]
    UnexpectedContentTypeHeader {
        expected: &'static str,
        got: String,
    },

    // The CouchDB server responded with an HTTP status code that the client
    // didn't expect.
    #[doc(hidden)]
    UnexpectedHttpStatus {
        got: hyper::status::StatusCode,
    },

    // URI-parse error.
    #[doc(hidden)]
    UriParse {
        cause: url::ParseError,
    },
}

impl std::error::Error for Error {
    fn description(&self) -> &str {
        use self::Error::*;
        match *self {
            BadDatabasePath(..) => "The database path is in an invalid format",
            BadDesignDocumentPath(..) => "The design document path is in an invalid format",
            BadDocumentPath(..) => "The document path is in an invalid format",
            BadMd5Hash => "The MD5 hash is in an invalid format",
            BadRequest(..) => "The client request is invalid",
            BadRevision => "The revision is in an invalid format",
            BadUuid => "The UUID is in an invalid format",
            BadViewPath(..) => "The view path is in an invalid format",
            DatabaseExists(..) => "The database already exists",
            Decode(..) => "The client failed to decode a JSON response from the CouchDB server",
            DocumentConflict(..) => "The client request conflicts with an existing document",
            Encode(..) => "The client failed to encode a JSON request to the CouchDB server",
            InternalServerError(..) => "An internal server error occurred",
            InvalidPercentEncoding { .. } => "The string has an invalid percent-encoding",
            Io{ ref description, .. } => description,
            NoContentTypeHeader { .. } => {
                "The CouchDB server responded without a Content-Type header"
            }
            NotFound(..) => "The resource does not exist",
            ReceiveFromThread { ref description, .. } => description,
            Transport(..) => "An HTTP transport error occurred",
            Unauthorized(..) => "The client is unauthorized to carry out the operation",
            UnexpectedContentTypeHeader { .. } => {
                "The CouchDB server responded with a Content-Type header the client did not expect"
            }
            UnexpectedHttpStatus { .. } => {
                "The CouchDB server responded with an HTTP status code the client did not expect"
            }
            UriParse { .. } => "Invalid URI argument",
        }
    }

    fn cause(&self) -> std::option::Option<&std::error::Error> {
        use self::Error::*;
        match *self {
            BadDatabasePath(ref kind) => kind.cause(),
            BadDesignDocumentPath(ref kind) => kind.cause(),
            BadDocumentPath(ref kind) => kind.cause(),
            BadMd5Hash => None,
            BadRequest(..) => None,
            BadRevision => None,
            BadUuid => None,
            BadViewPath(ref kind) => kind.cause(),
            DatabaseExists(..) => None,
            Decode(ref kind) => kind.cause(),
            DocumentConflict(..) => None,
            Encode(ref kind) => kind.cause(),
            InternalServerError(..) => None,
            InvalidPercentEncoding { .. } => None,
            Io{ref cause, ..} => Some(cause),
            NoContentTypeHeader { .. } => None,
            NotFound(..) => None,
            ReceiveFromThread { ref cause, .. } => Some(cause),
            Transport(ref kind) => kind.cause(),
            Unauthorized(..) => None,
            UnexpectedContentTypeHeader { .. } => None,
            UnexpectedHttpStatus { .. } => None,
            UriParse { ref cause } => Some(cause),
        }
    }
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        use self::Error::*;
        let d = {
            use std::error::Error;
            self.description()
        };
        match *self {
            BadDatabasePath(ref kind) => write!(f, "{}: {}", d, kind),
            BadDesignDocumentPath(ref kind) => write!(f, "{}: {}", d, kind),
            BadDocumentPath(ref kind) => write!(f, "{}: {}", d, kind),
            BadMd5Hash => write!(f, "{}", d),
            BadRequest(None) => write!(f, "{}", d),
            BadRequest(Some(ref response)) => write!(f, "{}: {}", d, response),
            BadRevision => write!(f, "{}", d),
            BadUuid => write!(f, "{}", d),
            BadViewPath(ref kind) => write!(f, "{}: {}", d, kind),
            DatabaseExists(None) => write!(f, "{}", d),
            DatabaseExists(Some(ref response)) => write!(f, "{}: {}", d, response),
            Decode(ref kind) => write!(f, "{}: {}", d, kind),
            DocumentConflict(None) => write!(f, "{}", d),
            DocumentConflict(Some(ref response)) => write!(f, "{}: {}", d, response),
            Encode(ref kind) => write!(f, "{}: {}", d, kind),
            InternalServerError(None) => write!(f, "{}", d),
            InternalServerError(Some(ref response)) => write!(f, "{}: {}", d, response),
            InvalidPercentEncoding{ref encoding} => write!(f, "{}: Got '{}'", d, encoding),
            Io{ref cause, ..} => write!(f, "{}: {}", d, cause),
            NoContentTypeHeader{ref expected} => write!(f, "{}: Expected '{}'", d, expected),
            NotFound(None) => write!(f, "{}", d),
            NotFound(Some(ref response)) => write!(f, "{}: {}", d, response),
            ReceiveFromThread{ref cause, ..} => write!(f, "{}: {}", d, cause),
            Transport(ref kind) => write!(f, "{}: {}", d, kind),
            Unauthorized(None) => write!(f, "{}", d),
            Unauthorized(Some(ref response)) => write!(f, "{}: {}", d, response),
            UnexpectedContentTypeHeader{ref expected, ref got} => {
                write!(f, "{}: Expected '{}', got '{}'", d, expected, got)
            }
            UnexpectedHttpStatus{ref got} => write!(f, "{}: Got {}", d, got),
            UriParse{ref cause} => write!(f, "{}: {}", d, cause),
        }
    }
}

#[derive(Debug)]
pub enum BadPathKind {
    // TODO: It would be helpful to say 'why' the percent-encoding is invalid.
    BadPercentEncoding,
    NoLeadingSlash,
    NotDatabase,
    NotDesignDocument,
    NotDocument,
    NotView,
}

impl BadPathKind {
    fn cause(&self) -> Option<&std::error::Error> {
        use self::BadPathKind::*;
        match *self {
            BadPercentEncoding => None,
            NoLeadingSlash => None,
            NotDatabase => None,
            NotDesignDocument => None,
            NotDocument => None,
            NotView => None,
        }
    }
}

impl std::fmt::Display for BadPathKind {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        use self::BadPathKind::*;
        match *self {
            BadPercentEncoding => write!(f, "Path contains an invalid percent-encoding"),
            NoLeadingSlash => write!(f, "No leading slash"),
            NotDatabase => write!(f, "Path does not specify a database"),
            NotDesignDocument => write!(f, "Path does not specify a design document"),
            NotDocument => write!(f, "Path does not specify a document"),
            NotView => write!(f, "Path does not specify a view"),
        }
    }
}

#[derive(Debug)]
pub enum DecodeErrorKind {
    Serde {
        cause: serde_json::Error,
    },
    TrailingContent,
}

impl DecodeErrorKind {
    fn cause(&self) -> Option<&std::error::Error> {
        use self::DecodeErrorKind::*;
        match *self {
            Serde { ref cause } => Some(cause),
            TrailingContent => None,
        }
    }
}

impl std::fmt::Display for DecodeErrorKind {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        use self::DecodeErrorKind::*;
        match *self {
            Serde { ref cause } => cause.fmt(f),
            TrailingContent => write!(f, "Unexpected content at end of response body"),
        }
    }
}

#[derive(Debug)]
pub enum EncodeErrorKind {
    Serde {
        cause: serde_json::Error,
    },
}

impl EncodeErrorKind {
    fn cause(&self) -> Option<&std::error::Error> {
        use self::EncodeErrorKind::*;
        match *self {
            Serde { ref cause } => Some(cause),
        }
    }
}

impl std::fmt::Display for EncodeErrorKind {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        use self::EncodeErrorKind::*;
        match *self {
            Serde { ref cause } => cause.fmt(f),
        }
    }
}

#[derive(Debug)]
pub enum TransportKind {
    Hyper(hyper::error::Error),
    Io(std::io::Error),
}

impl TransportKind {
    fn cause(&self) -> std::option::Option<&std::error::Error> {
        use self::TransportKind::*;
        match *self {
            Hyper(ref e) => Some(e),
            Io(ref e) => Some(e),
        }
    }
}

impl std::fmt::Display for TransportKind {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        use self::TransportKind::*;
        match *self {
            Hyper(ref e) => e.fmt(f),
            Io(ref e) => e.fmt(f),
        }
    }
}