firebase/
lib.rs

1/*!
2 # Firebase REST API for Rust
3 Please have a look at the ```Firebase``` struct to get started.
4 */
5
6extern crate curl;
7extern crate url;
8extern crate rustc_serialize;
9
10use std::str;
11use std::borrow::Cow;
12use std::collections::HashMap;
13use std::sync::Arc;
14use std::thread;
15use std::thread::JoinHandle;
16
17use curl::http;
18use url::Url;
19
20use rustc_serialize::Decodable;
21use rustc_serialize::json;
22pub use rustc_serialize::json::{Json, BuilderError, DecoderError};
23
24/// A Firebase instance to manage data.
25#[derive(Clone)]
26pub struct Firebase {
27    url: Arc<Url>,
28}
29
30// TODO: Change all instances of &str to Into<String>
31// TODO: Make FB instance from Url and String.
32// TODO: Make closure lives 'static or 'fb ?
33impl Firebase {
34    /// Creates a new Firebase instance from the url of the fb server
35    /// The url should be a valid **HTTPS** url, anything else will
36    /// result in an error:
37    ///
38    /// https://docs-examples.firebaseio.com/
39    ///
40    /// # Failures
41    /// - If a url is not specified with the HTTPS scheme, a ```Err(ParseError::UrlIsNotHTTPS)```
42    ///   will be returned.
43    /// - If a url cannot be parsed into a valid url then a ```Err(ParseError::Parser(url::ParseError)```
44    ///   will be returned.
45    pub fn new(url: &str) -> Result<Self, ParseError> {
46        let url = try!( parse(&url) );
47        if url.scheme != "https" {
48            return Err(ParseError::UrlIsNotHTTPS);
49        }
50        try!( unwrap_path(&url) );
51
52        Ok(Firebase {
53            url: Arc::new(url),
54        })
55    }
56
57    /// Creates a firebase reference from a borrow of a Url instance.
58    /// # Failures
59    /// - If a url is not specified with the HTTPS scheme, a ```Err(ParseError::UrlIsNotHTTPS)```
60    ///   will be returned.
61    /// - If a url cannot be parsed into a valid url then a ```Err(ParseError::Parser(url::ParseError)```
62    ///   will be returned.
63    pub fn from_url(url: &Url) -> Result<Self, ParseError> {
64        let url = url.clone();
65        try!( unwrap_path(&url) );
66
67        Ok(Firebase {
68            url: Arc::new(url),
69        })
70    }
71
72    /// Creates a new authenticated Firebase instance from the firebaseio url and an auth token.
73    ///
74    /// # Examples
75    /// ```
76    /// # use firebase::Firebase;
77    /// let fb = Firebase::authed("https://myfb.firebaseio.com", "deadbeefcafe");
78    /// // The url shoud now be: https://myfb.firebaseio.com?auth=deadbeefcafe
79    /// ```
80    ///
81    /// # Failures
82    /// - If a url is not specified with the HTTPS scheme, a ```Err(ParseError::UrlIsNotHTTPS)```
83    ///   will be returned.
84    /// - If a url cannot be parsed into a valid url then a ```Err(ParseError::Parser(url::ParseError)```
85    ///   will be returned.
86    pub fn authed(url: &str, auth_token: &str) -> Result<Self, ParseError> {
87        let mut url = try!( parse(&url) );
88        if url.scheme != "https" {
89            return Err(ParseError::UrlIsNotHTTPS);
90        }
91        try!( unwrap_path(&url) );
92
93        let opts = vec![ (AUTH, auth_token) ];
94        url.set_query_from_pairs(opts.into_iter());
95
96        Ok(Firebase {
97            url: Arc::new(url),
98        })
99    }
100
101    /// Creates a new firebase instance that extends the path of an old firebase instance.
102    /// Each time a reference is created a clone of the Firebase instance if done, all
103    /// Firebase instances follow this immutable style.
104    ///
105    /// #Examples
106    /// ```
107    /// # use firebase::Firebase;
108    /// // Points to the root of the db ( / )
109    /// let fb = Firebase::new("https://myfb.firebaseio.com").unwrap();
110    /// // A new reference to /friends/yasha
111    /// let yasha = fb.at("/friends/yasha").unwrap();
112    /// // A new reference to /friends/yasha/messages
113    /// let messages = yasha.at("messages").unwrap();
114    pub fn at(&self, add_path: &str) -> Result<Self, ParseError> {
115        let mut url = (*self.url).clone();
116
117        { // Add path to original path, already checked for path.
118            let mut path = url.path_mut().unwrap();
119            // Remove .json from the old path's end.
120            if let Some(end) = path.pop() {
121                path.push(end.trim_right_matches(".json").to_string());
122            }
123            let add_path = add_path.trim_matches('/');
124            let add_path = if !add_path.ends_with(".json") {
125                Cow::Owned(add_path.to_string() + ".json")
126            } else {
127                Cow::Borrowed(add_path)
128            };
129
130            for component in add_path.split("/").into_iter() {
131                path.push(component.to_string());
132            }
133        }
134
135        Ok(Firebase {
136            url: Arc::new(url),
137        })
138    }
139
140    /// Creates a FirebaseParams instance, this instance has query parameters
141    /// that are associated with it and that are used in every request made.
142    /// Since query parameters only affect incomming data from Firebase, you can only
143    /// GET data with a FirebaseParams instance.
144    ///
145    /// This constructor takes in a FbOps struct to define all of its parameters,
146    /// all undefined parameters can be omitted by extending the new FbOps struct
147    /// by its default.
148    ///
149    /// # Examples
150    /// ```
151    /// # use firebase::*;
152    /// let fb = Firebase::new("https://db.fb.com").unwrap();
153    /// let query = fb.ops(&FbOps {
154    ///    order_by:       Some("Hello World"),
155    ///    limit_to_first: Some(5),
156    ///    end_at:         Some(7),
157    ///    equal_to:       Some(3),
158    ///    shallow:        Some(true),
159    ///    format:         Some(true),
160    ///    .. FbOps::default()
161    /// });
162    /// ```
163    pub fn ops(&self, opts: &FbOps) -> FirebaseParams {
164        FirebaseParams::from_ops(&self.url, opts)
165    }
166
167    /// Returns the current URL as a string that will be used
168    /// to make the REST call when talking to Firebase.
169    pub fn get_url(&self) -> String {
170        self.url.serialize()
171    }
172
173    /// Gets data from Firebase.
174    /// # Examples
175    /// ```
176    /// # use firebase::Firebase;
177    /// let firebase = Firebase::new("https://shows.firebaseio.com").unwrap();
178    /// let episode = firebase.at("/futurama/episodes/140").unwrap();
179    /// let info = episode.get();
180    /// ```
181    pub fn get(&self) -> Result<Response, ReqErr> {
182        self.request(Method::GET, None)
183    }
184
185    /// Sets data to Firebase.
186    /// # Examples
187    /// ```
188    /// # use firebase::Firebase;
189    /// let firebase = Firebase::new("https://shows.firebaseio.com").unwrap();
190    /// let episode = firebase.at("/futurama/episodes/140/description").unwrap();
191    /// let info = episode.set("The Last Episode!");
192    /// ```
193    pub fn set(&self, data: &str) -> Result<Response, ReqErr> {
194        self.request(Method::PUT, Some(data))
195    }
196
197    /// Pushes data to Firebase.
198    /// # Examples
199    /// ```
200    /// # use firebase::Firebase;
201    /// let firebase = Firebase::new("https://shows.firebaseio.com").unwrap();
202    /// let episodes = firebase.at("/futurama/episodes").unwrap();
203    /// let info = episodes.push("The Lost Episode");
204    /// ```
205    pub fn push(&self, data: &str) -> Result<Response, ReqErr> {
206        self.request(Method::POST, Some(data))
207    }
208
209    /// Updates Firebase data.
210    /// # Examples
211    /// ```
212    /// # use firebase::Firebase;
213    /// let firebase = Firebase::new("https://shows.firebaseio.com").unwrap();
214    /// let desc = firebase.at("/futurama/episodes/140/description").unwrap();
215    /// let info = desc.update("The Penultimate Episode!");
216    /// ```
217    pub fn update(&self, data: &str) -> Result<Response, ReqErr> {
218        self.request(Method::PATCH, Some(data))
219    }
220
221    /// Removes Firebase data.
222    /// # Examples
223    /// ```
224    /// # use firebase::Firebase;
225    /// let firebase = Firebase::new("https://shows.firebaseio.com").unwrap();
226    /// let episode = firebase.at("/futurama/episodes/141").unwrap();
227    /// episode.remove();
228    /// ```
229    pub fn remove(&self) -> Result<Response, ReqErr> {
230        self.request(Method::DELETE, None)
231    }
232
233    /// Asynchronous version of the get method, takes a callback
234    /// and returns a handle to the thread making the request to Firebase.
235    /// # Examples
236    /// ```
237    /// # use firebase::Firebase;
238    /// let firebase = Firebase::new("https://shows.firebaseio.com").unwrap();
239    /// let desc = firebase.at("/futurama/episodes/141/description").unwrap();
240    /// let original = "The Lost Episode";
241    /// desc.get_async(move |result| {
242    ///     if result.unwrap().body != original {
243    ///         println!("The description changed!");
244    ///     }
245    /// });
246    pub fn get_async<F>(&self, callback: F) -> JoinHandle<()>
247    where F: Fn(Result<Response, ReqErr>) + Send + 'static {
248        Firebase::request_url_async(&self.url, Method::GET, None, callback)
249    }
250
251    /// Asynchronous version of the set method, takes a callback
252    /// and returns a handle to the thread making the request to Firebase.
253    pub fn set_async<S, F>(&self, data: S, callback: F) -> JoinHandle<()>
254    where F: Fn(Result<Response, ReqErr>) + Send + 'static, S: Into<String> {
255        Firebase::request_url_async(&self.url, Method::PUT, Some(data.into()), callback)
256    }
257
258    /// Asynchronous version of the push method, takes a callback
259    /// and returns a handle to the thread making the request to Firebase.
260    pub fn push_async<S, F>(&self, data: S, callback: F) -> JoinHandle<()>
261    where F: Fn(Result<Response, ReqErr>) + Send + 'static, S: Into<String> {
262        Firebase::request_url_async(&self.url, Method::POST, Some(data.into()), callback)
263    }
264
265    /// Asynchronous version of the update method, takes a callback
266    /// and returns a handle to the thread making the request to Firebase.
267    pub fn update_async<S, F>(&self, data: S, callback: F) -> JoinHandle<()>
268    where F: Fn(Result<Response, ReqErr>) + Send + 'static, S: Into<String> {
269        Firebase::request_url_async(&self.url, Method::PATCH, Some(data.into()), callback)
270    }
271
272    /// Asynchronous version of the remove method, takes a callback
273    /// and returns a handle to the thread making the request to Firebase.
274    pub fn remove_async<F>(&self, callback: F) -> JoinHandle<()>
275    where F: Fn(Result<Response, ReqErr>) + Send + 'static {
276        Firebase::request_url_async(&self.url, Method::DELETE, None, callback)
277    }
278
279    /// Creates a ```FirebaseParams``` instance, a Firebase struct that only
280    /// knows how to GET data, and sorts this data by the key provided.
281    pub fn order_by(&self, key: &str) -> FirebaseParams {
282        self.with_params(ORDER_BY, key)
283    }
284
285    /// Creates a ```FirebaseParams``` instance, a Firebase struct that only
286    /// knows how to GET data, and limits the number of entries returned
287    /// on each request to the first ```count```. Often used with ```order_by```.
288    pub fn limit_to_first(&self, count: u32) -> FirebaseParams {
289        self.with_params(LIMIT_TO_FIRST, count)
290    }
291
292    /// Creates a ```FirebaseParams``` instance, a Firebase struct that only
293    /// knows how to GET data, and limits the number of entries returned
294    /// on each request to the last ```count```. Often used with ```order_by```.
295    pub fn limit_to_last(&self, count: u32) -> FirebaseParams {
296        self.with_params(LIMIT_TO_LAST, count)
297    }
298
299    /// Creates a ```FirebaseParams``` instance, a Firebase struct that only
300    /// knows how to GET data, and only returns entries starting after
301    /// the specified index. Often used with ```order_by```.
302    pub fn start_at(&self, index: u32) -> FirebaseParams {
303        self.with_params(START_AT, index)
304    }
305
306    /// Creates a ```FirebaseParams``` instance, a Firebase struct that only
307    /// knows how to GET data, and only returns entries appearing before
308    /// the specified index. Often used with ```order_by```.
309    pub fn end_at(&self, index: u32) -> FirebaseParams {
310        self.with_params(END_AT, index)
311    }
312
313    /// Creates a ```FirebaseParams``` instance, a Firebase struct that only
314    /// knows how to GET data, and returns only the entry at the specified
315    /// index. Often used with ```order_by```.
316    pub fn equal_to(&self, index: u32) -> FirebaseParams {
317        self.with_params(EQUAL_TO, index)
318    }
319
320    /// Creates a ```FirebaseParams``` instance, a Firebase struct that only
321    /// knows how to GET data, and only returns a shallow copy of the db
322    /// in every request.
323    pub fn shallow(&self, flag: bool) -> FirebaseParams {
324        self.with_params(SHALLOW, flag)
325    }
326
327    /// Creates a ```FirebaseParams``` instance, a Firebase struct that only
328    /// knows how to GET data, and formats the data to be exported in every
329    /// request. (e.g. includes a priority field).
330    pub fn format(&self) -> FirebaseParams {
331        self.with_params(FORMAT, EXPORT)
332    }
333
334    #[inline]
335    fn request(&self, method: Method, data: Option<&str>) -> Result<Response, ReqErr> {
336        Firebase::request_url(&self.url, method, data)
337    }
338
339    fn request_url(url: &Url, method: Method, data: Option<&str>) -> Result<Response, ReqErr> {
340        let mut handler = http::handle();
341
342        let req = match method {
343            Method::GET     => handler.get(   url),
344            Method::POST    => handler.post(  url, data.unwrap()),
345            Method::PUT     => handler.put(   url, data.unwrap()),
346            Method::PATCH   => handler.patch( url, data.unwrap()),
347            Method::DELETE  => handler.delete(url),
348        };
349
350        let res = match req.exec() {
351            Ok(r)  => r,
352            Err(e) => return Err(ReqErr::NetworkErr(e)),
353        };
354
355        let body = match str::from_utf8(res.get_body()) {
356            Ok(b)  => b,
357            Err(e) => return Err(ReqErr::RespNotUTF8(e)),
358        };
359
360        Ok(Response {
361            body: body.to_string(),
362            code: res.get_code(),
363        })
364    }
365
366    fn request_url_async<F>(url: &Arc<Url>, method: Method, data: Option<String>, callback: F) -> JoinHandle<()>
367    where F: Fn(Result<Response, ReqErr>) + Send + 'static {
368        // Fast, because its in an arc.
369        let url = url.clone();
370
371        thread::spawn(move || {
372            callback(Firebase::request_url(&url, method, data.as_ref().map(|s| s as &str)));
373        })
374    }
375
376    fn with_params<T: ToString>(&self, key: &'static str, value: T) -> FirebaseParams {
377        FirebaseParams::new(&self.url, key, value)
378    }
379}
380
381/// The FirebaseParams struct is a Firebase reference with attatched
382/// query parameters that allow you to sort, limit, and format the data
383/// received from Firebase.
384///
385/// It has been made into its own struct because the Firebase API specifies
386/// that you can only GET data with query parameters. And so taking advantage of
387/// type systems, this struct can only GET data.
388///
389/// You can add any number of parameters to this struct by chaining calls together:
390///
391/// ```
392/// # use firebase::*;
393/// let episodes = Firebase::new("https://futurama.firebaseio.com/episodes/").unwrap();
394/// let alphabetic = episodes.order_by("\"title\"").limit_to_first(5);
395/// let first5 = alphabetic.get();
396/// ```
397///
398/// Setting the same parameter overwrites the previous parameter:
399///
400/// ```
401/// # use firebase::*;
402/// let episodes = Firebase::new("https://arrdev.firebaseio.com/episodes/").unwrap();
403/// // This will create a request that gets entries starting at the 0th index.
404/// let skip10 = episodes.start_at(10).start_at(0);
405/// ```
406#[derive(Clone)]
407pub struct FirebaseParams {
408    url: Arc<Url>,
409    params: HashMap<&'static str, String>,
410}
411
412impl FirebaseParams {
413    /// Gets data from Firebase.
414    /// # Examples
415    /// ```
416    /// # use firebase::Firebase;
417    /// let episodes = Firebase::new("https://futurama.firebaseio.com/episodes/").unwrap();
418    /// let alphabetic = episodes.order_by("\"title\"").limit_to_first(5);
419    /// let first5 = alphabetic.get();
420    /// ```
421    pub fn get(&self) -> Result<Response, ReqErr> {
422        Firebase::request_url(&self.url, Method::GET, None)
423    }
424
425    /// Asynchronous version of the get method, takes a callback
426    /// and returns a handle to the thread making the request to Firebase.
427    pub fn get_async<F>(&self, callback: F) -> JoinHandle<()>
428    where F: Fn(Result<Response, ReqErr>) + Send + 'static {
429        Firebase::request_url_async(&self.url, Method::GET, None, callback)
430    }
431
432    /// Returns the current URL as a string that will be used
433    /// to make the REST call when talking to Firebase.
434    pub fn get_url(&self) -> String {
435        self.url.serialize()
436    }
437
438    // TODO: Wrap in quotes if not already. Or always wrap in quotes.
439    /// Modifies the current ```FirebaseParams``` instance
440    /// and sorts this data by the key provided.
441    pub fn order_by(self, key: &str) -> Self {
442        self.add_param(ORDER_BY, key)
443    }
444
445    /// Modifies the current ```FirebaseParams``` instance
446    /// and limits the number of entries returned
447    /// on each request to the first ```count```. Often used with ```order_by```.
448    pub fn limit_to_first(self, count: u32) -> Self {
449        self.add_param(LIMIT_TO_FIRST, count)
450    }
451
452    /// Modifies the current ```FirebaseParams``` instance
453    /// and limits the number of entries returned
454    /// on each request to the last ```count```. Often used with ```order_by```.
455    pub fn limit_to_last(self, count: u32) -> Self {
456        self.add_param(LIMIT_TO_LAST, count)
457    }
458
459    /// Modifies the current ```FirebaseParams``` instance
460    /// and only returns entries starting after
461    /// the specified index. Often used with ```order_by```.
462    pub fn start_at(self, index: u32) -> Self {
463        self.add_param(START_AT, index)
464    }
465
466    /// Modifies the current ```FirebaseParams``` instance
467    /// and only returns entries appearing before
468    /// the specified index. Often used with ```order_by```.
469    pub fn end_at(self, index: u32) -> Self {
470        self.add_param(END_AT, index)
471    }
472
473    /// Modifies the current ```FirebaseParams``` instance
474    /// and returns only the entry at the specified
475    /// index. Often used with ```order_by```.
476    pub fn equal_to(self, value: u32) -> Self {
477        self.add_param(EQUAL_TO, value)
478    }
479
480    /// Modifies the current ```FirebaseParams``` instance
481    /// and only returns a shallow copy of the db
482    /// in every request.
483    pub fn shallow(self, flag: bool) -> Self {
484        self.add_param(SHALLOW, flag)
485    }
486
487    /// Modifies the current ```FirebaseParams``` instance
488    /// and formats the data to be exported in every
489    /// request. (e.g. includes a priority field)
490    pub fn format(self) -> Self {
491        self.add_param(FORMAT, EXPORT)
492    }
493
494    fn add_param<T: ToString>(mut self, key: &'static str, value: T) -> Self {
495        let value = value.to_string();
496        self.params.insert(key, value);
497        self.set_params();
498        self
499    }
500
501    fn set_params(&mut self) {
502        // Only clones the url when edited. This is CoW
503        // Many threads can run requests without ever cloning the url.
504        let mut url = (*self.url).clone();
505        url.set_query_from_pairs(self.params.iter().map(|(&k, v)| (k, v as &str)));
506        self.url = Arc::new(url);
507    }
508
509    fn get_auth(url: &Url) -> HashMap<&'static str, String> {
510        let mut pair: HashMap<&'static str, String> = HashMap::new();
511
512        if let Some(queries) = url.query_pairs() {
513            for &(ref k, ref v) in queries.iter() {
514                if k == AUTH {
515                    pair.insert(AUTH, v.to_string());
516                }
517            }
518        }
519        pair
520    }
521
522    fn new<T: ToString>(url: &Url, key: &'static str, value: T) -> Self {
523        let me = FirebaseParams {
524            url: Arc::new(url.clone()),
525            params: FirebaseParams::get_auth(&url),
526        };
527        me.add_param(key, value)
528    }
529
530    fn from_ops(url: &Url, opts: &FbOps) -> Self {
531        let mut me = FirebaseParams {
532            url: Arc::new(url.clone()),
533            params: FirebaseParams::get_auth(&url),
534        };
535        if let Some(order) = opts.order_by {
536            me.params.insert(ORDER_BY, order.to_string());
537        }
538        if let Some(first) = opts.limit_to_first {
539            me.params.insert(LIMIT_TO_FIRST, first.to_string());
540        }
541        if let Some(last) = opts.limit_to_last {
542            me.params.insert(LIMIT_TO_LAST, last.to_string());
543        }
544        if let Some(start) = opts.start_at {
545            me.params.insert(START_AT, start.to_string());
546        }
547        if let Some(end) = opts.end_at {
548            me.params.insert(END_AT, end.to_string());
549        }
550        if let Some(equal) = opts.equal_to {
551            me.params.insert(EQUAL_TO, equal.to_string());
552        }
553        if let Some(shallow) = opts.shallow {
554            me.params.insert(SHALLOW, shallow.to_string());
555        }
556        if let Some(format) = opts.format {
557            if format {
558                me.params.insert(FORMAT, EXPORT.to_string());
559            }
560        }
561        // Copy all of the params into the url.
562        me.set_params();
563        me
564    }
565}
566
567enum Method {
568    GET,
569    POST,
570    PUT,
571    PATCH,
572    DELETE,
573}
574
575const ORDER_BY:       &'static str = "orderBy";
576const LIMIT_TO_FIRST: &'static str = "limitToFirst";
577const LIMIT_TO_LAST:  &'static str = "limitToLast";
578const START_AT:       &'static str = "startAt";
579const END_AT:         &'static str = "endAt";
580const EQUAL_TO:       &'static str = "equalTo";
581const SHALLOW:        &'static str = "shallow";
582const FORMAT:         &'static str = "format";
583const EXPORT:         &'static str = "export";
584const AUTH:           &'static str = "auth";
585
586#[derive(Debug)]
587pub struct FbOps<'l> {
588    pub order_by:       Option<&'l str>,
589    pub limit_to_first: Option<u32>,
590    pub limit_to_last:  Option<u32>,
591    pub start_at:       Option<u32>,
592    pub end_at:         Option<u32>,
593    pub equal_to:       Option<u32>,
594    pub shallow:        Option<bool>,
595    pub format:         Option<bool>,
596}
597
598impl<'l> Default for FbOps<'l> {
599    fn default() -> Self {
600        FbOps {
601            order_by:       None,
602            limit_to_first: None,
603            limit_to_last:  None,
604            start_at:       None,
605            end_at:         None,
606            equal_to:       None,
607            shallow:        None,
608            format:         None,
609        }
610    }
611}
612
613#[derive(Debug)]
614pub enum ReqErr {
615    ReqNotJSON,
616    RespNotUTF8(str::Utf8Error),
617    NetworkErr(curl::ErrCode),
618}
619
620#[derive(Debug)]
621pub enum ParseError {
622    UrlHasNoPath,
623    UrlIsNotHTTPS,
624    Parser(url::ParseError),
625}
626
627#[derive(Debug)]
628pub struct Response {
629    pub body: String,
630    pub code: u32,
631}
632
633impl Response {
634    /// Returns true if the status code is 200
635    pub fn is_success(&self) -> bool {
636        self.code == 200
637    }
638
639    /// Turns the response body into a Json enum.
640    pub fn json(&self) -> Result<Json, BuilderError> {
641        Json::from_str(&self.body)
642    }
643
644    /// Encodes the data received into a struct matching the data.
645    /// # Examples
646    ///
647    /// ```
648    /// # use firebase::Response;
649    /// let response = Response {
650    ///     body: "324567898".to_string(),
651    ///     code: 200,
652    /// };
653    ///
654    /// let parsed: u32 = response.parse().unwrap();
655    /// println!("Data is: {}", parsed);
656    /// ```
657    pub fn parse<D>(&self) -> Result<D, DecoderError> where D: Decodable {
658        json::decode(&self.body)
659    }
660}
661
662fn parse(url: &str) -> Result<Url, ParseError> {
663    match Url::parse(&url) {
664        Ok(u)  => Ok(u),
665        Err(e) => Err(ParseError::Parser(e)),
666    }
667}
668
669fn unwrap_path(url: &Url) -> Result<&[String], ParseError> {
670    match url.path() {
671        None    => return Err(ParseError::UrlHasNoPath),
672        Some(p) => return Ok(p),
673    }
674}
675
676// This code will happen when Trait Specialization becomes available
677// in rust.
678// pub trait ToJsonStr {
679//     fn to_json_str(&self) -> Result<Cow<str>, /* TODO */ Box<Error>>;
680// }
681//
682// impl<'l> ToJsonStr for &'l str {
683//     fn to_json_str(&self) -> Result<Cow<str>, /* TODO */ Box<Error>> {
684//         Ok(Cow::Borrowed(self))
685//     }
686// }
687//
688// impl<S> ToJsonStr for S where S: Encodable {
689//     fn to_json_str(&self) -> Result<Cow<str>, /* TODO */ Box<Error>> {
690//         match json::encode(self) {
691//             Ok(encoded) => Ok(Cow::Owned(encoded)),
692//             Err(e)      => Err(Box::new(e)),
693//         }
694//     }
695// }