libworker 0.0.1

Idiomatic framework / abstractions over `edgeworker-sys` FFI bindings.
Documentation
use edgeworker_sys::cf::Cf as FfiCf;
use edgeworker_sys::cf::TlsClientAuth as FfiTlsClientAuth;

/// In addition to the methods on the `Request` struct, the `Cf` struct on an inbound Request contains information about the request provided by Cloudflare’s edge.
///
/// [Details](https://developers.cloudflare.com/workers/runtime-apis/request#incomingrequestcfproperties)
#[derive(Debug)]
pub struct Cf {
    inner: FfiCf,
}

impl Cf {
    /// The three-letter airport code (e.g. `ATX`, `LUX`) representing
    /// the colocation which processed the request
    pub fn colo(&self) -> String {
        self.inner.colo()
    }

    /// The Autonomous System Number (ASN) of the request, e.g. `395747`
    pub fn asn(&self) -> u32 {
        self.inner.asn()
    }

    /// The two-letter country code of origin for the request.
    /// This is the same value as that provided in the CF-IPCountry header, e.g.  `"US"`
    pub fn country(&self) -> Option<String> {
        self.inner.country()
    }

    /// The HTTP Protocol (e.g. "HTTP/2") used by the request
    pub fn http_protocol(&self) -> String {
        self.inner.http_protocol()
    }

    /// The browser-requested prioritization information in the request object,
    ///
    /// See [this blog post](https://blog.cloudflare.com/better-http-2-prioritization-for-a-faster-web/#customizingprioritizationwithworkers) for details.
    pub fn request_priority(&self) -> Option<RequestPriority> {
        if let Some(priority) = self.inner.request_priority() {
            let mut weight = 1;
            let mut exclusive = false;
            let mut group = 0;
            let mut group_weight = 0;

            priority
                .as_str()
                .split(';')
                .map(|key_value_pair| {
                    let mut iter = key_value_pair.split('=');

                    // this pair is guaranteed to have 2 elements
                    let key = iter.next().unwrap(); // first element
                    let value = iter.next().unwrap(); // second element

                    (key, value)
                })
                .for_each(|(key, value)| match key {
                    "weight" => weight = value.parse().unwrap(),
                    "exclusive" => exclusive = value == "1",
                    "group" => group = value.parse().unwrap(),
                    "group-weight" => group_weight = value.parse().unwrap(),
                    _ => unreachable!(),
                });

            Some(RequestPriority {
                weight,
                exclusive,
                group,
                group_weight,
            })
        } else {
            None
        }
    }

    /// The cipher for the connection to Cloudflare, e.g. "AEAD-AES128-GCM-SHA256".
    pub fn tls_cipher(&self) -> String {
        self.inner.tls_cipher()
    }

    /// Information about the client's authorization.
    /// Only set when using Cloudflare Access or API Shield.
    pub fn tls_client_auth(&self) -> Option<TlsClientAuth> {
        self.inner.tls_client_auth().map(Into::into)
    }

    /// The TLS version of the connection to Cloudflare, e.g. TLSv1.3.
    pub fn tls_version(&self) -> String {
        // TODO: should this be strongly typed? with ordering, etc.?
        self.inner.tls_version()
    }

    /// City of the incoming request, e.g. "Austin".
    pub fn city(&self) -> Option<String> {
        self.inner.city()
    }

    /// Continent of the incoming request, e.g. "NA"
    pub fn continent(&self) -> Option<String> {
        self.inner.continent()
    }

    /// Latitude and longitude of the incoming request, e.g. (30.27130, -97.74260)
    pub fn coordinates(&self) -> Option<(f32, f32)> {
        let lat_opt = self.inner.latitude();
        let lon_opt = self.inner.longitude();
        match (lat_opt, lon_opt) {
            (Some(lat_str), Some(lon_str)) => {
                // SAFETY: i think this is fine..?
                let lat = lat_str.parse().unwrap();
                let lon = lon_str.parse().unwrap();
                Some((lat, lon))
            }
            _ => None,
        }
    }

    /// Postal code of the incoming request, e.g. "78701"
    pub fn postal_code(&self) -> Option<String> {
        self.inner.postal_code()
    }

    /// Metro code (DMA) of the incoming request, e.g. "635"
    pub fn metro_code(&self) -> Option<String> {
        self.inner.metro_code()
    }

    /// If known, the [ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-2) name for the first level region associated with the IP address of the incoming request, e.g. "Texas".
    pub fn region(&self) -> Option<String> {
        self.inner.region()
    }

    /// If known, the [ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-2) code for the first level region associated with the IP address of the incoming request, e.g. "TX".
    pub fn region_code(&self) -> Option<String> {
        self.inner.region_code()
    }

    /// Timezone of the incoming request
    pub fn timezone(&self) -> impl chrono::TimeZone {
        let tz = self.inner.timezone();
        tz.parse::<chrono_tz::Tz>().unwrap()
    }
}

/// Browser-requested prioritization information.
#[derive(Debug, Clone, Copy)]
pub struct RequestPriority {
    /// The browser-requested weight for the HTTP/2 prioritization
    pub weight: usize,

    /// The browser-requested HTTP/2 exclusive flag (true for Chromium-based browsers, false for others).
    pub exclusive: bool,

    /// HTTP/2 stream ID for the request group (only non-zero for Firefox)
    pub group: usize,

    /// HTTP/2 weight for the request group (only non-zero for Firefox)
    pub group_weight: usize,
}

impl From<FfiCf> for Cf {
    fn from(inner: FfiCf) -> Self {
        Self { inner }
    }
}

/// Only set when using Cloudflare Access or API Shield
#[derive(Debug)]
pub struct TlsClientAuth {
    inner: FfiTlsClientAuth,
}

impl TlsClientAuth {
    // TODO: document these

    pub fn cert_issuer_dn_legacy(&self) -> String {
        self.inner.cert_issuer_dn_legacy()
    }

    pub fn cert_issuer_dn(&self) -> String {
        self.inner.cert_issuer_dn()
    }

    pub fn cert_issuer_dn_rfc2253(&self) -> String {
        self.inner.cert_issuer_dn_rfc2253()
    }

    pub fn cert_subject_dn_legacy(&self) -> String {
        self.inner.cert_subject_dn_legacy()
    }

    pub fn cert_verified(&self) -> String {
        self.inner.cert_verified()
    }

    pub fn cert_not_after(&self) -> String {
        self.inner.cert_not_after()
    }

    pub fn cert_subject_dn(&self) -> String {
        self.inner.cert_subject_dn()
    }

    pub fn cert_fingerprint_sha1(&self) -> String {
        self.inner.cert_fingerprint_sha1()
    }

    pub fn cert_not_before(&self) -> String {
        self.inner.cert_not_before()
    }

    pub fn cert_serial(&self) -> String {
        self.inner.cert_serial()
    }

    pub fn cert_presented(&self) -> String {
        self.inner.cert_presented()
    }

    pub fn cert_subject_dn_rfc225(&self) -> String {
        self.inner.cert_subject_dn_rfc225()
    }
}

impl From<FfiTlsClientAuth> for TlsClientAuth {
    fn from(inner: FfiTlsClientAuth) -> Self {
        Self { inner }
    }
}