1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
// Copyright (c) The Diem Core Contributors
// SPDX-License-Identifier: Apache-2.0

use once_cell::sync::Lazy;
use regex::Regex;
use std::{fmt, str::FromStr};

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SdkLang {
    Rust,
    Java,
    Python,
    Typescript,
    Go,
    CSharp,
    Cpp,
    Unknown,
}

impl Default for SdkLang {
    fn default() -> Self {
        SdkLang::Unknown
    }
}

impl SdkLang {
    #[allow(clippy::should_implement_trait)]
    pub fn from_str(user_agent_part: &str) -> SdkLang {
        match str::trim(user_agent_part) {
            "diem-client-sdk-rust" => SdkLang::Rust,
            "diem-client-sdk-java" => SdkLang::Java,
            "diem-client-sdk-python" => SdkLang::Python,
            "diem-client-sdk-typescript" => SdkLang::Typescript,
            "diem-client-sdk-golang" => SdkLang::Go,
            "diem-client-sdk-csharp" => SdkLang::CSharp,
            "diem-client-sdk-cpp" => SdkLang::Cpp,
            _ => SdkLang::Unknown,
        }
    }

    pub fn as_str(self) -> &'static str {
        match self {
            SdkLang::Rust => "rust",
            SdkLang::Java => "java",
            SdkLang::Python => "python",
            SdkLang::Typescript => "typescript",
            SdkLang::Go => "golang",
            SdkLang::CSharp => "csharp",
            SdkLang::Cpp => "cpp",
            SdkLang::Unknown => "unknown",
        }
    }
}

static SDK_VERSION_REGEX: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"\b([0-3])\.(\d{1,2})\.(\d{1,2})\b").unwrap());

#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct SdkVersion {
    pub major: u16,
    pub minor: u16,
    pub patch: u16,
}

impl SdkVersion {
    #[allow(clippy::should_implement_trait)]
    pub fn from_str(user_agent_part: &str) -> SdkVersion {
        if let Some(captures) = SDK_VERSION_REGEX.captures(user_agent_part) {
            if captures.len() == 4 {
                if let (Ok(major), Ok(minor), Ok(patch)) = (
                    u16::from_str(&captures[1]),
                    u16::from_str(&captures[2]),
                    u16::from_str(&captures[3]),
                ) {
                    return SdkVersion {
                        major,
                        minor,
                        patch,
                    };
                }
            }
        }
        SdkVersion::default()
    }
}

impl fmt::Display for SdkVersion {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
    }
}

#[derive(Clone, Copy, Default, Debug, Eq, PartialEq)]
pub struct SdkInfo {
    pub language: SdkLang,
    pub version: SdkVersion,
}

impl SdkInfo {
    pub fn from_user_agent(user_agent: &str) -> SdkInfo {
        // parse our sdk user agent strings, i.e `diem-client-sdk-python / 0.1.12`
        let lowercase_user_agent = user_agent.to_lowercase();
        let user_agent_parts: Vec<&str> = lowercase_user_agent.split('/').collect();
        if user_agent_parts.len() == 2 {
            let language = SdkLang::from_str(user_agent_parts[0]);
            let version = SdkVersion::from_str(user_agent_parts[1]);
            if language != SdkLang::Unknown && version != SdkVersion::default() {
                return SdkInfo { language, version };
            }
        }
        SdkInfo::default()
    }
}

pub fn sdk_info_from_user_agent(user_agent: Option<&str>) -> SdkInfo {
    match user_agent {
        Some(user_agent) => SdkInfo::from_user_agent(user_agent),
        None => SdkInfo::default(),
    }
}