fastly 0.12.0

Fastly Compute API
Documentation
//! Device detection based on the User-Agent header.

use serde::{Deserialize, Serialize};

use crate::abi::{self, FastlyStatus};

/// Look up the data associated with a particular User-Agent string.
pub fn lookup(user_agent: &str) -> Option<Device> {
    const INITIAL_DEVICE_DETECTION_BUF_SIZE: usize = 1024;
    lookup_impl(user_agent, INITIAL_DEVICE_DETECTION_BUF_SIZE)
}

fn lookup_impl(user_agent: &str, max_length: usize) -> Option<Device> {
    let mut buf = Vec::with_capacity(max_length);
    let mut nwritten: usize = 0;

    let status = unsafe {
        abi::fastly_device_detection::lookup(
            user_agent.as_ptr(),
            user_agent.len(),
            buf.as_mut_ptr(),
            buf.capacity(),
            &mut nwritten,
        )
    };

    let status = match status {
        // If the buffer length wasn't enough, try again with a larger buffer.
        // This failure mode should not repeat.
        FastlyStatus::BUFLEN if nwritten != 0 => {
            buf.resize(nwritten, 0);
            nwritten = 0;
            unsafe {
                abi::fastly_device_detection::lookup(
                    user_agent.as_ptr(),
                    user_agent.len(),
                    buf.as_mut_ptr(),
                    buf.capacity(),
                    &mut nwritten,
                )
            }
        }
        s => s,
    };

    match status.result() {
        Ok(_) => {
            assert!(
                nwritten <= buf.capacity(),
                "fastly_device_detection::lookup wrote too many bytes"
            );
            unsafe {
                buf.set_len(nwritten);
            }

            serde_json::from_slice::<'_, Device>(&buf).ok()
        }
        Err(_) => None,
    }
}

/// The device data associated with a particular User-Agent string.
#[derive(Debug, Deserialize, Serialize)]
pub struct Device {
    user_agent: Option<RawUserAgent>,
    os: Option<RawOS>,
    device: Option<RawDevice>,
}

impl Device {
    // Accessors for RawDevice

    /// The name of the client device.
    pub fn device_name(&self) -> Option<&str> {
        match &self.device {
            None => None,
            Some(d) => d.name.as_deref(),
        }
    }

    /// The brand of the client device, possibly different from the
    /// manufacturer of that device.
    pub fn brand(&self) -> Option<&str> {
        match &self.device {
            None => None,
            Some(d) => d.brand.as_deref(),
        }
    }

    /// The model of the client device.
    pub fn model(&self) -> Option<&str> {
        match &self.device {
            None => None,
            Some(d) => d.model.as_deref(),
        }
    }

    /// A string representation of the primary client platform hardware.
    /// The most commonly used device types are also identified via
    /// boolean variables. Because a device may have multiple device
    /// types and this variable only has the primary type, we recommend
    /// using the boolean variables for logic and using this string
    /// representation for logging.
    pub fn hwtype(&self) -> Option<&str> {
        match &self.device {
            None => None,
            Some(d) => d.hwtype.as_deref(),
        }
    }

    /// The client device is a reading device (like a Kindle).
    pub fn is_ereader(&self) -> Option<bool> {
        match &self.device {
            None => None,
            Some(d) => d.is_ereader,
        }
    }

    /// The client device is a video game console (like a PlayStation or Xbox).
    pub fn is_gameconsole(&self) -> Option<bool> {
        match &self.device {
            None => None,
            Some(d) => d.is_gameconsole,
        }
    }

    /// The client device is a media player (like Blu-ray players, iPod
    /// devices, and smart speakers such as Amazon Echo).
    pub fn is_mediaplayer(&self) -> Option<bool> {
        match &self.device {
            None => None,
            Some(d) => d.is_mediaplayer,
        }
    }

    /// The client device is a mobile phone.
    pub fn is_mobile(&self) -> Option<bool> {
        match &self.device {
            None => None,
            Some(d) => d.is_mobile,
        }
    }

    /// The client device is a smart TV.
    pub fn is_smarttv(&self) -> Option<bool> {
        match &self.device {
            None => None,
            Some(d) => d.is_smarttv,
        }
    }

    /// The client device is a tablet (like an iPad).
    pub fn is_tablet(&self) -> Option<bool> {
        match &self.device {
            None => None,
            Some(d) => d.is_tablet,
        }
    }

    /// The client device is a set-top box or other TV player (like a Roku or Apple TV).
    pub fn is_tvplayer(&self) -> Option<bool> {
        match &self.device {
            None => None,
            Some(d) => d.is_tvplayer,
        }
    }

    /// The client is a desktop web browser.
    pub fn is_desktop(&self) -> Option<bool> {
        match &self.device {
            None => None,
            Some(d) => d.is_desktop,
        }
    }

    /// The client device's screen is touch sensitive.
    pub fn is_touchscreen(&self) -> Option<bool> {
        match &self.device {
            None => None,
            Some(d) => d.is_touchscreen,
        }
    }

    // Accessors for RawUserAgent

    /// The name of the client's browser if known.
    pub fn user_agent_name(&self) -> Option<&str> {
        self.user_agent.as_ref()?.name.as_deref()
    }

    /// The client's browser major version in SemVer fashion.
    ///
    /// `x` in `x.y.z`
    pub fn user_agent_major_version(&self) -> Option<&str> {
        self.user_agent.as_ref()?.major.as_deref()
    }

    /// The client's browser minor version in SemVer fashion.
    ///
    /// `y` in `x.y.z`
    pub fn user_agent_minor_version(&self) -> Option<&str> {
        self.user_agent.as_ref()?.minor.as_deref()
    }

    /// The client's browser patch version in SemVer fashion.
    ///
    /// `z` in `x.y.z`
    pub fn user_agent_patch_version(&self) -> Option<&str> {
        self.user_agent.as_ref()?.patch.as_deref()
    }

    /// The client identifies itself as a bot and provides a common name.
    pub fn user_agent_bot_name(&self) -> Option<&str> {
        self.user_agent.as_ref()?.bot_name.as_deref()
    }

    /// The client's `User-Agent` was recognized as belonging to a bot.
    pub fn is_bot(&self) -> Option<bool> {
        self.user_agent.as_ref()?.is_bot
    }

    /// The client's `User-Agent` was recognized as belonging to a downloader.
    pub fn is_downloader(&self) -> Option<bool> {
        self.user_agent.as_ref()?.is_downloader
    }

    /// The client's `User-Agent` was recognized as belonging to a feedreader.
    pub fn is_feedreader(&self) -> Option<bool> {
        self.user_agent.as_ref()?.is_feedreader
    }

    /// The client's `User-Agent` was identified.
    ///
    /// This returns true when it is possible to populate client device properties
    /// based on information supplied in the `User-Agent`. Which means that in each field of [`Device`]
    /// we have at least one property that is `Some(T)`.
    pub fn is_identified(&self) -> Option<bool> {
        self.user_agent.as_ref()?.identified
    }

    // Accessors for RawOS fields

    /// The name of the Operating System the client is using.
    pub fn os_name(&self) -> Option<&str> {
        self.os.as_ref()?.name.as_deref()
    }

    /// The client Operating System's major version in SemVer format.
    ///
    /// `x` in `x.y.z-a`
    pub fn os_major_version(&self) -> Option<&str> {
        self.os.as_ref()?.major.as_deref()
    }

    /// The client Operating System's major version in SemVer format.
    ///
    /// `y` in `x.y.z-a`
    pub fn os_minor_version(&self) -> Option<&str> {
        self.os.as_ref()?.minor.as_deref()
    }

    /// The client Operating System's patch version in SemVer format.
    ///
    /// `z` in `x.y.z-a`
    pub fn os_patch_version(&self) -> Option<&str> {
        self.os.as_ref()?.patch.as_deref()
    }

    /// The client Operating System's patch-minor version in SemVer format.
    ///
    /// `a` in `x.y.z-a`
    pub fn os_patch_minor_version(&self) -> Option<&str> {
        self.os.as_ref()?.patch_minor.as_deref()
    }
}

#[derive(Debug, Deserialize, Serialize)]
struct RawUserAgent {
    name: Option<String>,
    major: Option<String>,
    minor: Option<String>,
    patch: Option<String>,
    bot_name: Option<String>,
    is_bot: Option<bool>,
    is_downloader: Option<bool>,
    is_feedreader: Option<bool>,
    identified: Option<bool>,
}

#[derive(Debug, Deserialize, Serialize)]
struct RawOS {
    name: Option<String>,
    major: Option<String>,
    minor: Option<String>,
    patch: Option<String>,
    patch_minor: Option<String>,
}

#[derive(Debug, Deserialize, Serialize)]
struct RawDevice {
    name: Option<String>,
    brand: Option<String>,
    model: Option<String>,
    hwtype: Option<String>,
    is_ereader: Option<bool>,
    is_gameconsole: Option<bool>,
    is_mediaplayer: Option<bool>,
    is_mobile: Option<bool>,
    is_smarttv: Option<bool>,
    is_tablet: Option<bool>,
    is_tvplayer: Option<bool>,
    is_desktop: Option<bool>,
    is_touchscreen: Option<bool>,
}