<commit_before>/*
 * Copyright (c) 2017-2019 Boucher, Antoni <bouanto@zoho.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

//! Asynchronous GUI library based on GTK+.
//!
//! This library provides a [`Widget`](trait.Widget.html) trait that you can use to create asynchronous GUI components.
//! This is the trait you will need to implement for your application.
//! It helps you to implement MVC (Model, View, Controller) in an elegant way.
//!
//! ## Installation
//! Add this to your `Cargo.toml`:
//!
//! ```toml
//! [dependencies]
//! gtk = "^0.3.0"
//! relm = "^0.11.0"
//! relm-derive = "^0.11.0"
//! ```
//!
//! More info can be found in the [readme](https://github.com/antoyo/relm#relm).

#![warn(
    missing_docs,
    trivial_casts,
    trivial_numeric_casts,
    unused_extern_crates,
    unused_import_braces,
    unused_qualifications,
)]

/*
 * TODO: maybe remove SimpleMsg.
 *
 * TODO: improve README so that examples can be copy/pasted.
 * TODO: add tests for relm-derive-state.
 *
 * FIXME: some relm widgets requires { and } (see the rusic music-player) while other do not.
 * FIXME: should not require to import WidgetExt because it calls show().
 * FIXME: cannot have relm event with tuple as a value like:
 * RelmWidget {
 *     RelmEvent((value1, value2)) => …
 * }
 * TODO: use pub(crate) instead of pub so that we're not bound to make the model and msg structs pub.
 *
 * TODO: add init() method to the Widget (or Update) trait as a shortcut for init::<Widget>()?
 *
 * TODO: alternative to tokio with a trait to get FD and add this FD to glib (perhaps using a
 * GSource).
 * For timers, add a connect_timeout!() macro or something.
 *
 * TODO: show a warning when a component is imediately destroyed.
 * FIXME: cannot add a trailing coma at the end of a initializer list.
 * TODO: switch from gtk::main() to MainLoop to avoid issues with nested loops.
 * TODO: prefix generated container name with _ to hide warnings.
 * TODO: remove the code generation related to using self in event handling.
 * TODO: remove the closure transformer code.
 *
 * TODO: move most of the examples in the tests/ directory.
 * TODO: add construct-only properties for relm widget (to replace initial parameters) to allow
 * setting them by name (or with default value).
 * TODO: find a way to do two-step initialization (to avoid using unitialized in model()).
 * TODO: consider using GBinding instead of manually adding calls to set_property().
 *
 * TODO: add a FAQ with the question related to getting an error when not importing the gtk traits.
 *
 * TODO: show a warning when components are destroyed after the end of call to Widget::view().
 * TODO: warn in the attribute when an event cycle is found.
 * TODO: add a Deref<Widget> for Component?
 * TODO: look at how Elm works with the <canvas> element.
 * TODO: the widget names should start with __relm_field_.
 *
 * TODO: reset widget name counters when creating new widget?
 *
 * TODO: refactor the code.
 *
 * TODO: chat client/server example.
 *
 * TODO: err if trying to use the SimpleMsg custom derive on stable.
 *
 * TODO: add default type of () for Model in Widget when it is stable.
 * TODO: optionnaly multi-threaded.
 */

extern crate cairo;
extern crate glib;
extern crate gobject_sys;
extern crate gtk;
extern crate relm_core;
extern crate relm_state;

mod component;
mod container;
mod drawing;
mod macros;
#[doc(hidden)]
pub mod vendor;
mod widget;

#[doc(hidden)]
pub use glib::Cast;
#[doc(hidden)]
pub use glib::translate::{FromGlibPtrNone, ToGlib, ToGlibPtr};
#[doc(hidden)]
pub use gobject_sys::{GParameter, g_object_newv};
use gtk::Continue;

pub use relm_core::{Channel, EventStream, Sender};
pub use relm_state::{
    DisplayVariant,
    IntoOption,
    IntoPair,
    Relm,
    Update,
    UpdateNew,
    execute,
};
use relm_state::init_component;

pub use component::Component;
pub use container::{Container, ContainerComponent, ContainerWidget};
pub use drawing::DrawHandler;
pub use widget::{Widget, WidgetTest};

/// Dummy macro to be used with `#[derive(Widget)]`.
#[macro_export]
macro_rules! impl_widget {
    ($($tt:tt)*) => {
        ()
    };
}

/// Macro to be used as a stable alternative to the `#[widget]` attribute.
///
/// An example can be found [here](https://github.com/antoyo/relm/blob/master/examples/buttons-derive/src/main.rs#L50).
#[macro_export]
macro_rules! relm_widget {
    ($($tts:tt)*) => {
        mod __relm_gen_private {
            use super::*;

            #[derive(Widget)]
            struct __RelmPrivateWidget {
                widget: impl_widget! {
                    $($tts)*
                }
            }
        }

        use_impl_self_type!($($tts)*);
    };
}

#[doc(hidden)]
#[macro_export]
macro_rules! use_impl_self_type {
    (impl $(::relm::)*Widget for $self_type:ident { $($tts:tt)* }) => {
        pub use self::__relm_gen_private::$self_type;
    };
}

fn create_widget_test<WIDGET>(model_param: WIDGET::ModelParam) -> (Component<WIDGET>, WIDGET::Widgets)
    where WIDGET: Widget + WidgetTest + 'static,
          WIDGET::Msg: DisplayVariant + 'static,
{
    let (widget, component, relm): (_, WIDGET, _) = create_widget(model_param);
    let widgets = component.get_widgets();
    init_component::<WIDGET>(widget.stream(), component, &relm);
    (widget, widgets)
}

/// Create a new relm widget without adding it to an existing widget.
/// This is useful when a relm widget is at the root of another relm widget.
pub fn create_component<CHILDWIDGET>(model_param: CHILDWIDGET::ModelParam)
        -> Component<CHILDWIDGET>
    where CHILDWIDGET: Widget + 'static,
          CHILDWIDGET::Msg: DisplayVariant + 'static,
{
    let (widget, component, child_relm) = create_widget::<CHILDWIDGET>(model_param);
    init_component::<CHILDWIDGET>(widget.stream(), component, &child_relm);
    widget
}

/// Create a new relm container widget without adding it to an existing widget.
/// This is useful when a relm widget is at the root of another relm widget.
pub fn create_container<CHILDWIDGET>(model_param: CHILDWIDGET::ModelParam)
        -> ContainerComponent<CHILDWIDGET>
    where CHILDWIDGET: Container + Widget + 'static,
          CHILDWIDGET::Msg: DisplayVariant + 'static,
{
    let (widget, component, child_relm) = create_widget::<CHILDWIDGET>(model_param);
    let container = component.container().clone();
    let containers = component.other_containers();
    init_component::<CHILDWIDGET>(widget.stream(), component, &child_relm);
    ContainerComponent::new(widget, container, containers)
}

/// Create a new relm widget with `model_param` as initialization value.
fn create_widget<WIDGET>(model_param: WIDGET::ModelParam)
    -> (Component<WIDGET>, WIDGET, Relm<WIDGET>)
    where WIDGET: Widget + 'static,
          WIDGET::Msg: DisplayVariant + 'static,
{
    let stream = EventStream::new();

    let relm = Relm::new(stream.clone());
    let model = WIDGET::model(&relm, model_param);
    let mut widget = WIDGET::view(&relm, model);
    widget.init_view();

    let root = widget.root().clone();
    (Component::new(stream, root), widget, relm)
}

/// Initialize a widget for a test.
///
/// It is to be used this way:
///
/// ```
/// # extern crate gtk;
/// # #[macro_use]
/// # extern crate relm;
/// # #[macro_use]
/// # extern crate relm_derive;
/// #
/// # use gtk::{Window, WindowType};
/// # use relm::{Relm, Update, Widget, WidgetTest};
/// #
/// # #[derive(Clone)]
/// # struct Win {
/// #     window: Window,
/// # }
/// #
/// # impl Update for Win {
/// #     type Model = ();
/// #     type ModelParam = ();
/// #     type Msg = Msg;
/// #
/// #     fn model(_: &Relm<Self>, _: ()) -> () {
/// #         ()
/// #     }
/// #
/// #     fn update(&mut self, event: Msg) {
/// #     }
/// # }
/// #
/// # impl WidgetTest for Win {
/// #     type Widgets = Win;
/// #
/// #     fn get_widgets(&self) -> Self::Widgets {
/// #         self.clone()
/// #     }
/// # }
/// #
/// # impl Widget for Win {
/// #     type Root = Window;
/// #
/// #     fn root(&self) -> Self::Root {
/// #         self.window.clone()
/// #     }
/// #
/// #     fn view(relm: &Relm<Self>, _model: Self::Model) -> Self {
/// #         let window = Window::new(WindowType::Toplevel);
/// #
/// #         Win {
/// #             window: window,
/// #         }
/// #     }
/// # }
/// #
/// # #[derive(Msg)]
/// # enum Msg {}
/// # fn main() {
/// let (component, widgets) = relm::init_test::<Win>(()).expect("init_test failed");
/// # }
/// ```
pub fn init_test<WIDGET>(model_param: WIDGET::ModelParam) ->
    Result<(Component<WIDGET>, WIDGET::Widgets), ()>
    where WIDGET: Widget + WidgetTest + 'static,
          WIDGET::Msg: DisplayVariant + 'static,
{
    gtk::init().map_err(|_| ())?;
    let component = create_widget_test::<WIDGET>(model_param);
    Ok(component)
}

/// Initialize a widget.
pub fn init<WIDGET>(model_param: WIDGET::ModelParam) -> Result<Component<WIDGET>, ()>
    where WIDGET: Widget + 'static,
          WIDGET::Msg: DisplayVariant + 'static
{
    let (widget, component, relm) = create_widget::<WIDGET>(model_param);
    init_component::<WIDGET>(widget.stream(), component, &relm);
    Ok(widget)
}

/// Create the specified relm `Widget` and run the main event loops.
///
/// ```
/// # extern crate gtk;
/// # #[macro_use]
/// # extern crate relm;
/// # #[macro_use]
/// # extern crate relm_derive;
/// #
/// # use gtk::{Window, WindowType};
/// # use relm::{Relm, Update, Widget};
/// #
/// # struct Win {
/// #     window: Window,
/// # }
/// #
/// # impl Update for Win {
/// #     type Model = ();
/// #     type ModelParam = ();
/// #     type Msg = Msg;
/// #
/// #     fn model(_: &Relm<Self>, _: ()) -> () {
/// #         ()
/// #     }
/// #
/// #     fn update(&mut self, event: Msg) {
/// #     }
/// # }
/// #
/// # impl Widget for Win {
/// #     type Root = Window;
/// #
/// #     fn root(&self) -> Self::Root {
/// #         self.window.clone()
/// #     }
/// #
/// #     fn view(relm: &Relm<Self>, _model: Self::Model) -> Self {
/// #         let window = Window::new(WindowType::Toplevel);
/// #
/// #         Win {
/// #             window: window,
/// #         }
/// #     }
/// # }
/// # #[derive(Msg)]
/// # enum Msg {}
/// # fn main() {
/// # }
/// #
/// # fn run() {
/// /// `Win` is a relm `Widget`.
/// Win::run(()).expect("Win::run failed");
/// # }
/// ```
pub fn run<WIDGET>(model_param: WIDGET::ModelParam) -> Result<(), ()>
    where WIDGET: Widget + 'static,
{
    gtk::init().map_err(|_| ())?;
    let _component = init::<WIDGET>(model_param)?;
    gtk::main();
    Ok(())
}

/// Emit the `msg` every `duration` ms.
pub fn interval<F: Fn() -> MSG + 'static, MSG: 'static>(stream: &EventStream<MSG>, duration: u32, constructor: F) {
    let stream = stream.clone();
    gtk::timeout_add(duration, move || {
        let msg = constructor();
        stream.emit(msg);
        Continue(true)
    });
}

/// After `duration` ms, emit `msg`.
pub fn timeout<F: Fn() -> MSG + 'static, MSG: 'static>(stream: &EventStream<MSG>, duration: u32, constructor: F) {
    let stream = stream.clone();
    gtk::timeout_add(duration, move || {
        let msg = constructor();
        stream.emit(msg);
        Continue(false)
    });
}
<commit_msg>update lib.rs to have more recent versions of gtk, relm, relm-derive in docs<commit_after>/*
 * Copyright (c) 2017-2019 Boucher, Antoni <bouanto@zoho.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

//! Asynchronous GUI library based on GTK+.
//!
//! This library provides a [`Widget`](trait.Widget.html) trait that you can use to create asynchronous GUI components.
//! This is the trait you will need to implement for your application.
//! It helps you to implement MVC (Model, View, Controller) in an elegant way.
//!
//! ## Installation
//! Add this to your `Cargo.toml`:
//!
//! ```toml
//! [dependencies]
//! gtk = "^0.6.0"
//! relm = "^0.16.0"
//! relm-derive = "^0.16.0"
//! ```
//!
//! More info can be found in the [readme](https://github.com/antoyo/relm#relm).

#![warn(
    missing_docs,
    trivial_casts,
    trivial_numeric_casts,
    unused_extern_crates,
    unused_import_braces,
    unused_qualifications,
)]

/*
 * TODO: maybe remove SimpleMsg.
 *
 * TODO: improve README so that examples can be copy/pasted.
 * TODO: add tests for relm-derive-state.
 *
 * FIXME: some relm widgets requires { and } (see the rusic music-player) while other do not.
 * FIXME: should not require to import WidgetExt because it calls show().
 * FIXME: cannot have relm event with tuple as a value like:
 * RelmWidget {
 *     RelmEvent((value1, value2)) => …
 * }
 * TODO: use pub(crate) instead of pub so that we're not bound to make the model and msg structs pub.
 *
 * TODO: add init() method to the Widget (or Update) trait as a shortcut for init::<Widget>()?
 *
 * TODO: alternative to tokio with a trait to get FD and add this FD to glib (perhaps using a
 * GSource).
 * For timers, add a connect_timeout!() macro or something.
 *
 * TODO: show a warning when a component is imediately destroyed.
 * FIXME: cannot add a trailing coma at the end of a initializer list.
 * TODO: switch from gtk::main() to MainLoop to avoid issues with nested loops.
 * TODO: prefix generated container name with _ to hide warnings.
 * TODO: remove the code generation related to using self in event handling.
 * TODO: remove the closure transformer code.
 *
 * TODO: move most of the examples in the tests/ directory.
 * TODO: add construct-only properties for relm widget (to replace initial parameters) to allow
 * setting them by name (or with default value).
 * TODO: find a way to do two-step initialization (to avoid using unitialized in model()).
 * TODO: consider using GBinding instead of manually adding calls to set_property().
 *
 * TODO: add a FAQ with the question related to getting an error when not importing the gtk traits.
 *
 * TODO: show a warning when components are destroyed after the end of call to Widget::view().
 * TODO: warn in the attribute when an event cycle is found.
 * TODO: add a Deref<Widget> for Component?
 * TODO: look at how Elm works with the <canvas> element.
 * TODO: the widget names should start with __relm_field_.
 *
 * TODO: reset widget name counters when creating new widget?
 *
 * TODO: refactor the code.
 *
 * TODO: chat client/server example.
 *
 * TODO: err if trying to use the SimpleMsg custom derive on stable.
 *
 * TODO: add default type of () for Model in Widget when it is stable.
 * TODO: optionnaly multi-threaded.
 */

extern crate cairo;
extern crate glib;
extern crate gobject_sys;
extern crate gtk;
extern crate relm_core;
extern crate relm_state;

mod component;
mod container;
mod drawing;
mod macros;
#[doc(hidden)]
pub mod vendor;
mod widget;

#[doc(hidden)]
pub use glib::Cast;
#[doc(hidden)]
pub use glib::translate::{FromGlibPtrNone, ToGlib, ToGlibPtr};
#[doc(hidden)]
pub use gobject_sys::{GParameter, g_object_newv};
use gtk::Continue;

pub use relm_core::{Channel, EventStream, Sender};
pub use relm_state::{
    DisplayVariant,
    IntoOption,
    IntoPair,
    Relm,
    Update,
    UpdateNew,
    execute,
};
use relm_state::init_component;

pub use component::Component;
pub use container::{Container, ContainerComponent, ContainerWidget};
pub use drawing::DrawHandler;
pub use widget::{Widget, WidgetTest};

/// Dummy macro to be used with `#[derive(Widget)]`.
#[macro_export]
macro_rules! impl_widget {
    ($($tt:tt)*) => {
        ()
    };
}

/// Macro to be used as a stable alternative to the `#[widget]` attribute.
///
/// An example can be found [here](https://github.com/antoyo/relm/blob/master/examples/buttons-derive/src/main.rs#L50).
#[macro_export]
macro_rules! relm_widget {
    ($($tts:tt)*) => {
        mod __relm_gen_private {
            use super::*;

            #[derive(Widget)]
            struct __RelmPrivateWidget {
                widget: impl_widget! {
                    $($tts)*
                }
            }
        }

        use_impl_self_type!($($tts)*);
    };
}

#[doc(hidden)]
#[macro_export]
macro_rules! use_impl_self_type {
    (impl $(::relm::)*Widget for $self_type:ident { $($tts:tt)* }) => {
        pub use self::__relm_gen_private::$self_type;
    };
}

fn create_widget_test<WIDGET>(model_param: WIDGET::ModelParam) -> (Component<WIDGET>, WIDGET::Widgets)
    where WIDGET: Widget + WidgetTest + 'static,
          WIDGET::Msg: DisplayVariant + 'static,
{
    let (widget, component, relm): (_, WIDGET, _) = create_widget(model_param);
    let widgets = component.get_widgets();
    init_component::<WIDGET>(widget.stream(), component, &relm);
    (widget, widgets)
}

/// Create a new relm widget without adding it to an existing widget.
/// This is useful when a relm widget is at the root of another relm widget.
pub fn create_component<CHILDWIDGET>(model_param: CHILDWIDGET::ModelParam)
        -> Component<CHILDWIDGET>
    where CHILDWIDGET: Widget + 'static,
          CHILDWIDGET::Msg: DisplayVariant + 'static,
{
    let (widget, component, child_relm) = create_widget::<CHILDWIDGET>(model_param);
    init_component::<CHILDWIDGET>(widget.stream(), component, &child_relm);
    widget
}

/// Create a new relm container widget without adding it to an existing widget.
/// This is useful when a relm widget is at the root of another relm widget.
pub fn create_container<CHILDWIDGET>(model_param: CHILDWIDGET::ModelParam)
        -> ContainerComponent<CHILDWIDGET>
    where CHILDWIDGET: Container + Widget + 'static,
          CHILDWIDGET::Msg: DisplayVariant + 'static,
{
    let (widget, component, child_relm) = create_widget::<CHILDWIDGET>(model_param);
    let container = component.container().clone();
    let containers = component.other_containers();
    init_component::<CHILDWIDGET>(widget.stream(), component, &child_relm);
    ContainerComponent::new(widget, container, containers)
}

/// Create a new relm widget with `model_param` as initialization value.
fn create_widget<WIDGET>(model_param: WIDGET::ModelParam)
    -> (Component<WIDGET>, WIDGET, Relm<WIDGET>)
    where WIDGET: Widget + 'static,
          WIDGET::Msg: DisplayVariant + 'static,
{
    let stream = EventStream::new();

    let relm = Relm::new(stream.clone());
    let model = WIDGET::model(&relm, model_param);
    let mut widget = WIDGET::view(&relm, model);
    widget.init_view();

    let root = widget.root().clone();
    (Component::new(stream, root), widget, relm)
}

/// Initialize a widget for a test.
///
/// It is to be used this way:
///
/// ```
/// # extern crate gtk;
/// # #[macro_use]
/// # extern crate relm;
/// # #[macro_use]
/// # extern crate relm_derive;
/// #
/// # use gtk::{Window, WindowType};
/// # use relm::{Relm, Update, Widget, WidgetTest};
/// #
/// # #[derive(Clone)]
/// # struct Win {
/// #     window: Window,
/// # }
/// #
/// # impl Update for Win {
/// #     type Model = ();
/// #     type ModelParam = ();
/// #     type Msg = Msg;
/// #
/// #     fn model(_: &Relm<Self>, _: ()) -> () {
/// #         ()
/// #     }
/// #
/// #     fn update(&mut self, event: Msg) {
/// #     }
/// # }
/// #
/// # impl WidgetTest for Win {
/// #     type Widgets = Win;
/// #
/// #     fn get_widgets(&self) -> Self::Widgets {
/// #         self.clone()
/// #     }
/// # }
/// #
/// # impl Widget for Win {
/// #     type Root = Window;
/// #
/// #     fn root(&self) -> Self::Root {
/// #         self.window.clone()
/// #     }
/// #
/// #     fn view(relm: &Relm<Self>, _model: Self::Model) -> Self {
/// #         let window = Window::new(WindowType::Toplevel);
/// #
/// #         Win {
/// #             window: window,
/// #         }
/// #     }
/// # }
/// #
/// # #[derive(Msg)]
/// # enum Msg {}
/// # fn main() {
/// let (component, widgets) = relm::init_test::<Win>(()).expect("init_test failed");
/// # }
/// ```
pub fn init_test<WIDGET>(model_param: WIDGET::ModelParam) ->
    Result<(Component<WIDGET>, WIDGET::Widgets), ()>
    where WIDGET: Widget + WidgetTest + 'static,
          WIDGET::Msg: DisplayVariant + 'static,
{
    gtk::init().map_err(|_| ())?;
    let component = create_widget_test::<WIDGET>(model_param);
    Ok(component)
}

/// Initialize a widget.
pub fn init<WIDGET>(model_param: WIDGET::ModelParam) -> Result<Component<WIDGET>, ()>
    where WIDGET: Widget + 'static,
          WIDGET::Msg: DisplayVariant + 'static
{
    let (widget, component, relm) = create_widget::<WIDGET>(model_param);
    init_component::<WIDGET>(widget.stream(), component, &relm);
    Ok(widget)
}

/// Create the specified relm `Widget` and run the main event loops.
///
/// ```
/// # extern crate gtk;
/// # #[macro_use]
/// # extern crate relm;
/// # #[macro_use]
/// # extern crate relm_derive;
/// #
/// # use gtk::{Window, WindowType};
/// # use relm::{Relm, Update, Widget};
/// #
/// # struct Win {
/// #     window: Window,
/// # }
/// #
/// # impl Update for Win {
/// #     type Model = ();
/// #     type ModelParam = ();
/// #     type Msg = Msg;
/// #
/// #     fn model(_: &Relm<Self>, _: ()) -> () {
/// #         ()
/// #     }
/// #
/// #     fn update(&mut self, event: Msg) {
/// #     }
/// # }
/// #
/// # impl Widget for Win {
/// #     type Root = Window;
/// #
/// #     fn root(&self) -> Self::Root {
/// #         self.window.clone()
/// #     }
/// #
/// #     fn view(relm: &Relm<Self>, _model: Self::Model) -> Self {
/// #         let window = Window::new(WindowType::Toplevel);
/// #
/// #         Win {
/// #             window: window,
/// #         }
/// #     }
/// # }
/// # #[derive(Msg)]
/// # enum Msg {}
/// # fn main() {
/// # }
/// #
/// # fn run() {
/// /// `Win` is a relm `Widget`.
/// Win::run(()).expect("Win::run failed");
/// # }
/// ```
pub fn run<WIDGET>(model_param: WIDGET::ModelParam) -> Result<(), ()>
    where WIDGET: Widget + 'static,
{
    gtk::init().map_err(|_| ())?;
    let _component = init::<WIDGET>(model_param)?;
    gtk::main();
    Ok(())
}

/// Emit the `msg` every `duration` ms.
pub fn interval<F: Fn() -> MSG + 'static, MSG: 'static>(stream: &EventStream<MSG>, duration: u32, constructor: F) {
    let stream = stream.clone();
    gtk::timeout_add(duration, move || {
        let msg = constructor();
        stream.emit(msg);
        Continue(true)
    });
}

/// After `duration` ms, emit `msg`.
pub fn timeout<F: Fn() -> MSG + 'static, MSG: 'static>(stream: &EventStream<MSG>, duration: u32, constructor: F) {
    let stream = stream.clone();
    gtk::timeout_add(duration, move || {
        let msg = constructor();
        stream.emit(msg);
        Continue(false)
    });
}
<|endoftext|>


<commit_before>//! This crate parses and processes `hyper::server::Request` data that contains
//! `multipart/form-data` content.
//!
//! The main entry point is `parse_multipart`

extern crate httparse;
extern crate hyper;
extern crate libc;
#[macro_use]
extern crate mime;
extern crate tempdir;
extern crate textnonce;

pub mod buf;
pub mod error;
mod headers;
#[cfg(test)]
mod mock;

use std::path::PathBuf;
use std::fs::File;
use std::io::{BufRead, BufReader, Read};

use hyper::header::{ContentType, Headers};
use hyper::server::Request as HyperRequest;
use mime::{Attr, Mime, Param, SubLevel, TopLevel, Value};
use tempdir::TempDir;
use textnonce::TextNonce;

use buf::BufReadPlus;
pub use error::Error;
use headers::ContentDisposition;

/// An uploaded file that was received as part of `multipart/form-data` parsing.
///
/// Files are streamed to disk because they may not fit in memory.
#[derive(Clone, Debug, PartialEq)]
pub struct UploadedFile {
    /// The temporary file where the data was saved.
    pub path: PathBuf,
    /// The filename that was specified in the data, unfiltered. It may or may not be legal on the
    /// local filesystem.
    pub filename: Option<String>,
    /// The unvalidated content-type that was specified in the data.
    pub content_type: Mime,
    /// The size of the file.
    pub size: usize,
}

/// The extracted text fields and uploaded files from a `multipart/form-data` request.
///
/// Use `parse_multipart` to devise this object from a request.
pub struct FormData {
    /// Key-value pairs for plain text fields. Technically, these are form data parts with no
    /// filename specified in the part's `Content-Disposition`.
    pub fields: Vec<(String, String)>,
    /// Key-value pairs for temporary files. Technically, these are form data parts with a filename
    /// specified in the part's `Content-Disposition`.
    pub files: Vec<(String, UploadedFile)>,
}

impl FormData {
    pub fn new() -> FormData {
        FormData { fields: vec![], files: vec![] }
    }
}

/// Parses and processes `hyper::server::Request` data that is `multipart/form-data` content.
///
/// The request is streamed, and this function saves embedded uploaded files to disk as they are
/// encountered by the parser.
pub fn parse_multipart(request: &mut Request) -> Result<FormData, Error> {
    let boundary = try!(get_boundary(request));
    let mut reader = BufReader::with_capacity(4096, request.read_mut());
    let mut form_data = FormData::new();
    try!(run_state_machine(boundary, &mut reader, &mut form_data, MultipartSubLevel::FormData));
    Ok(form_data)
}

// A state in the parsing state machine.
enum State {
    // Discard until after initial boundary and CRLF.
    Discarding,
    // Capture headers to blank line.
    ReadingHeaders,
    // Capture entire `multipart/mixed` body until boundary, then discard past CRLF.
    CapturingMixed(ContentDisposition, ContentType),
    // Capture value until boundary, then discard past CRLF.
    CapturingValue(ContentDisposition),
    // Capture file until boundary, then discard past CRLF.
    CapturingFile(ContentDisposition, Option<ContentType>),
}

// A multipart MIME parsing mode.
#[derive(PartialEq)]
enum MultipartSubLevel {
    // Represents `multipart/form-data`.
    FormData,
    // Represents `multipart/mixed` with a `name` key.
    Mixed(String),
}

// Parse either a `multipart/form-data` or `multipart/mixed` MIME body.
fn run_state_machine<R: BufRead>(boundary: String, reader: &mut R, form_data: &mut FormData,
                                 mode: MultipartSubLevel) -> Result<(), Error> {
    use State::*;
    use MultipartSubLevel::*;

    let boundary = boundary.into_bytes();
    let crlf_boundary = crlf_boundary(&boundary);
    let mut state = Discarding;

    loop {
        let mut buf: Vec<u8> = Vec::new();

        match state {
            Discarding => {
                // Read up to and including the boundary.
                let read = try!(reader.stream_until_token(&boundary, &mut buf));
                if read == 0 {
                    return Err(Error::Eof);
                }

                state = ReadingHeaders;
            },
            ReadingHeaders => {
                {
                    // If the next two lookahead characters are '--', parsing is finished.
                    let peeker = try!(reader.fill_buf());
                    if peeker.len() >= 2 && &peeker[..2] == b"--" {
                        return Ok(());
                    }
                }

                // Read up to and including the CRLF after the boundary.
                let read = try!(reader.stream_until_token(b"\r\n", &mut buf));
                if read == 0 {
                    return Err(Error::Eof);
                }

                buf.truncate(0);
                let read = try!(reader.stream_until_token(b"\r\n\r\n", &mut buf));
                if read == 0 {
                    return Err(Error::Eof);
                }
                // `parse_headers` requires this token at the end:
                buf.extend(b"\r\n\r\n".iter().map(|&i| i));

                // Parse the headers.
                let mut header_memory = [httparse::EMPTY_HEADER; 4];
                match httparse::parse_headers(&buf, &mut header_memory) {
                    Ok(httparse::Status::Complete((_, raw_headers))) => {
                        // Turn raw headers into hyper headers.
                        let headers = try!(Headers::from_raw(raw_headers));

                        let cd: &ContentDisposition = match headers.get() {
                            Some(cd) => cd,
                            None => return Err(Error::MissingDisposition),
                        };

                        let ct: Option<&ContentType> = headers.get();

                        match mode {
                            FormData if cd.disposition == "form-data" => {
                                state = if is_multipart_mixed(ct) {
                                    CapturingMixed(cd.clone(), ct.unwrap().clone())
                                } else if ct.is_some() || cd.filename.is_some() {
                                    CapturingFile(cd.clone(), ct.map(|ct| ct.clone()))
                                } else {
                                    CapturingValue(cd.clone())
                                };
                            },
                            Mixed(_) if cd.disposition == "file" => {
                                state = CapturingFile(cd.clone(), ct.map(|ct| ct.clone()));
                            },
                            _ => return Err(Error::InvalidDisposition),
                        }
                    },
                    Ok(httparse::Status::Partial) => return Err(Error::PartialHeaders),
                    Err(err) => return Err(From::from(err)),
                }
            },
            CapturingMixed(cd, ct) => {
                let boundary = try!(get_boundary_token(&(ct.0).2));
                let mode = Mixed(try!(cd.name.ok_or(Error::NoName)));
                try!(run_state_machine(boundary, reader, form_data, mode));
                state = Discarding;
            },
            CapturingValue(_) if mode != FormData => unreachable!(),
            CapturingValue(cd) => {
                buf.truncate(0);
                let _ = try!(reader.stream_until_token(&crlf_boundary, &mut buf));

                let key = try!(cd.name.ok_or(Error::NoName));
                let val = try!(String::from_utf8(buf));

                form_data.fields.push((key, val));

                state = ReadingHeaders;
            },
            CapturingFile(cd, ct) => {
                // Setup a file to capture the contents.
                let mut path = try!(TempDir::new("formdata")).into_path();
                path.push(TextNonce::sized_urlsafe(32).unwrap().into_string());
                let mut file = try!(File::create(path.clone()));

                // Stream out the file.
                let read = try!(reader.stream_until_token(&crlf_boundary, &mut file));

                // TODO: Handle Content-Transfer-Encoding.

                let key = match mode {
                    Mixed(ref name) => name.clone(),
                    FormData => try!(cd.name.ok_or(Error::NoName)),
                };

                let file = UploadedFile {
                    path: path,
                    filename: cd.filename.clone(),
                    content_type: ct.map_or(mime!(Text/Plain; Charset=Utf8), |ct| ct.0),
                    size: read - crlf_boundary.len()
                };

                form_data.files.push((key, file));
                state = ReadingHeaders;
            },
        }
    }
}

fn is_multipart_mixed(ct: Option<&ContentType>) -> bool {
    let ct = match ct {
        Some(ct) => ct,
        None => return false,
    };

    let ContentType(ref mime) = *ct;
    let Mime(ref top_level, ref sub_level, _) = *mime;

    if *top_level != TopLevel::Multipart {
        return false;
    }

    match *sub_level {
        SubLevel::Ext(ref ext) => ext == "mixed",
        _ => false,
    }
}

fn get_boundary(request: &Request) -> Result<String, Error> {
    // Verify that the request is 'Content-Type: multipart/form-data'.
    let ct: &ContentType = match request.headers().get() {
        Some(ct) => ct,
        None => return Err(Error::NoRequestContentType),
    };
    let ContentType(ref mime) = *ct;
    let Mime(ref top_level, ref sub_level, ref params) = *mime;

    if *top_level != TopLevel::Multipart {
        return Err(Error::NotMultipart);
    }

    if *sub_level != SubLevel::FormData {
        return Err(Error::NotFormData);
    }

    get_boundary_token(params)
}

fn get_boundary_token(params: &[Param]) -> Result<String, Error> {
    for &(ref attr, ref val) in params.iter() {
        if let (&Attr::Boundary, &Value::Ext(ref val)) = (attr, val) {
            return Ok(format!("--{}", val.clone()));
        }
    }

    Err(Error::BoundaryNotSpecified)
}

fn crlf_boundary(boundary: &Vec<u8>) -> Vec<u8> {
    let mut crlf_boundary = Vec::with_capacity(2 + boundary.len());
    crlf_boundary.extend(b"\r\n".iter().map(|&i| i));
    crlf_boundary.extend(boundary.clone());
    crlf_boundary
}

/// A wrapper for request data to provide parsing multipart requests to any front-end that provides
/// a `hyper::header::Headers` and a `std::io::Read` of the request's entire body.
pub trait Request {
    /// Returns a reference to the request's headers.
    fn headers(&self) -> &Headers;

    /// Returns a mutable reference to the request's body reader.
    fn read_mut(&mut self) -> &mut Read;
}

impl<'a,'b> Request for HyperRequest<'a,'b> {
    fn headers(&self) -> &Headers {
        &self.headers
    }

    fn read_mut(&mut self) -> &mut Read {
        self
    }
}

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

    use std::net::SocketAddr;

    use hyper::buffer::BufReader;
    use hyper::net::NetworkStream;
    use hyper::server::Request as HyperRequest;

    use mock::MockStream;

    #[test]
    fn parser() {
        let input = b"POST / HTTP/1.1\r\n\
                      Host: example.domain\r\n\
                      Content-Type: multipart/form-data; boundary=\"abcdefg\"\r\n\
                      Content-Length: 1000\r\n\
                      \r\n\
                      --abcdefg\r\n\
                      Content-Disposition: form-data; name=\"field1\"\r\n\
                      \r\n\
                      data1\r\n\
                      --abcdefg\r\n\
                      Content-Disposition: form-data; name=\"field2\"; filename=\"image.gif\"\r\n\
                      Content-Type: image/gif\r\n\
                      \r\n\
                      This is a file\r\n\
                      with two lines\r\n\
                      --abcdefg\r\n\
                      Content-Disposition: form-data; name=\"field3\"; filename=\"file.txt\"\r\n\
                      \r\n\
                      This is a file\r\n\
                      --abcdefg--";

        let mut mock = MockStream::with_input(input);

        let mock: &mut NetworkStream = &mut mock;
        let mut stream = BufReader::new(mock);
        let sock: SocketAddr = "127.0.0.1:80".parse().unwrap();
        let mut req = HyperRequest::new(&mut stream, sock).unwrap();

        match parse_multipart(&mut req) {
            Ok(form_data) => {
                assert_eq!(form_data.fields.len(), 1);
                for (key, val) in form_data.fields {
                    if &key == "field1" {
                        assert_eq!(&val, "data1");
                    }
                }

                assert_eq!(form_data.files.len(), 2);
                for (key, file) in form_data.files {
                    if &key == "field2" {
                        assert_eq!(file.size, 30);
                        assert_eq!(&file.filename.unwrap(), "image.gif");
                        assert_eq!(file.content_type, mime!(Image/Gif));
                    } else if &key == "field3" {
                        assert_eq!(file.size, 14);
                        assert_eq!(&file.filename.unwrap(), "file.txt");
                        assert_eq!(file.content_type, mime!(Text/Plain; Charset=Utf8));
                    }
                }
            },
            Err(err) => panic!("{}", err),
        }
    }

    #[test]
    fn mixed_parser() {
        let input = b"POST / HTTP/1.1\r\n\
                      Host: example.domain\r\n\
                      Content-Type: multipart/form-data; boundary=AaB03x\r\n\
                      Content-Length: 1000\r\n\
                      \r\n\
                      --AaB03x\r\n\
                      Content-Disposition: form-data; name=\"submit-name\"\r\n\
                      \r\n\
                      Larry\r\n\
                      --AaB03x\r\n\
                      Content-Disposition: form-data; name=\"files\"\r\n\
                      Content-Type: multipart/mixed; boundary=BbC04y\r\n\
                      \r\n\
                      --BbC04y\r\n\
                      Content-Disposition: file; filename=\"file1.txt\"\r\n\
                      \r\n\
                      ... contents of file1.txt ...\r\n\
                      --BbC04y\r\n\
                      Content-Disposition: file; filename=\"awesome_image.gif\"\r\n\
                      Content-Type: image/gif\r\n\
                      Content-Transfer-Encoding: binary\r\n\
                      \r\n\
                      ... contents of awesome_image.gif ...\r\n\
                      --BbC04y--\r\n\
                      --AaB03x--";

        let mut mock = MockStream::with_input(input);

        let mock: &mut NetworkStream = &mut mock;
        let mut stream = BufReader::new(mock);
        let sock: SocketAddr = "127.0.0.1:80".parse().unwrap();
        let mut req = HyperRequest::new(&mut stream, sock).unwrap();

        match parse_multipart(&mut req) {
            Ok(form_data) => {
                assert_eq!(form_data.fields.len(), 1);
                for (key, val) in form_data.fields {
                    if &key == "submit-name" {
                        assert_eq!(&val, "Larry");
                    }
                }

                assert_eq!(form_data.files.len(), 2);
                for (key, file) in form_data.files {
                    assert_eq!(&key, "files");
                    match &file.filename.unwrap()[..] {
                        "file1.txt" => {
                            assert_eq!(file.size, 29);
                            assert_eq!(file.content_type, mime!(Text/Plain; Charset=Utf8));
                        }
                        "awesome_image.gif" => {
                            assert_eq!(file.size, 37);
                            assert_eq!(file.content_type, mime!(Image/Gif));
                        },
                        _ => unreachable!(),
                    }
                }
            },
            Err(err) => panic!("{}", err),
        }
    }
}
<commit_msg>Derived traits on public types<commit_after>//! This crate parses and processes `hyper::server::Request` data that contains
//! `multipart/form-data` content.
//!
//! The main entry point is `parse_multipart`

extern crate httparse;
extern crate hyper;
extern crate libc;
#[macro_use]
extern crate mime;
extern crate tempdir;
extern crate textnonce;

pub mod buf;
pub mod error;
mod headers;
#[cfg(test)]
mod mock;

use std::path::PathBuf;
use std::fs::File;
use std::io::{BufRead, BufReader, Read};

use hyper::header::{ContentType, Headers};
use hyper::server::Request as HyperRequest;
use mime::{Attr, Mime, Param, SubLevel, TopLevel, Value};
use tempdir::TempDir;
use textnonce::TextNonce;

use buf::BufReadPlus;
pub use error::Error;
use headers::ContentDisposition;

/// An uploaded file that was received as part of `multipart/form-data` parsing.
///
/// Files are streamed to disk because they may not fit in memory.
#[derive(Clone, Debug, PartialEq)]
pub struct UploadedFile {
    /// The temporary file where the data was saved.
    pub path: PathBuf,
    /// The filename that was specified in the data, unfiltered. It may or may not be legal on the
    /// local filesystem.
    pub filename: Option<String>,
    /// The unvalidated content-type that was specified in the data.
    pub content_type: Mime,
    /// The size of the file.
    pub size: usize,
}

/// The extracted text fields and uploaded files from a `multipart/form-data` request.
///
/// Use `parse_multipart` to devise this object from a request.
#[derive(Clone, Debug, PartialEq)]
pub struct FormData {
    /// Key-value pairs for plain text fields. Technically, these are form data parts with no
    /// filename specified in the part's `Content-Disposition`.
    pub fields: Vec<(String, String)>,
    /// Key-value pairs for temporary files. Technically, these are form data parts with a filename
    /// specified in the part's `Content-Disposition`.
    pub files: Vec<(String, UploadedFile)>,
}

impl FormData {
    pub fn new() -> FormData {
        FormData { fields: vec![], files: vec![] }
    }
}

/// Parses and processes `hyper::server::Request` data that is `multipart/form-data` content.
///
/// The request is streamed, and this function saves embedded uploaded files to disk as they are
/// encountered by the parser.
pub fn parse_multipart(request: &mut Request) -> Result<FormData, Error> {
    let boundary = try!(get_boundary(request));
    let mut reader = BufReader::with_capacity(4096, request.read_mut());
    let mut form_data = FormData::new();
    try!(run_state_machine(boundary, &mut reader, &mut form_data, MultipartSubLevel::FormData));
    Ok(form_data)
}

// A state in the parsing state machine.
enum State {
    // Discard until after initial boundary and CRLF.
    Discarding,
    // Capture headers to blank line.
    ReadingHeaders,
    // Capture entire `multipart/mixed` body until boundary, then discard past CRLF.
    CapturingMixed(ContentDisposition, ContentType),
    // Capture value until boundary, then discard past CRLF.
    CapturingValue(ContentDisposition),
    // Capture file until boundary, then discard past CRLF.
    CapturingFile(ContentDisposition, Option<ContentType>),
}

// A multipart MIME parsing mode.
#[derive(PartialEq)]
enum MultipartSubLevel {
    // Represents `multipart/form-data`.
    FormData,
    // Represents `multipart/mixed` with a `name` key.
    Mixed(String),
}

// Parse either a `multipart/form-data` or `multipart/mixed` MIME body.
fn run_state_machine<R: BufRead>(boundary: String, reader: &mut R, form_data: &mut FormData,
                                 mode: MultipartSubLevel) -> Result<(), Error> {
    use State::*;
    use MultipartSubLevel::*;

    let boundary = boundary.into_bytes();
    let crlf_boundary = crlf_boundary(&boundary);
    let mut state = Discarding;

    loop {
        let mut buf: Vec<u8> = Vec::new();

        match state {
            Discarding => {
                // Read up to and including the boundary.
                let read = try!(reader.stream_until_token(&boundary, &mut buf));
                if read == 0 {
                    return Err(Error::Eof);
                }

                state = ReadingHeaders;
            },
            ReadingHeaders => {
                {
                    // If the next two lookahead characters are '--', parsing is finished.
                    let peeker = try!(reader.fill_buf());
                    if peeker.len() >= 2 && &peeker[..2] == b"--" {
                        return Ok(());
                    }
                }

                // Read up to and including the CRLF after the boundary.
                let read = try!(reader.stream_until_token(b"\r\n", &mut buf));
                if read == 0 {
                    return Err(Error::Eof);
                }

                buf.truncate(0);
                let read = try!(reader.stream_until_token(b"\r\n\r\n", &mut buf));
                if read == 0 {
                    return Err(Error::Eof);
                }
                // `parse_headers` requires this token at the end:
                buf.extend(b"\r\n\r\n".iter().map(|&i| i));

                // Parse the headers.
                let mut header_memory = [httparse::EMPTY_HEADER; 4];
                match httparse::parse_headers(&buf, &mut header_memory) {
                    Ok(httparse::Status::Complete((_, raw_headers))) => {
                        // Turn raw headers into hyper headers.
                        let headers = try!(Headers::from_raw(raw_headers));

                        let cd: &ContentDisposition = match headers.get() {
                            Some(cd) => cd,
                            None => return Err(Error::MissingDisposition),
                        };

                        let ct: Option<&ContentType> = headers.get();

                        match mode {
                            FormData if cd.disposition == "form-data" => {
                                state = if is_multipart_mixed(ct) {
                                    CapturingMixed(cd.clone(), ct.unwrap().clone())
                                } else if ct.is_some() || cd.filename.is_some() {
                                    CapturingFile(cd.clone(), ct.map(|ct| ct.clone()))
                                } else {
                                    CapturingValue(cd.clone())
                                };
                            },
                            Mixed(_) if cd.disposition == "file" => {
                                state = CapturingFile(cd.clone(), ct.map(|ct| ct.clone()));
                            },
                            _ => return Err(Error::InvalidDisposition),
                        }
                    },
                    Ok(httparse::Status::Partial) => return Err(Error::PartialHeaders),
                    Err(err) => return Err(From::from(err)),
                }
            },
            CapturingMixed(cd, ct) => {
                let boundary = try!(get_boundary_token(&(ct.0).2));
                let mode = Mixed(try!(cd.name.ok_or(Error::NoName)));
                try!(run_state_machine(boundary, reader, form_data, mode));
                state = Discarding;
            },
            CapturingValue(_) if mode != FormData => unreachable!(),
            CapturingValue(cd) => {
                buf.truncate(0);
                let _ = try!(reader.stream_until_token(&crlf_boundary, &mut buf));

                let key = try!(cd.name.ok_or(Error::NoName));
                let val = try!(String::from_utf8(buf));

                form_data.fields.push((key, val));

                state = ReadingHeaders;
            },
            CapturingFile(cd, ct) => {
                // Setup a file to capture the contents.
                let mut path = try!(TempDir::new("formdata")).into_path();
                path.push(TextNonce::sized_urlsafe(32).unwrap().into_string());
                let mut file = try!(File::create(path.clone()));

                // Stream out the file.
                let read = try!(reader.stream_until_token(&crlf_boundary, &mut file));

                // TODO: Handle Content-Transfer-Encoding.

                let key = match mode {
                    Mixed(ref name) => name.clone(),
                    FormData => try!(cd.name.ok_or(Error::NoName)),
                };

                let file = UploadedFile {
                    path: path,
                    filename: cd.filename.clone(),
                    content_type: ct.map_or(mime!(Text/Plain; Charset=Utf8), |ct| ct.0),
                    size: read - crlf_boundary.len()
                };

                form_data.files.push((key, file));
                state = ReadingHeaders;
            },
        }
    }
}

fn is_multipart_mixed(ct: Option<&ContentType>) -> bool {
    let ct = match ct {
        Some(ct) => ct,
        None => return false,
    };

    let ContentType(ref mime) = *ct;
    let Mime(ref top_level, ref sub_level, _) = *mime;

    if *top_level != TopLevel::Multipart {
        return false;
    }

    match *sub_level {
        SubLevel::Ext(ref ext) => ext == "mixed",
        _ => false,
    }
}

fn get_boundary(request: &Request) -> Result<String, Error> {
    // Verify that the request is 'Content-Type: multipart/form-data'.
    let ct: &ContentType = match request.headers().get() {
        Some(ct) => ct,
        None => return Err(Error::NoRequestContentType),
    };
    let ContentType(ref mime) = *ct;
    let Mime(ref top_level, ref sub_level, ref params) = *mime;

    if *top_level != TopLevel::Multipart {
        return Err(Error::NotMultipart);
    }

    if *sub_level != SubLevel::FormData {
        return Err(Error::NotFormData);
    }

    get_boundary_token(params)
}

fn get_boundary_token(params: &[Param]) -> Result<String, Error> {
    for &(ref attr, ref val) in params.iter() {
        if let (&Attr::Boundary, &Value::Ext(ref val)) = (attr, val) {
            return Ok(format!("--{}", val.clone()));
        }
    }

    Err(Error::BoundaryNotSpecified)
}

fn crlf_boundary(boundary: &Vec<u8>) -> Vec<u8> {
    let mut crlf_boundary = Vec::with_capacity(2 + boundary.len());
    crlf_boundary.extend(b"\r\n".iter().map(|&i| i));
    crlf_boundary.extend(boundary.clone());
    crlf_boundary
}

/// A wrapper for request data to provide parsing multipart requests to any front-end that provides
/// a `hyper::header::Headers` and a `std::io::Read` of the request's entire body.
pub trait Request {
    /// Returns a reference to the request's headers.
    fn headers(&self) -> &Headers;

    /// Returns a mutable reference to the request's body reader.
    fn read_mut(&mut self) -> &mut Read;
}

impl<'a,'b> Request for HyperRequest<'a,'b> {
    fn headers(&self) -> &Headers {
        &self.headers
    }

    fn read_mut(&mut self) -> &mut Read {
        self
    }
}

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

    use std::net::SocketAddr;

    use hyper::buffer::BufReader;
    use hyper::net::NetworkStream;
    use hyper::server::Request as HyperRequest;

    use mock::MockStream;

    #[test]
    fn parser() {
        let input = b"POST / HTTP/1.1\r\n\
                      Host: example.domain\r\n\
                      Content-Type: multipart/form-data; boundary=\"abcdefg\"\r\n\
                      Content-Length: 1000\r\n\
                      \r\n\
                      --abcdefg\r\n\
                      Content-Disposition: form-data; name=\"field1\"\r\n\
                      \r\n\
                      data1\r\n\
                      --abcdefg\r\n\
                      Content-Disposition: form-data; name=\"field2\"; filename=\"image.gif\"\r\n\
                      Content-Type: image/gif\r\n\
                      \r\n\
                      This is a file\r\n\
                      with two lines\r\n\
                      --abcdefg\r\n\
                      Content-Disposition: form-data; name=\"field3\"; filename=\"file.txt\"\r\n\
                      \r\n\
                      This is a file\r\n\
                      --abcdefg--";

        let mut mock = MockStream::with_input(input);

        let mock: &mut NetworkStream = &mut mock;
        let mut stream = BufReader::new(mock);
        let sock: SocketAddr = "127.0.0.1:80".parse().unwrap();
        let mut req = HyperRequest::new(&mut stream, sock).unwrap();

        match parse_multipart(&mut req) {
            Ok(form_data) => {
                assert_eq!(form_data.fields.len(), 1);
                for (key, val) in form_data.fields {
                    if &key == "field1" {
                        assert_eq!(&val, "data1");
                    }
                }

                assert_eq!(form_data.files.len(), 2);
                for (key, file) in form_data.files {
                    if &key == "field2" {
                        assert_eq!(file.size, 30);
                        assert_eq!(&file.filename.unwrap(), "image.gif");
                        assert_eq!(file.content_type, mime!(Image/Gif));
                    } else if &key == "field3" {
                        assert_eq!(file.size, 14);
                        assert_eq!(&file.filename.unwrap(), "file.txt");
                        assert_eq!(file.content_type, mime!(Text/Plain; Charset=Utf8));
                    }
                }
            },
            Err(err) => panic!("{}", err),
        }
    }

    #[test]
    fn mixed_parser() {
        let input = b"POST / HTTP/1.1\r\n\
                      Host: example.domain\r\n\
                      Content-Type: multipart/form-data; boundary=AaB03x\r\n\
                      Content-Length: 1000\r\n\
                      \r\n\
                      --AaB03x\r\n\
                      Content-Disposition: form-data; name=\"submit-name\"\r\n\
                      \r\n\
                      Larry\r\n\
                      --AaB03x\r\n\
                      Content-Disposition: form-data; name=\"files\"\r\n\
                      Content-Type: multipart/mixed; boundary=BbC04y\r\n\
                      \r\n\
                      --BbC04y\r\n\
                      Content-Disposition: file; filename=\"file1.txt\"\r\n\
                      \r\n\
                      ... contents of file1.txt ...\r\n\
                      --BbC04y\r\n\
                      Content-Disposition: file; filename=\"awesome_image.gif\"\r\n\
                      Content-Type: image/gif\r\n\
                      Content-Transfer-Encoding: binary\r\n\
                      \r\n\
                      ... contents of awesome_image.gif ...\r\n\
                      --BbC04y--\r\n\
                      --AaB03x--";

        let mut mock = MockStream::with_input(input);

        let mock: &mut NetworkStream = &mut mock;
        let mut stream = BufReader::new(mock);
        let sock: SocketAddr = "127.0.0.1:80".parse().unwrap();
        let mut req = HyperRequest::new(&mut stream, sock).unwrap();

        match parse_multipart(&mut req) {
            Ok(form_data) => {
                assert_eq!(form_data.fields.len(), 1);
                for (key, val) in form_data.fields {
                    if &key == "submit-name" {
                        assert_eq!(&val, "Larry");
                    }
                }

                assert_eq!(form_data.files.len(), 2);
                for (key, file) in form_data.files {
                    assert_eq!(&key, "files");
                    match &file.filename.unwrap()[..] {
                        "file1.txt" => {
                            assert_eq!(file.size, 29);
                            assert_eq!(file.content_type, mime!(Text/Plain; Charset=Utf8));
                        }
                        "awesome_image.gif" => {
                            assert_eq!(file.size, 37);
                            assert_eq!(file.content_type, mime!(Image/Gif));
                        },
                        _ => unreachable!(),
                    }
                }
            },
            Err(err) => panic!("{}", err),
        }
    }
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate log;
extern crate special_fun;
extern crate stats;

use special_fun::FloatSpecial;
use std::collections::VecDeque;
use std::f64;

#[derive(Clone, Debug)]
pub struct PhiFailureDetector {
    min_stddev: f64,
    history_size: usize,
    buf: VecDeque<u64>,
    prev_heartbeat: Option<u64>,
}

impl PhiFailureDetector {
    pub fn new() -> PhiFailureDetector {
        PhiFailureDetector {
            min_stddev: 1.0,
            history_size: 10,
            buf: VecDeque::new(),
            prev_heartbeat: None,
        }
    }
    pub fn min_stddev(self, min_stddev: f64) -> PhiFailureDetector {
        assert!(min_stddev > 0.0, "min_stddev must be > 0.0");
        PhiFailureDetector {
            min_stddev: min_stddev,
            ..self
        }
    }

    pub fn history_size(self, count: usize) -> PhiFailureDetector {
        assert!(count > 0, "history_size must > 0");
        PhiFailureDetector {
            history_size: count,
            ..self
        }
    }
    pub fn heartbeat(&mut self, t: u64) {
        match &mut self.prev_heartbeat {
            prev @ &mut None => {
                *prev = Some(t);
                return;
            }
            &mut Some(ref mut prev) => {
                if t < *prev {
                    return;
                };
                let delta = t - *prev;
                self.buf.push_back(delta);
                *prev = t;
                if self.buf.len() > self.history_size {
                    let _ = self.buf.pop_front();
                }
            }
        }
    }

    /// def ϕ(Tnow ) = − log10(Plater (Tnow − Tlast))
    pub fn phi(&self, now: u64) -> f64 {
        match &self.prev_heartbeat {
            &Some(prev_time) if now > prev_time => {
                trace!(
                    "now:{} - prev_heartbeat:{} = {:?}",
                    now,
                    prev_time,
                    now - prev_time
                );
                let p_later = self.p_later(now - prev_time);
                -p_later.log10()
            }
            &Some(prev_time) => {
                trace!("now:{} <= prev_heartbeat:{}", now, prev_time);
                0.0
            }
            &None => 0.0,
        }
    }

    /// Returns the time t (within epsilon) at which phi will be >= val .
    pub fn next_crossing_at(&self, now: u64, threshold: f64) -> u64 {
        let phappened = 1.0 - (10.0f64).powf(-threshold);

        let x = phappened.norm_inv();
        let mean = stats::mean(self.buf.iter().cloned());
        let stddev = stats::stddev(self.buf.iter().cloned()).max(self.min_stddev);
        let diff = x * stddev + mean;
        let then = now + diff.ceil() as u64;

        trace!(
            "threshold:{}; phappened:{}; x:{}; mean:{}; stddev:{}; diff:{}; then:{}",
            threshold,
            phappened,
            x,
            mean,
            stddev,
            diff,
            then
        );

        then
    }

    fn p_later(&self, diff: u64) -> f64 {
        let mean = stats::mean(self.buf.iter().cloned());
        let stddev = stats::stddev(self.buf.iter().cloned()).max(self.min_stddev);
        let x = (diff as f64 - mean) / stddev;
        // let cdf = 0.5*(1.0+ (x/(2.0f64).sqrt()).erf())
        let p = 1.0 - x.norm();
        trace!(
            "diff:{:e}; mean:{:e}; stddev:{:e} x:{:e}; p_later:{:e}",
            diff as f64,
            mean,
            stddev,
            x,
            p
        );
        // We want to avoid returning zero, as we want the logarithm of the probability.
        // And the log of zero is meaningless.
        if p < f64::MIN_POSITIVE {
            f64::MIN_POSITIVE
        } else {
            p
        }
    }
}

#[cfg(test)]
mod tests {
    extern crate env_logger;
    extern crate rand;
    extern crate rand_distr;
    use self::rand::thread_rng;
    use self::rand_distr::Distribution;
    use self::rand_distr::LogNormal;
    use super::PhiFailureDetector;
    #[test]
    fn should_fail_when_no_heartbeats() {
        env_logger::try_init().unwrap_or_default();

        let mut detector = PhiFailureDetector::new();
        for t in 0..100 {
            detector.heartbeat(t);
            let phi = detector.phi(t);
            trace!("at:{:?}, phi:{:?}; det: {:?}", t, phi, detector);
            if t > 10 {
                assert!(phi < 1.0);
            }
        }
        for t in 100..110 {
            let phi = detector.phi(t);
            trace!("at:{:?}, phi:{:?}; det: {:?}", t, phi, detector);
        }
        for &t in &[110, 200, 300] {
            let phi = detector.phi(t);
            trace!("at:{:?}, phi:{:?}; det: {:?}", t, phi, detector);
            assert!(phi > 1.0, "t:{:?}; phi:{:?} > 1.0", t, phi);
        }
    }

    #[test]
    fn should_recover() {
        env_logger::try_init().unwrap_or_default();
        let mut detector = PhiFailureDetector::new().history_size(3);
        for t in 0..10 {
            detector.heartbeat(t);
            let phi = detector.phi(t);
            trace!("at:{:?}, phi:{:?}; det: {:?}", t, phi, detector);
        }
        for t in 20..30 {
            detector.heartbeat(t);
            let phi = detector.phi(t);
            trace!("at:{:?}, phi:{:?}; det: {:?}", t, phi, detector);
            if t > 10 {
                assert!(phi < 1.0);
            }
        }
    }

    #[test]
    fn should_estimate_threshold_times() {
        env_logger::try_init().unwrap_or_default();
        let epsilon = 2;
        let mut detector = PhiFailureDetector::new().history_size(3);

        let mut t = 0;
        for n in 0u64..10 {
            let mut dist = LogNormal::new(10.0, 100.0).expect("lognormal");
            let diff = dist.sample(&mut thread_rng());
            t = n * 1000;
            trace!(
                "at:{:?}, diff:{:e}; phi:{:?}; det: {:?}",
                t,
                diff,
                detector.phi(t),
                detector
            );
            detector.heartbeat(t);
        }
        // Estimate the point at which
        let threshold = 1.0;
        let est_1 = detector.next_crossing_at(t, threshold);

        let pre = detector.phi(est_1 - epsilon);
        let at = detector.phi(est_1);
        assert!(
            pre < threshold && at >= threshold,
            "phi({}):{:?} < {:?} && phi({}):{:?} >= {:?}",
            est_1 - epsilon,
            pre,
            threshold,
            est_1,
            at,
            threshold
        );
    }
}
<commit_msg>Remove unneeded mutability qualifier.<commit_after>#[macro_use]
extern crate log;
extern crate special_fun;
extern crate stats;

use special_fun::FloatSpecial;
use std::collections::VecDeque;
use std::f64;

#[derive(Clone, Debug)]
pub struct PhiFailureDetector {
    min_stddev: f64,
    history_size: usize,
    buf: VecDeque<u64>,
    prev_heartbeat: Option<u64>,
}

impl PhiFailureDetector {
    pub fn new() -> PhiFailureDetector {
        PhiFailureDetector {
            min_stddev: 1.0,
            history_size: 10,
            buf: VecDeque::new(),
            prev_heartbeat: None,
        }
    }
    pub fn min_stddev(self, min_stddev: f64) -> PhiFailureDetector {
        assert!(min_stddev > 0.0, "min_stddev must be > 0.0");
        PhiFailureDetector {
            min_stddev: min_stddev,
            ..self
        }
    }

    pub fn history_size(self, count: usize) -> PhiFailureDetector {
        assert!(count > 0, "history_size must > 0");
        PhiFailureDetector {
            history_size: count,
            ..self
        }
    }
    pub fn heartbeat(&mut self, t: u64) {
        match &mut self.prev_heartbeat {
            prev @ &mut None => {
                *prev = Some(t);
                return;
            }
            &mut Some(ref mut prev) => {
                if t < *prev {
                    return;
                };
                let delta = t - *prev;
                self.buf.push_back(delta);
                *prev = t;
                if self.buf.len() > self.history_size {
                    let _ = self.buf.pop_front();
                }
            }
        }
    }

    /// def ϕ(Tnow ) = − log10(Plater (Tnow − Tlast))
    pub fn phi(&self, now: u64) -> f64 {
        match &self.prev_heartbeat {
            &Some(prev_time) if now > prev_time => {
                trace!(
                    "now:{} - prev_heartbeat:{} = {:?}",
                    now,
                    prev_time,
                    now - prev_time
                );
                let p_later = self.p_later(now - prev_time);
                -p_later.log10()
            }
            &Some(prev_time) => {
                trace!("now:{} <= prev_heartbeat:{}", now, prev_time);
                0.0
            }
            &None => 0.0,
        }
    }

    /// Returns the time t (within epsilon) at which phi will be >= val .
    pub fn next_crossing_at(&self, now: u64, threshold: f64) -> u64 {
        let phappened = 1.0 - (10.0f64).powf(-threshold);

        let x = phappened.norm_inv();
        let mean = stats::mean(self.buf.iter().cloned());
        let stddev = stats::stddev(self.buf.iter().cloned()).max(self.min_stddev);
        let diff = x * stddev + mean;
        let then = now + diff.ceil() as u64;

        trace!(
            "threshold:{}; phappened:{}; x:{}; mean:{}; stddev:{}; diff:{}; then:{}",
            threshold,
            phappened,
            x,
            mean,
            stddev,
            diff,
            then
        );

        then
    }

    fn p_later(&self, diff: u64) -> f64 {
        let mean = stats::mean(self.buf.iter().cloned());
        let stddev = stats::stddev(self.buf.iter().cloned()).max(self.min_stddev);
        let x = (diff as f64 - mean) / stddev;
        // let cdf = 0.5*(1.0+ (x/(2.0f64).sqrt()).erf())
        let p = 1.0 - x.norm();
        trace!(
            "diff:{:e}; mean:{:e}; stddev:{:e} x:{:e}; p_later:{:e}",
            diff as f64,
            mean,
            stddev,
            x,
            p
        );
        // We want to avoid returning zero, as we want the logarithm of the probability.
        // And the log of zero is meaningless.
        if p < f64::MIN_POSITIVE {
            f64::MIN_POSITIVE
        } else {
            p
        }
    }
}

#[cfg(test)]
mod tests {
    extern crate env_logger;
    extern crate rand;
    extern crate rand_distr;
    use self::rand::thread_rng;
    use self::rand_distr::Distribution;
    use self::rand_distr::LogNormal;
    use super::PhiFailureDetector;
    #[test]
    fn should_fail_when_no_heartbeats() {
        env_logger::try_init().unwrap_or_default();

        let mut detector = PhiFailureDetector::new();
        for t in 0..100 {
            detector.heartbeat(t);
            let phi = detector.phi(t);
            trace!("at:{:?}, phi:{:?}; det: {:?}", t, phi, detector);
            if t > 10 {
                assert!(phi < 1.0);
            }
        }
        for t in 100..110 {
            let phi = detector.phi(t);
            trace!("at:{:?}, phi:{:?}; det: {:?}", t, phi, detector);
        }
        for &t in &[110, 200, 300] {
            let phi = detector.phi(t);
            trace!("at:{:?}, phi:{:?}; det: {:?}", t, phi, detector);
            assert!(phi > 1.0, "t:{:?}; phi:{:?} > 1.0", t, phi);
        }
    }

    #[test]
    fn should_recover() {
        env_logger::try_init().unwrap_or_default();
        let mut detector = PhiFailureDetector::new().history_size(3);
        for t in 0..10 {
            detector.heartbeat(t);
            let phi = detector.phi(t);
            trace!("at:{:?}, phi:{:?}; det: {:?}", t, phi, detector);
        }
        for t in 20..30 {
            detector.heartbeat(t);
            let phi = detector.phi(t);
            trace!("at:{:?}, phi:{:?}; det: {:?}", t, phi, detector);
            if t > 10 {
                assert!(phi < 1.0);
            }
        }
    }

    #[test]
    fn should_estimate_threshold_times() {
        env_logger::try_init().unwrap_or_default();
        let epsilon = 2;
        let mut detector = PhiFailureDetector::new().history_size(3);

        let mut t = 0;
        for n in 0u64..10 {
            let dist = LogNormal::new(10.0, 100.0).expect("lognormal");
            let diff = dist.sample(&mut thread_rng());
            t = n * 1000;
            trace!(
                "at:{:?}, diff:{:e}; phi:{:?}; det: {:?}",
                t,
                diff,
                detector.phi(t),
                detector
            );
            detector.heartbeat(t);
        }
        // Estimate the point at which
        let threshold = 1.0;
        let est_1 = detector.next_crossing_at(t, threshold);

        let pre = detector.phi(est_1 - epsilon);
        let at = detector.phi(est_1);
        assert!(
            pre < threshold && at >= threshold,
            "phi({}):{:?} < {:?} && phi({}):{:?} >= {:?}",
            est_1 - epsilon,
            pre,
            threshold,
            est_1,
            at,
            threshold
        );
    }
}
<|endoftext|>


<commit_before>//! Crate `etcd` provides a client for [etcd](https://github.com/coreos/etcd), a distributed
//! key-value store from [CoreOS](https://coreos.com/).
//!
//! The client uses etcd's v2 API. Support for the v3 API is planned, and will be added via
//! separate types for backwards compatibility and to support both APIs simultaneously.
//!
//! The client uses asynchronous I/O, backed by the `futures` and `tokio` crates, and requires
//! both to be used alongside. Where possible, futures are returned using "impl Trait" instead of
//! boxing.
//!
//! The client is tested against etcd 2.3.8.
//!
//! # Usage
//!
//! `Client` is an HTTP client required for all API calls. It can be constructed to use HTTP or
//! HTTPS, and supports authenticating to the etcd cluster via HTTP basic authentication (username
//! and password) and/or X.509 client certificates.
//!
//! To get basic information about the versions of etcd running in a cluster, use the
//! `Client::versions` method. All other API calls are made by passing a `Client` reference to the
//! functions in the `auth`, `kv`, `members`, and `stats` modules. These modules contain functions
//! for API calls to the authentication and authorization API, the primary key-value store API, the
//! cluster membership API, and statistics API, respectively.
//!
//! # Examples
//!
//! Basic usage:
//!
//! ```no_run
//! use etcd::Client;
//! use etcd::kv::{self, Action};
//! use futures::Future;
//! use tokio::runtime::Runtime;
//!
//! fn main() {
//!     // Create a client to access a single cluster member. Addresses of multiple cluster
//!     // members can be provided and the client will try each one in sequence until it
//!     // receives a successful response.
//!     let client = Client::new(&["http://etcd.example.com:2379"], None).unwrap();
//!
//!     // Set the key "/foo" to the value "bar" with no expiration.
//!     let work = kv::set(&client, "/foo", "bar", None).and_then(move |_| {
//!         // Once the key has been set, ask for details about it.
//!         let get_request = kv::get(&client, "/foo", kv::GetOptions::default());
//!
//!         get_request.and_then(|response| {
//!             // The information returned tells you what kind of operation was performed.
//!             assert_eq!(response.data.action, Action::Get);
//!
//!             // The value of the key is what we set it to previously.
//!             assert_eq!(response.data.node.value, Some("bar".to_string()));
//!
//!             // Each API call also returns information about the etcd cluster extracted from
//!             // HTTP response headers.
//!             assert!(response.cluster_info.etcd_index.is_some());
//!
//!             Ok(())
//!         })
//!     });
//!
//!     // Start the event loop, driving the asynchronous code to completion.
//!     assert!(Runtime::new().unwrap().block_on(work).is_ok());
//! }
//! ```
//!
//! # Cargo features
//!
//! Crate `etcd` has one Cargo feature, `tls`, which adds HTTPS support via the `Client::https`
//! constructor. This feature is enabled by default.
#![deny(missing_debug_implementations, missing_docs, warnings)]

pub use crate::client::{BasicAuth, Client, ClusterInfo, Health, Response};
pub use crate::error::{ApiError, Error};
pub use crate::version::VersionInfo;

pub mod auth;
pub mod kv;
pub mod members;
pub mod stats;

mod client;
mod error;
mod first_ok;
mod http;
mod options;
mod version;
<commit_msg>Update docs to mention the health API.<commit_after>//! Crate `etcd` provides a client for [etcd](https://github.com/coreos/etcd), a distributed
//! key-value store from [CoreOS](https://coreos.com/).
//!
//! The client uses etcd's v2 API. Support for the v3 API is planned, and will be added via
//! separate types for backwards compatibility and to support both APIs simultaneously.
//!
//! The client uses asynchronous I/O, backed by the `futures` and `tokio` crates, and requires
//! both to be used alongside. Where possible, futures are returned using "impl Trait" instead of
//! boxing.
//!
//! The client is tested against etcd 2.3.8.
//!
//! # Usage
//!
//! `Client` is an HTTP client required for all API calls. It can be constructed to use HTTP or
//! HTTPS, and supports authenticating to the etcd cluster via HTTP basic authentication (username
//! and password) and/or X.509 client certificates.
//!
//! To get basic information about the health and versions of etcd running in a cluster, use the
//! `Client::health` and `Client::versions` methods, respectively. All other API calls are made by
//! passing a `Client` reference to the functions in the `auth`, `kv`, `members`, and `stats`
//! modules. These modules contain functions for API calls to the authentication and authorization
//! API, the primary key-value store API, the cluster membership API, and statistics API,
//! respectively.
//!
//! # Examples
//!
//! Basic usage:
//!
//! ```no_run
//! use etcd::Client;
//! use etcd::kv::{self, Action};
//! use futures::Future;
//! use tokio::runtime::Runtime;
//!
//! fn main() {
//!     // Create a client to access a single cluster member. Addresses of multiple cluster
//!     // members can be provided and the client will try each one in sequence until it
//!     // receives a successful response.
//!     let client = Client::new(&["http://etcd.example.com:2379"], None).unwrap();
//!
//!     // Set the key "/foo" to the value "bar" with no expiration.
//!     let work = kv::set(&client, "/foo", "bar", None).and_then(move |_| {
//!         // Once the key has been set, ask for details about it.
//!         let get_request = kv::get(&client, "/foo", kv::GetOptions::default());
//!
//!         get_request.and_then(|response| {
//!             // The information returned tells you what kind of operation was performed.
//!             assert_eq!(response.data.action, Action::Get);
//!
//!             // The value of the key is what we set it to previously.
//!             assert_eq!(response.data.node.value, Some("bar".to_string()));
//!
//!             // Each API call also returns information about the etcd cluster extracted from
//!             // HTTP response headers.
//!             assert!(response.cluster_info.etcd_index.is_some());
//!
//!             Ok(())
//!         })
//!     });
//!
//!     // Start the event loop, driving the asynchronous code to completion.
//!     assert!(Runtime::new().unwrap().block_on(work).is_ok());
//! }
//! ```
//!
//! # Cargo features
//!
//! Crate `etcd` has one Cargo feature, `tls`, which adds HTTPS support via the `Client::https`
//! constructor. This feature is enabled by default.
#![deny(missing_debug_implementations, missing_docs, warnings)]

pub use crate::client::{BasicAuth, Client, ClusterInfo, Health, Response};
pub use crate::error::{ApiError, Error};
pub use crate::version::VersionInfo;

pub mod auth;
pub mod kv;
pub mod members;
pub mod stats;

mod client;
mod error;
mod first_ok;
mod http;
mod options;
mod version;
<|endoftext|>


<commit_before>#![recursion_limit = "1024"]
#[macro_use]

extern crate error_chain;
extern crate git2;

pub mod errors;
pub mod git;

use std::fmt;
use std::process::Command;

use errors::*;
use git::Config;

#[derive(Clone, Debug, PartialEq)]
pub struct Author {
  pub name: String,
  pub email: String,
}

impl fmt::Display for Author {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    write!(f, "{} <{}>", self.name, self.email)
  }
}

pub struct GitTogether<C> {
  pub config: C,
}

impl<C: Config> GitTogether<C> {
  pub fn set_active(&mut self, inits: &[&str]) -> Result<Vec<Author>> {
    let authors = try!(self.get_authors(inits));
    try!(self.config.set("active", &inits.join("+")));
    Ok(authors)
  }

  pub fn signoff<'a>(&self, cmd: &'a mut Command) -> Result<&'a mut Command> {
    let active = try!(self.config.get("active"));
    let inits: Vec<_> = active.split('+').collect();
    let authors = try!(self.get_authors(&inits));

    let cmd = match authors.get(0) {
      Some(author) => {
        cmd.env("GIT_AUTHOR_NAME", author.name.clone())
          .env("GIT_AUTHOR_EMAIL", author.email.clone())
      }
      _ => cmd,
    };

    let cmd = match authors.get(1) {
      Some(committer) => {
        cmd.env("GIT_COMMITTER_NAME", committer.name.clone())
          .env("GIT_COMMITTER_EMAIL", committer.email.clone())
          .arg("--signoff")
      }
      _ => cmd,
    };

    Ok(cmd)
  }

  fn get_active(&self) -> Result<Vec<String>> {
    self.config
      .get("active")
      .map(|active| active.split('+').map(|s| s.into()).collect())
  }

  pub fn rotate_active(&mut self) -> Result<()> {
    self.get_active().and_then(|active| {
      let mut inits: Vec<_> = active.iter().map(String::as_ref).collect();
      if !inits.is_empty() {
        let author = inits.remove(0);
        inits.push(author);
      }
      self.set_active(&inits[..]).map(|_| ())
    })
  }

  fn get_authors(&self, inits: &[&str]) -> Result<Vec<Author>> {
    let domain = try!(self.config.get("domain"));
    inits.iter()
      .map(|&init| {
        self.config
          .get(&format!("authors.{}", init))
          .chain_err(|| ErrorKind::AuthorNotFound(init.into()))
          .and_then(|raw| {
            if raw.is_empty() {
              return Err(ErrorKind::InvalidAuthor(raw).into());
            }

            Self::author(&domain, &raw)
          })
      })
      .collect()
  }

  fn author(domain: &str, raw: &str) -> Result<Author> {
    let split: Vec<_> = raw.split(';').collect();
    if split.len() < 2 {
      return Err(ErrorKind::InvalidAuthor(raw.into()).into());
    }

    let name = split[0].trim().to_string();
    if name.is_empty() {
      return Err(ErrorKind::InvalidAuthor(raw.into()).into());
    }

    let email_seed = split[1].trim();
    if email_seed.is_empty() {
      return Err(ErrorKind::InvalidAuthor(raw.into()).into());
    }

    let email = if email_seed.contains('@') {
      email_seed.into()
    } else {
      format!("{}@{}", email_seed, domain)
    };

    Ok(Author {
      name: name,
      email: email,
    })
  }
}

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

  use std::cell::RefCell;
  use std::collections::HashMap;

  use errors::*;
  use git::Config;

  #[test]
  fn get_authors_no_domain() {
    let config = MockConfig::new(&[("authors.jh", "James Holden; jholden")]);
    let gt = GitTogether { config: config };

    assert!(gt.get_authors(&["jh"]).is_err());
  }

  #[test]
  fn get_authors() {
    let config =
      MockConfig::new(&[("domain", "rocinante.com"),
                        ("authors.jh", ""),
                        ("authors.nn", "Naomi Nagata"),
                        ("authors.ab", "Amos Burton; aburton"),
                        ("authors.ak", "Alex Kamal; akamal"),
                        ("authors.ca", "Chrisjen Avasarala;"),
                        ("authors.bd", "Bobbie Draper; bdraper@mars.mil"),
                        ("authors.jm", "Joe Miller; jmiller@starhelix.com")]);
    let gt = GitTogether { config: config };

    assert!(gt.get_authors(&["jh"]).is_err());
    assert!(gt.get_authors(&["nn"]).is_err());
    assert!(gt.get_authors(&["ca"]).is_err());
    assert!(gt.get_authors(&["jh", "bd"]).is_err());

    assert_eq!(gt.get_authors(&["ab", "ak"]).unwrap(),
               vec![Author {
                      name: "Amos Burton".into(),
                      email: "aburton@rocinante.com".into(),
                    },
                    Author {
                      name: "Alex Kamal".into(),
                      email: "akamal@rocinante.com".into(),
                    }]);
    assert_eq!(gt.get_authors(&["ab", "bd", "jm"]).unwrap(),
               vec![Author {
                      name: "Amos Burton".into(),
                      email: "aburton@rocinante.com".into(),
                    },
                    Author {
                      name: "Bobbie Draper".into(),
                      email: "bdraper@mars.mil".into(),
                    },
                    Author {
                      name: "Joe Miller".into(),
                      email: "jmiller@starhelix.com".into(),
                    }]);
  }

  #[test]
  fn set_active() {
    let config = MockConfig::new(&[("domain", "rocinante.com"),
                                   ("authors.jh", "James Holden; jholden"),
                                   ("authors.nn", "Naomi Nagata; nnagata")]);
    let mut gt = GitTogether { config: config };

    gt.set_active(&["jh"]).unwrap();
    assert_eq!(gt.get_active().unwrap(), vec!["jh"]);

    gt.set_active(&["jh", "nn"]).unwrap();
    assert_eq!(gt.get_active().unwrap(), vec!["jh", "nn"]);
  }

  #[test]
  fn rotate_active() {
    let config = MockConfig::new(&[("active", "jh+nn"),
                                   ("domain", "rocinante.com"),
                                   ("authors.jh", "James Holden; jholden"),
                                   ("authors.nn", "Naomi Nagata; nnagata")]);
    let mut gt = GitTogether { config: config };

    gt.rotate_active().unwrap();
    assert_eq!(gt.get_active().unwrap(), vec!["nn", "jh"]);
  }

  struct MockConfig {
    data: RefCell<HashMap<String, String>>,
  }

  impl MockConfig {
    fn new(data: &[(&str, &str)]) -> MockConfig {
      let data = data.iter()
        .map(|&(k, v)| (k.into(), v.into()))
        .collect();
      MockConfig { data: RefCell::new(data) }
    }
  }

  impl Config for MockConfig {
    fn get(&self, name: &str) -> Result<String> {
      self.data
        .borrow()
        .get(name.into())
        .cloned()
        .ok_or(format!("name not found: '{}'", name).into())
    }

    fn set(&mut self, name: &str, value: &str) -> Result<()> {
      self.data.borrow_mut().insert(name.into(), value.into());
      Ok(())
    }
  }
}
<commit_msg>remove unnecessary RefCell from MockConfig<commit_after>#![recursion_limit = "1024"]
#[macro_use]

extern crate error_chain;
extern crate git2;

pub mod errors;
pub mod git;

use std::fmt;
use std::process::Command;

use errors::*;
use git::Config;

#[derive(Clone, Debug, PartialEq)]
pub struct Author {
  pub name: String,
  pub email: String,
}

impl fmt::Display for Author {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    write!(f, "{} <{}>", self.name, self.email)
  }
}

pub struct GitTogether<C> {
  pub config: C,
}

impl<C: Config> GitTogether<C> {
  pub fn set_active(&mut self, inits: &[&str]) -> Result<Vec<Author>> {
    let authors = try!(self.get_authors(inits));
    try!(self.config.set("active", &inits.join("+")));
    Ok(authors)
  }

  pub fn signoff<'a>(&self, cmd: &'a mut Command) -> Result<&'a mut Command> {
    let active = try!(self.config.get("active"));
    let inits: Vec<_> = active.split('+').collect();
    let authors = try!(self.get_authors(&inits));

    let cmd = match authors.get(0) {
      Some(author) => {
        cmd.env("GIT_AUTHOR_NAME", author.name.clone())
          .env("GIT_AUTHOR_EMAIL", author.email.clone())
      }
      _ => cmd,
    };

    let cmd = match authors.get(1) {
      Some(committer) => {
        cmd.env("GIT_COMMITTER_NAME", committer.name.clone())
          .env("GIT_COMMITTER_EMAIL", committer.email.clone())
          .arg("--signoff")
      }
      _ => cmd,
    };

    Ok(cmd)
  }

  fn get_active(&self) -> Result<Vec<String>> {
    self.config
      .get("active")
      .map(|active| active.split('+').map(|s| s.into()).collect())
  }

  pub fn rotate_active(&mut self) -> Result<()> {
    self.get_active().and_then(|active| {
      let mut inits: Vec<_> = active.iter().map(String::as_ref).collect();
      if !inits.is_empty() {
        let author = inits.remove(0);
        inits.push(author);
      }
      self.set_active(&inits[..]).map(|_| ())
    })
  }

  fn get_authors(&self, inits: &[&str]) -> Result<Vec<Author>> {
    let domain = try!(self.config.get("domain"));
    inits.iter()
      .map(|&init| {
        self.config
          .get(&format!("authors.{}", init))
          .chain_err(|| ErrorKind::AuthorNotFound(init.into()))
          .and_then(|raw| {
            if raw.is_empty() {
              return Err(ErrorKind::InvalidAuthor(raw).into());
            }

            Self::author(&domain, &raw)
          })
      })
      .collect()
  }

  fn author(domain: &str, raw: &str) -> Result<Author> {
    let split: Vec<_> = raw.split(';').collect();
    if split.len() < 2 {
      return Err(ErrorKind::InvalidAuthor(raw.into()).into());
    }

    let name = split[0].trim().to_string();
    if name.is_empty() {
      return Err(ErrorKind::InvalidAuthor(raw.into()).into());
    }

    let email_seed = split[1].trim();
    if email_seed.is_empty() {
      return Err(ErrorKind::InvalidAuthor(raw.into()).into());
    }

    let email = if email_seed.contains('@') {
      email_seed.into()
    } else {
      format!("{}@{}", email_seed, domain)
    };

    Ok(Author {
      name: name,
      email: email,
    })
  }
}

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

  use std::collections::HashMap;

  use errors::*;
  use git::Config;

  #[test]
  fn get_authors_no_domain() {
    let config = MockConfig::new(&[("authors.jh", "James Holden; jholden")]);
    let gt = GitTogether { config: config };

    assert!(gt.get_authors(&["jh"]).is_err());
  }

  #[test]
  fn get_authors() {
    let config =
      MockConfig::new(&[("domain", "rocinante.com"),
                        ("authors.jh", ""),
                        ("authors.nn", "Naomi Nagata"),
                        ("authors.ab", "Amos Burton; aburton"),
                        ("authors.ak", "Alex Kamal; akamal"),
                        ("authors.ca", "Chrisjen Avasarala;"),
                        ("authors.bd", "Bobbie Draper; bdraper@mars.mil"),
                        ("authors.jm", "Joe Miller; jmiller@starhelix.com")]);
    let gt = GitTogether { config: config };

    assert!(gt.get_authors(&["jh"]).is_err());
    assert!(gt.get_authors(&["nn"]).is_err());
    assert!(gt.get_authors(&["ca"]).is_err());
    assert!(gt.get_authors(&["jh", "bd"]).is_err());

    assert_eq!(gt.get_authors(&["ab", "ak"]).unwrap(),
               vec![Author {
                      name: "Amos Burton".into(),
                      email: "aburton@rocinante.com".into(),
                    },
                    Author {
                      name: "Alex Kamal".into(),
                      email: "akamal@rocinante.com".into(),
                    }]);
    assert_eq!(gt.get_authors(&["ab", "bd", "jm"]).unwrap(),
               vec![Author {
                      name: "Amos Burton".into(),
                      email: "aburton@rocinante.com".into(),
                    },
                    Author {
                      name: "Bobbie Draper".into(),
                      email: "bdraper@mars.mil".into(),
                    },
                    Author {
                      name: "Joe Miller".into(),
                      email: "jmiller@starhelix.com".into(),
                    }]);
  }

  #[test]
  fn set_active() {
    let config = MockConfig::new(&[("domain", "rocinante.com"),
                                   ("authors.jh", "James Holden; jholden"),
                                   ("authors.nn", "Naomi Nagata; nnagata")]);
    let mut gt = GitTogether { config: config };

    gt.set_active(&["jh"]).unwrap();
    assert_eq!(gt.get_active().unwrap(), vec!["jh"]);

    gt.set_active(&["jh", "nn"]).unwrap();
    assert_eq!(gt.get_active().unwrap(), vec!["jh", "nn"]);
  }

  #[test]
  fn rotate_active() {
    let config = MockConfig::new(&[("active", "jh+nn"),
                                   ("domain", "rocinante.com"),
                                   ("authors.jh", "James Holden; jholden"),
                                   ("authors.nn", "Naomi Nagata; nnagata")]);
    let mut gt = GitTogether { config: config };

    gt.rotate_active().unwrap();
    assert_eq!(gt.get_active().unwrap(), vec!["nn", "jh"]);
  }

  struct MockConfig {
    data: HashMap<String, String>,
  }

  impl MockConfig {
    fn new(data: &[(&str, &str)]) -> MockConfig {
      MockConfig {
        data: data.iter().map(|&(k, v)| (k.into(), v.into())).collect(),
      }
    }
  }

  impl Config for MockConfig {
    fn get(&self, name: &str) -> Result<String> {
      self.data
        .get(name.into())
        .cloned()
        .ok_or(format!("name not found: '{}'", name).into())
    }

    fn set(&mut self, name: &str, value: &str) -> Result<()> {
      self.data.insert(name.into(), value.into());
      Ok(())
    }
  }
}
<|endoftext|>