fastly 0.3.2

Fastly Compute@Edge API
Documentation
//! Geographic data for IP addresses.

pub use chrono::FixedOffset;

use crate::{Request, RequestExt};
use serde::{Deserialize, Serialize};
use std::net::IpAddr;

/// Look up the geographic data associated with a particular IP address.
///
/// Returns `None` if no geographic data is available, such as when the IP address is reserved for
/// private use.
///
/// # Examples
///
/// To get geographic information for the downstream client:
///
/// ```no_run
/// let client_ip = fastly::downstream_client_ip_addr().unwrap();
/// let geo = fastly::geo::geo_lookup(client_ip).unwrap();
/// if let fastly::geo::ConnType::Satellite = geo.conn_type() {
///     println!("receiving a request from outer space 🛸");
/// }
/// ```
pub fn geo_lookup(ip: IpAddr) -> Option<Geo> {
    fn do_request(ip: IpAddr) -> Result<Option<Geo>, crate::Error> {
        let mut resp = Request::builder()
            .header("Fastly-XQD-API", "geolocation")
            .header("Fastly-XQD-arg1", ip.to_string())
            // dummy URL
            .uri("http://www.fastly.com/geolocation")
            .body(())?
            // dummy backend name
            .send("geolocation")?;
        let raw: Option<RawGeo> = serde_json::from_reader(resp.body_mut())?;
        Ok(raw.map(Into::into))
    }
    do_request(ip).unwrap_or(None)
}

#[derive(Clone, Debug, Deserialize, Serialize)]
struct RawGeo {
    as_name: String,
    as_number: u32,
    area_code: u16,
    city: String,
    conn_speed: ConnSpeed,
    conn_type: ConnType,
    continent: Continent,
    country_code: String,
    country_code3: String,
    country_name: String,
    latitude: f64,
    longitude: f64,
    metro_code: i64,
    postal_code: String,
    proxy_description: ProxyDescription,
    proxy_type: ProxyType,
    region: Option<String>,
    utc_offset: i32,
}

/// The geographic data associated with a particular IP address.
// TODO ACF 2020-04-20: make a nicer type for the AS fields once the IANA licensing question is
// sorted out. https://www.iana.org/assignments/as-numbers/as-numbers.xhtml
//
// TODO ACF 2020-04-20: we should be able to represent the continent, country, region, etc much more
// nicely than this, however the licensing for ISO data appears to be fraught. The `locale-codes`
// crate looks very nice, but it sources its data from a CC BY-SA 4.0 repo despite being
// MIT-licensed, which in turn scrapes wikipedia and other sources. For now, just use strings.
#[derive(Clone, Debug)]
pub struct Geo {
    as_name: String,
    as_number: u32,
    area_code: u16,
    city: String,
    conn_speed: ConnSpeed,
    conn_type: ConnType,
    continent: Continent,
    country_code: String,
    country_code3: String,
    country_name: String,
    latitude: f64,
    longitude: f64,
    metro_code: i64,
    postal_code: String,
    proxy_description: ProxyDescription,
    proxy_type: ProxyType,
    region: Option<String>,
    utc_offset: FixedOffset,
}

impl From<RawGeo> for Geo {
    fn from(raw: RawGeo) -> Self {
        let hours = raw.utc_offset / 100;
        let minutes = raw.utc_offset % 100;
        let seconds = (hours * 60 * 60) + (minutes * 60);
        let utc_offset = FixedOffset::east(seconds);
        Geo {
            as_name: raw.as_name,
            as_number: raw.as_number,
            area_code: raw.area_code,
            city: raw.city,
            conn_speed: raw.conn_speed,
            conn_type: raw.conn_type,
            continent: raw.continent,
            country_code: raw.country_code,
            country_code3: raw.country_code3,
            country_name: raw.country_name,
            latitude: raw.latitude,
            longitude: raw.longitude,
            metro_code: raw.metro_code,
            postal_code: raw.postal_code,
            proxy_description: raw.proxy_description,
            proxy_type: raw.proxy_type,
            region: raw.region,
            utc_offset,
        }
    }
}

impl Geo {
    /// The name of the organization associated with `as_number`.
    ///
    /// For example, `fastly` is the value given for IP addresses under AS-54113.
    pub fn as_name(&self) -> &str {
        self.as_name.as_str()
    }

    /// [Autonomous system](https://en.wikipedia.org/wiki/Autonomous_system_(Internet)) (AS) number.
    pub fn as_number(&self) -> u32 {
        self.as_number
    }

    /// The telephone area code associated with an IP address.
    ///
    /// These are only available for IP addresses in the United States, its territories, and Canada.
    pub fn area_code(&self) -> u16 {
        self.area_code
    }

    /// City or town name.
    pub fn city(&self) -> &str {
        self.city.as_str()
    }

    /// Connection speed.
    pub fn conn_speed(&self) -> ConnSpeed {
        self.conn_speed
    }

    /// Connection type.
    pub fn conn_type(&self) -> ConnType {
        self.conn_type
    }

    /// Continent.
    pub fn continent(&self) -> Continent {
        self.continent
    }

    /// A two-character [ISO 3166-1][iso] country code for the country associated with an IP address.
    ///
    /// The US country code is returned for IP addresses associated with overseas United States military bases.
    ///
    /// These values include subdivisions that are assigned their own country codes in ISO
    /// 3166-1. For example, subdivisions NO-21 and NO-22 are presented with the country code SJ for
    /// Svalbard and the Jan Mayen Islands.
    ///
    /// [iso]: https://en.wikipedia.org/wiki/ISO_3166-1
    pub fn country_code(&self) -> &str {
        self.country_code.as_str()
    }

    /// A three-character [ISO 3166-1 alpha-3][iso] country code for the country associated with the IP address.
    ///
    /// The USA country code is returned for IP addresses associated with overseas United States
    /// military bases.
    ///
    /// [iso]: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3
    pub fn country_code3(&self) -> &str {
        self.country_code3.as_str()
    }

    /// Country name.
    ///
    /// This field is the [ISO 3166-1][iso] English short name for a country.
    ///
    /// [iso]: https://en.wikipedia.org/wiki/ISO_3166-1
    pub fn country_name(&self) -> &str {
        self.country_name.as_str()
    }

    /// Latitude, in units of degrees from the equator.
    ///
    /// Values range from -90.0 to +90.0 inclusive, and are based on the [WGS 84][wgs84] coordinate
    /// reference system.
    ///
    /// [wgs84]: https://en.wikipedia.org/wiki/World_Geodetic_System
    pub fn latitude(&self) -> f64 {
        self.latitude
    }

    /// Longitude, in units of degrees from the [IERS Reference Meridian][iers].
    ///
    /// Values range from -180.0 to +180.0 inclusive, and are based on the [WGS 84][wgs84]
    /// coordinate reference system.
    ///
    /// [iers]: https://en.wikipedia.org/wiki/IERS_Reference_Meridian
    /// [wgs84]: https://en.wikipedia.org/wiki/World_Geodetic_System
    pub fn longitude(&self) -> f64 {
        self.longitude
    }

    /// Metro code, representing designated market areas (DMAs) in the United States.
    pub fn metro_code(&self) -> i64 {
        self.metro_code
    }

    /// The postal code associated with the IP address.
    ///
    /// These are available for some IP addresses in Australia, Canada, France, Germany, Italy,
    /// Spain, Switzerland, the United Kingdom, and the United States.
    ///
    /// For Canadian postal codes, this is the first 3 characters. For the United Kingdom, this is
    /// the first 2-4 characters (outward code). For countries with alphanumeric postal codes, this
    /// field is a lowercase transliteration.
    pub fn postal_code(&self) -> &str {
        self.postal_code.as_str()
    }

    /// Client proxy description.
    pub fn proxy_description(&self) -> ProxyDescription {
        self.proxy_description
    }

    /// Client proxy type.
    pub fn proxy_type(&self) -> ProxyType {
        self.proxy_type
    }

    /// [ISO 3166-2][iso] country subdivision code.
    ///
    /// For countries with multiple levels of subdivision (for example, nations within the United
    /// Kingdom), this variable gives the more specific subdivision.
    ///
    /// This field can be `None` for countries that do not have ISO country subdivision codes. For
    /// example, `None` is given for IP addresses assigned to the Åland Islands (country code AX,
    /// illustrated below).
    ///
    /// # Examples
    ///
    /// Region values are the subdivision part only. For typical use, a subdivision is normally
    /// formatted with its associated country code. The following example illustrates constructing
    /// an [ISO 3166-2][iso] two-part country and subdivision code from the respective fields:
    ///
    /// ```no_run
    /// # let client_ip = fastly::downstream_client_ip_addr().unwrap();
    /// # let geo = fastly::geo::geo_lookup(client_ip).unwrap();
    /// let code = if let Some(region) = geo.region() {
    ///     format!("{}-{}", geo.country_code(), region);
    /// } else {
    ///     format!("{}", geo.country_code());
    /// };
    /// ```
    ///
    /// | `code`     | Region Name       | Country            | ISO 3166-2 subdivision |
    /// | ---------- | ----------------- | ------------------ | ---------------------- |
    /// | `AX`       | Ödkarby           | Åland Islands      | (none)                 |
    /// | `DE-BE`    | Berlin	         | Germany            | Land (State)           |
    /// | `GB-BNH`   | Brighton and Hove | United Kingdom     | Unitary authority      |
    /// | `JP-13`    | 東京都 (Tōkyō-to)  | Japan              | Prefecture             |
    /// | `RU-MOW`   | Москва́ (Moscow)   | Russian Federation | Federal city           |
    /// | `SE-AB`    | Stockholms län    | Sweden             | Län (County)           |
    /// | `US-CA`    | California        | United States      | State                  |
    ///
    /// [iso]: https://en.wikipedia.org/wiki/ISO_3166-2
    pub fn region(&self) -> Option<&str> {
        self.region.as_ref().map(|s| s.as_str())
    }

    /// Time zone offset from coordinated universal time (UTC) for `city`.
    ///
    /// This is represented using the [`FixedOffset`](struct.FixedOffset.html) type from the
    /// [`chrono`][chrono] library. See `chrono`'s documentation for more details on how to format
    /// this type or use it in time calculations.
    ///
    /// [chrono]: https://docs.rs/chrono
    pub fn utc_offset(&self) -> FixedOffset {
        self.utc_offset
    }
}

/// Connection speed.
///
/// These connection speeds imply different latencies, as well as throughput.
///
/// See [OC rates][oc] and [T-carrier][t] for background on OC- and T- connections.
///
/// [oc]: https://en.wikipedia.org/wiki/Optical_Carrier_transmission_rates
/// [t]: https://en.wikipedia.org/wiki/T-carrier
#[serde(rename_all = "kebab-case")]
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub enum ConnSpeed {
    Broadband,
    Cable,
    Dialup,
    Mobile,
    Oc12,
    Oc3,
    T1,
    T3,
    Satellite,
    Wireless,
    Xdsl,
}

/// Connection type.
///
/// Defaults to `Unknown` when the connection type is not known.
#[serde(rename_all = "kebab-case")]
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub enum ConnType {
    Wired,
    Wifi,
    Mobile,
    Dialup,
    Satellite,
    #[serde(other)]
    #[serde(rename = "?")]
    Unknown,
}

/// Continent.
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub enum Continent {
    #[serde(rename = "AF")]
    Africa,
    #[serde(rename = "AN")]
    Antarctica,
    #[serde(rename = "AS")]
    Asia,
    #[serde(rename = "EU")]
    Europe,
    #[serde(rename = "NA")]
    NorthAmerica,
    #[serde(rename = "OC")]
    Oceania,
    #[serde(rename = "SA")]
    SouthAmerica,
}

/// Client proxy description.
///
/// Defaults to `Unknown` when an IP address is not known to be a proxy or VPN.
#[serde(rename_all = "kebab-case")]
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub enum ProxyDescription {
    /// Enables ubiquitous network access to a shared pool of configurable computing resources.
    Cloud,
    /// A host accessing the internet via a web security and data protection cloud provider.
    ///
    /// Example providers with this type of service are Zscaler, Scansafe, and Onavo.
    CloudSecurity,
    /// A proxy used by overriding the client's DNS value for an endpoint host to that of the proxy
    /// instead of the actual DNS value.
    Dns,
    /// The gateway nodes where encrypted or anonymous Tor traffic hits the internet.
    TorExit,
    /// Receives traffic on the Tor network and passes it along; also referred to as "routers".
    TorRelay,
    /// Virtual private network that encrypts and routes all traffic through the VPN server,
    /// including programs and applications.
    Vpn,
    /// Connectivity that is taking place through mobile device web browser software that proxies
    /// the user through a centralized location.
    ///
    /// Examples of such browsers are Opera mobile browsers and UCBrowser.
    WebBrowser,
    /// An IP address that is not known to be a proxy or VPN.
    #[serde(other)]
    #[serde(rename = "?")]
    Unknown,
}

/// Client proxy type.
///
/// Defaults to `Unknown` when an IP address is not known to be a proxy or VPN.
// TODO ACF 2020-04-22: the docs on https://docs.fastly.com/vcl/variables/client-geo-proxy-type/
// look like they need a refresher, so I did not transcribe them for the individual variants.
#[serde(rename_all = "kebab-case")]
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub enum ProxyType {
    Anonymous,
    Aol,
    Blackberry,
    Corporate,
    Edu,
    Hosting,
    Public,
    Transparent,
    #[serde(other)]
    #[serde(rename = "?")]
    Unknown,
}