<commit_before>//! AWS Regions and helper functions.
//!
//! Mostly used for translating the Region enum to a string AWS accepts.
//!
//! For example: `UsEast1` to "us-east-1"

use crate::credential::ProfileProvider;
use serde::ser::SerializeTuple;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use std;
use std::error::Error;
use std::fmt::{self, Display, Error as FmtError, Formatter};
use std::str::FromStr;

/// An AWS region.
///
/// # Default
///
/// `Region` implements the `Default` trait. Calling `Region::default()` will attempt to read the
/// `AWS_DEFAULT_REGION` or `AWS_REGION` environment variable. If it is malformed, it will fall back to `Region::UsEast1`.
/// If it is not present it will fallback on the value associated with the current profile in `~/.aws/config` or the file
/// specified by the `AWS_CONFIG_FILE` environment variable. If that is malformed of absent it will fall back on `Region::UsEast1`
///
/// # AWS-compatible services
///
/// `Region::Custom` can be used to connect to AWS-compatible services such as DynamoDB Local or Ceph.
///
/// ```
///     # use rusoto_core::Region;
///     Region::Custom {
///         name: "eu-east-3".to_owned(),
///         endpoint: "http://localhost:8000".to_owned(),
///     };
/// ```
///
/// # Caveats
///
/// `CnNorth1` is currently untested due to Rusoto maintainers not having access to AWS China.
#[derive(Clone, Debug, PartialEq)]
pub enum Region {
    /// Region that covers the Easter part of Asia Pacific
    ApEast1,

    /// Region that covers the North-Eastern part of Asia Pacific
    ApNortheast1,

    /// Region that covers the North-Eastern part of Asia Pacific
    ApNortheast2,

    /// Region that covers the Southern part of Asia Pacific
    ApSouth1,

    /// Region that covers the South-Eastern part of Asia Pacific
    ApSoutheast1,

    /// Region that covers the South-Eastern part of Asia Pacific
    ApSoutheast2,

    /// Region that covers Canada
    CaCentral1,

    /// Region that covers Central Europe
    EuCentral1,

    /// Region that covers Western Europe
    EuWest1,

    /// Region that covers Western Europe
    EuWest2,

    /// Region that covers Western Europe
    EuWest3,

    /// Region that covers Northern Europe
    EuNorth1,

    /// Region that covers South America
    SaEast1,

    /// Region that covers the Eastern part of the United States
    UsEast1,

    /// Region that covers the Eastern part of the United States
    UsEast2,

    /// Region that covers the Western part of the United States
    UsWest1,

    /// Region that covers the Western part of the United States
    UsWest2,

    /// Region that covers the Eastern part of the United States for the US Government
    UsGovEast1,

    /// Region that covers the Western part of the United States for the US Government
    UsGovWest1,

    /// Region that covers China
    CnNorth1,

    /// Region that covers North-Western  part of China
    CnNorthwest1,

    /// Specifies a custom region, such as a local Ceph target
    Custom {
        /// Name of the endpoint (e.g. `"eu-east-2"`).
        name: String,

        /// Endpoint to be used. For instance, `"https://s3.my-provider.net"` or just
        /// `"s3.my-provider.net"` (default scheme is https).
        endpoint: String,
    },
}

impl Region {
    /// Name of the region
    ///
    /// ```
    ///     # use rusoto_core::Region;
    ///     assert_eq!(Region::CaCentral1.name(), "ca-central-1");
    ///     assert_eq!(
    ///         Region::Custom { name: "eu-east-3".to_owned(), endpoint: "s3.net".to_owned() }.name(),
    ///         "eu-east-3"
    ///     );
    /// ```
    pub fn name(&self) -> &str {
        match *self {
            Region::ApEast1 => "ap-east-1",
            Region::ApNortheast1 => "ap-northeast-1",
            Region::ApNortheast2 => "ap-northeast-2",
            Region::ApSouth1 => "ap-south-1",
            Region::ApSoutheast1 => "ap-southeast-1",
            Region::ApSoutheast2 => "ap-southeast-2",
            Region::CaCentral1 => "ca-central-1",
            Region::EuCentral1 => "eu-central-1",
            Region::EuWest1 => "eu-west-1",
            Region::EuWest2 => "eu-west-2",
            Region::EuWest3 => "eu-west-3",
            Region::EuNorth1 => "eu-north-1",
            Region::SaEast1 => "sa-east-1",
            Region::UsEast1 => "us-east-1",
            Region::UsEast2 => "us-east-2",
            Region::UsWest1 => "us-west-1",
            Region::UsWest2 => "us-west-2",
            Region::UsGovEast1 => "us-gov-east-1",
            Region::UsGovWest1 => "us-gov-west-1",
            Region::CnNorth1 => "cn-north-1",
            Region::CnNorthwest1 => "cn-northwest-1",
            Region::Custom { ref name, .. } => name,
        }
    }
}

/// An error produced when attempting to convert a `str` into a `Region` fails.
#[derive(Debug, PartialEq)]
pub struct ParseRegionError {
    message: String,
}

// Manually created for lack of a way to flatten the `Region::Custom` variant
// Related: https://github.com/serde-rs/serde/issues/119
impl Serialize for Region {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut seq = serializer.serialize_tuple(2)?;
        if let Region::Custom {
            ref endpoint,
            ref name,
        } = *self
        {
            seq.serialize_element(&name)?;
            seq.serialize_element(&Some(&endpoint))?;
        } else {
            seq.serialize_element(self.name())?;
            seq.serialize_element(&None as &Option<&str>)?;
        }
        seq.end()
    }
}

struct RegionVisitor;

impl<'de> de::Visitor<'de> for RegionVisitor {
    type Value = Region;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("sequence of (name, Some(endpoint))")
    }

    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
    where
        A: de::SeqAccess<'de>,
    {
        let name: String = seq
            .next_element()?
            .ok_or_else(|| de::Error::custom("region is missing name"))?;
        let endpoint: Option<String> = seq
            .next_element()?
            .ok_or_else(|| de::Error::custom("region is missing endpoint"))?;
        match (name, endpoint) {
            (name, Some(endpoint)) => Ok(Region::Custom { name, endpoint }),
            (name, None) => name.parse().map_err(de::Error::custom),
        }
    }
}

// Manually created for lack of a way to flatten the `Region::Custom` variant
// Related: https://github.com/serde-rs/serde/issues/119
impl<'de> Deserialize<'de> for Region {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_tuple(2, RegionVisitor)
    }
}

impl FromStr for Region {
    type Err = ParseRegionError;

    fn from_str(s: &str) -> Result<Region, ParseRegionError> {
        let v: &str = &s.to_lowercase();
        match v {
            "ap-east-1" | "apeast1" => Ok(Region::ApEast1),
            "ap-northeast-1" | "apnortheast1" => Ok(Region::ApNortheast1),
            "ap-northeast-2" | "apnortheast2" => Ok(Region::ApNortheast2),
            "ap-south-1" | "apsouth1" => Ok(Region::ApSouth1),
            "ap-southeast-1" | "apsoutheast1" => Ok(Region::ApSoutheast1),
            "ap-southeast-2" | "apsoutheast2" => Ok(Region::ApSoutheast2),
            "ca-central-1" | "cacentral1" => Ok(Region::CaCentral1),
            "eu-central-1" | "eucentral1" => Ok(Region::EuCentral1),
            "eu-west-1" | "euwest1" => Ok(Region::EuWest1),
            "eu-west-2" | "euwest2" => Ok(Region::EuWest2),
            "eu-west-3" | "euwest3" => Ok(Region::EuWest3),
            "eu-north-1" | "eunorth1" => Ok(Region::EuNorth1),
            "sa-east-1" | "saeast1" => Ok(Region::SaEast1),
            "us-east-1" | "useast1" => Ok(Region::UsEast1),
            "us-east-2" | "useast2" => Ok(Region::UsEast2),
            "us-west-1" | "uswest1" => Ok(Region::UsWest1),
            "us-west-2" | "uswest2" => Ok(Region::UsWest2),
            "us-gov-east-1" | "usgoveast1" => Ok(Region::UsGovEast1),
            "us-gov-west-1" | "usgovwest1" => Ok(Region::UsGovWest1),
            "cn-north-1" | "cnnorth1" => Ok(Region::CnNorth1),
            "cn-northwest-1" | "cnnorthwest1" => Ok(Region::CnNorthwest1),
            s => Err(ParseRegionError::new(s)),
        }
    }
}

impl ParseRegionError {
    /// Parses a region given as a string literal into a type `Region'
    pub fn new(input: &str) -> Self {
        ParseRegionError {
            message: format!("Not a valid AWS region: {}", input),
        }
    }
}

impl Error for ParseRegionError {
    fn description(&self) -> &str {
        &self.message
    }
}

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

impl Default for Region {
    fn default() -> Region {
        match std::env::var("AWS_DEFAULT_REGION").or_else(|_| std::env::var("AWS_REGION")) {
            Ok(ref v) => Region::from_str(v).unwrap_or(Region::UsEast1),
            Err(_) => match ProfileProvider::region() {
                Ok(Some(region)) => Region::from_str(&region).unwrap_or(Region::UsEast1),
                _ => Region::UsEast1,
            },
        }
    }
}

#[cfg(test)]
mod tests {
    extern crate serde_test;
    use self::serde_test::{assert_tokens, Token};
    use super::*;

    #[test]
    fn from_str() {
        assert_eq!(
            "foo"
                .parse::<Region>()
                .err()
                .expect("Parsing foo as a Region was not an error")
                .to_string(),
            "Not a valid AWS region: foo".to_owned()
        );
        assert_eq!("ap-east-1".parse(), Ok(Region::ApEast1));
        assert_eq!("ap-northeast-1".parse(), Ok(Region::ApNortheast1));
        assert_eq!("ap-northeast-2".parse(), Ok(Region::ApNortheast2));
        assert_eq!("ap-south-1".parse(), Ok(Region::ApSouth1));
        assert_eq!("ap-southeast-1".parse(), Ok(Region::ApSoutheast1));
        assert_eq!("ap-southeast-2".parse(), Ok(Region::ApSoutheast2));
        assert_eq!("ca-central-1".parse(), Ok(Region::CaCentral1));
        assert_eq!("eu-central-1".parse(), Ok(Region::EuCentral1));
        assert_eq!("eu-west-1".parse(), Ok(Region::EuWest1));
        assert_eq!("eu-west-2".parse(), Ok(Region::EuWest2));
        assert_eq!("eu-west-3".parse(), Ok(Region::EuWest3));
        assert_eq!("eu-north-1".parse(), Ok(Region::EuNorth1));
        assert_eq!("sa-east-1".parse(), Ok(Region::SaEast1));
        assert_eq!("us-east-1".parse(), Ok(Region::UsEast1));
        assert_eq!("us-east-2".parse(), Ok(Region::UsEast2));
        assert_eq!("us-west-1".parse(), Ok(Region::UsWest1));
        assert_eq!("us-west-2".parse(), Ok(Region::UsWest2));
        assert_eq!("us-gov-east-1".parse(), Ok(Region::UsGovEast1));
        assert_eq!("us-gov-west-1".parse(), Ok(Region::UsGovWest1));
        assert_eq!("cn-north-1".parse(), Ok(Region::CnNorth1));
        assert_eq!("cn-northwest-1".parse(), Ok(Region::CnNorthwest1));
    }

    #[test]
    fn region_serialize_deserialize() {
        assert_tokens(&Region::ApEast1, &tokens_for_region("ap-east-1"));
        assert_tokens(&Region::ApNortheast1, &tokens_for_region("ap-northeast-1"));
        assert_tokens(&Region::ApNortheast2, &tokens_for_region("ap-northeast-2"));
        assert_tokens(&Region::ApSouth1, &tokens_for_region("ap-south-1"));
        assert_tokens(&Region::ApSoutheast1, &tokens_for_region("ap-southeast-1"));
        assert_tokens(&Region::ApSoutheast2, &tokens_for_region("ap-southeast-2"));
        assert_tokens(&Region::CaCentral1, &tokens_for_region("ca-central-1"));
        assert_tokens(&Region::EuCentral1, &tokens_for_region("eu-central-1"));
        assert_tokens(&Region::EuWest1, &tokens_for_region("eu-west-1"));
        assert_tokens(&Region::EuWest2, &tokens_for_region("eu-west-2"));
        assert_tokens(&Region::EuWest3, &tokens_for_region("eu-west-3"));
        assert_tokens(&Region::EuNorth1, &tokens_for_region("eu-north-1"));
        assert_tokens(&Region::SaEast1, &tokens_for_region("sa-east-1"));
        assert_tokens(&Region::UsEast1, &tokens_for_region("us-east-1"));
        assert_tokens(&Region::UsEast2, &tokens_for_region("us-east-2"));
        assert_tokens(&Region::UsWest1, &tokens_for_region("us-west-1"));
        assert_tokens(&Region::UsWest2, &tokens_for_region("us-west-2"));
        assert_tokens(&Region::UsGovEast1, &tokens_for_region("us-gov-east-1"));
        assert_tokens(&Region::UsGovWest1, &tokens_for_region("us-gov-west-1"));
        assert_tokens(&Region::CnNorth1, &tokens_for_region("cn-north-1"));
        assert_tokens(&Region::CnNorthwest1, &tokens_for_region("cn-northwest-1"))
    }

    fn tokens_for_region(name: &'static str) -> [Token; 4] {
        [
            Token::Tuple { len: 2 },
            Token::String(name),
            Token::None,
            Token::TupleEnd,
        ]
    }

    #[test]
    fn region_serialize_deserialize_custom() {
        let custom_region = Region::Custom {
            endpoint: "http://localhost:8000".to_owned(),
            name: "eu-east-1".to_owned(),
        };
        assert_tokens(
            &custom_region,
            &[
                Token::Tuple { len: 2 },
                Token::String("eu-east-1"),
                Token::Some,
                Token::String("http://localhost:8000"),
                Token::TupleEnd,
            ],
        );
    }
}
<commit_msg>fix typo (`Easter` -> `Eastern`)<commit_after>//! AWS Regions and helper functions.
//!
//! Mostly used for translating the Region enum to a string AWS accepts.
//!
//! For example: `UsEast1` to "us-east-1"

use crate::credential::ProfileProvider;
use serde::ser::SerializeTuple;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use std;
use std::error::Error;
use std::fmt::{self, Display, Error as FmtError, Formatter};
use std::str::FromStr;

/// An AWS region.
///
/// # Default
///
/// `Region` implements the `Default` trait. Calling `Region::default()` will attempt to read the
/// `AWS_DEFAULT_REGION` or `AWS_REGION` environment variable. If it is malformed, it will fall back to `Region::UsEast1`.
/// If it is not present it will fallback on the value associated with the current profile in `~/.aws/config` or the file
/// specified by the `AWS_CONFIG_FILE` environment variable. If that is malformed of absent it will fall back on `Region::UsEast1`
///
/// # AWS-compatible services
///
/// `Region::Custom` can be used to connect to AWS-compatible services such as DynamoDB Local or Ceph.
///
/// ```
///     # use rusoto_core::Region;
///     Region::Custom {
///         name: "eu-east-3".to_owned(),
///         endpoint: "http://localhost:8000".to_owned(),
///     };
/// ```
///
/// # Caveats
///
/// `CnNorth1` is currently untested due to Rusoto maintainers not having access to AWS China.
#[derive(Clone, Debug, PartialEq)]
pub enum Region {
    /// Region that covers the Eastern part of Asia Pacific
    ApEast1,

    /// Region that covers the North-Eastern part of Asia Pacific
    ApNortheast1,

    /// Region that covers the North-Eastern part of Asia Pacific
    ApNortheast2,

    /// Region that covers the Southern part of Asia Pacific
    ApSouth1,

    /// Region that covers the South-Eastern part of Asia Pacific
    ApSoutheast1,

    /// Region that covers the South-Eastern part of Asia Pacific
    ApSoutheast2,

    /// Region that covers Canada
    CaCentral1,

    /// Region that covers Central Europe
    EuCentral1,

    /// Region that covers Western Europe
    EuWest1,

    /// Region that covers Western Europe
    EuWest2,

    /// Region that covers Western Europe
    EuWest3,

    /// Region that covers Northern Europe
    EuNorth1,

    /// Region that covers South America
    SaEast1,

    /// Region that covers the Eastern part of the United States
    UsEast1,

    /// Region that covers the Eastern part of the United States
    UsEast2,

    /// Region that covers the Western part of the United States
    UsWest1,

    /// Region that covers the Western part of the United States
    UsWest2,

    /// Region that covers the Eastern part of the United States for the US Government
    UsGovEast1,

    /// Region that covers the Western part of the United States for the US Government
    UsGovWest1,

    /// Region that covers China
    CnNorth1,

    /// Region that covers North-Western  part of China
    CnNorthwest1,

    /// Specifies a custom region, such as a local Ceph target
    Custom {
        /// Name of the endpoint (e.g. `"eu-east-2"`).
        name: String,

        /// Endpoint to be used. For instance, `"https://s3.my-provider.net"` or just
        /// `"s3.my-provider.net"` (default scheme is https).
        endpoint: String,
    },
}

impl Region {
    /// Name of the region
    ///
    /// ```
    ///     # use rusoto_core::Region;
    ///     assert_eq!(Region::CaCentral1.name(), "ca-central-1");
    ///     assert_eq!(
    ///         Region::Custom { name: "eu-east-3".to_owned(), endpoint: "s3.net".to_owned() }.name(),
    ///         "eu-east-3"
    ///     );
    /// ```
    pub fn name(&self) -> &str {
        match *self {
            Region::ApEast1 => "ap-east-1",
            Region::ApNortheast1 => "ap-northeast-1",
            Region::ApNortheast2 => "ap-northeast-2",
            Region::ApSouth1 => "ap-south-1",
            Region::ApSoutheast1 => "ap-southeast-1",
            Region::ApSoutheast2 => "ap-southeast-2",
            Region::CaCentral1 => "ca-central-1",
            Region::EuCentral1 => "eu-central-1",
            Region::EuWest1 => "eu-west-1",
            Region::EuWest2 => "eu-west-2",
            Region::EuWest3 => "eu-west-3",
            Region::EuNorth1 => "eu-north-1",
            Region::SaEast1 => "sa-east-1",
            Region::UsEast1 => "us-east-1",
            Region::UsEast2 => "us-east-2",
            Region::UsWest1 => "us-west-1",
            Region::UsWest2 => "us-west-2",
            Region::UsGovEast1 => "us-gov-east-1",
            Region::UsGovWest1 => "us-gov-west-1",
            Region::CnNorth1 => "cn-north-1",
            Region::CnNorthwest1 => "cn-northwest-1",
            Region::Custom { ref name, .. } => name,
        }
    }
}

/// An error produced when attempting to convert a `str` into a `Region` fails.
#[derive(Debug, PartialEq)]
pub struct ParseRegionError {
    message: String,
}

// Manually created for lack of a way to flatten the `Region::Custom` variant
// Related: https://github.com/serde-rs/serde/issues/119
impl Serialize for Region {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut seq = serializer.serialize_tuple(2)?;
        if let Region::Custom {
            ref endpoint,
            ref name,
        } = *self
        {
            seq.serialize_element(&name)?;
            seq.serialize_element(&Some(&endpoint))?;
        } else {
            seq.serialize_element(self.name())?;
            seq.serialize_element(&None as &Option<&str>)?;
        }
        seq.end()
    }
}

struct RegionVisitor;

impl<'de> de::Visitor<'de> for RegionVisitor {
    type Value = Region;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("sequence of (name, Some(endpoint))")
    }

    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
    where
        A: de::SeqAccess<'de>,
    {
        let name: String = seq
            .next_element()?
            .ok_or_else(|| de::Error::custom("region is missing name"))?;
        let endpoint: Option<String> = seq
            .next_element()?
            .ok_or_else(|| de::Error::custom("region is missing endpoint"))?;
        match (name, endpoint) {
            (name, Some(endpoint)) => Ok(Region::Custom { name, endpoint }),
            (name, None) => name.parse().map_err(de::Error::custom),
        }
    }
}

// Manually created for lack of a way to flatten the `Region::Custom` variant
// Related: https://github.com/serde-rs/serde/issues/119
impl<'de> Deserialize<'de> for Region {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_tuple(2, RegionVisitor)
    }
}

impl FromStr for Region {
    type Err = ParseRegionError;

    fn from_str(s: &str) -> Result<Region, ParseRegionError> {
        let v: &str = &s.to_lowercase();
        match v {
            "ap-east-1" | "apeast1" => Ok(Region::ApEast1),
            "ap-northeast-1" | "apnortheast1" => Ok(Region::ApNortheast1),
            "ap-northeast-2" | "apnortheast2" => Ok(Region::ApNortheast2),
            "ap-south-1" | "apsouth1" => Ok(Region::ApSouth1),
            "ap-southeast-1" | "apsoutheast1" => Ok(Region::ApSoutheast1),
            "ap-southeast-2" | "apsoutheast2" => Ok(Region::ApSoutheast2),
            "ca-central-1" | "cacentral1" => Ok(Region::CaCentral1),
            "eu-central-1" | "eucentral1" => Ok(Region::EuCentral1),
            "eu-west-1" | "euwest1" => Ok(Region::EuWest1),
            "eu-west-2" | "euwest2" => Ok(Region::EuWest2),
            "eu-west-3" | "euwest3" => Ok(Region::EuWest3),
            "eu-north-1" | "eunorth1" => Ok(Region::EuNorth1),
            "sa-east-1" | "saeast1" => Ok(Region::SaEast1),
            "us-east-1" | "useast1" => Ok(Region::UsEast1),
            "us-east-2" | "useast2" => Ok(Region::UsEast2),
            "us-west-1" | "uswest1" => Ok(Region::UsWest1),
            "us-west-2" | "uswest2" => Ok(Region::UsWest2),
            "us-gov-east-1" | "usgoveast1" => Ok(Region::UsGovEast1),
            "us-gov-west-1" | "usgovwest1" => Ok(Region::UsGovWest1),
            "cn-north-1" | "cnnorth1" => Ok(Region::CnNorth1),
            "cn-northwest-1" | "cnnorthwest1" => Ok(Region::CnNorthwest1),
            s => Err(ParseRegionError::new(s)),
        }
    }
}

impl ParseRegionError {
    /// Parses a region given as a string literal into a type `Region'
    pub fn new(input: &str) -> Self {
        ParseRegionError {
            message: format!("Not a valid AWS region: {}", input),
        }
    }
}

impl Error for ParseRegionError {
    fn description(&self) -> &str {
        &self.message
    }
}

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

impl Default for Region {
    fn default() -> Region {
        match std::env::var("AWS_DEFAULT_REGION").or_else(|_| std::env::var("AWS_REGION")) {
            Ok(ref v) => Region::from_str(v).unwrap_or(Region::UsEast1),
            Err(_) => match ProfileProvider::region() {
                Ok(Some(region)) => Region::from_str(&region).unwrap_or(Region::UsEast1),
                _ => Region::UsEast1,
            },
        }
    }
}

#[cfg(test)]
mod tests {
    extern crate serde_test;
    use self::serde_test::{assert_tokens, Token};
    use super::*;

    #[test]
    fn from_str() {
        assert_eq!(
            "foo"
                .parse::<Region>()
                .err()
                .expect("Parsing foo as a Region was not an error")
                .to_string(),
            "Not a valid AWS region: foo".to_owned()
        );
        assert_eq!("ap-east-1".parse(), Ok(Region::ApEast1));
        assert_eq!("ap-northeast-1".parse(), Ok(Region::ApNortheast1));
        assert_eq!("ap-northeast-2".parse(), Ok(Region::ApNortheast2));
        assert_eq!("ap-south-1".parse(), Ok(Region::ApSouth1));
        assert_eq!("ap-southeast-1".parse(), Ok(Region::ApSoutheast1));
        assert_eq!("ap-southeast-2".parse(), Ok(Region::ApSoutheast2));
        assert_eq!("ca-central-1".parse(), Ok(Region::CaCentral1));
        assert_eq!("eu-central-1".parse(), Ok(Region::EuCentral1));
        assert_eq!("eu-west-1".parse(), Ok(Region::EuWest1));
        assert_eq!("eu-west-2".parse(), Ok(Region::EuWest2));
        assert_eq!("eu-west-3".parse(), Ok(Region::EuWest3));
        assert_eq!("eu-north-1".parse(), Ok(Region::EuNorth1));
        assert_eq!("sa-east-1".parse(), Ok(Region::SaEast1));
        assert_eq!("us-east-1".parse(), Ok(Region::UsEast1));
        assert_eq!("us-east-2".parse(), Ok(Region::UsEast2));
        assert_eq!("us-west-1".parse(), Ok(Region::UsWest1));
        assert_eq!("us-west-2".parse(), Ok(Region::UsWest2));
        assert_eq!("us-gov-east-1".parse(), Ok(Region::UsGovEast1));
        assert_eq!("us-gov-west-1".parse(), Ok(Region::UsGovWest1));
        assert_eq!("cn-north-1".parse(), Ok(Region::CnNorth1));
        assert_eq!("cn-northwest-1".parse(), Ok(Region::CnNorthwest1));
    }

    #[test]
    fn region_serialize_deserialize() {
        assert_tokens(&Region::ApEast1, &tokens_for_region("ap-east-1"));
        assert_tokens(&Region::ApNortheast1, &tokens_for_region("ap-northeast-1"));
        assert_tokens(&Region::ApNortheast2, &tokens_for_region("ap-northeast-2"));
        assert_tokens(&Region::ApSouth1, &tokens_for_region("ap-south-1"));
        assert_tokens(&Region::ApSoutheast1, &tokens_for_region("ap-southeast-1"));
        assert_tokens(&Region::ApSoutheast2, &tokens_for_region("ap-southeast-2"));
        assert_tokens(&Region::CaCentral1, &tokens_for_region("ca-central-1"));
        assert_tokens(&Region::EuCentral1, &tokens_for_region("eu-central-1"));
        assert_tokens(&Region::EuWest1, &tokens_for_region("eu-west-1"));
        assert_tokens(&Region::EuWest2, &tokens_for_region("eu-west-2"));
        assert_tokens(&Region::EuWest3, &tokens_for_region("eu-west-3"));
        assert_tokens(&Region::EuNorth1, &tokens_for_region("eu-north-1"));
        assert_tokens(&Region::SaEast1, &tokens_for_region("sa-east-1"));
        assert_tokens(&Region::UsEast1, &tokens_for_region("us-east-1"));
        assert_tokens(&Region::UsEast2, &tokens_for_region("us-east-2"));
        assert_tokens(&Region::UsWest1, &tokens_for_region("us-west-1"));
        assert_tokens(&Region::UsWest2, &tokens_for_region("us-west-2"));
        assert_tokens(&Region::UsGovEast1, &tokens_for_region("us-gov-east-1"));
        assert_tokens(&Region::UsGovWest1, &tokens_for_region("us-gov-west-1"));
        assert_tokens(&Region::CnNorth1, &tokens_for_region("cn-north-1"));
        assert_tokens(&Region::CnNorthwest1, &tokens_for_region("cn-northwest-1"))
    }

    fn tokens_for_region(name: &'static str) -> [Token; 4] {
        [
            Token::Tuple { len: 2 },
            Token::String(name),
            Token::None,
            Token::TupleEnd,
        ]
    }

    #[test]
    fn region_serialize_deserialize_custom() {
        let custom_region = Region::Custom {
            endpoint: "http://localhost:8000".to_owned(),
            name: "eu-east-1".to_owned(),
        };
        assert_tokens(
            &custom_region,
            &[
                Token::Tuple { len: 2 },
                Token::String("eu-east-1"),
                Token::Some,
                Token::String("http://localhost:8000"),
                Token::TupleEnd,
            ],
        );
    }
}
<|endoftext|>


<commit_before>use chromiumoxide::{cdp::browser_protocol::page::CaptureScreenshotFormat, Browser, BrowserConfig};
use codec::{
    async_trait::async_trait,
    eyre::Result,
    stencila_schema::{ImageObject, Node},
    utils::vec_string,
    Codec, CodecTrait, DecodeOptions, EncodeOptions,
};
use codec_html::HtmlCodec;
use futures::StreamExt;
use std::{fs, path::Path};

/// Encode and decode a document node to a PNG image.
///
/// This codec will "decode" strings or files to an `ImageObject` and uses a
/// headless browser to encode nodes as a screenshot of the HTML
/// encoding of the node.
pub struct PngCodec {}

#[async_trait]
impl CodecTrait for PngCodec {
    fn spec() -> Codec {
        Codec {
            status: "alpha".to_string(),
            formats: vec_string!["png"],
            root_types: vec_string!["*"],
            ..Default::default()
        }
    }

    /// Decode a document node from a string
    ///
    /// Simply returns an `ImageObject` with the string as its `content_url`
    /// (i.e assumes that the string is a DataURI with a Base64 encoded PNG)
    fn from_str(str: &str, _options: Option<DecodeOptions>) -> Result<Node> {
        Ok(Node::ImageObject(ImageObject {
            content_url: str.to_string(),
            ..Default::default()
        }))
    }

    /// Decode a document node from a file system path
    ///
    /// Simply returns an `ImageObject` with the path as its `content_url`
    async fn from_path(path: &Path, _options: Option<DecodeOptions>) -> Result<Node> {
        Ok(Node::ImageObject(ImageObject {
            content_url: path.display().to_string(),
            ..Default::default()
        }))
    }

    /// Encode a document node to a string
    ///
    /// Returns a Base64 encoded dataURI with media type `image/png`.
    async fn to_string_async(node: &Node, options: Option<EncodeOptions>) -> Result<String> {
        let bytes = nodes_to_bytes(&[node], options).await?;
        let string = ["data:image/png;base64,", &base64::encode(&bytes[0])].concat();
        Ok(string)
    }

    /// Encode a document node to a file system path
    ///
    /// This override is necessary to avoid the dataURI prefix and Base64 encoding that `to_string_async`
    /// does. It simply writes that bytes to a file at the path.
    async fn to_path(node: &Node, path: &Path, options: Option<EncodeOptions>) -> Result<()> {
        let bytes = nodes_to_bytes(&[node], options).await?;
        fs::write(path, &bytes[0])?;
        Ok(())
    }
}

/// Encode a set of document nodes to PNGs as bytes
///
/// This function is based around creating multiple PNGs, rather than a single one, to
/// reduce the per-image overhead of starting the browser, loading the theme etc.
pub async fn nodes_to_bytes(
    nodes: &[&Node],
    options: Option<EncodeOptions>,
) -> Result<Vec<Vec<u8>>> {
    // Return early if possible to avoid the following, including requiring Chrome.
    if nodes.is_empty() {
        return Ok(Vec::new());
    }

    // Generate HTML for each node
    let mut html = String::new();
    for (index, node) in nodes.iter().enumerate() {
        let node_html = HtmlCodec::to_string(
            node,
            Some(EncodeOptions {
                standalone: false,
                bundle: true,
                ..Default::default()
            }),
        )?;
        html.push_str(&format!(r#"
{}
"#, index, node_html));
    }

    // Wrap the HTML with a header etc so that the theme is set and CSS is loaded
    let EncodeOptions { theme, .. } = options.unwrap_or_default();
    let html = codec_html::wrap_standalone("PNG", &theme, &html);

    // Launch the browser
    let chrome = binaries::require_any(&[("chrome", "*"), ("chromium", "*")]).await?;
    let config = BrowserConfig::builder()
        .chrome_executable(chrome.path)
        .build()
        .expect("Should build config");
    let (browser, mut handler) = Browser::launch(config).await?;
    tokio::task::spawn(async move {
        loop {
            let _ = handler.next().await.unwrap();
        }
    });

    // Create a page and set its HTML
    let page = browser.new_page("about:blank").await?;
    page.set_content(html).await?.wait_for_navigation().await?;

    // Take a screenshot of each element
    let mut pngs = Vec::with_capacity(nodes.len());
    for index in 0..nodes.len() {
        let element = page.find_element(&format!("#node-{}", index)).await?;
        let bytes = element.screenshot(CaptureScreenshotFormat::Png).await?;
        pngs.push(bytes)
    }

    Ok(pngs)
}

#[cfg(test)]
mod tests {
    use super::*;
    use codec::stencila_schema::CodeChunk;
    use test_utils::tempfile;

    #[tokio::test]
    async fn encode() -> super::Result<()> {
        let node = Node::CodeChunk(CodeChunk {
            programming_language: "python".to_string(),
            text: "print(\"Hello world!\")".to_string(),
            outputs: Some(vec![Node::String("Hello world!".to_string())]),
            ..Default::default()
        });

        let dir = tempfile::tempdir()?;
        let path = dir.path().join("temp.png");
        PngCodec::to_path(
            &node,
            &path,
            Some(EncodeOptions {
                theme: "rpng".to_string(),
                ..Default::default()
            }),
        )
        .await?;
        assert!(path.exists());

        let data = PngCodec::to_string_async(&node, None).await?;
        assert!(data.starts_with("data:image/png;base64,"));

        Ok(())
    }
}
<commit_msg>fix(PNG codec): Abort handler task before dropping browser<commit_after>use chromiumoxide::{cdp::browser_protocol::page::CaptureScreenshotFormat, Browser, BrowserConfig};
use codec::{
    async_trait::async_trait,
    eyre::Result,
    stencila_schema::{ImageObject, Node},
    utils::vec_string,
    Codec, CodecTrait, DecodeOptions, EncodeOptions,
};
use codec_html::HtmlCodec;
use futures::StreamExt;
use std::{fs, path::Path};

/// Encode and decode a document node to a PNG image.
///
/// This codec will "decode" strings or files to an `ImageObject` and uses a
/// headless browser to encode nodes as a screenshot of the HTML
/// encoding of the node.
pub struct PngCodec {}

#[async_trait]
impl CodecTrait for PngCodec {
    fn spec() -> Codec {
        Codec {
            status: "alpha".to_string(),
            formats: vec_string!["png"],
            root_types: vec_string!["*"],
            ..Default::default()
        }
    }

    /// Decode a document node from a string
    ///
    /// Simply returns an `ImageObject` with the string as its `content_url`
    /// (i.e assumes that the string is a DataURI with a Base64 encoded PNG)
    fn from_str(str: &str, _options: Option<DecodeOptions>) -> Result<Node> {
        Ok(Node::ImageObject(ImageObject {
            content_url: str.to_string(),
            ..Default::default()
        }))
    }

    /// Decode a document node from a file system path
    ///
    /// Simply returns an `ImageObject` with the path as its `content_url`
    async fn from_path(path: &Path, _options: Option<DecodeOptions>) -> Result<Node> {
        Ok(Node::ImageObject(ImageObject {
            content_url: path.display().to_string(),
            ..Default::default()
        }))
    }

    /// Encode a document node to a string
    ///
    /// Returns a Base64 encoded dataURI with media type `image/png`.
    async fn to_string_async(node: &Node, options: Option<EncodeOptions>) -> Result<String> {
        let bytes = nodes_to_bytes(&[node], options).await?;
        let string = ["data:image/png;base64,", &base64::encode(&bytes[0])].concat();
        Ok(string)
    }

    /// Encode a document node to a file system path
    ///
    /// This override is necessary to avoid the dataURI prefix and Base64 encoding that `to_string_async`
    /// does. It simply writes that bytes to a file at the path.
    async fn to_path(node: &Node, path: &Path, options: Option<EncodeOptions>) -> Result<()> {
        let bytes = nodes_to_bytes(&[node], options).await?;
        fs::write(path, &bytes[0])?;
        Ok(())
    }
}

/// Encode a set of document nodes to PNGs as bytes
///
/// This function is based around creating multiple PNGs, rather than a single one, to
/// reduce the per-image overhead of starting the browser, loading the theme etc.
pub async fn nodes_to_bytes(
    nodes: &[&Node],
    options: Option<EncodeOptions>,
) -> Result<Vec<Vec<u8>>> {
    // Return early if possible to avoid the following, including requiring Chrome.
    if nodes.is_empty() {
        return Ok(Vec::new());
    }

    // Generate HTML for each node
    let mut html = String::new();
    for (index, node) in nodes.iter().enumerate() {
        let node_html = HtmlCodec::to_string(
            node,
            Some(EncodeOptions {
                standalone: false,
                bundle: true,
                ..Default::default()
            }),
        )?;
        html.push_str(&format!(r#"
{}
"#, index, node_html));
    }

    // Wrap the HTML with a header etc so that the theme is set and CSS is loaded
    let EncodeOptions { theme, .. } = options.unwrap_or_default();
    let html = codec_html::wrap_standalone("PNG", &theme, &html);

    // Launch the browser
    let chrome = binaries::require_any(&[("chrome", "*"), ("chromium", "*")]).await?;
    let config = BrowserConfig::builder()
        .chrome_executable(chrome.path)
        .build()
        .expect("Should build config");
    let (browser, mut handler) = Browser::launch(config).await?;
    let handler_task = tokio::task::spawn(async move {
        loop {
            let _ = handler.next().await.unwrap();
        }
    });

    // Create a page and set its HTML
    let page = browser.new_page("about:blank").await?;
    page.set_content(html).await?.wait_for_navigation().await?;

    // Take a screenshot of each element
    let mut pngs = Vec::with_capacity(nodes.len());
    for index in 0..nodes.len() {
        let element = page.find_element(&format!("#node-{}", index)).await?;
        let bytes = element.screenshot(CaptureScreenshotFormat::Png).await?;
        pngs.push(bytes)
    }

    // Abort the handler task (if this is not done can get a `ResetWithoutClosingHandshake`
    // when this function ends
    handler_task.abort();

    Ok(pngs)
}

#[cfg(test)]
mod tests {
    use super::*;
    use codec::stencila_schema::CodeChunk;
    use test_utils::tempfile;

    #[tokio::test]
    async fn encode() -> super::Result<()> {
        let node = Node::CodeChunk(CodeChunk {
            programming_language: "python".to_string(),
            text: "print(\"Hello world!\")".to_string(),
            outputs: Some(vec![Node::String("Hello world!".to_string())]),
            ..Default::default()
        });

        let dir = tempfile::tempdir()?;
        let path = dir.path().join("temp.png");
        PngCodec::to_path(
            &node,
            &path,
            Some(EncodeOptions {
                theme: "rpng".to_string(),
                ..Default::default()
            }),
        )
        .await?;
        assert!(path.exists());

        let data = PngCodec::to_string_async(&node, None).await?;
        assert!(data.starts_with("data:image/png;base64,"));

        Ok(())
    }
}
<|endoftext|>


<commit_before>// Copyright 2018 The Grin Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! High level JSON/HTTP client API

use failure::{Fail, ResultExt};
use http::uri::{InvalidUri, Uri};
use hyper::header::{ACCEPT, USER_AGENT};
use hyper::rt::{Future, Stream};
use hyper::{Body, Client, Request};
use serde::{Deserialize, Serialize};
use serde_json;

use futures::future::{err, ok, Either};
use tokio_core::reactor::Core;

use rest::{Error, ErrorKind};

/// Helper function to easily issue a HTTP GET request against a given URL that
/// returns a JSON object. Handles request building, JSON deserialization and
/// response code checking.
pub fn get<'a, T>(url: &'a str) -> Result<T, Error>
where
	for<'de> T: Deserialize<'de>,
{
	let uri = url.parse::<Uri>().map_err::<Error, _>(|e: InvalidUri| {
		e.context(ErrorKind::Argument(format!("Invalid url {}", url)))
			.into()
	})?;
	let req = Request::builder()
		.method("GET")
		.uri(uri)
		.header(USER_AGENT, "grin-client")
		.header(ACCEPT, "application/json")
		.body(Body::empty())
		.map_err(|_e| ErrorKind::RequestError("Bad request".to_owned()))?;

	handle_request(req)
}

/// Helper function to easily issue a HTTP POST request with the provided JSON
/// object as body on a given URL that returns a JSON object. Handles request
/// building, JSON serialization and deserialization, and response code
/// checking.
pub fn post<IN, OUT>(url: &str, input: &IN) -> Result<OUT, Error>
where
	IN: Serialize,
	for<'de> OUT: Deserialize<'de>,
{
	let req = create_post_request(url, input)?;
	handle_request(req)
}

/// Helper function to easily issue a HTTP POST request with the provided JSON
/// object as body on a given URL that returns nothing. Handles request
/// building, JSON serialization, and response code
/// checking.
pub fn post_no_ret<IN>(url: &str, input: &IN) -> Result<(), Error>
where
	IN: Serialize,
{
	let req = create_post_request(url, input)?;
	send_request(req)?;
	Ok(())
}

fn create_post_request<IN>(url: &str, input: &IN) -> Result<Request<Body>, Error>
where
	IN: Serialize,
{
	let json = serde_json::to_string(input).context(ErrorKind::Internal(
		"Could not serialize data to JSON".to_owned(),
	))?;
	let uri = url.parse::<Uri>().map_err::<Error, _>(|e: InvalidUri| {
		e.context(ErrorKind::Argument(format!("Invalid url {}", url)))
			.into()
	})?;
	Request::builder()
		.method("POST")
		.uri(uri)
		.header(USER_AGENT, "grin-client")
		.header(ACCEPT, "application/json")
		.body(json.into())
		.map_err::<Error, _>(|e| {
			e.context(ErrorKind::RequestError("Bad request".to_owned()))
				.into()
		})
}

fn handle_request<T>(req: Request<Body>) -> Result<T, Error>
where
	for<'de> T: Deserialize<'de>,
{
	let data = send_request(req)?;
	serde_json::from_str(&data).map_err(|e| {
		e.context(ErrorKind::ResponseError("Cannot parse response".to_owned()))
			.into()
	})
}

fn send_request(req: Request<Body>) -> Result<String, Error> {
	let mut event_loop = Core::new().unwrap();
	let client = Client::new();

	let task = client
		.request(req)
		.map_err(|_e| ErrorKind::RequestError("Cannot make request".to_owned()))
		.and_then(|resp| {
			if !resp.status().is_success() {
				Either::A(err(ErrorKind::RequestError(
					"Wrong response code".to_owned(),
				)))
			} else {
				Either::B(
					resp.into_body()
						.map_err(|_e| {
							ErrorKind::RequestError("Cannot read response body".to_owned())
						})
						.concat2()
						.and_then(|ch| ok(String::from_utf8_lossy(&ch.to_vec()).to_string())),
				)
			}
		});

	Ok(event_loop.run(task)?)
}
<commit_msg>Fix wallet owner_api on new Hyper (#1312)<commit_after>// Copyright 2018 The Grin Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! High level JSON/HTTP client API

use failure::{Fail, ResultExt};
use http::uri::{InvalidUri, Uri};
use hyper::header::{ACCEPT, USER_AGENT};
use hyper::rt::{Future, Stream};
use hyper::{Body, Client, Request};
use serde::{Deserialize, Serialize};
use serde_json;

use futures::future::{err, ok, Either};
use tokio::runtime::Runtime;

use rest::{Error, ErrorKind};

pub type ClientResponseFuture<T> = Box<Future<Item = T, Error = Error> + Send>;

/// Helper function to easily issue a HTTP GET request against a given URL that
/// returns a JSON object. Handles request building, JSON deserialization and
/// response code checking.
pub fn get<'a, T>(url: &'a str) -> Result<T, Error>
where
	for<'de> T: Deserialize<'de>,
{
	handle_request(build_request(url, "GET", None)?)
}

/// Helper function to easily issue an async HTTP GET request against a given
/// URL that returns a future. Handles request building, JSON deserialization
/// and response code checking.
pub fn get_async<'a, T>(url: &'a str) -> ClientResponseFuture<T>
where
	for<'de> T: Deserialize<'de> + Send + 'static,
{
	match build_request(url, "GET", None) {
		Ok(req) => Box::new(handle_request_async(req)),
		Err(e) => Box::new(err(e)),
	}
}

/// Helper function to easily issue a HTTP POST request with the provided JSON
/// object as body on a given URL that returns a JSON object. Handles request
/// building, JSON serialization and deserialization, and response code
/// checking.
pub fn post<IN, OUT>(url: &str, input: &IN) -> Result<OUT, Error>
where
	IN: Serialize,
	for<'de> OUT: Deserialize<'de>,
{
	let req = create_post_request(url, input)?;
	handle_request(req)
}

/// Helper function to easily issue an async HTTP POST request with the
/// provided JSON object as body on a given URL that returns a future. Handles
/// request building, JSON serialization and deserialization, and response code
/// checking.
pub fn post_async<IN, OUT>(url: &str, input: &IN) -> ClientResponseFuture<OUT>
where
	IN: Serialize,
	OUT: Send + 'static,
	for<'de> OUT: Deserialize<'de>,
{
	match create_post_request(url, input) {
		Ok(req) => Box::new(handle_request_async(req)),
		Err(e) => Box::new(err(e)),
	}
}

/// Helper function to easily issue a HTTP POST request with the provided JSON
/// object as body on a given URL that returns nothing. Handles request
/// building, JSON serialization, and response code
/// checking.
pub fn post_no_ret<IN>(url: &str, input: &IN) -> Result<(), Error>
where
	IN: Serialize,
{
	let req = create_post_request(url, input)?;
	send_request(req)?;
	Ok(())
}

/// Helper function to easily issue an async HTTP POST request with the
/// provided JSON object as body on a given URL that returns a future. Handles
/// request building, JSON serialization and deserialization, and response code
/// checking.
pub fn post_no_ret_async<IN>(url: &str, input: &IN) -> ClientResponseFuture<()>
where
	IN: Serialize,
{
	match create_post_request(url, input) {
		Ok(req) => Box::new(send_request_async(req).and_then(|_| ok(()))),
		Err(e) => Box::new(err(e)),
	}
}

fn build_request<'a>(
	url: &'a str,
	method: &str,
	body: Option<String>,
) -> Result<Request<Body>, Error> {
	let uri = url.parse::<Uri>().map_err::<Error, _>(|e: InvalidUri| {
		e.context(ErrorKind::Argument(format!("Invalid url {}", url)))
			.into()
	})?;
	Request::builder()
		.method(method)
		.uri(uri)
		.header(USER_AGENT, "grin-client")
		.header(ACCEPT, "application/json")
		.body(match body {
			None => Body::empty(),
			Some(json) => json.into(),
		})
		.map_err(|e| {
			ErrorKind::RequestError(format!("Bad request {} {}: {}", method, url, e)).into()
		})
}

fn create_post_request<IN>(url: &str, input: &IN) -> Result<Request<Body>, Error>
where
	IN: Serialize,
{
	let json = serde_json::to_string(input).context(ErrorKind::Internal(
		"Could not serialize data to JSON".to_owned(),
	))?;
	build_request(url, "POST", Some(json))
}

fn handle_request<T>(req: Request<Body>) -> Result<T, Error>
where
	for<'de> T: Deserialize<'de>,
{
	let data = send_request(req)?;
	serde_json::from_str(&data).map_err(|e| {
		e.context(ErrorKind::ResponseError("Cannot parse response".to_owned()))
			.into()
	})
}

fn handle_request_async<T>(req: Request<Body>) -> ClientResponseFuture<T>
where
	for<'de> T: Deserialize<'de> + Send + 'static,
{
	Box::new(send_request_async(req).and_then(|data| {
		serde_json::from_str(&data).map_err(|e| {
			e.context(ErrorKind::ResponseError("Cannot parse response".to_owned()))
				.into()
		})
	}))
}

fn send_request_async(req: Request<Body>) -> Box<Future<Item = String, Error = Error> + Send> {
	let client = Client::new();
	Box::new(
		client
			.request(req)
			.map_err(|e| ErrorKind::RequestError(format!("Cannot make request: {}", e)).into())
			.and_then(|resp| {
				if !resp.status().is_success() {
					Either::A(err(ErrorKind::RequestError(
						"Wrong response code".to_owned(),
					).into()))
				} else {
					Either::B(
						resp.into_body()
							.map_err(|e| {
								ErrorKind::RequestError(format!("Cannot read response body: {}", e))
									.into()
							})
							.concat2()
							.and_then(|ch| ok(String::from_utf8_lossy(&ch.to_vec()).to_string())),
					)
				}
			}),
	)
}

fn send_request(req: Request<Body>) -> Result<String, Error> {
	let task = send_request_async(req);
	let mut rt = Runtime::new().unwrap();
	Ok(rt.block_on(task)?)
}
<|endoftext|>


<commit_before>// Copyright 2018 The Grin Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! High level JSON/HTTP client API

use crate::rest::{Error, ErrorKind};
use crate::util::to_base64;
use failure::{Fail, ResultExt};
use futures::future::{err, ok, Either};
use http::uri::{InvalidUri, Uri};
use hyper::header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE, USER_AGENT};
use hyper::rt::{Future, Stream};
use hyper::{Body, Client, Request};
use hyper_rustls;
use serde::{Deserialize, Serialize};
use serde_json;
use tokio::runtime::Runtime;

pub type ClientResponseFuture<T> = Box<dyn Future<Item = T, Error = Error> + Send>;

/// Helper function to easily issue a HTTP GET request against a given URL that
/// returns a JSON object. Handles request building, JSON deserialization and
/// response code checking.
pub fn get<'a, T>(url: &'a str, api_secret: Option<String>) -> Result<T, Error>
where
	for<'de> T: Deserialize<'de>,
{
	handle_request(build_request(url, "GET", api_secret, None)?)
}

/// Helper function to easily issue an async HTTP GET request against a given
/// URL that returns a future. Handles request building, JSON deserialization
/// and response code checking.
pub fn get_async<'a, T>(url: &'a str, api_secret: Option<String>) -> ClientResponseFuture<T>
where
	for<'de> T: Deserialize<'de> + Send + 'static,
{
	match build_request(url, "GET", api_secret, None) {
		Ok(req) => Box::new(handle_request_async(req)),
		Err(e) => Box::new(err(e)),
	}
}

/// Helper function to easily issue a HTTP GET request
/// on a given URL that returns nothing. Handles request
/// building and response code checking.
pub fn get_no_ret(url: &str, api_secret: Option<String>) -> Result<(), Error> {
	let req = build_request(url, "GET", api_secret, None)?;
	send_request(req)?;
	Ok(())
}

/// Helper function to easily issue a HTTP POST request with the provided JSON
/// object as body on a given URL that returns a JSON object. Handles request
/// building, JSON serialization and deserialization, and response code
/// checking.
pub fn post<IN, OUT>(url: &str, api_secret: Option<String>, input: &IN) -> Result<OUT, Error>
where
	IN: Serialize,
	for<'de> OUT: Deserialize<'de>,
{
	let req = create_post_request(url, api_secret, input)?;
	handle_request(req)
}

/// Helper function to easily issue an async HTTP POST request with the
/// provided JSON object as body on a given URL that returns a future. Handles
/// request building, JSON serialization and deserialization, and response code
/// checking.
pub fn post_async<IN, OUT>(
	url: &str,
	input: &IN,
	api_secret: Option<String>,
) -> ClientResponseFuture<OUT>
where
	IN: Serialize,
	OUT: Send + 'static,
	for<'de> OUT: Deserialize<'de>,
{
	match create_post_request(url, api_secret, input) {
		Ok(req) => Box::new(handle_request_async(req)),
		Err(e) => Box::new(err(e)),
	}
}

/// Helper function to easily issue a HTTP POST request with the provided JSON
/// object as body on a given URL that returns nothing. Handles request
/// building, JSON serialization, and response code
/// checking.
pub fn post_no_ret<IN>(url: &str, api_secret: Option<String>, input: &IN) -> Result<(), Error>
where
	IN: Serialize,
{
	let req = create_post_request(url, api_secret, input)?;
	send_request(req)?;
	Ok(())
}

/// Helper function to easily issue an async HTTP POST request with the
/// provided JSON object as body on a given URL that returns a future. Handles
/// request building, JSON serialization and deserialization, and response code
/// checking.
pub fn post_no_ret_async<IN>(
	url: &str,
	api_secret: Option<String>,
	input: &IN,
) -> ClientResponseFuture<()>
where
	IN: Serialize,
{
	match create_post_request(url, api_secret, input) {
		Ok(req) => Box::new(send_request_async(req).and_then(|_| ok(()))),
		Err(e) => Box::new(err(e)),
	}
}

fn build_request<'a>(
	url: &'a str,
	method: &str,
	api_secret: Option<String>,
	body: Option<String>,
) -> Result<Request<Body>, Error> {
	let uri = url.parse::<Uri>().map_err::<Error, _>(|e: InvalidUri| {
		e.context(ErrorKind::Argument(format!("Invalid url {}", url)))
			.into()
	})?;
	let mut builder = Request::builder();
	if let Some(api_secret) = api_secret {
		let basic_auth = format!("Basic {}", to_base64(&format!("grin:{}", api_secret)));
		builder.header(AUTHORIZATION, basic_auth);
	}

	builder
		.method(method)
		.uri(uri)
		.header(USER_AGENT, "grin-client")
		.header(ACCEPT, "application/json")
		.header(CONTENT_TYPE, "application/json")
		.body(match body {
			None => Body::empty(),
			Some(json) => json.into(),
		})
		.map_err(|e| {
			ErrorKind::RequestError(format!("Bad request {} {}: {}", method, url, e)).into()
		})
}

fn create_post_request<IN>(
	url: &str,
	api_secret: Option<String>,
	input: &IN,
) -> Result<Request<Body>, Error>
where
	IN: Serialize,
{
	let json = serde_json::to_string(input).context(ErrorKind::Internal(
		"Could not serialize data to JSON".to_owned(),
	))?;
	build_request(url, "POST", api_secret, Some(json))
}

fn handle_request<T>(req: Request<Body>) -> Result<T, Error>
where
	for<'de> T: Deserialize<'de>,
{
	let data = send_request(req)?;
	serde_json::from_str(&data).map_err(|e| {
		e.context(ErrorKind::ResponseError("Cannot parse response".to_owned()))
			.into()
	})
}

fn handle_request_async<T>(req: Request<Body>) -> ClientResponseFuture<T>
where
	for<'de> T: Deserialize<'de> + Send + 'static,
{
	Box::new(send_request_async(req).and_then(|data| {
		serde_json::from_str(&data).map_err(|e| {
			e.context(ErrorKind::ResponseError("Cannot parse response".to_owned()))
				.into()
		})
	}))
}

fn send_request_async(req: Request<Body>) -> Box<dyn Future<Item = String, Error = Error> + Send> {
	let https = hyper_rustls::HttpsConnector::new(1);
	let client = Client::builder().build::<_, Body>(https);
	Box::new(
		client
			.request(req)
			.map_err(|e| ErrorKind::RequestError(format!("Cannot make request: {}", e)).into())
			.and_then(|resp| {
				if !resp.status().is_success() {
					Either::A(err(ErrorKind::RequestError(
						"Wrong response code".to_owned(),
					)
					.into()))
				} else {
					Either::B(
						resp.into_body()
							.map_err(|e| {
								ErrorKind::RequestError(format!("Cannot read response body: {}", e))
									.into()
							})
							.concat2()
							.and_then(|ch| ok(String::from_utf8_lossy(&ch.to_vec()).to_string())),
					)
				}
			}),
	)
}

fn send_request(req: Request<Body>) -> Result<String, Error> {
	let task = send_request_async(req);
	let mut rt =
		Runtime::new().context(ErrorKind::Internal("can't create Tokio runtime".to_owned()))?;
	Ok(rt.block_on(task)?)
}
<commit_msg>Show response code in API client error message (#2683)<commit_after>// Copyright 2018 The Grin Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! High level JSON/HTTP client API

use crate::rest::{Error, ErrorKind};
use crate::util::to_base64;
use failure::{Fail, ResultExt};
use futures::future::{err, ok, Either};
use http::uri::{InvalidUri, Uri};
use hyper::header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE, USER_AGENT};
use hyper::rt::{Future, Stream};
use hyper::{Body, Client, Request};
use hyper_rustls;
use serde::{Deserialize, Serialize};
use serde_json;
use tokio::runtime::Runtime;

pub type ClientResponseFuture<T> = Box<dyn Future<Item = T, Error = Error> + Send>;

/// Helper function to easily issue a HTTP GET request against a given URL that
/// returns a JSON object. Handles request building, JSON deserialization and
/// response code checking.
pub fn get<'a, T>(url: &'a str, api_secret: Option<String>) -> Result<T, Error>
where
	for<'de> T: Deserialize<'de>,
{
	handle_request(build_request(url, "GET", api_secret, None)?)
}

/// Helper function to easily issue an async HTTP GET request against a given
/// URL that returns a future. Handles request building, JSON deserialization
/// and response code checking.
pub fn get_async<'a, T>(url: &'a str, api_secret: Option<String>) -> ClientResponseFuture<T>
where
	for<'de> T: Deserialize<'de> + Send + 'static,
{
	match build_request(url, "GET", api_secret, None) {
		Ok(req) => Box::new(handle_request_async(req)),
		Err(e) => Box::new(err(e)),
	}
}

/// Helper function to easily issue a HTTP GET request
/// on a given URL that returns nothing. Handles request
/// building and response code checking.
pub fn get_no_ret(url: &str, api_secret: Option<String>) -> Result<(), Error> {
	let req = build_request(url, "GET", api_secret, None)?;
	send_request(req)?;
	Ok(())
}

/// Helper function to easily issue a HTTP POST request with the provided JSON
/// object as body on a given URL that returns a JSON object. Handles request
/// building, JSON serialization and deserialization, and response code
/// checking.
pub fn post<IN, OUT>(url: &str, api_secret: Option<String>, input: &IN) -> Result<OUT, Error>
where
	IN: Serialize,
	for<'de> OUT: Deserialize<'de>,
{
	let req = create_post_request(url, api_secret, input)?;
	handle_request(req)
}

/// Helper function to easily issue an async HTTP POST request with the
/// provided JSON object as body on a given URL that returns a future. Handles
/// request building, JSON serialization and deserialization, and response code
/// checking.
pub fn post_async<IN, OUT>(
	url: &str,
	input: &IN,
	api_secret: Option<String>,
) -> ClientResponseFuture<OUT>
where
	IN: Serialize,
	OUT: Send + 'static,
	for<'de> OUT: Deserialize<'de>,
{
	match create_post_request(url, api_secret, input) {
		Ok(req) => Box::new(handle_request_async(req)),
		Err(e) => Box::new(err(e)),
	}
}

/// Helper function to easily issue a HTTP POST request with the provided JSON
/// object as body on a given URL that returns nothing. Handles request
/// building, JSON serialization, and response code
/// checking.
pub fn post_no_ret<IN>(url: &str, api_secret: Option<String>, input: &IN) -> Result<(), Error>
where
	IN: Serialize,
{
	let req = create_post_request(url, api_secret, input)?;
	send_request(req)?;
	Ok(())
}

/// Helper function to easily issue an async HTTP POST request with the
/// provided JSON object as body on a given URL that returns a future. Handles
/// request building, JSON serialization and deserialization, and response code
/// checking.
pub fn post_no_ret_async<IN>(
	url: &str,
	api_secret: Option<String>,
	input: &IN,
) -> ClientResponseFuture<()>
where
	IN: Serialize,
{
	match create_post_request(url, api_secret, input) {
		Ok(req) => Box::new(send_request_async(req).and_then(|_| ok(()))),
		Err(e) => Box::new(err(e)),
	}
}

fn build_request<'a>(
	url: &'a str,
	method: &str,
	api_secret: Option<String>,
	body: Option<String>,
) -> Result<Request<Body>, Error> {
	let uri = url.parse::<Uri>().map_err::<Error, _>(|e: InvalidUri| {
		e.context(ErrorKind::Argument(format!("Invalid url {}", url)))
			.into()
	})?;
	let mut builder = Request::builder();
	if let Some(api_secret) = api_secret {
		let basic_auth = format!("Basic {}", to_base64(&format!("grin:{}", api_secret)));
		builder.header(AUTHORIZATION, basic_auth);
	}

	builder
		.method(method)
		.uri(uri)
		.header(USER_AGENT, "grin-client")
		.header(ACCEPT, "application/json")
		.header(CONTENT_TYPE, "application/json")
		.body(match body {
			None => Body::empty(),
			Some(json) => json.into(),
		})
		.map_err(|e| {
			ErrorKind::RequestError(format!("Bad request {} {}: {}", method, url, e)).into()
		})
}

fn create_post_request<IN>(
	url: &str,
	api_secret: Option<String>,
	input: &IN,
) -> Result<Request<Body>, Error>
where
	IN: Serialize,
{
	let json = serde_json::to_string(input).context(ErrorKind::Internal(
		"Could not serialize data to JSON".to_owned(),
	))?;
	build_request(url, "POST", api_secret, Some(json))
}

fn handle_request<T>(req: Request<Body>) -> Result<T, Error>
where
	for<'de> T: Deserialize<'de>,
{
	let data = send_request(req)?;
	serde_json::from_str(&data).map_err(|e| {
		e.context(ErrorKind::ResponseError("Cannot parse response".to_owned()))
			.into()
	})
}

fn handle_request_async<T>(req: Request<Body>) -> ClientResponseFuture<T>
where
	for<'de> T: Deserialize<'de> + Send + 'static,
{
	Box::new(send_request_async(req).and_then(|data| {
		serde_json::from_str(&data).map_err(|e| {
			e.context(ErrorKind::ResponseError("Cannot parse response".to_owned()))
				.into()
		})
	}))
}

fn send_request_async(req: Request<Body>) -> Box<dyn Future<Item = String, Error = Error> + Send> {
	let https = hyper_rustls::HttpsConnector::new(1);
	let client = Client::builder().build::<_, Body>(https);
	Box::new(
		client
			.request(req)
			.map_err(|e| ErrorKind::RequestError(format!("Cannot make request: {}", e)).into())
			.and_then(|resp| {
				if !resp.status().is_success() {
					Either::A(err(ErrorKind::RequestError(format!(
						"Wrong response code: {}",
						resp.status()
					))
					.into()))
				} else {
					Either::B(
						resp.into_body()
							.map_err(|e| {
								ErrorKind::RequestError(format!("Cannot read response body: {}", e))
									.into()
							})
							.concat2()
							.and_then(|ch| ok(String::from_utf8_lossy(&ch.to_vec()).to_string())),
					)
				}
			}),
	)
}

fn send_request(req: Request<Body>) -> Result<String, Error> {
	let task = send_request_async(req);
	let mut rt =
		Runtime::new().context(ErrorKind::Internal("can't create Tokio runtime".to_owned()))?;
	Ok(rt.block_on(task)?)
}
<|endoftext|>


<commit_before>pub mod book;
pub mod logger;

use bus::BusReader;
use ::error::PoloError;

pub type Accountant = book::Accountant;
pub type Logger = logger::Logger;

pub type ActorResult = Result<u32,PoloError>;

pub trait Actor: Send + Clone {
  fn process_message(&mut self, msg: String) -> Result<(),PoloError>;

  fn listen(&mut self, mut channel: BusReader<Option<String>>) -> ActorResult {
    let mut counter = 0;
    loop {
      // TODO: maybe send reference, not String?
      let msg = channel.recv().unwrap();
      counter = counter + 1;
      if let Some(text) = msg {
        self.process_message(text).unwrap();
      } else {
        break;
      }
    };
    Ok(counter)
  }
}<commit_msg>do not panic on incompatible messages<commit_after>pub mod book;
pub mod logger;

use bus::BusReader;
use ::error::PoloError;

pub type Accountant = book::Accountant;
pub type Logger = logger::Logger;

pub type ActorResult = Result<u32,PoloError>;

pub trait Actor: Send + Clone {
  fn process_message(&mut self, msg: String) -> Result<(),PoloError>;

  fn listen(&mut self, mut channel: BusReader<Option<String>>) -> ActorResult {
    let mut counter = 0;
    loop {
      // TODO: maybe send reference, not String?
      let msg = channel.recv().unwrap();
      counter = counter + 1;
      if let Some(text) = msg {
        self.process_message(text)
          .unwrap_or_else(|err| println!("Error processing message {:?}", err));
      } else {
        break;
      }
    };
    Ok(counter)
  }
}<|endoftext|>


<commit_before>use std::os::raw::{c_ulong, c_void};

/// Governs behaviour of EnvironmentAttribute
#[repr(i32)]
#[allow(non_camel_case_types)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum EnvironmentAttribute {
    SQL_ATTR_ODBC_VERSION = 200,
    SQL_ATTR_CONNECTION_POOLING = 201,
    SQL_ATTR_CP_MATCH = 202,
    // For private driver manager
    SQL_ATTR_APPLICATION_KEY = 203,
    SQL_ATTR_OUTPUT_NTS = 10001,
}
pub use EnvironmentAttribute::*;

/// ODBC verions
///
/// Used in conjunction with `SQL_ATTR_ODBC_VERSION` and `SQLSetEnvAttr` to declare the ODBC
/// version used by the application.
#[allow(non_camel_case_types)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum OdbcVersion {
    SQL_OV_ODBC2 = 2,
    SQL_OV_ODBC3 = 3,
    #[cfg(feature = "odbc_version_3_80")] SQL_OV_ODBC3_80 = 380,
    #[cfg(feature = "odbc_version_4")] SQL_OV_ODBC4 = 400,
}
pub use OdbcVersion::*;

impl From<OdbcVersion> for *mut c_void {
    fn from(source: OdbcVersion) -> *mut c_void {
        source as c_ulong as *mut c_void
    }
}
<commit_msg>lib.rs: Add #[repr(u32)] to OdbcVersion<commit_after>use std::os::raw::{c_ulong, c_void};

/// Governs behaviour of EnvironmentAttribute
#[repr(i32)]
#[allow(non_camel_case_types)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum EnvironmentAttribute {
    SQL_ATTR_ODBC_VERSION = 200,
    SQL_ATTR_CONNECTION_POOLING = 201,
    SQL_ATTR_CP_MATCH = 202,
    // For private driver manager
    SQL_ATTR_APPLICATION_KEY = 203,
    SQL_ATTR_OUTPUT_NTS = 10001,
}
pub use EnvironmentAttribute::*;

/// ODBC verions
///
/// Used in conjunction with `SQL_ATTR_ODBC_VERSION` and `SQLSetEnvAttr` to declare the ODBC
/// version used by the application.
#[allow(non_camel_case_types)]
#[repr(u32)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum OdbcVersion {
    SQL_OV_ODBC2 = 2,
    SQL_OV_ODBC3 = 3,
    #[cfg(feature = "odbc_version_3_80")] SQL_OV_ODBC3_80 = 380,
    #[cfg(feature = "odbc_version_4")] SQL_OV_ODBC4 = 400,
}
pub use OdbcVersion::*;

impl From<OdbcVersion> for *mut c_void {
    fn from(source: OdbcVersion) -> *mut c_void {
        source as c_ulong as *mut c_void
    }
}
<|endoftext|>


<commit_before>use futures::{Async, Future, Poll, Sink};
use futures::stream::Stream;
use futures::sink::Send;
use futures_state_stream::{StateStream, StreamEvent};

use native_tls::TlsConnector;

use std::io;
use std::net::ToSocketAddrs;

use tokio_core::net::{TcpStream, TcpStreamNew};
use tokio_io::AsyncRead;
use tokio_core::reactor::Handle;
use tokio_tls::{ConnectAsync, TlsConnectorExt};

use imap_proto::*;
use imap_proto::builders::command::Command;
use proto::*;

pub mod builder {
    pub use imap_proto::builders::command::*;
}

pub struct Client {
    transport: ImapTransport,
    state: ClientState,
}

impl Client {
    pub fn connect(server: &str, handle: &Handle) -> ConnectFuture {
        let addr = format!("{}:993", server);
        let addr = addr.to_socket_addrs().unwrap().next().unwrap();
        let stream = TcpStream::connect(&addr, handle);
        ConnectFuture::TcpConnecting(stream, server.to_string())
    }

    pub fn call(self, cmd: Command) -> ResponseStream {
        let Self { transport, mut state } = self;
        let request_id = state.request_ids.next().unwrap();
        let (cmd_bytes, next_state) = cmd.to_parts();
        let future = transport.send(Request(request_id.clone(), cmd_bytes));
        ResponseStream::new(future, state, request_id, next_state)
    }
}

pub struct ResponseStream {
    future: Option<Send<ImapTransport>>,
    transport: Option<ImapTransport>,
    state: Option<ClientState>,
    request_id: RequestId,
    next_state: Option<State>,
    done: bool,
}

impl ResponseStream {
    pub fn new(future: Send<ImapTransport>, state: ClientState,
               request_id: RequestId, next_state: Option<State>) -> Self {
        Self {
            future: Some(future),
            transport: None,
            state: Some(state),
            request_id: request_id,
            next_state: next_state,
            done: false,
        }
    }
}

impl StateStream for ResponseStream {
    type Item = ResponseData;
    type State = Client;
    type Error = io::Error;
    fn poll(&mut self) -> Poll<StreamEvent<Self::Item, Self::State>, Self::Error> {
        if self.future.is_some() {
            let mut future = self.future.take().unwrap();
            match future.poll() {
                Ok(Async::Ready(transport)) => {
                    self.transport = Some(transport);
                },
                Ok(Async::NotReady) => {
                    self.future = Some(future);
                    return Ok(Async::NotReady);
                },
                Err(e) => {
                    return Err(e);
                },
            }
        }
        if !self.transport.is_some() {
            return Ok(Async::NotReady);
        }
        let mut transport = self.transport.take().unwrap();
        if self.done {
            let mut state = self.state.take().unwrap();
            if self.next_state.is_some() {
                state.state = self.next_state.take().unwrap();
            }
            let client = Client { transport, state };
            return Ok(Async::Ready(StreamEvent::Done(client)));
        }
        loop {
            match transport.poll() {
                Ok(Async::Ready(Some(rsp))) => {
                    if let Some(req_id) = rsp.request_id() {
                        self.done = *req_id == self.request_id;
                    };
                    self.transport = Some(transport);
                    return Ok(Async::Ready(StreamEvent::Next(rsp)));
                },
                Ok(Async::Ready(None)) | Ok(Async::NotReady) => {
                    break;
                },
                Err(e) => {
                    return Err(e);
                },
            }
        }
        self.transport = Some(transport);
        Ok(Async::NotReady)
    }
}

pub enum ConnectFuture {
    #[doc(hidden)]
    TcpConnecting(TcpStreamNew, String),
    #[doc(hidden)]
    TlsHandshake(ConnectAsync<TcpStream>),
    #[doc(hidden)]
    ServerGreeting(Option<ImapTransport>),
}

impl Future for ConnectFuture {
    type Item = (Client, ResponseData);
    type Error = io::Error;
    fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
        let mut new = None;
        if let ConnectFuture::TcpConnecting(ref mut future, ref domain) = *self {
            let stream = try_ready!(future.poll());
            let ctx = TlsConnector::builder().unwrap().build().unwrap();
            let future = ctx.connect_async(domain, stream);
            new = Some(ConnectFuture::TlsHandshake(future));
        }
        if new.is_some() {
            *self = new.take().unwrap();
        }
        if let ConnectFuture::TlsHandshake(ref mut future) = *self {
            let transport = try_ready!(future.map_err(|e| {
                io::Error::new(io::ErrorKind::Other, e)
            }).poll()).framed(ImapCodec::default());
            new = Some(ConnectFuture::ServerGreeting(Some(transport)));
        }
        if new.is_some() {
            *self = new.take().unwrap();
        }
        if let ConnectFuture::ServerGreeting(ref mut wrapped) = *self {
            let msg = try_ready!(wrapped.as_mut().unwrap().poll()).unwrap();
            return Ok(Async::Ready((Client {
                transport: wrapped.take().unwrap(),
                state: ClientState::new(),
            }, msg)));
        }
        Ok(Async::NotReady)
    }
}

pub struct ClientState {
    state: State,
    request_ids: IdGenerator,
}

impl ClientState {
    pub fn new() -> Self {
        Self {
            state: State::NotAuthenticated,
            request_ids: IdGenerator::new(),
        }
    }
}

impl Default for ClientState {
    fn default() -> Self { Self::new() }
}

pub struct IdGenerator {
    next: u64,
}

impl IdGenerator {
    pub fn new() -> Self {
        Self { next: 0 }
    }
}

impl Default for IdGenerator {
    fn default() -> Self { Self::new() }
}

impl Iterator for IdGenerator {
    type Item = RequestId;
    fn next(&mut self) -> Option<Self::Item> {
        self.next += 1;
        Some(RequestId(format!("A{:04}", self.next % 10_000)))
    }
}
<commit_msg>Remove `loop` keyword<commit_after>use futures::{Async, Future, Poll, Sink};
use futures::stream::Stream;
use futures::sink::Send;
use futures_state_stream::{StateStream, StreamEvent};

use native_tls::TlsConnector;

use std::io;
use std::net::ToSocketAddrs;

use tokio_core::net::{TcpStream, TcpStreamNew};
use tokio_io::AsyncRead;
use tokio_core::reactor::Handle;
use tokio_tls::{ConnectAsync, TlsConnectorExt};

use imap_proto::*;
use imap_proto::builders::command::Command;
use proto::*;

pub mod builder {
    pub use imap_proto::builders::command::*;
}

pub struct Client {
    transport: ImapTransport,
    state: ClientState,
}

impl Client {
    pub fn connect(server: &str, handle: &Handle) -> ConnectFuture {
        let addr = format!("{}:993", server);
        let addr = addr.to_socket_addrs().unwrap().next().unwrap();
        let stream = TcpStream::connect(&addr, handle);
        ConnectFuture::TcpConnecting(stream, server.to_string())
    }

    pub fn call(self, cmd: Command) -> ResponseStream {
        let Self { transport, mut state } = self;
        let request_id = state.request_ids.next().unwrap();
        let (cmd_bytes, next_state) = cmd.to_parts();
        let future = transport.send(Request(request_id.clone(), cmd_bytes));
        ResponseStream::new(future, state, request_id, next_state)
    }
}

pub struct ResponseStream {
    future: Option<Send<ImapTransport>>,
    transport: Option<ImapTransport>,
    state: Option<ClientState>,
    request_id: RequestId,
    next_state: Option<State>,
    done: bool,
}

impl ResponseStream {
    pub fn new(future: Send<ImapTransport>, state: ClientState,
               request_id: RequestId, next_state: Option<State>) -> Self {
        Self {
            future: Some(future),
            transport: None,
            state: Some(state),
            request_id: request_id,
            next_state: next_state,
            done: false,
        }
    }
}

impl StateStream for ResponseStream {
    type Item = ResponseData;
    type State = Client;
    type Error = io::Error;
    fn poll(&mut self) -> Poll<StreamEvent<Self::Item, Self::State>, Self::Error> {
        if self.future.is_some() {
            let mut future = self.future.take().unwrap();
            match future.poll() {
                Ok(Async::Ready(transport)) => {
                    self.transport = Some(transport);
                },
                Ok(Async::NotReady) => {
                    self.future = Some(future);
                    return Ok(Async::NotReady);
                },
                Err(e) => {
                    return Err(e);
                },
            }
        }
        if !self.transport.is_some() {
            return Ok(Async::NotReady);
        }
        let mut transport = self.transport.take().unwrap();
        if self.done {
            let mut state = self.state.take().unwrap();
            if self.next_state.is_some() {
                state.state = self.next_state.take().unwrap();
            }
            let client = Client { transport, state };
            return Ok(Async::Ready(StreamEvent::Done(client)));
        }
        match transport.poll() {
            Ok(Async::Ready(Some(rsp))) => {
                if let Some(req_id) = rsp.request_id() {
                    self.done = *req_id == self.request_id;
                };
                self.transport = Some(transport);
                return Ok(Async::Ready(StreamEvent::Next(rsp)));
            },
            Err(e) => {
                return Err(e);
            },
            _ => ()
        }
        self.transport = Some(transport);
        Ok(Async::NotReady)
    }
}

pub enum ConnectFuture {
    #[doc(hidden)]
    TcpConnecting(TcpStreamNew, String),
    #[doc(hidden)]
    TlsHandshake(ConnectAsync<TcpStream>),
    #[doc(hidden)]
    ServerGreeting(Option<ImapTransport>),
}

impl Future for ConnectFuture {
    type Item = (Client, ResponseData);
    type Error = io::Error;
    fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
        let mut new = None;
        if let ConnectFuture::TcpConnecting(ref mut future, ref domain) = *self {
            let stream = try_ready!(future.poll());
            let ctx = TlsConnector::builder().unwrap().build().unwrap();
            let future = ctx.connect_async(domain, stream);
            new = Some(ConnectFuture::TlsHandshake(future));
        }
        if new.is_some() {
            *self = new.take().unwrap();
        }
        if let ConnectFuture::TlsHandshake(ref mut future) = *self {
            let transport = try_ready!(future.map_err(|e| {
                io::Error::new(io::ErrorKind::Other, e)
            }).poll()).framed(ImapCodec::default());
            new = Some(ConnectFuture::ServerGreeting(Some(transport)));
        }
        if new.is_some() {
            *self = new.take().unwrap();
        }
        if let ConnectFuture::ServerGreeting(ref mut wrapped) = *self {
            let msg = try_ready!(wrapped.as_mut().unwrap().poll()).unwrap();
            return Ok(Async::Ready((Client {
                transport: wrapped.take().unwrap(),
                state: ClientState::new(),
            }, msg)));
        }
        Ok(Async::NotReady)
    }
}

pub struct ClientState {
    state: State,
    request_ids: IdGenerator,
}

impl ClientState {
    pub fn new() -> Self {
        Self {
            state: State::NotAuthenticated,
            request_ids: IdGenerator::new(),
        }
    }
}

impl Default for ClientState {
    fn default() -> Self { Self::new() }
}

pub struct IdGenerator {
    next: u64,
}

impl IdGenerator {
    pub fn new() -> Self {
        Self { next: 0 }
    }
}

impl Default for IdGenerator {
    fn default() -> Self { Self::new() }
}

impl Iterator for IdGenerator {
    type Item = RequestId;
    fn next(&mut self) -> Option<Self::Item> {
        self.next += 1;
        Some(RequestId(format!("A{:04}", self.next % 10_000)))
    }
}
<|endoftext|>


<commit_before>// Copyright 2020 Google LLC
//
// Use of this source code is governed by an MIT-style license that can be found
// in the LICENSE file or at https://opensource.org/licenses/MIT.

//! An Fleetspeak client connector library.
//!
//! This library exposes a set of functions for writing client-side Fleetspeak
//! services. Each of these functions operates on a global connection object
//! that is lazily established. If this global connection cannot be established,
//! the library will panic (because without this connection Fleetspeak will shut
//! down the service anyway).
//!
//! Note that each service should send startup information upon its inception
//! and continue to heartbeat from time to time to notify the Fleetspeak client
//! that it did not get stuck.

mod connection;
mod error;

use std::fs::File;
use std::sync::Mutex;
use std::time::Duration;

use lazy_static::lazy_static;

pub use self::connection::Packet;
pub use self::error::{ReadError, WriteError};

/// Sends a heartbeat information to the standard Fleetspeak client.
///
/// All client services should heartbeat from time to time. Otherwise, from the
/// Fleetspeak perspective, the service is unresponsive and should be restarted.
///
/// The exact frequency of the required heartbeat is defined in the service
/// configuration file.
pub fn heartbeat() -> Result<(), WriteError> {
    locked(&CONNECTION.output, |buf| self::connection::heartbeat(buf))
}

/// Sends the startup information to the standard Fleetspeak client.
///
/// All clients are required to send this information on startup. If the client
/// does not receive this information quickly enough, the service will be
/// killed.
///
/// The `version` string should contain a self-reported version of the service.
/// This data is used primarily for statistics.
pub fn startup(version: &str) -> Result<(), WriteError> {
    locked(&CONNECTION.output, |buf| self::connection::startup(buf, version))
}

/// Sends the message to the Fleetspeak server through the standard client.
///
/// The message is sent to the server-side `service` and tagged with the `kind`
/// type. Note that this message type is rather irrelevant for Fleetspeak and
/// it is up to the service what to do with this information.
pub fn send<M>(packet: Packet<M>) -> Result<(), WriteError>
where
    M: prost::Message,
{
    locked(&CONNECTION.output, |buf| self::connection::send(buf, packet))
}

/// Receives the message from the Fleetspeak server through the standard client.
///
/// This function will block until there is a message to be read from the input.
/// Errors are reported in case of any I/O failure or if the read message was
/// malformed (e.g. it cannot be parsed to the expected type).
pub fn receive<M>() -> Result<Packet<M>, ReadError>
where
    M: prost::Message + Default,
{
    locked(&CONNECTION.input, |buf| self::connection::receive(buf))
}

pub fn collect<M>(rate: Duration) -> Result<Packet<M>, std::io::Error>
where
    M: prost::Message + Default + 'static,
{
    let (sender, receiver) = std::sync::mpsc::channel();

    std::thread::spawn(move || {
        let packet = receive();

        // We do not care whether the packet was sent, because this call can
        // fail only if the receiver closed itself. Since the receiver loops
        // indefinitely, it closes only in case there was a heartbeat error. But
        // at that point we are no longer interested in the packet.
        let _ = sender.send(packet);
    });

    loop {
        use std::sync::mpsc::TryRecvError::*;

        // The sender will block indefinitely until there is a message to pick,
        // so the disconnection branch is not possible.
        match receiver.try_recv() {
            Ok(packet) => return Ok(packet?),
            Err(Empty) => (),
            Err(Disconnected) => panic!(),
        }

        heartbeat()?;
        std::thread::sleep(rate);
    }
}

/// Executes the given function with a file extracted from the mutex.
///
/// It might happen that the mutex becomes poisoned and this call will panic in
/// result. This should not be a problem in practice, because mutex poisoning
/// is a result of one of the threads being aborted. In case of a such scenario,
/// it is likely the service needs to be restarted anyway.
fn locked<F, T, E>(mutex: &Mutex<File>, f: F) -> Result<T, E>
where
    F: FnOnce(&mut File) -> Result<T, E>
{
    let mut file = mutex.lock().expect("poisoned connection mutex");
    f(&mut file)
}

struct Connection {
    input: Mutex<File>,
    output: Mutex<File>,
}

lazy_static! {
    static ref CONNECTION: Connection = {
        let mut input = open("FLEETSPEAK_COMMS_CHANNEL_INFD");
        let mut output = open("FLEETSPEAK_COMMS_CHANNEL_OUTFD");

        use self::connection::handshake;
        handshake(&mut input, &mut output).expect("handshake failure");

        Connection {
            input: Mutex::new(input),
            output: Mutex::new(output),
        }
    };
}

/// Opens a file object pointed by an environment variable.
///
/// Note that this function will panic if the environment variable `var` is not
/// a valid file descriptor (in which case the library cannot be initialized and
/// the service is unlikely to work anyway).
fn open(var: &str) -> File {
    let fd = std::env::var(var)
        .expect(&format!("invalid variable `{}`", var))
        .parse()
        .expect(&format!("failed to parse file descriptor"));

    // TODO: Add support for Windows.
    unsafe {
        std::os::unix::io::FromRawFd::from_raw_fd(fd)
    }
}
<commit_msg>Reorder items in the client module.<commit_after>// Copyright 2020 Google LLC
//
// Use of this source code is governed by an MIT-style license that can be found
// in the LICENSE file or at https://opensource.org/licenses/MIT.

//! An Fleetspeak client connector library.
//!
//! This library exposes a set of functions for writing client-side Fleetspeak
//! services. Each of these functions operates on a global connection object
//! that is lazily established. If this global connection cannot be established,
//! the library will panic (because without this connection Fleetspeak will shut
//! down the service anyway).
//!
//! Note that each service should send startup information upon its inception
//! and continue to heartbeat from time to time to notify the Fleetspeak client
//! that it did not get stuck.

mod connection;
mod error;

use std::fs::File;
use std::sync::Mutex;
use std::time::Duration;

use lazy_static::lazy_static;

pub use self::connection::Packet;
pub use self::error::{ReadError, WriteError};

/// Sends a heartbeat information to the standard Fleetspeak client.
///
/// All client services should heartbeat from time to time. Otherwise, from the
/// Fleetspeak perspective, the service is unresponsive and should be restarted.
///
/// The exact frequency of the required heartbeat is defined in the service
/// configuration file.
pub fn heartbeat() -> Result<(), WriteError> {
    locked(&CONNECTION.output, |buf| self::connection::heartbeat(buf))
}

/// Sends the startup information to the standard Fleetspeak client.
///
/// All clients are required to send this information on startup. If the client
/// does not receive this information quickly enough, the service will be
/// killed.
///
/// The `version` string should contain a self-reported version of the service.
/// This data is used primarily for statistics.
pub fn startup(version: &str) -> Result<(), WriteError> {
    locked(&CONNECTION.output, |buf| self::connection::startup(buf, version))
}

/// Sends the message to the Fleetspeak server through the standard client.
///
/// The message is sent to the server-side `service` and tagged with the `kind`
/// type. Note that this message type is rather irrelevant for Fleetspeak and
/// it is up to the service what to do with this information.
pub fn send<M>(packet: Packet<M>) -> Result<(), WriteError>
where
    M: prost::Message,
{
    locked(&CONNECTION.output, |buf| self::connection::send(buf, packet))
}

/// Receives the message from the Fleetspeak server through the standard client.
///
/// This function will block until there is a message to be read from the input.
/// Errors are reported in case of any I/O failure or if the read message was
/// malformed (e.g. it cannot be parsed to the expected type).
pub fn receive<M>() -> Result<Packet<M>, ReadError>
where
    M: prost::Message + Default,
{
    locked(&CONNECTION.input, |buf| self::connection::receive(buf))
}

pub fn collect<M>(rate: Duration) -> Result<Packet<M>, std::io::Error>
where
    M: prost::Message + Default + 'static,
{
    let (sender, receiver) = std::sync::mpsc::channel();

    std::thread::spawn(move || {
        let packet = receive();

        // We do not care whether the packet was sent, because this call can
        // fail only if the receiver closed itself. Since the receiver loops
        // indefinitely, it closes only in case there was a heartbeat error. But
        // at that point we are no longer interested in the packet.
        let _ = sender.send(packet);
    });

    loop {
        use std::sync::mpsc::TryRecvError::*;

        // The sender will block indefinitely until there is a message to pick,
        // so the disconnection branch is not possible.
        match receiver.try_recv() {
            Ok(packet) => return Ok(packet?),
            Err(Empty) => (),
            Err(Disconnected) => panic!(),
        }

        heartbeat()?;
        std::thread::sleep(rate);
    }
}

struct Connection {
    input: Mutex<File>,
    output: Mutex<File>,
}

lazy_static! {
    static ref CONNECTION: Connection = {
        let mut input = open("FLEETSPEAK_COMMS_CHANNEL_INFD");
        let mut output = open("FLEETSPEAK_COMMS_CHANNEL_OUTFD");

        use self::connection::handshake;
        handshake(&mut input, &mut output).expect("handshake failure");

        Connection {
            input: Mutex::new(input),
            output: Mutex::new(output),
        }
    };
}

/// Executes the given function with a file extracted from the mutex.
///
/// It might happen that the mutex becomes poisoned and this call will panic in
/// result. This should not be a problem in practice, because mutex poisoning
/// is a result of one of the threads being aborted. In case of a such scenario,
/// it is likely the service needs to be restarted anyway.
fn locked<F, T, E>(mutex: &Mutex<File>, f: F) -> Result<T, E>
where
    F: FnOnce(&mut File) -> Result<T, E>
{
    let mut file = mutex.lock().expect("poisoned connection mutex");
    f(&mut file)
}

/// Opens a file object pointed by an environment variable.
///
/// Note that this function will panic if the environment variable `var` is not
/// a valid file descriptor (in which case the library cannot be initialized and
/// the service is unlikely to work anyway).
fn open(var: &str) -> File {
    let fd = std::env::var(var)
        .expect(&format!("invalid variable `{}`", var))
        .parse()
        .expect(&format!("failed to parse file descriptor"));

    // TODO: Add support for Windows.
    unsafe {
        std::os::unix::io::FromRawFd::from_raw_fd(fd)
    }
}
<|endoftext|>