<commit_before>//! The cookies module contains types for working with request and response cookies.

use cookie_crate;
use header;
use std::borrow::Cow;
use std::fmt;
use std::time::SystemTime;

/// Convert a time::Tm time to SystemTime.
fn tm_to_systemtime(tm: ::time::Tm) -> SystemTime {
    let seconds = tm.to_timespec().sec;
    let duration = std::time::Duration::from_secs(seconds.abs() as u64);
    if seconds > 0 {
        SystemTime::UNIX_EPOCH + duration
    } else {
        SystemTime::UNIX_EPOCH - duration
    }
}

/// Error representing a parse failure of a 'Set-Cookie' header.
pub struct CookieParseError(cookie::ParseError);

impl<'a> fmt::Debug for CookieParseError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.0.fmt(f)
    }
}

impl<'a> fmt::Display for CookieParseError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.0.fmt(f)
    }
}

impl std::error::Error for CookieParseError {}

/// A single HTTP cookie.
pub struct Cookie<'a>(cookie::Cookie<'a>);

impl<'a> fmt::Debug for Cookie<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.0.fmt(f)
    }
}

impl Cookie<'static> {
    /// Construct a new cookie with the given name and value.
    pub fn new<N, V>(name: N, value: V) -> Self
    where
        N: Into<Cow<'static, str>>,
        V: Into<Cow<'static, str>>,
    {
        Self(cookie::Cookie::new(name, value))
    }
}

impl<'a> Cookie<'a> {
    fn parse(value: &'a ::header::HeaderValue) -> Result<Cookie<'a>, CookieParseError> {
        std::str::from_utf8(value.as_bytes())
            .map_err(cookie::ParseError::from)
            .and_then(cookie::Cookie::parse)
            .map_err(CookieParseError)
            .map(Cookie)
    }

    pub(crate) fn into_inner(self) -> cookie::Cookie<'a> {
        self.0
    }

    /// The name of the cookie.
    pub fn name(&self) -> &str {
        self.0.name()
    }

    /// The value of the cookie.
    pub fn value(&self) -> &str {
        self.0.value()
    }

    /// Returns true if the 'HttpOnly' directive is enabled.
    pub fn http_only(&self) -> bool {
        self.0.http_only().unwrap_or(false)
    }

    /// Returns true if the 'Secure' directive is enabled.
    pub fn secure(&self) -> bool {
        self.0.secure().unwrap_or(false)
    }

    /// Returns true if  'SameSite' directive is 'Lax'.
    pub fn same_site_lax(&self) -> bool {
        self.0.same_site() == Some(cookie_crate::SameSite::Lax)
    }

    /// Returns true if  'SameSite' directive is 'Strict'.
    pub fn same_site_strict(&self) -> bool {
        self.0.same_site() == Some(cookie_crate::SameSite::Strict)
    }

    /// Returns the path directive of the cookie, if set.
    pub fn path(&self) -> Option<&str> {
        self.0.path()
    }

    /// Returns the domain directive of the cookie, if set.
    pub fn domain(&self) -> Option<&str> {
        self.0.domain()
    }

    /// Get the Max-Age information.
    pub fn max_age(&self) -> Option<std::time::Duration> {
        self.0.max_age().map(|d| std::time::Duration::new(d.num_seconds() as u64, 0))
    }

    /// The cookie expiration time.
    pub fn expires(&self) -> Option<SystemTime> {
        self.0.expires().map(tm_to_systemtime)
    }
}

pub(crate) fn extract_response_cookies<'a>(
    headers: &'a hyper::HeaderMap,
) -> impl Iterator<Item = Result<Cookie<'a>, CookieParseError>> + 'a {
    headers
        .get_all(header::SET_COOKIE)
        .iter()
        .map(|value| Cookie::parse(value))
}

/// A persistent cookie store that provides session support.
#[derive(Default)]
pub(crate) struct CookieStore(pub(crate) ::cookie_store::CookieStore);

impl<'a> fmt::Debug for CookieStore {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.0.fmt(f)
    }
}
<commit_msg>use Cookie constructor instead of Self()<commit_after>//! The cookies module contains types for working with request and response cookies.

use cookie_crate;
use header;
use std::borrow::Cow;
use std::fmt;
use std::time::SystemTime;

/// Convert a time::Tm time to SystemTime.
fn tm_to_systemtime(tm: ::time::Tm) -> SystemTime {
    let seconds = tm.to_timespec().sec;
    let duration = std::time::Duration::from_secs(seconds.abs() as u64);
    if seconds > 0 {
        SystemTime::UNIX_EPOCH + duration
    } else {
        SystemTime::UNIX_EPOCH - duration
    }
}

/// Error representing a parse failure of a 'Set-Cookie' header.
pub struct CookieParseError(cookie::ParseError);

impl<'a> fmt::Debug for CookieParseError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.0.fmt(f)
    }
}

impl<'a> fmt::Display for CookieParseError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.0.fmt(f)
    }
}

impl std::error::Error for CookieParseError {}

/// A single HTTP cookie.
pub struct Cookie<'a>(cookie::Cookie<'a>);

impl<'a> fmt::Debug for Cookie<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.0.fmt(f)
    }
}

impl Cookie<'static> {
    /// Construct a new cookie with the given name and value.
    pub fn new<N, V>(name: N, value: V) -> Self
    where
        N: Into<Cow<'static, str>>,
        V: Into<Cow<'static, str>>,
    {
        Cookie(cookie::Cookie::new(name, value))
    }
}

impl<'a> Cookie<'a> {
    fn parse(value: &'a ::header::HeaderValue) -> Result<Cookie<'a>, CookieParseError> {
        std::str::from_utf8(value.as_bytes())
            .map_err(cookie::ParseError::from)
            .and_then(cookie::Cookie::parse)
            .map_err(CookieParseError)
            .map(Cookie)
    }

    pub(crate) fn into_inner(self) -> cookie::Cookie<'a> {
        self.0
    }

    /// The name of the cookie.
    pub fn name(&self) -> &str {
        self.0.name()
    }

    /// The value of the cookie.
    pub fn value(&self) -> &str {
        self.0.value()
    }

    /// Returns true if the 'HttpOnly' directive is enabled.
    pub fn http_only(&self) -> bool {
        self.0.http_only().unwrap_or(false)
    }

    /// Returns true if the 'Secure' directive is enabled.
    pub fn secure(&self) -> bool {
        self.0.secure().unwrap_or(false)
    }

    /// Returns true if  'SameSite' directive is 'Lax'.
    pub fn same_site_lax(&self) -> bool {
        self.0.same_site() == Some(cookie_crate::SameSite::Lax)
    }

    /// Returns true if  'SameSite' directive is 'Strict'.
    pub fn same_site_strict(&self) -> bool {
        self.0.same_site() == Some(cookie_crate::SameSite::Strict)
    }

    /// Returns the path directive of the cookie, if set.
    pub fn path(&self) -> Option<&str> {
        self.0.path()
    }

    /// Returns the domain directive of the cookie, if set.
    pub fn domain(&self) -> Option<&str> {
        self.0.domain()
    }

    /// Get the Max-Age information.
    pub fn max_age(&self) -> Option<std::time::Duration> {
        self.0.max_age().map(|d| std::time::Duration::new(d.num_seconds() as u64, 0))
    }

    /// The cookie expiration time.
    pub fn expires(&self) -> Option<SystemTime> {
        self.0.expires().map(tm_to_systemtime)
    }
}

pub(crate) fn extract_response_cookies<'a>(
    headers: &'a hyper::HeaderMap,
) -> impl Iterator<Item = Result<Cookie<'a>, CookieParseError>> + 'a {
    headers
        .get_all(header::SET_COOKIE)
        .iter()
        .map(|value| Cookie::parse(value))
}

/// A persistent cookie store that provides session support.
#[derive(Default)]
pub(crate) struct CookieStore(pub(crate) ::cookie_store::CookieStore);

impl<'a> fmt::Debug for CookieStore {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.0.fmt(f)
    }
}
<|endoftext|>


<commit_before>use std::collections::HashMap;
use std::io::{Cursor, Read, Seek, SeekFrom};
use std::fmt;
use std::error::Error;
use std::sync::mpsc;
use std::rc::Rc;
use std::cell::RefCell;

use mio::*;
use mio::tcp::*;
use http_muncher::Parser;
use rustc_serialize::base64::{ToBase64, STANDARD};
use sha1::Sha1;
use bytes::{Buf, ByteBuf, MutByteBuf};
use byteorder::{ByteOrder, BigEndian};

use http::HttpParser;
use websocket_essentials::{Frame, OpCode, StatusCode, BufferedFrameReader, ParseError};
use interface::{WebSocketEvent, WebSocketInternalMessage};

const WEBSOCKET_KEY: &'static [u8] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

fn gen_key(key: &str) -> String {
    let mut m = Sha1::new();
    let mut buf = [0u8; 20];

    m.update(key.as_bytes());
    m.update(WEBSOCKET_KEY);

    m.output(&mut buf);

    return buf.to_base64(STANDARD);
}

enum ClientState {
    AwaitingHandshake(RefCell<Parser<HttpParser>>),
    HandshakeResponse,
    Connected
}

pub struct WebSocketClient {
    pub socket: TcpStream,
    pub interest: EventSet,
    headers: Rc<RefCell<HashMap<String, String>>>,
    state: ClientState,
    outgoing: Vec<Frame>,
    outgoing_bytes: ByteBuf,
    tx: mpsc::Sender<WebSocketEvent>,
    event_loop_tx: Sender<WebSocketInternalMessage>,
    token: Token,
    frame_reader: BufferedFrameReader,
    close_connection: bool
}

impl WebSocketClient {
    pub fn new(socket: TcpStream, token: Token, server_sink: mpsc::Sender<WebSocketEvent>,
               event_loop_sink: Sender<WebSocketInternalMessage>) -> WebSocketClient {
        let headers = Rc::new(RefCell::new(HashMap::new()));

        WebSocketClient {
            socket: socket,
            headers: headers.clone(),
            interest: EventSet::readable(),
            state: ClientState::AwaitingHandshake(RefCell::new(Parser::request(HttpParser {
                current_key: None,
                headers: headers.clone()
            }))),
            outgoing: Vec::new(),
            outgoing_bytes: ByteBuf::none(),
            tx: server_sink,
            event_loop_tx: event_loop_sink,
            token: token,
            frame_reader: BufferedFrameReader::new(),
            close_connection: false
        }
    }

    pub fn send_message(&mut self, msg: WebSocketEvent) -> Result<(), String> {
        let frame = match msg {
            WebSocketEvent::TextMessage(_, ref data) => Some(Frame::from(&*data.clone())),
            WebSocketEvent::BinaryMessage(_, ref data) => Some(Frame::from(&*data.clone())),
            WebSocketEvent::Close(_, status_code) => Some(Frame::close(status_code)),
            WebSocketEvent::Ping(_, ref payload) => Some(Frame::ping(payload.clone())),
            _ => None
        };

        if frame.is_none() {
            return Err("Wrong message type to send".to_string());
        }

        self.outgoing.push(frame.unwrap());

        if self.interest.is_readable() {
            trace!("{:?} sending {} frames, switching to write", self.token, self.outgoing.len());

            self.interest.insert(EventSet::writable());
            self.interest.remove(EventSet::readable());

            try!(self.event_loop_tx.send(WebSocketInternalMessage::Reregister(self.token))
                 .map_err(|e| e.description().to_string()));
        }

        Ok(())
    }

    fn close_with_status(&mut self, status: StatusCode) {
        self.outgoing.push(Frame::close(status));
    }

    pub fn write(&mut self) {
        match self.state {
            ClientState::HandshakeResponse => self.write_handshake(),
            ClientState::Connected => self.write_frames(),
            _ => {}
        }
    }

    fn write_handshake(&mut self) {
        let headers = self.headers.borrow();
        let response_key = gen_key(&*headers.get("Sec-WebSocket-Key").unwrap());
        let response = fmt::format(format_args!("HTTP/1.1 101 Switching Protocols\r\n\
                                                 Connection: Upgrade\r\n\
                                                 Sec-WebSocket-Accept: {}\r\n\
                                                 Upgrade: websocket\r\n\r\n", response_key));
        self.socket.try_write(response.as_bytes()).unwrap();

        // Change the state
        self.state = ClientState::Connected;

        // Send the connection event
        self.tx.send(WebSocketEvent::Connect(self.token));

        self.interest.remove(EventSet::writable());
        self.interest.insert(EventSet::readable());
    }

    fn serialize_frames(&mut self) -> Vec<u8> {
        // FIXME: calculate capacity
        let mut out_buf = Vec::new();
        {
            for frame in self.outgoing.iter() {
                if let Err(e) = frame.write(&mut out_buf) {
                    println!("error on write: {}", e);
                }
            }
        }
        out_buf
    }

    fn write_frames(&mut self) {
        loop {
            if !self.outgoing_bytes.has_remaining() {
                if self.outgoing.len() > 0 {
                    trace!("{:?} has {} more frames to send in queue", self.token, self.outgoing.len());
                    let out_buf = self.serialize_frames();
                    self.outgoing_bytes = ByteBuf::from_slice(&*out_buf);
                    if !self.close_connection {
                        self.close_connection = self.outgoing.iter().any(|ref frame| frame.is_close());
                    }
                    self.outgoing.clear();
                } else {
                    // Buffer is exhausted and we have no more frames to send out.
                    trace!("{:?} wrote all bytes; switching to reading", self.token);
                    if self.close_connection {
                        trace!("{:?} closing connection", self.token);
                        self.socket.shutdown(Shutdown::Write);
                    }
                    self.interest.remove(EventSet::writable());
                    self.interest.insert(EventSet::readable());
                    break;
                }
            }

            match self.socket.try_write_buf(&mut self.outgoing_bytes) {
                Ok(Some(write_bytes)) => {
                    trace!("{:?} wrote {} bytes, remaining: {}", self.token, write_bytes, self.outgoing_bytes.remaining());
                },
                Ok(None) => {
                    // This write call would block
                    break;
                },
                Err(e) => {
                    // Write error - close this connnection immediately
                    error!("{:?} Error occured while writing bytes: {}", self.token, e);
                    self.interest.remove(EventSet::writable());
                    self.interest.insert(EventSet::hup());
                    break;
                }
            }
        }
    }

    pub fn read(&mut self) {
        match self.state {
            ClientState::AwaitingHandshake(_) => self.read_handshake(),
            ClientState::Connected => self.read_frame(),
            _ => {}
        }
    }

    fn read_frame(&mut self) {
        loop {
            let mut buf = ByteBuf::mut_with_capacity(16384);
            match self.socket.try_read_buf(&mut buf) {
                Err(e) => {
                    error!("{:?} Error while reading socket: {:?}", self.token, e);
                    self.interest.remove(EventSet::readable());
                    self.interest.insert(EventSet::hup());
                    return
                },
                Ok(None) =>
                    // Socket buffer has got no more bytes.
                    break,
                Ok(Some(0)) => {
                    // Remote end has closed connection, we can close it now, too.
                    self.interest.remove(EventSet::readable());
                    self.interest.insert(EventSet::hup());
                    return;
                },
                Ok(Some(read_bytes)) => {
                    trace!("{:?} read {} bytes", self.token, read_bytes);
                    let mut read_buf = buf.flip();
                    let mut frames_cnt = 0;
                    loop {
                        match self.frame_reader.read(&mut read_buf) {
                            Err(err @ ParseError::InvalidOpCode(..)) => {
                                error!("{:?} Invalid OpCode: {}", self.token, err);
                                self.close_with_status(StatusCode::ProtocolError);
                                break;
                            },
                            Err(e) => {
                                error!("{:?} Error while reading frame: {}", self.token, e);
                                self.interest.remove(EventSet::readable());
                                self.interest.insert(EventSet::hup());
                                return;
                            },
                            Ok(None) => break,
                            Ok(Some(frame)) => {
                                frames_cnt += 1;
                                match frame.get_opcode() {
                                    OpCode::TextFrame => {
                                        let payload = ::std::str::from_utf8(&*frame.payload);
                                        if let Err(e) = payload {
                                            error!("{:?} Utf8 decode error: {}", self.token, e);
                                            self.close_with_status(StatusCode::ProtocolError);
                                            break;
                                        }
                                        self.tx.send(WebSocketEvent::TextMessage(self.token, payload.unwrap().to_owned()));
                                    },
                                    OpCode::BinaryFrame => {
                                        self.tx.send(WebSocketEvent::BinaryMessage(self.token, (&*frame.payload).to_owned()));
                                    },
                                    OpCode::Ping => {
                                        if frame.payload.len() > 125 {
                                            error!("{:?} Control frame length is > 125", self.token);
                                            self.close_with_status(StatusCode::ProtocolError);
                                        } else {
                                            self.outgoing.push(Frame::pong(&frame));
                                        }
                                    },
                                    OpCode::ConnectionClose => {
                                        let close_ev = if frame.payload.len() >= 2 {
                                            let status_code = BigEndian::read_u16(&frame.payload[0..2]);
                                            WebSocketEvent::Close(self.token, StatusCode::from(status_code))
                                        } else {
                                            // No status code has been provided
                                            WebSocketEvent::Close(self.token, StatusCode::Custom(0))
                                        };
                                        self.tx.send(close_ev);

                                        if let Ok(response) = Frame::close_from(&frame) {
                                            self.outgoing.push(response);
                                        } else {
                                            self.close_with_status(StatusCode::ProtocolError);
                                        }
                                    },
                                    _ => {}
                                }
                            }
                        }
                    }
                    trace!("{:?} parsed {} frames", self.token, frames_cnt);
                    buf = read_buf.flip();
                }
            }
        }

        // Write any buffered outgoing frames
        if self.outgoing.len() > 0 {
            trace!("{:?} received {} frames, switching to write", self.token, self.outgoing.len());
            self.interest.remove(EventSet::readable());
            self.interest.insert(EventSet::writable());
        }
    }

    fn read_handshake(&mut self) {
        loop {
            let mut buf = [0; 2048];
            match self.socket.try_read(&mut buf) {
                Err(e) => {
                    println!("Error while reading socket: {:?}", e);
                    return
                },
                Ok(None) =>
                    // Socket buffer has got no more bytes.
                    break,
                Ok(Some(_)) => {
                    let is_upgrade = if let ClientState::AwaitingHandshake(ref parser_state) = self.state {
                        let mut parser = parser_state.borrow_mut();
                        parser.parse(&buf);
                        parser.is_upgrade()
                    } else { false };

                    if is_upgrade {
                        // Change the current state
                        self.state = ClientState::HandshakeResponse;

                        // Change current interest to `Writable`
                        self.interest.remove(EventSet::readable());
                        self.interest.insert(EventSet::writable());
                        break;
                    }
                }
            }
        }
    }
}
<commit_msg>Get rid of redundant copying<commit_after>use std::collections::HashMap;
use std::io::{Cursor, Read, Seek, SeekFrom};
use std::fmt;
use std::error::Error;
use std::sync::mpsc;
use std::rc::Rc;
use std::cell::RefCell;

use mio::*;
use mio::tcp::*;
use http_muncher::Parser;
use rustc_serialize::base64::{ToBase64, STANDARD};
use sha1::Sha1;
use bytes::{Buf, ByteBuf, MutByteBuf};
use byteorder::{ByteOrder, BigEndian};

use http::HttpParser;
use websocket_essentials::{Frame, OpCode, StatusCode, BufferedFrameReader, ParseError};
use interface::{WebSocketEvent, WebSocketInternalMessage};

const WEBSOCKET_KEY: &'static [u8] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

fn gen_key(key: &str) -> String {
    let mut m = Sha1::new();
    let mut buf = [0u8; 20];

    m.update(key.as_bytes());
    m.update(WEBSOCKET_KEY);

    m.output(&mut buf);

    return buf.to_base64(STANDARD);
}

enum ClientState {
    AwaitingHandshake(RefCell<Parser<HttpParser>>),
    HandshakeResponse,
    Connected
}

pub struct WebSocketClient {
    pub socket: TcpStream,
    pub interest: EventSet,
    headers: Rc<RefCell<HashMap<String, String>>>,
    state: ClientState,
    outgoing: Vec<Frame>,
    outgoing_bytes: ByteBuf,
    tx: mpsc::Sender<WebSocketEvent>,
    event_loop_tx: Sender<WebSocketInternalMessage>,
    token: Token,
    frame_reader: BufferedFrameReader,
    close_connection: bool
}

impl WebSocketClient {
    pub fn new(socket: TcpStream, token: Token, server_sink: mpsc::Sender<WebSocketEvent>,
               event_loop_sink: Sender<WebSocketInternalMessage>) -> WebSocketClient {
        let headers = Rc::new(RefCell::new(HashMap::new()));

        WebSocketClient {
            socket: socket,
            headers: headers.clone(),
            interest: EventSet::readable(),
            state: ClientState::AwaitingHandshake(RefCell::new(Parser::request(HttpParser {
                current_key: None,
                headers: headers.clone()
            }))),
            outgoing: Vec::new(),
            outgoing_bytes: ByteBuf::none(),
            tx: server_sink,
            event_loop_tx: event_loop_sink,
            token: token,
            frame_reader: BufferedFrameReader::new(),
            close_connection: false
        }
    }

    pub fn send_message(&mut self, msg: WebSocketEvent) -> Result<(), String> {
        let frame = match msg {
            WebSocketEvent::TextMessage(_, data) => Some(Frame::from(data)),
            WebSocketEvent::BinaryMessage(_, data) => Some(Frame::from(data)),
            WebSocketEvent::Close(_, status_code) => Some(Frame::close(status_code)),
            WebSocketEvent::Ping(_, ref payload) => Some(Frame::ping(&*payload)),
            _ => None
        };

        if frame.is_none() {
            return Err("Wrong message type to send".to_string());
        }

        self.outgoing.push(frame.unwrap());

        if self.interest.is_readable() {
            trace!("{:?} sending {} frames, switching to write", self.token, self.outgoing.len());

            self.interest.insert(EventSet::writable());
            self.interest.remove(EventSet::readable());

            try!(self.event_loop_tx.send(WebSocketInternalMessage::Reregister(self.token))
                 .map_err(|e| e.description().to_string()));
        }

        Ok(())
    }

    fn close_with_status(&mut self, status: StatusCode) {
        self.outgoing.push(Frame::close(status));
    }

    pub fn write(&mut self) {
        match self.state {
            ClientState::HandshakeResponse => self.write_handshake(),
            ClientState::Connected => self.write_frames(),
            _ => {}
        }
    }

    fn write_handshake(&mut self) {
        let headers = self.headers.borrow();
        let response_key = gen_key(&*headers.get("Sec-WebSocket-Key").unwrap());
        let response = fmt::format(format_args!("HTTP/1.1 101 Switching Protocols\r\n\
                                                 Connection: Upgrade\r\n\
                                                 Sec-WebSocket-Accept: {}\r\n\
                                                 Upgrade: websocket\r\n\r\n", response_key));
        self.socket.try_write(response.as_bytes()).unwrap();

        // Change the state
        self.state = ClientState::Connected;

        // Send the connection event
        self.tx.send(WebSocketEvent::Connect(self.token));

        self.interest.remove(EventSet::writable());
        self.interest.insert(EventSet::readable());
    }

    fn serialize_frames(&mut self) -> Vec<u8> {
        // FIXME: calculate capacity
        let mut out_buf = Vec::new();
        {
            for frame in self.outgoing.iter() {
                if let Err(e) = frame.write(&mut out_buf) {
                    println!("error on write: {}", e);
                }
            }
        }
        out_buf
    }

    fn write_frames(&mut self) {
        loop {
            if !self.outgoing_bytes.has_remaining() {
                if self.outgoing.len() > 0 {
                    trace!("{:?} has {} more frames to send in queue", self.token, self.outgoing.len());
                    let out_buf = self.serialize_frames();
                    self.outgoing_bytes = ByteBuf::from_slice(&*out_buf);
                    if !self.close_connection {
                        self.close_connection = self.outgoing.iter().any(|ref frame| frame.is_close());
                    }
                    self.outgoing.clear();
                } else {
                    // Buffer is exhausted and we have no more frames to send out.
                    trace!("{:?} wrote all bytes; switching to reading", self.token);
                    if self.close_connection {
                        trace!("{:?} closing connection", self.token);
                        self.socket.shutdown(Shutdown::Write);
                    }
                    self.interest.remove(EventSet::writable());
                    self.interest.insert(EventSet::readable());
                    break;
                }
            }

            match self.socket.try_write_buf(&mut self.outgoing_bytes) {
                Ok(Some(write_bytes)) => {
                    trace!("{:?} wrote {} bytes, remaining: {}", self.token, write_bytes, self.outgoing_bytes.remaining());
                },
                Ok(None) => {
                    // This write call would block
                    break;
                },
                Err(e) => {
                    // Write error - close this connnection immediately
                    error!("{:?} Error occured while writing bytes: {}", self.token, e);
                    self.interest.remove(EventSet::writable());
                    self.interest.insert(EventSet::hup());
                    break;
                }
            }
        }
    }

    pub fn read(&mut self) {
        match self.state {
            ClientState::AwaitingHandshake(_) => self.read_handshake(),
            ClientState::Connected => self.read_frame(),
            _ => {}
        }
    }

    fn read_frame(&mut self) {
        loop {
            let mut buf = ByteBuf::mut_with_capacity(16384);
            match self.socket.try_read_buf(&mut buf) {
                Err(e) => {
                    error!("{:?} Error while reading socket: {:?}", self.token, e);
                    self.interest.remove(EventSet::readable());
                    self.interest.insert(EventSet::hup());
                    return
                },
                Ok(None) =>
                    // Socket buffer has got no more bytes.
                    break,
                Ok(Some(0)) => {
                    // Remote end has closed connection, we can close it now, too.
                    self.interest.remove(EventSet::readable());
                    self.interest.insert(EventSet::hup());
                    return;
                },
                Ok(Some(read_bytes)) => {
                    trace!("{:?} read {} bytes", self.token, read_bytes);
                    let mut read_buf = buf.flip();
                    let mut frames_cnt = 0;
                    loop {
                        match self.frame_reader.read(&mut read_buf) {
                            Err(err @ ParseError::InvalidOpCode(..)) => {
                                error!("{:?} Invalid OpCode: {}", self.token, err);
                                self.close_with_status(StatusCode::ProtocolError);
                                break;
                            },
                            Err(e) => {
                                error!("{:?} Error while reading frame: {}", self.token, e);
                                self.interest.remove(EventSet::readable());
                                self.interest.insert(EventSet::hup());
                                return;
                            },
                            Ok(None) => break,
                            Ok(Some(frame)) => {
                                frames_cnt += 1;
                                match frame.get_opcode() {
                                    OpCode::TextFrame => {
                                        let payload = String::from_utf8(frame.into_vec());
                                        if let Err(e) = payload {
                                            error!("{:?} Utf8 decode error: {}", self.token, e);
                                            self.close_with_status(StatusCode::ProtocolError);
                                            break;
                                        }
                                        self.tx.send(WebSocketEvent::TextMessage(self.token, payload.unwrap()));
                                    },
                                    OpCode::BinaryFrame => {
                                        self.tx.send(WebSocketEvent::BinaryMessage(self.token, frame.into_vec()));
                                    },
                                    OpCode::Ping => {
                                        if frame.payload().len() > 125 {
                                            error!("{:?} Control frame length is > 125", self.token);
                                            self.close_with_status(StatusCode::ProtocolError);
                                        } else {
                                            self.outgoing.push(Frame::pong(&frame));
                                        }
                                    },
                                    OpCode::ConnectionClose => {
                                        let close_ev = if frame.payload().len() >= 2 {
                                            let status_code = BigEndian::read_u16(&frame.payload()[0..2]);
                                            WebSocketEvent::Close(self.token, StatusCode::from(status_code))
                                        } else {
                                            // No status code has been provided
                                            WebSocketEvent::Close(self.token, StatusCode::Custom(0))
                                        };
                                        self.tx.send(close_ev);

                                        if let Ok(response) = Frame::close_from(&frame) {
                                            self.outgoing.push(response);
                                        } else {
                                            self.close_with_status(StatusCode::ProtocolError);
                                        }
                                    },
                                    _ => {}
                                }
                            }
                        }
                    }
                    trace!("{:?} parsed {} frames", self.token, frames_cnt);
                    buf = read_buf.flip();
                }
            }
        }

        // Write any buffered outgoing frames
        if self.outgoing.len() > 0 {
            trace!("{:?} received {} frames, switching to write", self.token, self.outgoing.len());
            self.interest.remove(EventSet::readable());
            self.interest.insert(EventSet::writable());
        }
    }

    fn read_handshake(&mut self) {
        loop {
            let mut buf = [0; 2048];
            match self.socket.try_read(&mut buf) {
                Err(e) => {
                    println!("Error while reading socket: {:?}", e);
                    return
                },
                Ok(None) =>
                    // Socket buffer has got no more bytes.
                    break,
                Ok(Some(_)) => {
                    let is_upgrade = if let ClientState::AwaitingHandshake(ref parser_state) = self.state {
                        let mut parser = parser_state.borrow_mut();
                        parser.parse(&buf);
                        parser.is_upgrade()
                    } else { false };

                    if is_upgrade {
                        // Change the current state
                        self.state = ClientState::HandshakeResponse;

                        // Change current interest to `Writable`
                        self.interest.remove(EventSet::readable());
                        self.interest.insert(EventSet::writable());
                        break;
                    }
                }
            }
        }
    }
}
<|endoftext|>


<commit_before>extern crate config;
extern crate num_cpus;

use std::env;
use std::error::Error;
use std::io;
use std::io::{Result, ErrorKind};
use std::path::{Path, PathBuf};

use config::config::reader;
use config::config::types;

pub struct Config {
    pub process_limit: usize,
    pub cache_dir: PathBuf,
    pub cache_limit_mb: u32,
}

const CONFIG_FILE_NAME: &'static str = "octobuild.conf";

#[cfg(windows)]
const DEFAULT_CACHE_DIR: &'static str = "~/.octobuild";
#[cfg(unix)]
const DEFAULT_CACHE_DIR: &'static str = "~/.cache/.octobuild";

const PARAM_CACHE_LIMIT: &'static str = "cache_limit_mb";
const PARAM_CACHE_PATH: &'static str = "cache_path";
const PARAM_PROCESS_LIMIT: &'static str = "process_limit";

impl Config {
	pub fn new() -> Result<Self> {
		let local = get_local_config_path().and_then(|v| reader::from_file(&v).ok());
		let global = get_global_config_path().and_then(|v| reader::from_file(&v).ok());
		Config::load(&local, &global, false)
	}

	pub fn defaults() -> Result<Self> {
		Config::load(&None, &None, true)
	}

	fn load(local: &Option<types::Config>, global: &Option<types::Config>, defaults: bool) -> Result<Self> {
		let cache_limit_mb = get_config(local, global, |v| v.lookup_integer64(PARAM_CACHE_LIMIT)).map(|v| v as u32).unwrap_or(16 * 1024);
		let cache_path = match defaults {
				true => None,
				false => env::var("OCTOBUILD_CACHE").ok().and_then(|v| if v == "" {None} else {Some(v)}),
			}
			.or_else(|| get_config(local, global, |v| v.lookup_str(PARAM_CACHE_PATH).map(|v| v.to_string())))
			.unwrap_or(DEFAULT_CACHE_DIR.to_string());
		let process_limit = get_config(local, global, |v| v.lookup_integer32(PARAM_PROCESS_LIMIT)).map(|v| v as usize).unwrap_or_else(|| num_cpus::get());

		Ok(Config {
			process_limit: process_limit,
			cache_dir: try! (replace_home(&cache_path)),
			cache_limit_mb: cache_limit_mb,
		})
	}

	fn show(&self) {
		println!("  {} = {};", PARAM_PROCESS_LIMIT, self.process_limit);
		println!("  {} = {};", PARAM_CACHE_LIMIT, self.cache_limit_mb);
		println!("  {} = \"{}\";", PARAM_CACHE_PATH, self.cache_dir.to_str().unwrap());
	}

	pub fn help() {
		println!("Octobuild configuration:");
		println!("  system config path: {}", get_global_config_path().map(|v| v.to_str().unwrap().to_string()).unwrap_or("none".to_string()));
		println!("  user config path:   {}", get_local_config_path().map(|v| v.to_str().unwrap().to_string()).unwrap_or("none".to_string()));
		println!("");
		println!("Actual configuration:");
		match Config::new() {
			Ok(c) => {
				c.show();
			}
			Err(e) => {
				println!("  ERROR: {}", e.description());
			}
		}
		println!("");
		println!("Default configuration:");
		match Config::defaults() {
			Ok(c) => {
				c.show();
			}
			Err(e) => {
				println!("  ERROR: {}", e.description());
			}
		}
		println!("");
	}
}

fn get_config<F, T>(local: &Option<types::Config>, global: &Option<types::Config>, op: F) -> Option<T> where F: Fn(&types::Config) -> Option<T> {
	local.as_ref().and_then(&op).or_else(|| global.as_ref().and_then(&op))
}

fn get_local_config_path() -> Option<PathBuf> {
	env::home_dir().map(|v| v.join(&(".".to_string() + CONFIG_FILE_NAME)))
}

#[cfg(windows)]
fn get_global_config_path() -> Option<PathBuf> {
	env::var("ProgramData").ok().map(|v| Path::new(&v).join("octobuild").join(CONFIG_FILE_NAME))
}

#[cfg(unix)]
fn get_global_config_path() -> Option<PathBuf> {
	Some(Path::new("/etc/octobuild").join(CONFIG_FILE_NAME).to_path_buf())
}

fn replace_home(path: &str) -> Result<PathBuf> {
	if path.starts_with("~/") {
		env::home_dir().map(|v| v.join(&path[2..])).ok_or(io::Error::new(ErrorKind::NotFound, "Can't determinate user HOME path"))
	} else {
		Ok(Path::new(path).to_path_buf())
	}
}<commit_msg>Escape backslash before display path for configuration file<commit_after>extern crate config;
extern crate num_cpus;

use std::env;
use std::error::Error;
use std::io;
use std::io::{Result, ErrorKind};
use std::path::{Path, PathBuf};

use config::config::reader;
use config::config::types;

pub struct Config {
    pub process_limit: usize,
    pub cache_dir: PathBuf,
    pub cache_limit_mb: u32,
}

const CONFIG_FILE_NAME: &'static str = "octobuild.conf";

#[cfg(windows)]
const DEFAULT_CACHE_DIR: &'static str = "~/.octobuild";
#[cfg(unix)]
const DEFAULT_CACHE_DIR: &'static str = "~/.cache/.octobuild";

const PARAM_CACHE_LIMIT: &'static str = "cache_limit_mb";
const PARAM_CACHE_PATH: &'static str = "cache_path";
const PARAM_PROCESS_LIMIT: &'static str = "process_limit";

impl Config {
	pub fn new() -> Result<Self> {
		let local = get_local_config_path().and_then(|v| reader::from_file(&v).ok());
		let global = get_global_config_path().and_then(|v| reader::from_file(&v).ok());
		Config::load(&local, &global, false)
	}

	pub fn defaults() -> Result<Self> {
		Config::load(&None, &None, true)
	}

	fn load(local: &Option<types::Config>, global: &Option<types::Config>, defaults: bool) -> Result<Self> {
		let cache_limit_mb = get_config(local, global, |v| v.lookup_integer64(PARAM_CACHE_LIMIT)).map(|v| v as u32).unwrap_or(16 * 1024);
		let cache_path = match defaults {
				true => None,
				false => env::var("OCTOBUILD_CACHE").ok().and_then(|v| if v == "" {None} else {Some(v)}),
			}
			.or_else(|| get_config(local, global, |v| v.lookup_str(PARAM_CACHE_PATH).map(|v| v.to_string())))
			.unwrap_or(DEFAULT_CACHE_DIR.to_string());
		let process_limit = get_config(local, global, |v| v.lookup_integer32(PARAM_PROCESS_LIMIT)).map(|v| v as usize).unwrap_or_else(|| num_cpus::get());

		Ok(Config {
			process_limit: process_limit,
			cache_dir: try! (replace_home(&cache_path)),
			cache_limit_mb: cache_limit_mb,
		})
	}

	fn show(&self) {
		println!("  {} = {};", PARAM_PROCESS_LIMIT, self.process_limit);
		println!("  {} = {};", PARAM_CACHE_LIMIT, self.cache_limit_mb);
		println!("  {} = \"{}\";", PARAM_CACHE_PATH, self.cache_dir.to_str().unwrap().replace("\\", "\\\\"));
	}

	pub fn help() {
		println!("Octobuild configuration:");
		println!("  system config path: {}", get_global_config_path().map(|v| v.to_str().unwrap().to_string()).unwrap_or("none".to_string()));
		println!("  user config path:   {}", get_local_config_path().map(|v| v.to_str().unwrap().to_string()).unwrap_or("none".to_string()));
		println!("");
		println!("Actual configuration:");
		match Config::new() {
			Ok(c) => {
				c.show();
			}
			Err(e) => {
				println!("  ERROR: {}", e.description());
			}
		}
		println!("");
		println!("Default configuration:");
		match Config::defaults() {
			Ok(c) => {
				c.show();
			}
			Err(e) => {
				println!("  ERROR: {}", e.description());
			}
		}
		println!("");
	}
}

fn get_config<F, T>(local: &Option<types::Config>, global: &Option<types::Config>, op: F) -> Option<T> where F: Fn(&types::Config) -> Option<T> {
	None
		.or_else(|| local.as_ref().and_then(&op))
		.or_else(|| global.as_ref().and_then(&op))
}

fn get_local_config_path() -> Option<PathBuf> {
	env::home_dir().map(|v| v.join(&(".".to_string() + CONFIG_FILE_NAME)))
}

#[cfg(windows)]
fn get_global_config_path() -> Option<PathBuf> {
	env::var("ProgramData").ok().map(|v| Path::new(&v).join("octobuild").join(CONFIG_FILE_NAME))
}

#[cfg(unix)]
fn get_global_config_path() -> Option<PathBuf> {
	Some(Path::new("/etc/octobuild").join(CONFIG_FILE_NAME).to_path_buf())
}

fn replace_home(path: &str) -> Result<PathBuf> {
	if path.starts_with("~/") {
		env::home_dir().map(|v| v.join(&path[2..])).ok_or(io::Error::new(ErrorKind::NotFound, "Can't determinate user HOME path"))
	} else {
		Ok(Path::new(path).to_path_buf())
	}
}<|endoftext|>


<commit_before>//! This module implements config access.
use std::io;
use std::fs;
use std::env;
use std::path::PathBuf;

use log;
use clap::ArgMatches;
use url::Url;
use ini::Ini;

use prelude::*;
use constants::{DEFAULT_URL, VERSION, PROTOCOL_VERSION};

/// Represents the auth information
#[derive(Debug, Clone)]
pub enum Auth {
    Key(String),
    Token(String),
}

/// Represents a DSN
#[derive(Debug, Clone)]
pub struct Dsn {
    pub host: String,
    pub protocol: String,
    pub port: u16,
    pub client_id: String,
    pub secret: String,
    pub project_id: u64,
}

impl Dsn {
    /// Parses a Dsn from a given string.
    fn from_str(dsn: &str) -> Result<Dsn> {
        let url = Url::parse(dsn)?;
        let project_id = if let Some(component_iter) = url.path_segments() {
            let components: Vec<_> = component_iter.collect();
            if components.len() != 1 {
                fail!("invalid dsn: invalid project ID");
            }
            components[0].parse().or(Err("invalid dsn: invalid project id"))?
        } else {
            fail!("invalid dsn: missing project ID");
        };
        if !(url.scheme() == "http" || url.scheme() == "https") {
            fail!(format!("invalid dsn: unknown protocol '{}'", url.scheme()));
        }

        if url.username() == "" {
            fail!("invalid dsn: missing client id");
        }

        Ok(Dsn {
            protocol: url.scheme().into(),
            host: url.host_str().ok_or("invalid dsn: missing host")?.into(),
            port: url.port_or_known_default().unwrap(),
            client_id: url.username().into(),
            secret: url.password().ok_or("invalid dsn: missing secret")?.into(),
            project_id: project_id,
        })
    }

    /// Returns the URL where events should be sent.
    pub fn get_submit_url(&self) -> String {
        format!("{}://{}:{}/api/{}/store/",
                self.protocol,
                self.host,
                self.port,
                self.project_id)
    }

    /// Returns the given auth header (ts is the timestamp of the event)
    pub fn get_auth_header(&self, ts: f64) -> String {
        format!("Sentry \
            sentry_timestamp={}, \
            sentry_client=sentry-cli/{}, \
            sentry_version={}, \
            sentry_key={}, \
            sentry_secret={}",
                ts,
                VERSION,
                PROTOCOL_VERSION,
                self.client_id,
                self.secret)
    }
}

/// Represents the `sentry-cli` config.
#[derive(Clone)]
pub struct Config {
    pub filename: PathBuf,
    pub auth: Option<Auth>,
    pub url: String,
    pub log_level: log::LogLevelFilter,
    pub ini: Ini,
}

impl Config {
    /// Loads the CLI config from the default location and returns it.
    pub fn from_cli_config() -> Result<Config> {
        let (filename, ini) = load_cli_config()?;
        Ok(Config {
            filename: filename,
            auth: get_default_auth(&ini),
            url: get_default_url(&ini),
            log_level: get_default_log_level(&ini)?,
            ini: ini,
        })
    }

    /// Indicates whether keepalive support should be enabled.  This
    /// mostly corresponds to an ini config but also has some sensible
    /// default handling.
    pub fn allow_keepalive(&self) -> bool {
        let val = self.ini.get_from(Some("http"), "keepalive");
        match val {
            // keepalive is broken on our dev server.  Since this makes local development
            // quite frustrating we disable keepalive (handle reuse) when we connect to
            // unprotected servers where it does not matter that much.
            None => !self.has_insecure_server(),
            Some(val) => val == "true",
        }
    }

    /// Returns the proxy URL if defined.
    pub fn get_proxy_url(&self) -> Option<&str> {
        self.ini.get_from(Some("http"), "proxy_url")
    }

    /// Returns the proxy username if defined.
    pub fn get_proxy_username(&self) -> Option<&str> {
        self.ini.get_from(Some("http"), "proxy_username")
    }

    /// Returns the proxy password if defined.
    pub fn get_proxy_password(&self) -> Option<&str> {
        self.ini.get_from(Some("http"), "proxy_password")
    }

    /// Indicates if SSL is enabled or disabled for the server.
    pub fn has_insecure_server(&self) -> bool {
        self.url.starts_with("http://")
    }

    /// Indicates whether SSL verification should be on or off.
    pub fn should_verify_ssl(&self) -> bool {
        let val = self.ini.get_from(Some("http"), "verify_ssl");
        match val {
            None => true,
            Some(val) => val == "true",
        }
    }

    /// Controls the SSL revocation check on windows.  This can be used as a
    /// workaround for misconfigured local SSL proxies.
    pub fn disable_ssl_revocation_check(&self) -> bool {
        let val = self.ini.get_from(Some("http"), "check_ssl_revoke");
        match val {
            None => true,
            Some(val) => val == "true",
        }
    }

    /// Given a match object from clap, this returns the org from it.
    pub fn get_org(&self, matches: &ArgMatches) -> Result<String> {
        Ok(matches.value_of("org")
               .map(|x| x.to_owned())
               .or_else(|| env::var("SENTRY_ORG").ok())
               .or_else(|| self.ini.get_from(Some("defaults"), "org").map(|x| x.to_owned()))
               .ok_or("An organization slug is required (provide with --org)")?)
    }

    /// Given a match object from clap, this returns a tuple in the
    /// form `(org, project)` which can either come from the match
    /// object or some defaults (envvar, ini etc.).
    pub fn get_org_and_project(&self, matches: &ArgMatches) -> Result<(String, String)> {
        let org = self.get_org(matches)?;
        let project = if let Some(project) = matches.value_of("project") {
            project.to_owned()
        } else {
            self.get_project_default()?
        };
        Ok((org, project))
    }

    /// Return the default value for a project.
    pub fn get_project_default(&self) -> Result<String> {
        Ok(env::var("SENTRY_PROJECT").ok()
            .or_else(|| self.ini.get_from(Some("defaults"), "project").map(|x| x.to_owned()))
            .ok_or("A project slug is required")?)
    }

    /// Returns the defaults for org and project.
    pub fn get_org_and_project_defaults(&self) -> (Option<String>, Option<String>) {
        (env::var("SENTRY_ORG")
             .ok()
             .or_else(|| self.ini.get_from(Some("defaults"), "org").map(|x| x.to_owned())),
         env::var("SENTRY_PROJECT")
             .ok()
             .or_else(|| self.ini.get_from(Some("defaults"), "project").map(|x| x.to_owned())))
    }

    /// Return the DSN
    pub fn get_dsn(&self) -> Result<Dsn> {
        if let Some(ref val) = env::var("SENTRY_DSN").ok() {
            Dsn::from_str(val)
        } else if let Some(val) = self.ini.get_from(Some("auth"), "dsn") {
            Dsn::from_str(val)
        } else {
            fail!("No DSN provided");
        }
    }
}

fn find_project_config_file() -> Option<PathBuf> {
    env::current_dir().ok().and_then(|mut path| {
        loop {
            path.push(".sentryclirc");
            if path.exists() {
                return Some(path);
            }
            path.pop();
            if !path.pop() {
                return None;
            }
        }
    })
}

fn load_cli_config() -> Result<(PathBuf, Ini)> {
    let mut home_fn = env::home_dir().ok_or("Could not find home dir")?;
    home_fn.push(".sentryclirc");

    let mut rv = match fs::File::open(&home_fn) {
        Ok(mut file) => Ini::read_from(&mut file)?,
        Err(err) => {
            if err.kind() == io::ErrorKind::NotFound {
                Ini::new()
            } else {
                fail!(err);
            }
        }
    };

    if let Some(project_config_path) = find_project_config_file() {
        let ini = Ini::read_from(&mut fs::File::open(&project_config_path)?)?;
        for (section, props) in ini.iter() {
            for (key, value) in props {
                rv.set_to(section.clone(), key.clone(), value.to_owned());
            }
        }
        Ok((project_config_path, rv))
    } else {
        Ok((home_fn, rv))
    }
}

fn get_default_auth(ini: &Ini) -> Option<Auth> {
    if let Some(ref val) = env::var("SENTRY_AUTH_TOKEN").ok() {
        Some(Auth::Token(val.to_owned()))
    } else if let Some(ref val) = env::var("SENTRY_API_KEY").ok() {
        Some(Auth::Key(val.to_owned()))
    } else if let Some(val) = ini.get_from(Some("auth"), "token") {
        Some(Auth::Token(val.to_owned()))
    } else if let Some(val) = ini.get_from(Some("auth"), "api_key") {
        Some(Auth::Key(val.to_owned()))
    } else {
        None
    }
}

fn get_default_url(ini: &Ini) -> String {
    if let Some(ref val) = env::var("SENTRY_URL").ok() {
        val.to_owned()
    } else if let Some(val) = ini.get_from(Some("defaults"), "url") {
        val.to_owned()
    } else {
        DEFAULT_URL.to_owned()
    }
}

fn get_default_log_level(ini: &Ini) -> Result<log::LogLevelFilter> {
    if let Ok(level_str) = env::var("SENTRY_LOG_LEVEL") {
        if let Ok(level) = level_str.parse() {
            return Ok(level);
        }
    }

    if let Some(level_str) = ini.get_from(Some("log"), "level") {
        if let Ok(level) = level_str.parse() {
            return Ok(level);
        }
    }

    Ok(log::LogLevelFilter::Warn)
}
<commit_msg>Read http_proxy and set it in ini config<commit_after>//! This module implements config access.
use std::io;
use std::fs;
use std::env;
use std::path::PathBuf;

use log;
use clap::ArgMatches;
use url::Url;
use ini::Ini;

use prelude::*;
use constants::{DEFAULT_URL, VERSION, PROTOCOL_VERSION};

/// Represents the auth information
#[derive(Debug, Clone)]
pub enum Auth {
    Key(String),
    Token(String),
}

/// Represents a DSN
#[derive(Debug, Clone)]
pub struct Dsn {
    pub host: String,
    pub protocol: String,
    pub port: u16,
    pub client_id: String,
    pub secret: String,
    pub project_id: u64,
}

impl Dsn {
    /// Parses a Dsn from a given string.
    fn from_str(dsn: &str) -> Result<Dsn> {
        let url = Url::parse(dsn)?;
        let project_id = if let Some(component_iter) = url.path_segments() {
            let components: Vec<_> = component_iter.collect();
            if components.len() != 1 {
                fail!("invalid dsn: invalid project ID");
            }
            components[0].parse().or(Err("invalid dsn: invalid project id"))?
        } else {
            fail!("invalid dsn: missing project ID");
        };
        if !(url.scheme() == "http" || url.scheme() == "https") {
            fail!(format!("invalid dsn: unknown protocol '{}'", url.scheme()));
        }

        if url.username() == "" {
            fail!("invalid dsn: missing client id");
        }

        Ok(Dsn {
            protocol: url.scheme().into(),
            host: url.host_str().ok_or("invalid dsn: missing host")?.into(),
            port: url.port_or_known_default().unwrap(),
            client_id: url.username().into(),
            secret: url.password().ok_or("invalid dsn: missing secret")?.into(),
            project_id: project_id,
        })
    }

    /// Returns the URL where events should be sent.
    pub fn get_submit_url(&self) -> String {
        format!("{}://{}:{}/api/{}/store/",
                self.protocol,
                self.host,
                self.port,
                self.project_id)
    }

    /// Returns the given auth header (ts is the timestamp of the event)
    pub fn get_auth_header(&self, ts: f64) -> String {
        format!("Sentry \
            sentry_timestamp={}, \
            sentry_client=sentry-cli/{}, \
            sentry_version={}, \
            sentry_key={}, \
            sentry_secret={}",
                ts,
                VERSION,
                PROTOCOL_VERSION,
                self.client_id,
                self.secret)
    }
}

/// Represents the `sentry-cli` config.
#[derive(Clone)]
pub struct Config {
    pub filename: PathBuf,
    pub auth: Option<Auth>,
    pub url: String,
    pub log_level: log::LogLevelFilter,
    pub ini: Ini,
}

impl Config {
    /// Loads the CLI config from the default location and returns it.
    pub fn from_cli_config() -> Result<Config> {
        let (filename, ini) = load_cli_config()?;
        Ok(Config {
            filename: filename,
            auth: get_default_auth(&ini),
            url: get_default_url(&ini),
            log_level: get_default_log_level(&ini)?,
            ini: ini,
        })
    }

    /// Indicates whether keepalive support should be enabled.  This
    /// mostly corresponds to an ini config but also has some sensible
    /// default handling.
    pub fn allow_keepalive(&self) -> bool {
        let val = self.ini.get_from(Some("http"), "keepalive");
        match val {
            // keepalive is broken on our dev server.  Since this makes local development
            // quite frustrating we disable keepalive (handle reuse) when we connect to
            // unprotected servers where it does not matter that much.
            None => !self.has_insecure_server(),
            Some(val) => val == "true",
        }
    }

    /// Returns the proxy URL if defined.
    pub fn get_proxy_url(&self) -> Option<&str> {
        self.ini.get_from(Some("http"), "proxy_url")
    }

    /// Returns the proxy username if defined.
    pub fn get_proxy_username(&self) -> Option<&str> {
        self.ini.get_from(Some("http"), "proxy_username")
    }

    /// Returns the proxy password if defined.
    pub fn get_proxy_password(&self) -> Option<&str> {
        self.ini.get_from(Some("http"), "proxy_password")
    }

    /// Indicates if SSL is enabled or disabled for the server.
    pub fn has_insecure_server(&self) -> bool {
        self.url.starts_with("http://")
    }

    /// Indicates whether SSL verification should be on or off.
    pub fn should_verify_ssl(&self) -> bool {
        let val = self.ini.get_from(Some("http"), "verify_ssl");
        match val {
            None => true,
            Some(val) => val == "true",
        }
    }

    /// Controls the SSL revocation check on windows.  This can be used as a
    /// workaround for misconfigured local SSL proxies.
    pub fn disable_ssl_revocation_check(&self) -> bool {
        let val = self.ini.get_from(Some("http"), "check_ssl_revoke");
        match val {
            None => true,
            Some(val) => val == "true",
        }
    }

    /// Given a match object from clap, this returns the org from it.
    pub fn get_org(&self, matches: &ArgMatches) -> Result<String> {
        Ok(matches.value_of("org")
               .map(|x| x.to_owned())
               .or_else(|| env::var("SENTRY_ORG").ok())
               .or_else(|| self.ini.get_from(Some("defaults"), "org").map(|x| x.to_owned()))
               .ok_or("An organization slug is required (provide with --org)")?)
    }

    /// Given a match object from clap, this returns a tuple in the
    /// form `(org, project)` which can either come from the match
    /// object or some defaults (envvar, ini etc.).
    pub fn get_org_and_project(&self, matches: &ArgMatches) -> Result<(String, String)> {
        let org = self.get_org(matches)?;
        let project = if let Some(project) = matches.value_of("project") {
            project.to_owned()
        } else {
            self.get_project_default()?
        };
        Ok((org, project))
    }

    /// Return the default value for a project.
    pub fn get_project_default(&self) -> Result<String> {
        Ok(env::var("SENTRY_PROJECT").ok()
            .or_else(|| self.ini.get_from(Some("defaults"), "project").map(|x| x.to_owned()))
            .ok_or("A project slug is required")?)
    }

    /// Returns the defaults for org and project.
    pub fn get_org_and_project_defaults(&self) -> (Option<String>, Option<String>) {
        (env::var("SENTRY_ORG")
             .ok()
             .or_else(|| self.ini.get_from(Some("defaults"), "org").map(|x| x.to_owned())),
         env::var("SENTRY_PROJECT")
             .ok()
             .or_else(|| self.ini.get_from(Some("defaults"), "project").map(|x| x.to_owned())))
    }

    /// Return the DSN
    pub fn get_dsn(&self) -> Result<Dsn> {
        if let Some(ref val) = env::var("SENTRY_DSN").ok() {
            Dsn::from_str(val)
        } else if let Some(val) = self.ini.get_from(Some("auth"), "dsn") {
            Dsn::from_str(val)
        } else {
            fail!("No DSN provided");
        }
    }
}

fn find_project_config_file() -> Option<PathBuf> {
    env::current_dir().ok().and_then(|mut path| {
        loop {
            path.push(".sentryclirc");
            if path.exists() {
                return Some(path);
            }
            path.pop();
            if !path.pop() {
                return None;
            }
        }
    })
}

fn load_env_defaults(ini: &mut Ini) {
    if let Ok(proxy) = env::var("http_proxy") {
        ini.set_to(Some("http"), "proxy_url".into(), proxy);
    }
}

fn load_cli_config() -> Result<(PathBuf, Ini)> {
    let mut home_fn = env::home_dir().ok_or("Could not find home dir")?;
    home_fn.push(".sentryclirc");

    let mut rv = match fs::File::open(&home_fn) {
        Ok(mut file) => Ini::read_from(&mut file)?,
        Err(err) => {
            if err.kind() == io::ErrorKind::NotFound {
                Ini::new()
            } else {
                fail!(err);
            }
        }
    };

    if let Some(project_config_path) = find_project_config_file() {
        let ini = Ini::read_from(&mut fs::File::open(&project_config_path)?)?;
        for (section, props) in ini.iter() {
            for (key, value) in props {
                rv.set_to(section.clone(), key.clone(), value.to_owned());
            }
        }
        load_env_defaults(&mut rv);
        Ok((project_config_path, rv))
    } else {
        load_env_defaults(&mut rv);
        Ok((home_fn, rv))
    }
}

fn get_default_auth(ini: &Ini) -> Option<Auth> {
    if let Some(ref val) = env::var("SENTRY_AUTH_TOKEN").ok() {
        Some(Auth::Token(val.to_owned()))
    } else if let Some(ref val) = env::var("SENTRY_API_KEY").ok() {
        Some(Auth::Key(val.to_owned()))
    } else if let Some(val) = ini.get_from(Some("auth"), "token") {
        Some(Auth::Token(val.to_owned()))
    } else if let Some(val) = ini.get_from(Some("auth"), "api_key") {
        Some(Auth::Key(val.to_owned()))
    } else {
        None
    }
}

fn get_default_url(ini: &Ini) -> String {
    if let Some(ref val) = env::var("SENTRY_URL").ok() {
        val.to_owned()
    } else if let Some(val) = ini.get_from(Some("defaults"), "url") {
        val.to_owned()
    } else {
        DEFAULT_URL.to_owned()
    }
}

fn get_default_log_level(ini: &Ini) -> Result<log::LogLevelFilter> {
    if let Ok(level_str) = env::var("SENTRY_LOG_LEVEL") {
        if let Ok(level) = level_str.parse() {
            return Ok(level);
        }
    }

    if let Some(level_str) = ini.get_from(Some("log"), "level") {
        if let Ok(level) = level_str.parse() {
            return Ok(level);
        }
    }

    Ok(log::LogLevelFilter::Warn)
}
<|endoftext|>


<commit_before>//! Pool configuration.
use std::fmt;
use std::time::Duration;

use {HandleError, NopErrorHandler, CustomizeConnection, NopConnectionCustomizer};

/// A builder for `Config`.
///
/// See the documentation of `Config` for more details about the default value
/// and meaning of the configuration parameters.
#[derive(Debug)]
pub struct Builder<C, E> {
    c: Config<C, E>,
}

impl<C, E> Builder<C, E> {
    /// Constructs a new `Builder`.
    ///
    /// Parameters are initialized with their default values.
    pub fn new() -> Builder<C, E> {
        Builder { c: Config::default() }
    }

    /// Sets `pool_size`.
    ///
    /// # Panics
    ///
    /// Panics if `pool_size` is 0.
    pub fn pool_size(mut self, pool_size: u32) -> Builder<C, E> {
        assert!(pool_size > 0, "pool_size must be positive");
        self.c.pool_size = pool_size;
        self
    }

    /// Sets `min_idle`.
    pub fn min_idle(mut self, min_idle: Option<u32>) -> Builder<C, E> {
        self.c.min_idle = min_idle;
        self
    }

    /// Sets `helper_threads`.
    ///
    /// # Panics
    ///
    /// Panics if `helper_threads` is 0.
    pub fn helper_threads(mut self, helper_threads: u32) -> Builder<C, E> {
        assert!(helper_threads > 0, "helper_threads must be positive");
        self.c.helper_threads = helper_threads;
        self
    }

    /// Sets `test_on_check_out`.
    pub fn test_on_check_out(mut self, test_on_check_out: bool) -> Builder<C, E> {
        self.c.test_on_check_out = test_on_check_out;
        self
    }

    /// Sets `initialization_fail_fast`.
    pub fn initialization_fail_fast(mut self, initialization_fail_fast: bool) -> Builder<C, E> {
        self.c.initialization_fail_fast = initialization_fail_fast;
        self
    }

    /// Sets `max_lifetime`.
    ///
    /// # Panics
    ///
    /// Panics if `max_lifetime` is the zero `Duration`.
    pub fn max_lifetime(mut self, max_lifetime: Option<Duration>) -> Builder<C, E> {
        assert!(max_lifetime != Some(Duration::from_secs(0)),
                "max_lifetime must be positive");
        self.c.max_lifetime = max_lifetime;
        self
    }

    /// Sets `idle_timeout`.
    ///
    /// # Panics
    ///
    /// Panics if `idle_timeout` is the zero `Duration`.
    pub fn idle_timeout(mut self, idle_timeout: Option<Duration>) -> Builder<C, E> {
        assert!(idle_timeout != Some(Duration::from_secs(0)),
                "idle_timeout must be positive");
        self.c.idle_timeout = idle_timeout;
        self
    }

    /// Sets `connection_timeout` to the specified duration.
    ///
    /// # Panics
    ///
    /// Panics if `connection_timeout` is the zero duration
    pub fn connection_timeout(mut self, connection_timeout: Duration) -> Builder<C, E> {
        assert!(connection_timeout > Duration::from_secs(0),
                "connection_timeout must be positive");
        self.c.connection_timeout = connection_timeout;
        self
    }

    /// # Deprecated
    ///
    /// Use `connection_timeout` instead.
    pub fn connection_timeout_ms(self, connection_timeout_ms: u32) -> Builder<C, E> {
        self.connection_timeout(Duration::from_millis(connection_timeout_ms as u64))
    }

    /// Sets the `error_handler`.
    pub fn error_handler(mut self, error_handler: Box<HandleError<E>>) -> Builder<C, E> {
        self.c.error_handler = error_handler;
        self
    }

    /// Sets the `connection_customizer`.
    pub fn connection_customizer(mut self,
                                 connection_customizer: Box<CustomizeConnection<C, E>>)
                                 -> Builder<C, E> {
        self.c.connection_customizer = connection_customizer;
        self
    }

    /// Consumes the `Builder`, turning it into a `Config`.
    ///
    /// # Panics
    ///
    /// Panics if `min_idle` is larger than `pool_size`.
    pub fn build(self) -> Config<C, E> {
        if let Some(min_idle) = self.c.min_idle {
            assert!(self.c.pool_size >= min_idle,
                    "min_idle must be no larger than pool_size");
        }

        self.c
    }
}

/// A struct specifying the runtime configuration of a pool.
///
/// `Config` implements `Default`, which provides a set of reasonable default
/// values. It can be constructed using a `Builder`.
pub struct Config<C, E> {
    pool_size: u32,
    min_idle: Option<u32>,
    helper_threads: u32,
    test_on_check_out: bool,
    initialization_fail_fast: bool,
    max_lifetime: Option<Duration>,
    idle_timeout: Option<Duration>,
    connection_timeout: Duration,
    error_handler: Box<HandleError<E>>,
    connection_customizer: Box<CustomizeConnection<C, E>>,
}

impl<C, E> fmt::Debug for Config<C, E> {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt.debug_struct("Config")
           .field("pool_size", &self.pool_size)
           .field("min_idle", &self.min_idle)
           .field("helper_threads", &self.helper_threads)
           .field("test_on_check_out", &self.test_on_check_out)
           .field("initialization_fail_fast", &self.initialization_fail_fast)
           .field("max_lifetmie", &self.max_lifetime)
           .field("idle_timeout", &self.idle_timeout)
           .field("connection_timeout", &self.connection_timeout)
           .finish()
    }
}

impl<C, E> Default for Config<C, E> {
    fn default() -> Config<C, E> {
        Config {
            pool_size: 10,
            min_idle: None,
            helper_threads: 3,
            test_on_check_out: true,
            initialization_fail_fast: true,
            idle_timeout: Some(Duration::from_secs(10 * 60)),
            max_lifetime: Some(Duration::from_secs(30 * 60)),
            connection_timeout: Duration::from_secs(30),
            error_handler: Box::new(NopErrorHandler),
            connection_customizer: Box::new(NopConnectionCustomizer),
        }
    }
}

impl<C, E> Config<C, E> {
    /// Creates a new `Builder` which can be used to construct a customized
    /// `Config`.
    ///
    /// All parameters are initialized to their default values.
    pub fn builder() -> Builder<C, E> {
        Builder::new()
    }

    /// The maximum number of connections managed by the pool.
    ///
    /// Defaults to 10.
    pub fn pool_size(&self) -> u32 {
        self.pool_size
    }

    /// If set, the pool will try to maintain at least this many idle
    /// connections at all times, while respecting the value of `pool_size`.
    ///
    /// Defaults to None (equivalent to the value of `pool_size`).
    pub fn min_idle(&self) -> Option<u32> {
        self.min_idle
    }

    /// The number of threads that the pool will use for asynchronous
    /// operations such as connection creation and health checks.
    ///
    /// Defaults to 3.
    pub fn helper_threads(&self) -> u32 {
        self.helper_threads
    }

    /// If true, the health of a connection will be verified via a call to
    /// `ConnectionManager::is_valid` before it is checked out of the pool.
    ///
    /// Defaults to true.
    pub fn test_on_check_out(&self) -> bool {
        self.test_on_check_out
    }

    /// If true, `Pool::new` will synchronously initialize its connections,
    /// returning an error if they could not be created.
    ///
    /// Defaults to true.
    pub fn initialization_fail_fast(&self) -> bool {
        self.initialization_fail_fast
    }

    /// If set, connections will be closed after sitting idle for at most 30
    /// seconds beyond this duration.
    ///
    /// Defaults to 10 minutes.
    pub fn idle_timeout(&self) -> Option<Duration> {
        self.idle_timeout
    }

    /// If set, connections will be closed after existing for at most 30 seconds
    /// beyond this duration. If a connection reaches its maximum lifetime while
    /// checked out it will be closed when it is returned to the pool.
    ///
    /// Defaults to 30 minutes.
    pub fn max_lifetime(&self) -> Option<Duration> {
        self.max_lifetime
    }

    /// Calls to `Pool::get` will wait this long for a connection to become
    /// available before returning an error.
    ///
    /// Defaults to 30 seconds.
    pub fn connection_timeout(&self) -> Duration {
        self.connection_timeout
    }

    /// # Deprecated
    ///
    /// Use `connection_timeout` instead.
    pub fn connection_timeout_ms(&self) -> u32 {
        self.connection_timeout.as_secs() as u32 * 1000 +
        self.connection_timeout.subsec_nanos() / 1_000_000
    }

    /// The handler for error reported in the pool.
    ///
    /// Defaults to `r2d2::NopErrorHandler`.
    pub fn error_handler(&self) -> &HandleError<E> {
        &*self.error_handler
    }

    /// The connection customizer used by the pool.
    ///
    /// Defaults to `r2d2::NopConnectionCustomizer`.
    pub fn connection_customizer(&self) -> &CustomizeConnection<C, E> {
        &*self.connection_customizer
    }
}

#[cfg(test)]
mod test {
    use std::time::Duration;

    use super::*;

    #[test]
    fn builder() {
        let config = Config::<(), ()>::builder()
                         .pool_size(1)
                         .helper_threads(2)
                         .test_on_check_out(false)
                         .initialization_fail_fast(false)
                         .connection_timeout_ms(3 * 1000)
                         .build();
        assert_eq!(1, config.pool_size());
        assert_eq!(2, config.helper_threads());
        assert_eq!(false, config.test_on_check_out());
        assert_eq!(false, config.initialization_fail_fast());
        assert_eq!(3 * 1000, config.connection_timeout_ms());
    }

    #[test]
    #[should_panic(expected = "pool_size must be positive")]
    fn builder_zero_pool_size() {
        Config::<(), ()>::builder().pool_size(0);
    }

    #[test]
    #[should_panic(expected = "helper_threads must be positive")]
    fn builder_zero_helper_threads() {
        Config::<(), ()>::builder().helper_threads(0);
    }

    #[test]
    #[should_panic(expected = "connection_timeout must be positive")]
    fn builder_zero_connection_timeout() {
        Config::<(), ()>::builder().connection_timeout(Duration::from_secs(0));
    }

    #[test]
    #[should_panic(expected = "idle_timeout must be positive")]
    fn builder_zero_idle_timeout() {
        Config::<(), ()>::builder().idle_timeout(Some(Duration::from_secs(0)));
    }

    #[test]
    #[should_panic(expected = "max_lifetime must be positive")]
    fn builder_zero_max_lifetime() {
        Config::<(), ()>::builder().max_lifetime(Some(Duration::from_secs(0)));
    }

    #[test]
    #[should_panic(expected = "min_idle must be no larger than pool_size")]
    fn builder_too_many_num_idle() {
        Config::<(), ()>::builder().pool_size(1).min_idle(Some(2)).build();
    }
}
<commit_msg>Fix typo in impl of fmt::Debug for Config<C,E><commit_after>//! Pool configuration.
use std::fmt;
use std::time::Duration;

use {HandleError, NopErrorHandler, CustomizeConnection, NopConnectionCustomizer};

/// A builder for `Config`.
///
/// See the documentation of `Config` for more details about the default value
/// and meaning of the configuration parameters.
#[derive(Debug)]
pub struct Builder<C, E> {
    c: Config<C, E>,
}

impl<C, E> Builder<C, E> {
    /// Constructs a new `Builder`.
    ///
    /// Parameters are initialized with their default values.
    pub fn new() -> Builder<C, E> {
        Builder { c: Config::default() }
    }

    /// Sets `pool_size`.
    ///
    /// # Panics
    ///
    /// Panics if `pool_size` is 0.
    pub fn pool_size(mut self, pool_size: u32) -> Builder<C, E> {
        assert!(pool_size > 0, "pool_size must be positive");
        self.c.pool_size = pool_size;
        self
    }

    /// Sets `min_idle`.
    pub fn min_idle(mut self, min_idle: Option<u32>) -> Builder<C, E> {
        self.c.min_idle = min_idle;
        self
    }

    /// Sets `helper_threads`.
    ///
    /// # Panics
    ///
    /// Panics if `helper_threads` is 0.
    pub fn helper_threads(mut self, helper_threads: u32) -> Builder<C, E> {
        assert!(helper_threads > 0, "helper_threads must be positive");
        self.c.helper_threads = helper_threads;
        self
    }

    /// Sets `test_on_check_out`.
    pub fn test_on_check_out(mut self, test_on_check_out: bool) -> Builder<C, E> {
        self.c.test_on_check_out = test_on_check_out;
        self
    }

    /// Sets `initialization_fail_fast`.
    pub fn initialization_fail_fast(mut self, initialization_fail_fast: bool) -> Builder<C, E> {
        self.c.initialization_fail_fast = initialization_fail_fast;
        self
    }

    /// Sets `max_lifetime`.
    ///
    /// # Panics
    ///
    /// Panics if `max_lifetime` is the zero `Duration`.
    pub fn max_lifetime(mut self, max_lifetime: Option<Duration>) -> Builder<C, E> {
        assert!(max_lifetime != Some(Duration::from_secs(0)),
                "max_lifetime must be positive");
        self.c.max_lifetime = max_lifetime;
        self
    }

    /// Sets `idle_timeout`.
    ///
    /// # Panics
    ///
    /// Panics if `idle_timeout` is the zero `Duration`.
    pub fn idle_timeout(mut self, idle_timeout: Option<Duration>) -> Builder<C, E> {
        assert!(idle_timeout != Some(Duration::from_secs(0)),
                "idle_timeout must be positive");
        self.c.idle_timeout = idle_timeout;
        self
    }

    /// Sets `connection_timeout` to the specified duration.
    ///
    /// # Panics
    ///
    /// Panics if `connection_timeout` is the zero duration
    pub fn connection_timeout(mut self, connection_timeout: Duration) -> Builder<C, E> {
        assert!(connection_timeout > Duration::from_secs(0),
                "connection_timeout must be positive");
        self.c.connection_timeout = connection_timeout;
        self
    }

    /// # Deprecated
    ///
    /// Use `connection_timeout` instead.
    pub fn connection_timeout_ms(self, connection_timeout_ms: u32) -> Builder<C, E> {
        self.connection_timeout(Duration::from_millis(connection_timeout_ms as u64))
    }

    /// Sets the `error_handler`.
    pub fn error_handler(mut self, error_handler: Box<HandleError<E>>) -> Builder<C, E> {
        self.c.error_handler = error_handler;
        self
    }

    /// Sets the `connection_customizer`.
    pub fn connection_customizer(mut self,
                                 connection_customizer: Box<CustomizeConnection<C, E>>)
                                 -> Builder<C, E> {
        self.c.connection_customizer = connection_customizer;
        self
    }

    /// Consumes the `Builder`, turning it into a `Config`.
    ///
    /// # Panics
    ///
    /// Panics if `min_idle` is larger than `pool_size`.
    pub fn build(self) -> Config<C, E> {
        if let Some(min_idle) = self.c.min_idle {
            assert!(self.c.pool_size >= min_idle,
                    "min_idle must be no larger than pool_size");
        }

        self.c
    }
}

/// A struct specifying the runtime configuration of a pool.
///
/// `Config` implements `Default`, which provides a set of reasonable default
/// values. It can be constructed using a `Builder`.
pub struct Config<C, E> {
    pool_size: u32,
    min_idle: Option<u32>,
    helper_threads: u32,
    test_on_check_out: bool,
    initialization_fail_fast: bool,
    max_lifetime: Option<Duration>,
    idle_timeout: Option<Duration>,
    connection_timeout: Duration,
    error_handler: Box<HandleError<E>>,
    connection_customizer: Box<CustomizeConnection<C, E>>,
}

impl<C, E> fmt::Debug for Config<C, E> {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt.debug_struct("Config")
           .field("pool_size", &self.pool_size)
           .field("min_idle", &self.min_idle)
           .field("helper_threads", &self.helper_threads)
           .field("test_on_check_out", &self.test_on_check_out)
           .field("initialization_fail_fast", &self.initialization_fail_fast)
           .field("max_lifetime", &self.max_lifetime)
           .field("idle_timeout", &self.idle_timeout)
           .field("connection_timeout", &self.connection_timeout)
           .finish()
    }
}

impl<C, E> Default for Config<C, E> {
    fn default() -> Config<C, E> {
        Config {
            pool_size: 10,
            min_idle: None,
            helper_threads: 3,
            test_on_check_out: true,
            initialization_fail_fast: true,
            idle_timeout: Some(Duration::from_secs(10 * 60)),
            max_lifetime: Some(Duration::from_secs(30 * 60)),
            connection_timeout: Duration::from_secs(30),
            error_handler: Box::new(NopErrorHandler),
            connection_customizer: Box::new(NopConnectionCustomizer),
        }
    }
}

impl<C, E> Config<C, E> {
    /// Creates a new `Builder` which can be used to construct a customized
    /// `Config`.
    ///
    /// All parameters are initialized to their default values.
    pub fn builder() -> Builder<C, E> {
        Builder::new()
    }

    /// The maximum number of connections managed by the pool.
    ///
    /// Defaults to 10.
    pub fn pool_size(&self) -> u32 {
        self.pool_size
    }

    /// If set, the pool will try to maintain at least this many idle
    /// connections at all times, while respecting the value of `pool_size`.
    ///
    /// Defaults to None (equivalent to the value of `pool_size`).
    pub fn min_idle(&self) -> Option<u32> {
        self.min_idle
    }

    /// The number of threads that the pool will use for asynchronous
    /// operations such as connection creation and health checks.
    ///
    /// Defaults to 3.
    pub fn helper_threads(&self) -> u32 {
        self.helper_threads
    }

    /// If true, the health of a connection will be verified via a call to
    /// `ConnectionManager::is_valid` before it is checked out of the pool.
    ///
    /// Defaults to true.
    pub fn test_on_check_out(&self) -> bool {
        self.test_on_check_out
    }

    /// If true, `Pool::new` will synchronously initialize its connections,
    /// returning an error if they could not be created.
    ///
    /// Defaults to true.
    pub fn initialization_fail_fast(&self) -> bool {
        self.initialization_fail_fast
    }

    /// If set, connections will be closed after sitting idle for at most 30
    /// seconds beyond this duration.
    ///
    /// Defaults to 10 minutes.
    pub fn idle_timeout(&self) -> Option<Duration> {
        self.idle_timeout
    }

    /// If set, connections will be closed after existing for at most 30 seconds
    /// beyond this duration. If a connection reaches its maximum lifetime while
    /// checked out it will be closed when it is returned to the pool.
    ///
    /// Defaults to 30 minutes.
    pub fn max_lifetime(&self) -> Option<Duration> {
        self.max_lifetime
    }

    /// Calls to `Pool::get` will wait this long for a connection to become
    /// available before returning an error.
    ///
    /// Defaults to 30 seconds.
    pub fn connection_timeout(&self) -> Duration {
        self.connection_timeout
    }

    /// # Deprecated
    ///
    /// Use `connection_timeout` instead.
    pub fn connection_timeout_ms(&self) -> u32 {
        self.connection_timeout.as_secs() as u32 * 1000 +
        self.connection_timeout.subsec_nanos() / 1_000_000
    }

    /// The handler for error reported in the pool.
    ///
    /// Defaults to `r2d2::NopErrorHandler`.
    pub fn error_handler(&self) -> &HandleError<E> {
        &*self.error_handler
    }

    /// The connection customizer used by the pool.
    ///
    /// Defaults to `r2d2::NopConnectionCustomizer`.
    pub fn connection_customizer(&self) -> &CustomizeConnection<C, E> {
        &*self.connection_customizer
    }
}

#[cfg(test)]
mod test {
    use std::time::Duration;

    use super::*;

    #[test]
    fn builder() {
        let config = Config::<(), ()>::builder()
                         .pool_size(1)
                         .helper_threads(2)
                         .test_on_check_out(false)
                         .initialization_fail_fast(false)
                         .connection_timeout_ms(3 * 1000)
                         .build();
        assert_eq!(1, config.pool_size());
        assert_eq!(2, config.helper_threads());
        assert_eq!(false, config.test_on_check_out());
        assert_eq!(false, config.initialization_fail_fast());
        assert_eq!(3 * 1000, config.connection_timeout_ms());
    }

    #[test]
    #[should_panic(expected = "pool_size must be positive")]
    fn builder_zero_pool_size() {
        Config::<(), ()>::builder().pool_size(0);
    }

    #[test]
    #[should_panic(expected = "helper_threads must be positive")]
    fn builder_zero_helper_threads() {
        Config::<(), ()>::builder().helper_threads(0);
    }

    #[test]
    #[should_panic(expected = "connection_timeout must be positive")]
    fn builder_zero_connection_timeout() {
        Config::<(), ()>::builder().connection_timeout(Duration::from_secs(0));
    }

    #[test]
    #[should_panic(expected = "idle_timeout must be positive")]
    fn builder_zero_idle_timeout() {
        Config::<(), ()>::builder().idle_timeout(Some(Duration::from_secs(0)));
    }

    #[test]
    #[should_panic(expected = "max_lifetime must be positive")]
    fn builder_zero_max_lifetime() {
        Config::<(), ()>::builder().max_lifetime(Some(Duration::from_secs(0)));
    }

    #[test]
    #[should_panic(expected = "min_idle must be no larger than pool_size")]
    fn builder_too_many_num_idle() {
        Config::<(), ()>::builder().pool_size(1).min_idle(Some(2)).build();
    }
}
<|endoftext|>