<commit_before>use url;
use log::LogLevel::Error as ErrorLevel;
#[cfg(feature="ssl")]
use openssl::ssl::{SslMethod, SslStream, SslConnectorBuilder};

use message::Message;
use frame::Frame;
use protocol::CloseCode;
use handshake::{Handshake, Request, Response};
use result::{Result, Error, Kind};
use util::{Token, Timeout};

#[cfg(feature="ssl")]
use util::TcpStream;

/// The core trait of this library.
/// Implementing this trait provides the business logic of the WebSocket application.
pub trait Handler {

    // general

    /// Called when a request to shutdown all connections has been received.
    #[inline]
    fn on_shutdown(&mut self) {
        debug!("Handler received WebSocket shutdown request.");
    }

    // WebSocket events

    /// Called when the WebSocket handshake is successful and the connection is open for sending
    /// and receiving messages.
    fn on_open(&mut self, shake: Handshake) -> Result<()> {
        if let Some(addr) = try!(shake.remote_addr()) {
            debug!("Connection with {} now open", addr);
        }
        Ok(())
    }

    /// Called on incoming messages.
    fn on_message(&mut self, msg: Message) -> Result<()> {
        debug!("Received message {:?}", msg);
        Ok(())
    }

    /// Called any time this endpoint receives a close control frame.
    /// This may be because the other endpoint is initiating a closing handshake,
    /// or it may be the other endpoint confirming the handshake initiated by this endpoint.
    fn on_close(&mut self, code: CloseCode, reason: &str) {
        debug!("Connection closing due to ({:?}) {}", code, reason);
    }

    /// Called when an error occurs on the WebSocket.
    fn on_error(&mut self, err: Error) {
        // Ignore connection reset errors by default, but allow library clients to see them by
        // overriding this method if they want
        if let Kind::Io(ref err) = err.kind {
            if let Some(104) = err.raw_os_error() {
                return
            }
        }

        error!("{:?}", err);
        if !log_enabled!(ErrorLevel) {
            println!("Encountered an error: {}\nEnable a logger to see more information.", err);
        }
    }

    // handshake events

    /// A method for handling the low-level workings of the request portion of the WebSocket
    /// handshake.
    ///
    /// Implementors should select a WebSocket protocol and extensions where they are supported.
    ///
    /// Implementors can inspect the Request and must return a Response or an error
    /// indicating that the handshake failed. The default implementation provides conformance with
    /// the WebSocket protocol, and implementors should use the `Response::from_request` method and
    /// then modify the resulting response as necessary in order to maintain conformance.
    ///
    /// This method will not be called when the handler represents a client endpoint. Use
    /// `build_request` to provide an initial handshake request.
    ///
    /// # Examples
    ///
    /// ```ignore
    /// let mut res = try!(Response::from_request(req));
    /// if try!(req.extensions()).iter().find(|&&ext| ext.contains("myextension-name")).is_some() {
    ///     res.add_extension("myextension-name")
    /// }
    /// Ok(res)
    /// ```
    #[inline]
    fn on_request(&mut self, req: &Request) -> Result<Response> {
        debug!("Handler received request:\n{}", req);
        Response::from_request(req)
    }

    /// A method for handling the low-level workings of the response portion of the WebSocket
    /// handshake.
    ///
    /// Implementors can inspect the Response and choose to fail the connection by
    /// returning an error. This method will not be called when the handler represents a server
    /// endpoint. The response should indicate which WebSocket protocol and extensions the server
    /// has agreed to if any.
    #[inline]
    fn on_response(&mut self, res: &Response) -> Result<()> {
        debug!("Handler received response:\n{}", res);
        Ok(())
    }

    // timeout events

    /// Called when a timeout is triggered.
    ///
    /// This method will be called when the eventloop encounters a timeout on the specified
    /// token. To schedule a timeout with your specific token use the `Sender::timeout` method.
    ///
    /// # Examples
    ///
    /// ```ignore
    /// const GRATI: Token = Token(1);
    ///
    /// ... Handler
    ///
    /// fn on_open(&mut self, _: Handshake) -> Result<()> {
    ///     // schedule a timeout to send a gratuitous pong every 5 seconds
    ///     self.ws.timeout(5_000, GRATI)
    /// }
    ///
    /// fn on_timeout(&mut self, event: Token) -> Result<()> {
    ///     if event == GRATI {
    ///         // send gratuitous pong
    ///         try!(self.ws.pong(vec![]))
    ///         // reschedule the timeout
    ///         self.ws.timeout(5_000, GRATI)
    ///     } else {
    ///         Err(Error::new(ErrorKind::Internal, "Invalid timeout token encountered!"))
    ///     }
    /// }
    /// ```
    #[inline]
    fn on_timeout(&mut self, event: Token) -> Result<()> {
        debug!("Handler received timeout token: {:?}", event);
        Ok(())
    }

    /// Called when a timeout has been scheduled on the eventloop.
    ///
    /// This method is the hook for obtaining a Timeout object that may be used to cancel a
    /// timeout. This is a noop by default.
    ///
    /// # Examples
    ///
    /// ```ignore
    /// const PING: Token = Token(1);
    /// const EXPIRE: Token = Token(2);
    ///
    /// ... Handler
    ///
    /// fn on_open(&mut self, _: Handshake) -> Result<()> {
    ///     // schedule a timeout to send a ping every 5 seconds
    ///     try!(self.ws.timeout(5_000, PING));
    ///     // schedule a timeout to close the connection if there is no activity for 30 seconds
    ///     self.ws.timeout(30_000, EXPIRE)
    /// }
    ///
    /// fn on_timeout(&mut self, event: Token) -> Result<()> {
    ///     match event {
    ///         PING => {
    ///             self.ws.ping(vec![]);
    ///             self.ws.timeout(5_000, PING)
    ///         }
    ///         EXPIRE => self.ws.close(CloseCode::Away),
    ///         _ => Err(Error::new(ErrorKind::Internal, "Invalid timeout token encountered!")),
    ///     }
    /// }
    ///
    /// fn on_new_timeout(&mut self, event: Token, timeout: Timeout) -> Result<()> {
    ///     if event == EXPIRE {
    ///         if let Some(t) = self.timeout.take() {
    ///             try!(self.ws.cancel(t))
    ///         }
    ///         self.timeout = Some(timeout)
    ///     }
    ///     Ok(())
    /// }
    ///
    /// fn on_frame(&mut self, frame: Frame) -> Result<Option<Frame>> {
    ///     // some activity has occurred, let's reset the expiration
    ///     try!(self.ws.timeout(30_000, EXPIRE));
    ///     Ok(Some(frame))
    /// }
    /// ```
    #[inline]
    fn on_new_timeout(&mut self, _: Token, _: Timeout) -> Result<()> {
        // default implementation discards the timeout handle
        Ok(())
    }

    // frame events

    /// A method for handling incoming frames.
    ///
    /// This method provides very low-level access to the details of the WebSocket protocol. It may
    /// be necessary to implement this method in order to provide a particular extension, but
    /// incorrect implementation may cause the other endpoint to fail the connection.
    ///
    /// Returning `Ok(None)` will cause the connection to forget about a particular frame. This is
    /// useful if you want ot filter out a frame or if you don't want any of the default handler
    /// methods to run.
    ///
    /// By default this method simply ensures that no reserved bits are set.
    #[inline]
    fn on_frame(&mut self, frame: Frame) -> Result<Option<Frame>> {
        debug!("Handler received: {}", frame);
        // default implementation doesn't allow for reserved bits to be set
        if frame.has_rsv1() || frame.has_rsv2() || frame.has_rsv3() {
            Err(Error::new(Kind::Protocol, "Encountered frame with reserved bits set."))
        } else {
            Ok(Some(frame))
        }
    }

    /// A method for handling outgoing frames.
    ///
    /// This method provides very low-level access to the details of the WebSocket protocol. It may
    /// be necessary to implement this method in order to provide a particular extension, but
    /// incorrect implementation may cause the other endpoint to fail the connection.
    ///
    /// Returning `Ok(None)` will cause the connection to forget about a particular frame, meaning
    /// that it will not be sent. You can use this approach to merge multiple frames into a single
    /// frame before sending the message.
    ///
    /// For messages, this method will be called with a single complete, final frame before any
    /// fragmentation is performed. Automatic fragmentation will be performed on the returned
    /// frame, if any, based on the `fragment_size` setting.
    ///
    /// By default this method simply ensures that no reserved bits are set.
    #[inline]
    fn on_send_frame(&mut self, frame: Frame) -> Result<Option<Frame>> {
        debug!("Handler will send: {}", frame);
        // default implementation doesn't allow for reserved bits to be set
        if frame.has_rsv1() || frame.has_rsv2() || frame.has_rsv3() {
            Err(Error::new(Kind::Protocol, "Encountered frame with reserved bits set."))
        } else {
            Ok(Some(frame))
        }
    }

    // constructors

    /// A method for creating the initial handshake request for WebSocket clients.
    ///
    /// The default implementation provides conformance with the WebSocket protocol, but this
    /// method may be overriden. In order to facilitate conformance,
    /// implementors should use the `Request::from_url` method and then modify the resulting
    /// request as necessary.
    ///
    /// Implementors should indicate any available WebSocket extensions here.
    ///
    /// # Examples
    /// ```ignore
    /// let mut req = try!(Request::from_url(url));
    /// req.add_extension("permessage-deflate; client_max_window_bits");
    /// Ok(req)
    /// ```
    #[inline]
    fn build_request(&mut self, url: &url::Url) -> Result<Request> {
        debug!("Handler is building request to {}.", url);
        Request::from_url(url)
    }

    /// A method for wrapping a client TcpStream with Ssl Authentication machinery
    ///
    /// Override this method to customize how the connection is encrypted. By default
    /// this will use the Server Name Indication extension in conformance with RFC6455.
    #[inline]
    #[cfg(feature="ssl")]
    fn upgrade_ssl_client(&mut self, stream: TcpStream, url: &url::Url) -> Result<SslStream<TcpStream>>
    {
        let domain = try!(url.domain().ok_or(Error::new(
            Kind::Protocol,
            format!("Unable to parse domain from {}. Needed for SSL.", url))));
        let connector = try!(SslConnectorBuilder::new(SslMethod::tls()).map_err(|e| {
            Error::new(Kind::Internal, format!("Failed to upgrade client to SSL: {}", e))
        })).build();
        connector.connect(domain, stream).map_err(Error::from)
    }

    /// A method for wrapping a server TcpStream with Ssl Authentication machinery
    ///
    /// Override this method to customize how the connection is encrypted. By default
    /// this method is not implemented.
    #[inline]
    #[cfg(feature="ssl")]
    fn upgrade_ssl_server(&mut self, _: TcpStream) -> Result<SslStream<TcpStream>>
    {
        unimplemented!()
    }
}

impl<F> Handler for F
    where F: Fn(Message) -> Result<()>
{
    fn on_message(&mut self, msg: Message) -> Result<()> {
        self(msg)
    }
}

mod test {
    #![allow(unused_imports, unused_variables, dead_code)]
    use super::*;
    use url;
    use mio;
    use handshake::{Request, Response, Handshake};
    use protocol::CloseCode;
    use frame;
    use message;
    use result::Result;

    #[derive(Debug, Eq, PartialEq)]
    struct M;
    impl Handler for M {
        fn on_message(&mut self, _: message::Message) -> Result<()> {
            Ok(println!("test"))
        }

        fn on_frame(&mut self, f: frame::Frame) -> Result<Option<frame::Frame>> {
            Ok(None)
        }
    }

    #[test]
    fn handler() {
        struct H;

        impl Handler for H {

            fn on_open(&mut self, shake: Handshake) -> Result<()> {
                assert!(shake.request.key().is_ok());
                assert!(shake.response.key().is_ok());
                Ok(())
            }

            fn on_message(&mut self, msg: message::Message) -> Result<()> {
                Ok(assert_eq!(msg, message::Message::Text(String::from("testme"))))
            }

            fn on_close(&mut self, code: CloseCode, _: &str) {
                assert_eq!(code, CloseCode::Normal)
            }

        }

        let mut h = H;
        let url = url::Url::parse("wss://127.0.0.1:3012").unwrap();
        let req = Request::from_url(&url).unwrap();
        let res = Response::from_request(&req).unwrap();
        h.on_open(Handshake{
            request: req,
            response: res,
            peer_addr: None,
            local_addr: None,
        }).unwrap();
        h.on_message(message::Message::Text("testme".to_owned())).unwrap();
        h.on_close(CloseCode::Normal, "");
    }

    #[test]
    fn closure_handler() {
        let mut close = |msg| {
            assert_eq!(msg, message::Message::Binary(vec![1, 2, 3]));
            Ok(())
        };

        close.on_message(message::Message::Binary(vec![1, 2, 3])).unwrap();
    }
}
<commit_msg>use trace for frame log<commit_after>use url;
use log::LogLevel::Error as ErrorLevel;
#[cfg(feature="ssl")]
use openssl::ssl::{SslMethod, SslStream, SslConnectorBuilder};

use message::Message;
use frame::Frame;
use protocol::CloseCode;
use handshake::{Handshake, Request, Response};
use result::{Result, Error, Kind};
use util::{Token, Timeout};

#[cfg(feature="ssl")]
use util::TcpStream;

/// The core trait of this library.
/// Implementing this trait provides the business logic of the WebSocket application.
pub trait Handler {

    // general

    /// Called when a request to shutdown all connections has been received.
    #[inline]
    fn on_shutdown(&mut self) {
        debug!("Handler received WebSocket shutdown request.");
    }

    // WebSocket events

    /// Called when the WebSocket handshake is successful and the connection is open for sending
    /// and receiving messages.
    fn on_open(&mut self, shake: Handshake) -> Result<()> {
        if let Some(addr) = try!(shake.remote_addr()) {
            debug!("Connection with {} now open", addr);
        }
        Ok(())
    }

    /// Called on incoming messages.
    fn on_message(&mut self, msg: Message) -> Result<()> {
        debug!("Received message {:?}", msg);
        Ok(())
    }

    /// Called any time this endpoint receives a close control frame.
    /// This may be because the other endpoint is initiating a closing handshake,
    /// or it may be the other endpoint confirming the handshake initiated by this endpoint.
    fn on_close(&mut self, code: CloseCode, reason: &str) {
        debug!("Connection closing due to ({:?}) {}", code, reason);
    }

    /// Called when an error occurs on the WebSocket.
    fn on_error(&mut self, err: Error) {
        // Ignore connection reset errors by default, but allow library clients to see them by
        // overriding this method if they want
        if let Kind::Io(ref err) = err.kind {
            if let Some(104) = err.raw_os_error() {
                return
            }
        }

        error!("{:?}", err);
        if !log_enabled!(ErrorLevel) {
            println!("Encountered an error: {}\nEnable a logger to see more information.", err);
        }
    }

    // handshake events

    /// A method for handling the low-level workings of the request portion of the WebSocket
    /// handshake.
    ///
    /// Implementors should select a WebSocket protocol and extensions where they are supported.
    ///
    /// Implementors can inspect the Request and must return a Response or an error
    /// indicating that the handshake failed. The default implementation provides conformance with
    /// the WebSocket protocol, and implementors should use the `Response::from_request` method and
    /// then modify the resulting response as necessary in order to maintain conformance.
    ///
    /// This method will not be called when the handler represents a client endpoint. Use
    /// `build_request` to provide an initial handshake request.
    ///
    /// # Examples
    ///
    /// ```ignore
    /// let mut res = try!(Response::from_request(req));
    /// if try!(req.extensions()).iter().find(|&&ext| ext.contains("myextension-name")).is_some() {
    ///     res.add_extension("myextension-name")
    /// }
    /// Ok(res)
    /// ```
    #[inline]
    fn on_request(&mut self, req: &Request) -> Result<Response> {
        debug!("Handler received request:\n{}", req);
        Response::from_request(req)
    }

    /// A method for handling the low-level workings of the response portion of the WebSocket
    /// handshake.
    ///
    /// Implementors can inspect the Response and choose to fail the connection by
    /// returning an error. This method will not be called when the handler represents a server
    /// endpoint. The response should indicate which WebSocket protocol and extensions the server
    /// has agreed to if any.
    #[inline]
    fn on_response(&mut self, res: &Response) -> Result<()> {
        debug!("Handler received response:\n{}", res);
        Ok(())
    }

    // timeout events

    /// Called when a timeout is triggered.
    ///
    /// This method will be called when the eventloop encounters a timeout on the specified
    /// token. To schedule a timeout with your specific token use the `Sender::timeout` method.
    ///
    /// # Examples
    ///
    /// ```ignore
    /// const GRATI: Token = Token(1);
    ///
    /// ... Handler
    ///
    /// fn on_open(&mut self, _: Handshake) -> Result<()> {
    ///     // schedule a timeout to send a gratuitous pong every 5 seconds
    ///     self.ws.timeout(5_000, GRATI)
    /// }
    ///
    /// fn on_timeout(&mut self, event: Token) -> Result<()> {
    ///     if event == GRATI {
    ///         // send gratuitous pong
    ///         try!(self.ws.pong(vec![]))
    ///         // reschedule the timeout
    ///         self.ws.timeout(5_000, GRATI)
    ///     } else {
    ///         Err(Error::new(ErrorKind::Internal, "Invalid timeout token encountered!"))
    ///     }
    /// }
    /// ```
    #[inline]
    fn on_timeout(&mut self, event: Token) -> Result<()> {
        debug!("Handler received timeout token: {:?}", event);
        Ok(())
    }

    /// Called when a timeout has been scheduled on the eventloop.
    ///
    /// This method is the hook for obtaining a Timeout object that may be used to cancel a
    /// timeout. This is a noop by default.
    ///
    /// # Examples
    ///
    /// ```ignore
    /// const PING: Token = Token(1);
    /// const EXPIRE: Token = Token(2);
    ///
    /// ... Handler
    ///
    /// fn on_open(&mut self, _: Handshake) -> Result<()> {
    ///     // schedule a timeout to send a ping every 5 seconds
    ///     try!(self.ws.timeout(5_000, PING));
    ///     // schedule a timeout to close the connection if there is no activity for 30 seconds
    ///     self.ws.timeout(30_000, EXPIRE)
    /// }
    ///
    /// fn on_timeout(&mut self, event: Token) -> Result<()> {
    ///     match event {
    ///         PING => {
    ///             self.ws.ping(vec![]);
    ///             self.ws.timeout(5_000, PING)
    ///         }
    ///         EXPIRE => self.ws.close(CloseCode::Away),
    ///         _ => Err(Error::new(ErrorKind::Internal, "Invalid timeout token encountered!")),
    ///     }
    /// }
    ///
    /// fn on_new_timeout(&mut self, event: Token, timeout: Timeout) -> Result<()> {
    ///     if event == EXPIRE {
    ///         if let Some(t) = self.timeout.take() {
    ///             try!(self.ws.cancel(t))
    ///         }
    ///         self.timeout = Some(timeout)
    ///     }
    ///     Ok(())
    /// }
    ///
    /// fn on_frame(&mut self, frame: Frame) -> Result<Option<Frame>> {
    ///     // some activity has occurred, let's reset the expiration
    ///     try!(self.ws.timeout(30_000, EXPIRE));
    ///     Ok(Some(frame))
    /// }
    /// ```
    #[inline]
    fn on_new_timeout(&mut self, _: Token, _: Timeout) -> Result<()> {
        // default implementation discards the timeout handle
        Ok(())
    }

    // frame events

    /// A method for handling incoming frames.
    ///
    /// This method provides very low-level access to the details of the WebSocket protocol. It may
    /// be necessary to implement this method in order to provide a particular extension, but
    /// incorrect implementation may cause the other endpoint to fail the connection.
    ///
    /// Returning `Ok(None)` will cause the connection to forget about a particular frame. This is
    /// useful if you want ot filter out a frame or if you don't want any of the default handler
    /// methods to run.
    ///
    /// By default this method simply ensures that no reserved bits are set.
    #[inline]
    fn on_frame(&mut self, frame: Frame) -> Result<Option<Frame>> {
        debug!("Handler received: {}", frame);
        // default implementation doesn't allow for reserved bits to be set
        if frame.has_rsv1() || frame.has_rsv2() || frame.has_rsv3() {
            Err(Error::new(Kind::Protocol, "Encountered frame with reserved bits set."))
        } else {
            Ok(Some(frame))
        }
    }

    /// A method for handling outgoing frames.
    ///
    /// This method provides very low-level access to the details of the WebSocket protocol. It may
    /// be necessary to implement this method in order to provide a particular extension, but
    /// incorrect implementation may cause the other endpoint to fail the connection.
    ///
    /// Returning `Ok(None)` will cause the connection to forget about a particular frame, meaning
    /// that it will not be sent. You can use this approach to merge multiple frames into a single
    /// frame before sending the message.
    ///
    /// For messages, this method will be called with a single complete, final frame before any
    /// fragmentation is performed. Automatic fragmentation will be performed on the returned
    /// frame, if any, based on the `fragment_size` setting.
    ///
    /// By default this method simply ensures that no reserved bits are set.
    #[inline]
    fn on_send_frame(&mut self, frame: Frame) -> Result<Option<Frame>> {
        trace!("Handler will send: {}", frame);
        // default implementation doesn't allow for reserved bits to be set
        if frame.has_rsv1() || frame.has_rsv2() || frame.has_rsv3() {
            Err(Error::new(Kind::Protocol, "Encountered frame with reserved bits set."))
        } else {
            Ok(Some(frame))
        }
    }

    // constructors

    /// A method for creating the initial handshake request for WebSocket clients.
    ///
    /// The default implementation provides conformance with the WebSocket protocol, but this
    /// method may be overriden. In order to facilitate conformance,
    /// implementors should use the `Request::from_url` method and then modify the resulting
    /// request as necessary.
    ///
    /// Implementors should indicate any available WebSocket extensions here.
    ///
    /// # Examples
    /// ```ignore
    /// let mut req = try!(Request::from_url(url));
    /// req.add_extension("permessage-deflate; client_max_window_bits");
    /// Ok(req)
    /// ```
    #[inline]
    fn build_request(&mut self, url: &url::Url) -> Result<Request> {
        trace!("Handler is building request to {}.", url);
        Request::from_url(url)
    }

    /// A method for wrapping a client TcpStream with Ssl Authentication machinery
    ///
    /// Override this method to customize how the connection is encrypted. By default
    /// this will use the Server Name Indication extension in conformance with RFC6455.
    #[inline]
    #[cfg(feature="ssl")]
    fn upgrade_ssl_client(&mut self, stream: TcpStream, url: &url::Url) -> Result<SslStream<TcpStream>>
    {
        let domain = try!(url.domain().ok_or(Error::new(
            Kind::Protocol,
            format!("Unable to parse domain from {}. Needed for SSL.", url))));
        let connector = try!(SslConnectorBuilder::new(SslMethod::tls()).map_err(|e| {
            Error::new(Kind::Internal, format!("Failed to upgrade client to SSL: {}", e))
        })).build();
        connector.connect(domain, stream).map_err(Error::from)
    }

    /// A method for wrapping a server TcpStream with Ssl Authentication machinery
    ///
    /// Override this method to customize how the connection is encrypted. By default
    /// this method is not implemented.
    #[inline]
    #[cfg(feature="ssl")]
    fn upgrade_ssl_server(&mut self, _: TcpStream) -> Result<SslStream<TcpStream>>
    {
        unimplemented!()
    }
}

impl<F> Handler for F
    where F: Fn(Message) -> Result<()>
{
    fn on_message(&mut self, msg: Message) -> Result<()> {
        self(msg)
    }
}

mod test {
    #![allow(unused_imports, unused_variables, dead_code)]
    use super::*;
    use url;
    use mio;
    use handshake::{Request, Response, Handshake};
    use protocol::CloseCode;
    use frame;
    use message;
    use result::Result;

    #[derive(Debug, Eq, PartialEq)]
    struct M;
    impl Handler for M {
        fn on_message(&mut self, _: message::Message) -> Result<()> {
            Ok(println!("test"))
        }

        fn on_frame(&mut self, f: frame::Frame) -> Result<Option<frame::Frame>> {
            Ok(None)
        }
    }

    #[test]
    fn handler() {
        struct H;

        impl Handler for H {

            fn on_open(&mut self, shake: Handshake) -> Result<()> {
                assert!(shake.request.key().is_ok());
                assert!(shake.response.key().is_ok());
                Ok(())
            }

            fn on_message(&mut self, msg: message::Message) -> Result<()> {
                Ok(assert_eq!(msg, message::Message::Text(String::from("testme"))))
            }

            fn on_close(&mut self, code: CloseCode, _: &str) {
                assert_eq!(code, CloseCode::Normal)
            }

        }

        let mut h = H;
        let url = url::Url::parse("wss://127.0.0.1:3012").unwrap();
        let req = Request::from_url(&url).unwrap();
        let res = Response::from_request(&req).unwrap();
        h.on_open(Handshake{
            request: req,
            response: res,
            peer_addr: None,
            local_addr: None,
        }).unwrap();
        h.on_message(message::Message::Text("testme".to_owned())).unwrap();
        h.on_close(CloseCode::Normal, "");
    }

    #[test]
    fn closure_handler() {
        let mut close = |msg| {
            assert_eq!(msg, message::Message::Binary(vec![1, 2, 3]));
            Ok(())
        };

        close.on_message(message::Message::Binary(vec![1, 2, 3])).unwrap();
    }
}
<|endoftext|>


<commit_before>// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use crate::MAX_DATA_STORE_SIZE;
use serde_json::{to_vec, Value};
use std::fmt;

/// The Mmds is the Microvm Metadata Service represented as an untyped json.
#[derive(Clone)]
pub struct Mmds {
    data_store: Value,
    is_initialized: bool,
    data_store_limit: usize,
    version: MmdsVersion,
}

/// MMDS version.
#[derive(Clone, Copy)]
pub enum MmdsVersion {
    V1,
    V2,
}

/// MMDS possible outputs.
pub enum OutputFormat {
    Json,
    Imds,
}

#[derive(Debug, PartialEq)]
pub enum Error {
    DataStoreLimitExceeded,
    NotFound,
    NotInitialized,
    UnsupportedValueType,
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::DataStoreLimitExceeded => write!(f, "The MMDS patch request doesn't fit."),
            Error::NotFound => write!(f, "The MMDS resource does not exist."),
            Error::NotInitialized => write!(f, "The MMDS data store is not initialized."),
            Error::UnsupportedValueType => write!(
                f,
                "Cannot retrieve value. The value has an unsupported type."
            ),
        }
    }
}

impl Default for Mmds {
    fn default() -> Self {
        Mmds {
            data_store: Value::default(),
            is_initialized: false,
            data_store_limit: MAX_DATA_STORE_SIZE,
            version: MmdsVersion::V1,
        }
    }
}

impl Mmds {
    /// This method is needed to check if data store is initialized.
    /// When a PATCH request is made on an uninitialized Mmds structure this method
    /// should return a NotFound error.
    fn check_data_store_initialized(&self) -> Result<(), Error> {
        if self.is_initialized {
            Ok(())
        } else {
            Err(Error::NotInitialized)
        }
    }

    /// Set the MMDS version.
    pub fn set_version(&mut self, version: MmdsVersion) {
        self.version = version;
    }

    /// Return the MMDS version.
    pub fn version(&self) -> MmdsVersion {
        self.version
    }

    pub fn set_data_store_limit(&mut self, data_store_limit: usize) {
        self.data_store_limit = data_store_limit;
    }

    // We do not check data_store size here because a request with a body
    // bigger than the imposed limit will be stoped by micro_http before
    // reaching here.
    pub fn put_data(&mut self, data: Value) -> Result<(), Error> {
        self.data_store = data;
        self.is_initialized = true;
        Ok(())
    }

    pub fn patch_data(&mut self, patch_data: Value) -> Result<(), Error> {
        self.check_data_store_initialized()?;
        let mut data_store_clone = self.data_store.clone();

        super::json_patch(&mut data_store_clone, &patch_data);
        // It is safe to unwrap because our data store keys are all strings and
        // we are using default serializer which does not return error.
        if to_vec(&data_store_clone).unwrap().len() > self.data_store_limit {
            return Err(Error::DataStoreLimitExceeded);
        }
        self.data_store = data_store_clone;
        Ok(())
    }

    // We do not check size of data_store before returning a result because due
    // to limit from put/patch the data_store can not be bigger than the limit
    // imposed by the server.
    pub fn get_data_str(&self) -> String {
        if self.data_store.is_null() {
            return String::from("{}");
        }
        self.data_store.to_string()
    }

    /// Returns the serde::Value in IMDS format plaintext.
    /// Currently, only JSON objects and strings can be IMDS formatted.
    ///
    /// See the docs for detailed description of the IMDS format:
    /// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html
    ///
    /// # Examples
    ///
    /// ```json
    /// {
    ///     "key1" : {
    ///         "key11": "value11"
    ///         "key12": "value12"
    ///     }
    ///     "key2" : "value3"
    ///     "key3" : "value3"
    /// }
    ///```
    ///
    /// IMDS formatted JSON object:
    /// ```text
    /// key1/
    /// key2
    /// key3
    /// ```
    ///
    /// JSON string:
    /// ```json
    /// "value"
    /// ```
    ///
    /// IMDS formatted string:
    /// ```text
    /// value
    /// ```
    ///
    /// If the `serde_json::Value` is not supported, an `UnsupportedValueType` error is returned.
    fn format_imds(json: &Value) -> Result<String, Error> {
        // If the `dict` is Value::Null, Error::NotFound is thrown.
        // If the `dict` is not a dictionary, a Vec with the value corresponding to
        // the key is returned.
        match json.as_object() {
            Some(map) => {
                let mut ret = Vec::new();
                // When the object is a map, push all the keys in the Vec.
                for key in map.keys() {
                    let mut key = key.clone();
                    // If the key corresponds to a dictionary, a "/" is appended
                    // to the key name.
                    if map[&key].is_object() {
                        key.push('/');
                    }

                    ret.push(key);
                }
                Ok(ret.join("\n"))
            }
            None => {
                // When the object is not a map, return the value.
                // Support only `Value::String`.
                match json.as_str() {
                    Some(str_val) => Ok(str_val.to_string()),
                    None => Err(Error::UnsupportedValueType),
                }
            }
        }
    }

    /// Returns the subtree located at path. When the path corresponds to a leaf, it returns the value.
    /// Returns Error::NotFound when the path is invalid.
    pub fn get_value(&self, path: String, format: OutputFormat) -> Result<String, Error> {
        // The pointer function splits the input by "/". With a trailing "/", pointer does not
        // know how to get the object.
        let value = if path.ends_with('/') {
            self.data_store.pointer(&path.as_str()[..(path.len() - 1)])
        } else {
            self.data_store.pointer(path.as_str())
        };

        if let Some(json) = value {
            match format {
                OutputFormat::Json => Ok(json.to_string()),
                OutputFormat::Imds => Mmds::format_imds(json),
            }
        } else {
            Err(Error::NotFound)
        }
    }
}

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

    #[test]
    fn test_mmds() {
        let mut mmds = Mmds::default();

        assert_eq!(
            mmds.check_data_store_initialized().unwrap_err().to_string(),
            "The MMDS data store is not initialized.".to_string(),
        );

        let mut mmds_json = "{\"meta-data\":{\"iam\":\"dummy\"},\"user-data\":\"1522850095\"}";

        mmds.put_data(serde_json::from_str(mmds_json).unwrap())
            .unwrap();
        assert!(mmds.check_data_store_initialized().is_ok());

        assert_eq!(mmds.get_data_str(), mmds_json);

        // update the user-data field add test that patch works as expected
        let patch_json = "{\"user-data\":\"10\"}";
        mmds.patch_data(serde_json::from_str(patch_json).unwrap())
            .unwrap();
        mmds_json = "{\"meta-data\":{\"iam\":\"dummy\"},\"user-data\":\"10\"}";
        assert_eq!(mmds.get_data_str(), mmds_json);
    }

    #[test]
    fn test_get_value() {
        let mut mmds = Mmds::default();
        let data = r#"{
            "name": {
                "first": "John",
                "second": "Doe"
            },
            "age": 43,
            "phones": [
                "+401234567",
                "+441234567"
            ],
            "member": false,
            "shares_percentage": 12.12,
            "balance": -24
        }"#;
        let data_store: Value = serde_json::from_str(data).unwrap();
        mmds.put_data(data_store).unwrap();

        // Test invalid path.
        assert_eq!(
            mmds.get_value("/invalid_path".to_string(), OutputFormat::Json),
            Err(Error::NotFound)
        );
        assert_eq!(
            mmds.get_value("/invalid_path".to_string(), OutputFormat::Imds),
            Err(Error::NotFound)
        );

        // Retrieve an object.
        let mut expected_json = r#"{
                "first": "John",
                "second": "Doe"
            }"#
        .to_string();
        expected_json.retain(|c| !c.is_whitespace());
        assert_eq!(
            mmds.get_value("/name".to_string(), OutputFormat::Json)
                .unwrap(),
            expected_json
        );
        let expected_imds = "first\nsecond";
        assert_eq!(
            mmds.get_value("/name".to_string(), OutputFormat::Imds)
                .unwrap(),
            expected_imds
        );

        // Retrieve an integer.
        assert_eq!(
            mmds.get_value("/age".to_string(), OutputFormat::Json)
                .unwrap(),
            "43"
        );
        assert_eq!(
            mmds.get_value("/age".to_string(), OutputFormat::Imds)
                .err()
                .unwrap(),
            Error::UnsupportedValueType
        );

        // Test path ends with /; Value is a dictionary.
        // Retrieve an array.
        let mut expected = r#"[
                "+401234567",
                "+441234567"
            ]"#
        .to_string();
        expected.retain(|c| !c.is_whitespace());
        assert_eq!(
            mmds.get_value("/phones/".to_string(), OutputFormat::Json)
                .unwrap(),
            expected
        );
        assert_eq!(
            mmds.get_value("/phones/".to_string(), OutputFormat::Imds)
                .err()
                .unwrap(),
            Error::UnsupportedValueType
        );

        // Test path does NOT end with /; Value is a dictionary.
        assert_eq!(
            mmds.get_value("/phones".to_string(), OutputFormat::Json)
                .unwrap(),
            expected
        );
        assert_eq!(
            mmds.get_value("/phones".to_string(), OutputFormat::Imds)
                .err()
                .unwrap(),
            Error::UnsupportedValueType
        );

        // Retrieve the first element of an array.
        assert_eq!(
            mmds.get_value("/phones/0/".to_string(), OutputFormat::Json)
                .unwrap(),
            "\"+401234567\""
        );
        assert_eq!(
            mmds.get_value("/phones/0/".to_string(), OutputFormat::Imds)
                .unwrap(),
            "+401234567"
        );

        // Retrieve a boolean.
        assert_eq!(
            mmds.get_value("/member".to_string(), OutputFormat::Json)
                .unwrap(),
            "false"
        );
        assert_eq!(
            mmds.get_value("/member".to_string(), OutputFormat::Imds)
                .err()
                .unwrap(),
            Error::UnsupportedValueType
        );

        // Retrieve a float.
        assert_eq!(
            mmds.get_value("/shares_percentage".to_string(), OutputFormat::Json)
                .unwrap(),
            "12.12"
        );
        assert_eq!(
            mmds.get_value("/shares_percentage".to_string(), OutputFormat::Imds)
                .err()
                .unwrap(),
            Error::UnsupportedValueType
        );

        // Retrieve a negative integer.
        assert_eq!(
            mmds.get_value("/balance".to_string(), OutputFormat::Json)
                .unwrap(),
            "-24"
        );
        assert_eq!(
            mmds.get_value("/balance".to_string(), OutputFormat::Imds)
                .err()
                .unwrap(),
            Error::UnsupportedValueType
        );
    }

    #[test]
    fn test_update_data_store() {
        let mut mmds = Mmds::default();
        mmds.set_data_store_limit(MAX_DATA_STORE_SIZE);

        let data = r#"{
            "name": {
                "first": "John",
                "second": "Doe"
            },
            "age": "43"
        }"#;
        let data_store: Value = serde_json::from_str(data).unwrap();
        assert!(mmds.put_data(data_store).is_ok());

        let data = r#"{
            "name": {
                "first": "John",
                "second": "Doe"
            },
            "age": "100"
        }"#;
        let data_store: Value = serde_json::from_str(data).unwrap();
        assert!(mmds.patch_data(data_store).is_ok());

        let data = r#"{
            "name": {
                "first": "John",
                "second": "Doe"
            },
            "age": 43
        }"#;
        let data_store: Value = serde_json::from_str(data).unwrap();
        assert!(mmds.put_data(data_store).is_ok());

        let data = r#"{
            "name": {
                "first": "John",
                "second": null
            },
            "age": "43"
        }"#;
        let data_store: Value = serde_json::from_str(data).unwrap();
        assert!(mmds.patch_data(data_store).is_ok());

        let filling = (0..51151).map(|_| "X").collect::<String>();
        let data = "{\"new_key\": \"".to_string() + &filling + "\"}";

        let data_store: Value = serde_json::from_str(&data).unwrap();
        assert!(mmds.patch_data(data_store).is_ok());

        let data = "{\"new_key2\" : \"smth\"}";
        let data_store: Value = serde_json::from_str(&data).unwrap();
        assert_eq!(
            mmds.patch_data(data_store).unwrap_err(),
            Error::DataStoreLimitExceeded
        );
        assert!(!mmds.get_data_str().contains("smth"));

        let data = "{\"new_key\" : \"smth\"}";
        let data_store: Value = serde_json::from_str(&data).unwrap();
        assert!(mmds.patch_data(data_store).is_ok());
        assert!(mmds.get_data_str().contains("smth"));
        assert_eq!(mmds.get_data_str().len(), 53);

        let data = "{\"new_key2\" : \"smth2\"}";
        let data_store: Value = serde_json::from_str(&data).unwrap();
        assert!(mmds.patch_data(data_store).is_ok());
        assert!(mmds.get_data_str().contains("smth2"));
        assert_eq!(mmds.get_data_str().len(), 72);
    }
}
<commit_msg>mmds: add unit tests for setting/getting version<commit_after>// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use crate::MAX_DATA_STORE_SIZE;
use serde_json::{to_vec, Value};
use std::fmt;
use std::fmt::{Display, Formatter};

/// The Mmds is the Microvm Metadata Service represented as an untyped json.
#[derive(Clone)]
pub struct Mmds {
    data_store: Value,
    is_initialized: bool,
    data_store_limit: usize,
    version: MmdsVersion,
}

/// MMDS version.
#[derive(Clone, Copy)]
pub enum MmdsVersion {
    V1,
    V2,
}

impl Display for MmdsVersion {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        match self {
            MmdsVersion::V1 => write!(f, "V1"),
            MmdsVersion::V2 => write!(f, "V2"),
        }
    }
}

/// MMDS possible outputs.
pub enum OutputFormat {
    Json,
    Imds,
}

#[derive(Debug, PartialEq)]
pub enum Error {
    DataStoreLimitExceeded,
    NotFound,
    NotInitialized,
    UnsupportedValueType,
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::DataStoreLimitExceeded => write!(f, "The MMDS patch request doesn't fit."),
            Error::NotFound => write!(f, "The MMDS resource does not exist."),
            Error::NotInitialized => write!(f, "The MMDS data store is not initialized."),
            Error::UnsupportedValueType => write!(
                f,
                "Cannot retrieve value. The value has an unsupported type."
            ),
        }
    }
}

impl Default for Mmds {
    fn default() -> Self {
        Mmds {
            data_store: Value::default(),
            is_initialized: false,
            data_store_limit: MAX_DATA_STORE_SIZE,
            version: MmdsVersion::V1,
        }
    }
}

impl Mmds {
    /// This method is needed to check if data store is initialized.
    /// When a PATCH request is made on an uninitialized Mmds structure this method
    /// should return a NotFound error.
    fn check_data_store_initialized(&self) -> Result<(), Error> {
        if self.is_initialized {
            Ok(())
        } else {
            Err(Error::NotInitialized)
        }
    }

    /// Set the MMDS version.
    pub fn set_version(&mut self, version: MmdsVersion) {
        self.version = version;
    }

    /// Return the MMDS version.
    pub fn version(&self) -> MmdsVersion {
        self.version
    }

    pub fn set_data_store_limit(&mut self, data_store_limit: usize) {
        self.data_store_limit = data_store_limit;
    }

    // We do not check data_store size here because a request with a body
    // bigger than the imposed limit will be stoped by micro_http before
    // reaching here.
    pub fn put_data(&mut self, data: Value) -> Result<(), Error> {
        self.data_store = data;
        self.is_initialized = true;
        Ok(())
    }

    pub fn patch_data(&mut self, patch_data: Value) -> Result<(), Error> {
        self.check_data_store_initialized()?;
        let mut data_store_clone = self.data_store.clone();

        super::json_patch(&mut data_store_clone, &patch_data);
        // It is safe to unwrap because our data store keys are all strings and
        // we are using default serializer which does not return error.
        if to_vec(&data_store_clone).unwrap().len() > self.data_store_limit {
            return Err(Error::DataStoreLimitExceeded);
        }
        self.data_store = data_store_clone;
        Ok(())
    }

    // We do not check size of data_store before returning a result because due
    // to limit from put/patch the data_store can not be bigger than the limit
    // imposed by the server.
    pub fn get_data_str(&self) -> String {
        if self.data_store.is_null() {
            return String::from("{}");
        }
        self.data_store.to_string()
    }

    /// Returns the serde::Value in IMDS format plaintext.
    /// Currently, only JSON objects and strings can be IMDS formatted.
    ///
    /// See the docs for detailed description of the IMDS format:
    /// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html
    ///
    /// # Examples
    ///
    /// ```json
    /// {
    ///     "key1" : {
    ///         "key11": "value11"
    ///         "key12": "value12"
    ///     }
    ///     "key2" : "value3"
    ///     "key3" : "value3"
    /// }
    ///```
    ///
    /// IMDS formatted JSON object:
    /// ```text
    /// key1/
    /// key2
    /// key3
    /// ```
    ///
    /// JSON string:
    /// ```json
    /// "value"
    /// ```
    ///
    /// IMDS formatted string:
    /// ```text
    /// value
    /// ```
    ///
    /// If the `serde_json::Value` is not supported, an `UnsupportedValueType` error is returned.
    fn format_imds(json: &Value) -> Result<String, Error> {
        // If the `dict` is Value::Null, Error::NotFound is thrown.
        // If the `dict` is not a dictionary, a Vec with the value corresponding to
        // the key is returned.
        match json.as_object() {
            Some(map) => {
                let mut ret = Vec::new();
                // When the object is a map, push all the keys in the Vec.
                for key in map.keys() {
                    let mut key = key.clone();
                    // If the key corresponds to a dictionary, a "/" is appended
                    // to the key name.
                    if map[&key].is_object() {
                        key.push('/');
                    }

                    ret.push(key);
                }
                Ok(ret.join("\n"))
            }
            None => {
                // When the object is not a map, return the value.
                // Support only `Value::String`.
                match json.as_str() {
                    Some(str_val) => Ok(str_val.to_string()),
                    None => Err(Error::UnsupportedValueType),
                }
            }
        }
    }

    /// Returns the subtree located at path. When the path corresponds to a leaf, it returns the value.
    /// Returns Error::NotFound when the path is invalid.
    pub fn get_value(&self, path: String, format: OutputFormat) -> Result<String, Error> {
        // The pointer function splits the input by "/". With a trailing "/", pointer does not
        // know how to get the object.
        let value = if path.ends_with('/') {
            self.data_store.pointer(&path.as_str()[..(path.len() - 1)])
        } else {
            self.data_store.pointer(path.as_str())
        };

        if let Some(json) = value {
            match format {
                OutputFormat::Json => Ok(json.to_string()),
                OutputFormat::Imds => Mmds::format_imds(json),
            }
        } else {
            Err(Error::NotFound)
        }
    }
}

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

    #[test]
    fn test_display_mmds_version() {
        assert_eq!(MmdsVersion::V1.to_string(), "V1");
        assert_eq!(MmdsVersion::V2.to_string(), "V2");
    }

    #[test]
    fn test_mmds_version() {
        let mut mmds = Mmds::default();

        // Test default MMDS version.
        assert_eq!(mmds.version().to_string(), MmdsVersion::V1.to_string());

        // Test setting MMDS version to v1.
        mmds.set_version(MmdsVersion::V2);
        assert_eq!(mmds.version().to_string(), MmdsVersion::V2.to_string());

        // Test setting MMDS version back to default.
        mmds.set_version(MmdsVersion::V1);
        assert_eq!(mmds.version().to_string(), MmdsVersion::V1.to_string());
    }

    #[test]
    fn test_mmds() {
        let mut mmds = Mmds::default();

        assert_eq!(
            mmds.check_data_store_initialized().unwrap_err().to_string(),
            "The MMDS data store is not initialized.".to_string(),
        );

        let mut mmds_json = "{\"meta-data\":{\"iam\":\"dummy\"},\"user-data\":\"1522850095\"}";

        mmds.put_data(serde_json::from_str(mmds_json).unwrap())
            .unwrap();
        assert!(mmds.check_data_store_initialized().is_ok());

        assert_eq!(mmds.get_data_str(), mmds_json);

        // update the user-data field add test that patch works as expected
        let patch_json = "{\"user-data\":\"10\"}";
        mmds.patch_data(serde_json::from_str(patch_json).unwrap())
            .unwrap();
        mmds_json = "{\"meta-data\":{\"iam\":\"dummy\"},\"user-data\":\"10\"}";
        assert_eq!(mmds.get_data_str(), mmds_json);
    }

    #[test]
    fn test_get_value() {
        let mut mmds = Mmds::default();
        let data = r#"{
            "name": {
                "first": "John",
                "second": "Doe"
            },
            "age": 43,
            "phones": [
                "+401234567",
                "+441234567"
            ],
            "member": false,
            "shares_percentage": 12.12,
            "balance": -24
        }"#;
        let data_store: Value = serde_json::from_str(data).unwrap();
        mmds.put_data(data_store).unwrap();

        // Test invalid path.
        assert_eq!(
            mmds.get_value("/invalid_path".to_string(), OutputFormat::Json),
            Err(Error::NotFound)
        );
        assert_eq!(
            mmds.get_value("/invalid_path".to_string(), OutputFormat::Imds),
            Err(Error::NotFound)
        );

        // Retrieve an object.
        let mut expected_json = r#"{
                "first": "John",
                "second": "Doe"
            }"#
        .to_string();
        expected_json.retain(|c| !c.is_whitespace());
        assert_eq!(
            mmds.get_value("/name".to_string(), OutputFormat::Json)
                .unwrap(),
            expected_json
        );
        let expected_imds = "first\nsecond";
        assert_eq!(
            mmds.get_value("/name".to_string(), OutputFormat::Imds)
                .unwrap(),
            expected_imds
        );

        // Retrieve an integer.
        assert_eq!(
            mmds.get_value("/age".to_string(), OutputFormat::Json)
                .unwrap(),
            "43"
        );
        assert_eq!(
            mmds.get_value("/age".to_string(), OutputFormat::Imds)
                .err()
                .unwrap(),
            Error::UnsupportedValueType
        );

        // Test path ends with /; Value is a dictionary.
        // Retrieve an array.
        let mut expected = r#"[
                "+401234567",
                "+441234567"
            ]"#
        .to_string();
        expected.retain(|c| !c.is_whitespace());
        assert_eq!(
            mmds.get_value("/phones/".to_string(), OutputFormat::Json)
                .unwrap(),
            expected
        );
        assert_eq!(
            mmds.get_value("/phones/".to_string(), OutputFormat::Imds)
                .err()
                .unwrap(),
            Error::UnsupportedValueType
        );

        // Test path does NOT end with /; Value is a dictionary.
        assert_eq!(
            mmds.get_value("/phones".to_string(), OutputFormat::Json)
                .unwrap(),
            expected
        );
        assert_eq!(
            mmds.get_value("/phones".to_string(), OutputFormat::Imds)
                .err()
                .unwrap(),
            Error::UnsupportedValueType
        );

        // Retrieve the first element of an array.
        assert_eq!(
            mmds.get_value("/phones/0/".to_string(), OutputFormat::Json)
                .unwrap(),
            "\"+401234567\""
        );
        assert_eq!(
            mmds.get_value("/phones/0/".to_string(), OutputFormat::Imds)
                .unwrap(),
            "+401234567"
        );

        // Retrieve a boolean.
        assert_eq!(
            mmds.get_value("/member".to_string(), OutputFormat::Json)
                .unwrap(),
            "false"
        );
        assert_eq!(
            mmds.get_value("/member".to_string(), OutputFormat::Imds)
                .err()
                .unwrap(),
            Error::UnsupportedValueType
        );

        // Retrieve a float.
        assert_eq!(
            mmds.get_value("/shares_percentage".to_string(), OutputFormat::Json)
                .unwrap(),
            "12.12"
        );
        assert_eq!(
            mmds.get_value("/shares_percentage".to_string(), OutputFormat::Imds)
                .err()
                .unwrap(),
            Error::UnsupportedValueType
        );

        // Retrieve a negative integer.
        assert_eq!(
            mmds.get_value("/balance".to_string(), OutputFormat::Json)
                .unwrap(),
            "-24"
        );
        assert_eq!(
            mmds.get_value("/balance".to_string(), OutputFormat::Imds)
                .err()
                .unwrap(),
            Error::UnsupportedValueType
        );
    }

    #[test]
    fn test_update_data_store() {
        let mut mmds = Mmds::default();
        mmds.set_data_store_limit(MAX_DATA_STORE_SIZE);

        let data = r#"{
            "name": {
                "first": "John",
                "second": "Doe"
            },
            "age": "43"
        }"#;
        let data_store: Value = serde_json::from_str(data).unwrap();
        assert!(mmds.put_data(data_store).is_ok());

        let data = r#"{
            "name": {
                "first": "John",
                "second": "Doe"
            },
            "age": "100"
        }"#;
        let data_store: Value = serde_json::from_str(data).unwrap();
        assert!(mmds.patch_data(data_store).is_ok());

        let data = r#"{
            "name": {
                "first": "John",
                "second": "Doe"
            },
            "age": 43
        }"#;
        let data_store: Value = serde_json::from_str(data).unwrap();
        assert!(mmds.put_data(data_store).is_ok());

        let data = r#"{
            "name": {
                "first": "John",
                "second": null
            },
            "age": "43"
        }"#;
        let data_store: Value = serde_json::from_str(data).unwrap();
        assert!(mmds.patch_data(data_store).is_ok());

        let filling = (0..51151).map(|_| "X").collect::<String>();
        let data = "{\"new_key\": \"".to_string() + &filling + "\"}";

        let data_store: Value = serde_json::from_str(&data).unwrap();
        assert!(mmds.patch_data(data_store).is_ok());

        let data = "{\"new_key2\" : \"smth\"}";
        let data_store: Value = serde_json::from_str(&data).unwrap();
        assert_eq!(
            mmds.patch_data(data_store).unwrap_err(),
            Error::DataStoreLimitExceeded
        );
        assert!(!mmds.get_data_str().contains("smth"));

        let data = "{\"new_key\" : \"smth\"}";
        let data_store: Value = serde_json::from_str(&data).unwrap();
        assert!(mmds.patch_data(data_store).is_ok());
        assert!(mmds.get_data_str().contains("smth"));
        assert_eq!(mmds.get_data_str().len(), 53);

        let data = "{\"new_key2\" : \"smth2\"}";
        let data_store: Value = serde_json::from_str(&data).unwrap();
        assert!(mmds.patch_data(data_store).is_ok());
        assert!(mmds.get_data_str().contains("smth2"));
        assert_eq!(mmds.get_data_str().len(), 72);
    }
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Common handling of keyboard input and state management for text input controls

use clipboard_provider::ClipboardProvider;
use dom::bindings::js::JSRef;
use dom::keyboardevent::{KeyboardEvent, KeyboardEventHelpers, key_value};
use msg::constellation_msg::{SHIFT, CONTROL, ALT, SUPER};
use msg::constellation_msg::{Key, KeyModifiers};
use util::str::DOMString;

use std::borrow::ToOwned;
use std::cmp::{min, max};
use std::default::Default;

#[derive(Copy, Clone, PartialEq)]
pub enum Selection {
    Selected,
    NotSelected
}

#[jstraceable]
#[derive(Copy, Clone)]
pub struct TextPoint {
    /// 0-based line number
    pub line: usize,
    /// 0-based column number
    pub index: usize,
}

/// Encapsulated state for handling keyboard input in a single or multiline text input control.
#[jstraceable]
pub struct TextInput<T: ClipboardProvider> {
    /// Current text input content, split across lines without trailing '\n'
    lines: Vec<DOMString>,
    /// Current cursor input point
    pub edit_point: TextPoint,
    /// Beginning of selection range with edit_point as end that can span multiple lines.
    selection_begin: Option<TextPoint>,
    /// Is this a multiline input?
    multiline: bool,
    clipboard_provider: T,
}

/// Resulting action to be taken by the owner of a text input that is handling an event.
pub enum KeyReaction {
    TriggerDefaultAction,
    DispatchInput,
    Nothing,
}

impl Default for TextPoint {
    fn default() -> TextPoint {
        TextPoint {
            line: 0,
            index: 0,
        }
    }
}

/// Control whether this control should allow multiple lines.
#[derive(PartialEq)]
pub enum Lines {
    Single,
    Multiple,
}

/// The direction in which to delete a character.
#[derive(PartialEq)]
pub enum DeleteDir {
    Forward,
    Backward
}

/// Was the keyboard event accompanied by the standard control modifier,
/// i.e. cmd on Mac OS or ctrl on other platforms.
#[cfg(target_os="macos")]
fn is_control_key(mods: KeyModifiers) -> bool {
    mods.contains(SUPER) && !mods.contains(CONTROL | ALT)
}

#[cfg(not(target_os="macos"))]
fn is_control_key(mods: KeyModifiers) -> bool {
    mods.contains(CONTROL) && !mods.contains(SUPER | ALT)
}

fn is_printable_key(key: Key) -> bool {
    match key {
        Key::Space | Key::Apostrophe | Key::Comma | Key::Minus |
        Key::Period | Key::Slash | Key::GraveAccent | Key::Num0 |
        Key::Num1 | Key::Num2 | Key::Num3 | Key::Num4 | Key::Num5 |
        Key::Num6 | Key::Num7 | Key::Num8 | Key::Num9 | Key::Semicolon |
        Key::Equal | Key::A | Key::B | Key::C | Key::D | Key::E | Key::F |
        Key::G | Key::H | Key::I | Key::J | Key::K | Key::L | Key::M | Key::N |
        Key::O | Key::P | Key::Q | Key::R | Key::S | Key::T | Key::U | Key::V |
        Key::W | Key::X | Key::Y | Key::Z | Key::LeftBracket | Key::Backslash |
        Key::RightBracket | Key::Kp0 | Key::Kp1 | Key::Kp2 | Key::Kp3 |
        Key::Kp4 | Key::Kp5 | Key::Kp6 | Key::Kp7 | Key::Kp8 | Key::Kp9 |
        Key::KpDecimal | Key::KpDivide | Key::KpMultiply | Key::KpSubtract |
        Key::KpAdd | Key::KpEqual => true,
        _ => false,
    }
}

impl<T: ClipboardProvider> TextInput<T> {
    /// Instantiate a new text input control
    pub fn new(lines: Lines, initial: DOMString, clipboard_provider: T) -> TextInput<T> {
        let mut i = TextInput {
            lines: vec!(),
            edit_point: Default::default(),
            selection_begin: None,
            multiline: lines == Lines::Multiple,
            clipboard_provider: clipboard_provider
        };
        i.set_content(initial);
        i
    }

    /// Remove a character at the current editing point
    pub fn delete_char(&mut self, dir: DeleteDir) {
        if self.selection_begin.is_none() {
            self.adjust_horizontal(if dir == DeleteDir::Forward {
                1
            } else {
                -1
            }, Selection::Selected);
        }
        self.replace_selection("".to_owned());
    }

    /// Insert a character at the current editing point
    pub fn insert_char(&mut self, ch: char) {
        if self.selection_begin.is_none() {
            self.selection_begin = Some(self.edit_point);
        }
        self.replace_selection(ch.to_string());
    }

    /// Insert a string at the current editing point
    fn insert_string(&mut self, s: &str) {
        // it looks like this could be made performant by avoiding some redundant
        //  selection-related checks, but use the simple implementation for now
        for ch in s.chars() {
            self.insert_char(ch);
        }
    }

    pub fn get_sorted_selection(&self) -> (TextPoint, TextPoint) {
        let begin = self.selection_begin.unwrap();
        let end = self.edit_point;

        if begin.line < end.line || (begin.line == end.line && begin.index < end.index) {
            (begin, end)
        } else {
            (end, begin)
        }
    }

    pub fn replace_selection(&mut self, insert: String) {
        let (begin, end) = self.get_sorted_selection();
        self.clear_selection();

        let new_lines = {
            let prefix = self.lines[begin.line].slice_chars(0, begin.index);
            let suffix = self.lines[end.line].slice_chars(end.index, self.lines[end.line].chars().count());
            let lines_prefix = &self.lines[..begin.line];
            let lines_suffix = &self.lines[end.line + 1..];

            let mut insert_lines = if self.multiline {
                insert.split('\n').map(|s| s.to_owned()).collect()
            } else {
                vec!(insert)
            };

            let mut new_line = prefix.to_owned();
            new_line.push_str(&insert_lines[0]);
            insert_lines[0] = new_line;

            let last_insert_lines_index = insert_lines.len() - 1;
            self.edit_point.index = insert_lines[last_insert_lines_index].chars().count();
            self.edit_point.line = begin.line + last_insert_lines_index;

            insert_lines[last_insert_lines_index].push_str(suffix);

            let mut new_lines = vec!();
            new_lines.push_all(lines_prefix);
            new_lines.push_all(&insert_lines);
            new_lines.push_all(lines_suffix);
            new_lines
        };

        self.lines = new_lines;
    }

    /// Return the length of the current line under the editing point.
    pub fn current_line_length(&self) -> usize {
        self.lines[self.edit_point.line].chars().count()
    }

    /// Adjust the editing point position by a given of lines. The resulting column is
    /// as close to the original column position as possible.
    pub fn adjust_vertical(&mut self, adjust: isize, select: Selection) {
        if !self.multiline {
            return;
        }

        if select == Selection::Selected {
            if self.selection_begin.is_none() {
                self.selection_begin = Some(self.edit_point);
            }
        } else {
            self.clear_selection();
        }

        assert!(self.edit_point.line < self.lines.len());

        let target_line: isize = self.edit_point.line as isize + adjust;

        if target_line < 0 {
            self.edit_point.index = 0;
            self.edit_point.line = 0;
            return;
        } else if target_line as usize >= self.lines.len() {
            self.edit_point.line = self.lines.len() - 1;
            self.edit_point.index = self.current_line_length();
            return;
        }

        self.edit_point.line = target_line as usize;
        self.edit_point.index = min(self.current_line_length(), self.edit_point.index);
    }

    /// Adjust the editing point position by a given number of columns. If the adjustment
    /// requested is larger than is available in the current line, the editing point is
    /// adjusted vertically and the process repeats with the remaining adjustment requested.
    pub fn adjust_horizontal(&mut self, adjust: isize, select: Selection) {
        if select == Selection::Selected {
            if self.selection_begin.is_none() {
                self.selection_begin = Some(self.edit_point);
            }
        } else {
            if self.selection_begin.is_some() {
                let (begin, end) = self.get_sorted_selection();
                self.edit_point = if adjust < 0 {begin} else {end};
                self.clear_selection();
                return
            }
        }

        if adjust < 0 {
            let remaining = self.edit_point.index;
            if adjust.abs() as usize > remaining && self.edit_point.line > 0 {
                self.adjust_vertical(-1, select);
                self.edit_point.index = self.current_line_length();
                self.adjust_horizontal(adjust + remaining as isize + 1, select);
            } else {
                self.edit_point.index = max(0, self.edit_point.index as isize + adjust) as usize;
            }
        } else {
            let remaining = self.current_line_length() - self.edit_point.index;
            if adjust as usize > remaining && self.lines.len() > self.edit_point.line + 1 {
                self.adjust_vertical(1, select);
                self.edit_point.index = 0;
                // one shift is consumed by the change of line, hence the -1
                self.adjust_horizontal(adjust - remaining as isize - 1, select);
            } else {
                self.edit_point.index = min(self.current_line_length(),
                                            self.edit_point.index + adjust as usize);
            }
        }
    }

    /// Deal with a newline input.
    pub fn handle_return(&mut self) -> KeyReaction {
        if !self.multiline {
            return KeyReaction::TriggerDefaultAction;
        }
        self.insert_char('\n');
        return KeyReaction::DispatchInput;
    }

    /// Select all text in the input control.
    pub fn select_all(&mut self) {
        self.selection_begin = Some(TextPoint {
            line: 0,
            index: 0,
        });
        let last_line = self.lines.len() - 1;
        self.edit_point.line = last_line;
        self.edit_point.index = self.lines[last_line].chars().count();
    }

    /// Remove the current selection.
    pub fn clear_selection(&mut self) {
        self.selection_begin = None;
    }

    /// Process a given `KeyboardEvent` and return an action for the caller to execute.
    pub fn handle_keydown(&mut self, event: JSRef<KeyboardEvent>) -> KeyReaction {
        if let Some(key) = event.get_key() {
            self.handle_keydown_aux(key, event.get_key_modifiers())
        } else {
            KeyReaction::Nothing
        }
    }
    pub fn handle_keydown_aux(&mut self, key: Key, mods: KeyModifiers) -> KeyReaction {
        let maybe_select = if mods.contains(SHIFT) { Selection::Selected } else { Selection::NotSelected };
        match key {
           Key::A if is_control_key(mods) => {
                self.select_all();
                KeyReaction::Nothing
            },
           Key::V if is_control_key(mods) => {
                let contents = self.clipboard_provider.clipboard_contents();
                self.insert_string(&contents);
                KeyReaction::DispatchInput
            },
            _ if is_printable_key(key) => {
                self.insert_string(key_value(key, mods));
                KeyReaction::DispatchInput
            }
            Key::Space => {
                self.insert_char(' ');
                KeyReaction::DispatchInput
            }
            Key::Delete => {
                self.delete_char(DeleteDir::Forward);
                KeyReaction::DispatchInput
            }
            Key::Backspace => {
                self.delete_char(DeleteDir::Backward);
                KeyReaction::DispatchInput
            }
            Key::Left => {
                self.adjust_horizontal(-1, maybe_select);
                KeyReaction::Nothing
            }
            Key::Right => {
                self.adjust_horizontal(1, maybe_select);
                KeyReaction::Nothing
            }
            Key::Up => {
                self.adjust_vertical(-1, maybe_select);
                KeyReaction::Nothing
            }
            Key::Down => {
                self.adjust_vertical(1, maybe_select);
                KeyReaction::Nothing
            }
            Key::Enter | Key::KpEnter => self.handle_return(),
            Key::Home => {
                self.edit_point.index = 0;
                KeyReaction::Nothing
            }
            Key::End => {
                self.edit_point.index = self.current_line_length();
                KeyReaction::Nothing
            }
            Key::PageUp => {
                self.adjust_vertical(-28, maybe_select);
                KeyReaction::Nothing
            }
            Key::PageDown => {
                self.adjust_vertical(28, maybe_select);
                KeyReaction::Nothing
            }
            Key::Tab => KeyReaction::TriggerDefaultAction,
            _ => KeyReaction::Nothing,
        }
    }

    /// Get the current contents of the text input. Multiple lines are joined by \n.
    pub fn get_content(&self) -> DOMString {
        let mut content = "".to_owned();
        for (i, line) in self.lines.iter().enumerate() {
            content.push_str(&line);
            if i < self.lines.len() - 1 {
                content.push('\n');
            }
        }
        content
    }

    /// Set the current contents of the text input. If this is control supports multiple lines,
    /// any \n encountered will be stripped and force a new logical line.
    pub fn set_content(&mut self, content: DOMString) {
        self.lines = if self.multiline {
            content.split('\n').map(|s| s.to_owned()).collect()
        } else {
            vec!(content)
        };
        self.edit_point.line = min(self.edit_point.line, self.lines.len() - 1);
        self.edit_point.index = min(self.edit_point.index, self.current_line_length());
    }
}
<commit_msg>Auto merge of #6340 - aweinstock314:reduce-textinput-allocations, r=Ms2ger<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Common handling of keyboard input and state management for text input controls

use clipboard_provider::ClipboardProvider;
use dom::bindings::js::JSRef;
use dom::keyboardevent::{KeyboardEvent, KeyboardEventHelpers, key_value};
use msg::constellation_msg::{SHIFT, CONTROL, ALT, SUPER};
use msg::constellation_msg::{Key, KeyModifiers};
use util::str::DOMString;

use std::borrow::ToOwned;
use std::cmp::{min, max};
use std::default::Default;

#[derive(Copy, Clone, PartialEq)]
pub enum Selection {
    Selected,
    NotSelected
}

#[jstraceable]
#[derive(Copy, Clone)]
pub struct TextPoint {
    /// 0-based line number
    pub line: usize,
    /// 0-based column number
    pub index: usize,
}

/// Encapsulated state for handling keyboard input in a single or multiline text input control.
#[jstraceable]
pub struct TextInput<T: ClipboardProvider> {
    /// Current text input content, split across lines without trailing '\n'
    lines: Vec<DOMString>,
    /// Current cursor input point
    pub edit_point: TextPoint,
    /// Beginning of selection range with edit_point as end that can span multiple lines.
    selection_begin: Option<TextPoint>,
    /// Is this a multiline input?
    multiline: bool,
    clipboard_provider: T,
}

/// Resulting action to be taken by the owner of a text input that is handling an event.
pub enum KeyReaction {
    TriggerDefaultAction,
    DispatchInput,
    Nothing,
}

impl Default for TextPoint {
    fn default() -> TextPoint {
        TextPoint {
            line: 0,
            index: 0,
        }
    }
}

/// Control whether this control should allow multiple lines.
#[derive(PartialEq)]
pub enum Lines {
    Single,
    Multiple,
}

/// The direction in which to delete a character.
#[derive(PartialEq)]
pub enum DeleteDir {
    Forward,
    Backward
}

/// Was the keyboard event accompanied by the standard control modifier,
/// i.e. cmd on Mac OS or ctrl on other platforms.
#[cfg(target_os="macos")]
fn is_control_key(mods: KeyModifiers) -> bool {
    mods.contains(SUPER) && !mods.contains(CONTROL | ALT)
}

#[cfg(not(target_os="macos"))]
fn is_control_key(mods: KeyModifiers) -> bool {
    mods.contains(CONTROL) && !mods.contains(SUPER | ALT)
}

fn is_printable_key(key: Key) -> bool {
    match key {
        Key::Space | Key::Apostrophe | Key::Comma | Key::Minus |
        Key::Period | Key::Slash | Key::GraveAccent | Key::Num0 |
        Key::Num1 | Key::Num2 | Key::Num3 | Key::Num4 | Key::Num5 |
        Key::Num6 | Key::Num7 | Key::Num8 | Key::Num9 | Key::Semicolon |
        Key::Equal | Key::A | Key::B | Key::C | Key::D | Key::E | Key::F |
        Key::G | Key::H | Key::I | Key::J | Key::K | Key::L | Key::M | Key::N |
        Key::O | Key::P | Key::Q | Key::R | Key::S | Key::T | Key::U | Key::V |
        Key::W | Key::X | Key::Y | Key::Z | Key::LeftBracket | Key::Backslash |
        Key::RightBracket | Key::Kp0 | Key::Kp1 | Key::Kp2 | Key::Kp3 |
        Key::Kp4 | Key::Kp5 | Key::Kp6 | Key::Kp7 | Key::Kp8 | Key::Kp9 |
        Key::KpDecimal | Key::KpDivide | Key::KpMultiply | Key::KpSubtract |
        Key::KpAdd | Key::KpEqual => true,
        _ => false,
    }
}

impl<T: ClipboardProvider> TextInput<T> {
    /// Instantiate a new text input control
    pub fn new(lines: Lines, initial: DOMString, clipboard_provider: T) -> TextInput<T> {
        let mut i = TextInput {
            lines: vec!(),
            edit_point: Default::default(),
            selection_begin: None,
            multiline: lines == Lines::Multiple,
            clipboard_provider: clipboard_provider
        };
        i.set_content(initial);
        i
    }

    /// Remove a character at the current editing point
    pub fn delete_char(&mut self, dir: DeleteDir) {
        if self.selection_begin.is_none() {
            self.adjust_horizontal(if dir == DeleteDir::Forward {
                1
            } else {
                -1
            }, Selection::Selected);
        }
        self.replace_selection("".to_owned());
    }

    /// Insert a character at the current editing point
    pub fn insert_char(&mut self, ch: char) {
        self.insert_string(ch.to_string());
    }

    /// Insert a string at the current editing point
    fn insert_string<S: Into<String>>(&mut self, s: S) {
        if self.selection_begin.is_none() {
            self.selection_begin = Some(self.edit_point);
        }
        self.replace_selection(s.into());
    }

    pub fn get_sorted_selection(&self) -> (TextPoint, TextPoint) {
        let begin = self.selection_begin.unwrap();
        let end = self.edit_point;

        if begin.line < end.line || (begin.line == end.line && begin.index < end.index) {
            (begin, end)
        } else {
            (end, begin)
        }
    }

    pub fn replace_selection(&mut self, insert: String) {
        let (begin, end) = self.get_sorted_selection();
        self.clear_selection();

        let new_lines = {
            let prefix = self.lines[begin.line].slice_chars(0, begin.index);
            let suffix = self.lines[end.line].slice_chars(end.index, self.lines[end.line].chars().count());
            let lines_prefix = &self.lines[..begin.line];
            let lines_suffix = &self.lines[end.line + 1..];

            let mut insert_lines = if self.multiline {
                insert.split('\n').map(|s| s.to_owned()).collect()
            } else {
                vec!(insert)
            };

            let mut new_line = prefix.to_owned();
            new_line.push_str(&insert_lines[0]);
            insert_lines[0] = new_line;

            let last_insert_lines_index = insert_lines.len() - 1;
            self.edit_point.index = insert_lines[last_insert_lines_index].chars().count();
            self.edit_point.line = begin.line + last_insert_lines_index;

            insert_lines[last_insert_lines_index].push_str(suffix);

            let mut new_lines = vec!();
            new_lines.push_all(lines_prefix);
            new_lines.push_all(&insert_lines);
            new_lines.push_all(lines_suffix);
            new_lines
        };

        self.lines = new_lines;
    }

    /// Return the length of the current line under the editing point.
    pub fn current_line_length(&self) -> usize {
        self.lines[self.edit_point.line].chars().count()
    }

    /// Adjust the editing point position by a given of lines. The resulting column is
    /// as close to the original column position as possible.
    pub fn adjust_vertical(&mut self, adjust: isize, select: Selection) {
        if !self.multiline {
            return;
        }

        if select == Selection::Selected {
            if self.selection_begin.is_none() {
                self.selection_begin = Some(self.edit_point);
            }
        } else {
            self.clear_selection();
        }

        assert!(self.edit_point.line < self.lines.len());

        let target_line: isize = self.edit_point.line as isize + adjust;

        if target_line < 0 {
            self.edit_point.index = 0;
            self.edit_point.line = 0;
            return;
        } else if target_line as usize >= self.lines.len() {
            self.edit_point.line = self.lines.len() - 1;
            self.edit_point.index = self.current_line_length();
            return;
        }

        self.edit_point.line = target_line as usize;
        self.edit_point.index = min(self.current_line_length(), self.edit_point.index);
    }

    /// Adjust the editing point position by a given number of columns. If the adjustment
    /// requested is larger than is available in the current line, the editing point is
    /// adjusted vertically and the process repeats with the remaining adjustment requested.
    pub fn adjust_horizontal(&mut self, adjust: isize, select: Selection) {
        if select == Selection::Selected {
            if self.selection_begin.is_none() {
                self.selection_begin = Some(self.edit_point);
            }
        } else {
            if self.selection_begin.is_some() {
                let (begin, end) = self.get_sorted_selection();
                self.edit_point = if adjust < 0 {begin} else {end};
                self.clear_selection();
                return
            }
        }

        if adjust < 0 {
            let remaining = self.edit_point.index;
            if adjust.abs() as usize > remaining && self.edit_point.line > 0 {
                self.adjust_vertical(-1, select);
                self.edit_point.index = self.current_line_length();
                self.adjust_horizontal(adjust + remaining as isize + 1, select);
            } else {
                self.edit_point.index = max(0, self.edit_point.index as isize + adjust) as usize;
            }
        } else {
            let remaining = self.current_line_length() - self.edit_point.index;
            if adjust as usize > remaining && self.lines.len() > self.edit_point.line + 1 {
                self.adjust_vertical(1, select);
                self.edit_point.index = 0;
                // one shift is consumed by the change of line, hence the -1
                self.adjust_horizontal(adjust - remaining as isize - 1, select);
            } else {
                self.edit_point.index = min(self.current_line_length(),
                                            self.edit_point.index + adjust as usize);
            }
        }
    }

    /// Deal with a newline input.
    pub fn handle_return(&mut self) -> KeyReaction {
        if !self.multiline {
            return KeyReaction::TriggerDefaultAction;
        }
        self.insert_char('\n');
        return KeyReaction::DispatchInput;
    }

    /// Select all text in the input control.
    pub fn select_all(&mut self) {
        self.selection_begin = Some(TextPoint {
            line: 0,
            index: 0,
        });
        let last_line = self.lines.len() - 1;
        self.edit_point.line = last_line;
        self.edit_point.index = self.lines[last_line].chars().count();
    }

    /// Remove the current selection.
    pub fn clear_selection(&mut self) {
        self.selection_begin = None;
    }

    /// Process a given `KeyboardEvent` and return an action for the caller to execute.
    pub fn handle_keydown(&mut self, event: JSRef<KeyboardEvent>) -> KeyReaction {
        if let Some(key) = event.get_key() {
            self.handle_keydown_aux(key, event.get_key_modifiers())
        } else {
            KeyReaction::Nothing
        }
    }
    pub fn handle_keydown_aux(&mut self, key: Key, mods: KeyModifiers) -> KeyReaction {
        let maybe_select = if mods.contains(SHIFT) { Selection::Selected } else { Selection::NotSelected };
        match key {
           Key::A if is_control_key(mods) => {
                self.select_all();
                KeyReaction::Nothing
            },
           Key::V if is_control_key(mods) => {
                let contents = self.clipboard_provider.clipboard_contents();
                self.insert_string(contents);
                KeyReaction::DispatchInput
            },
            _ if is_printable_key(key) => {
                self.insert_string(key_value(key, mods));
                KeyReaction::DispatchInput
            }
            Key::Space => {
                self.insert_char(' ');
                KeyReaction::DispatchInput
            }
            Key::Delete => {
                self.delete_char(DeleteDir::Forward);
                KeyReaction::DispatchInput
            }
            Key::Backspace => {
                self.delete_char(DeleteDir::Backward);
                KeyReaction::DispatchInput
            }
            Key::Left => {
                self.adjust_horizontal(-1, maybe_select);
                KeyReaction::Nothing
            }
            Key::Right => {
                self.adjust_horizontal(1, maybe_select);
                KeyReaction::Nothing
            }
            Key::Up => {
                self.adjust_vertical(-1, maybe_select);
                KeyReaction::Nothing
            }
            Key::Down => {
                self.adjust_vertical(1, maybe_select);
                KeyReaction::Nothing
            }
            Key::Enter | Key::KpEnter => self.handle_return(),
            Key::Home => {
                self.edit_point.index = 0;
                KeyReaction::Nothing
            }
            Key::End => {
                self.edit_point.index = self.current_line_length();
                KeyReaction::Nothing
            }
            Key::PageUp => {
                self.adjust_vertical(-28, maybe_select);
                KeyReaction::Nothing
            }
            Key::PageDown => {
                self.adjust_vertical(28, maybe_select);
                KeyReaction::Nothing
            }
            Key::Tab => KeyReaction::TriggerDefaultAction,
            _ => KeyReaction::Nothing,
        }
    }

    /// Get the current contents of the text input. Multiple lines are joined by \n.
    pub fn get_content(&self) -> DOMString {
        let mut content = "".to_owned();
        for (i, line) in self.lines.iter().enumerate() {
            content.push_str(&line);
            if i < self.lines.len() - 1 {
                content.push('\n');
            }
        }
        content
    }

    /// Set the current contents of the text input. If this is control supports multiple lines,
    /// any \n encountered will be stripped and force a new logical line.
    pub fn set_content(&mut self, content: DOMString) {
        self.lines = if self.multiline {
            content.split('\n').map(|s| s.to_owned()).collect()
        } else {
            vec!(content)
        };
        self.edit_point.line = min(self.edit_point.line, self.lines.len() - 1);
        self.edit_point.index = min(self.edit_point.index, self.current_line_length());
    }
}
<|endoftext|>