Skip to main content

keygen_rs/
service.rs

1use crate::client::Client;
2use crate::errors::Error;
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct PingResponse {
8    pub message: String,
9    pub version: Option<String>,
10    pub timestamp: Option<String>,
11}
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct ServiceInfo {
15    /// Server timestamp
16    pub timestamp: Option<String>,
17    /// API version from response or headers
18    pub api_version: Option<String>,
19    /// Ping message
20    pub message: Option<String>,
21    /// Server headers
22    pub headers: HashMap<String, String>,
23}
24
25/// Get service information using the /v1/ping endpoint
26/// This can help determine the Keygen.sh service version and capabilities
27pub async fn get_service_info() -> Result<ServiceInfo, Error> {
28    let client = Client::from_global_config()?;
29
30    // Use the ping endpoint to get version information
31    let response = client.get_text("ping").await?;
32
33    let mut headers = HashMap::new();
34    for (name, value) in response.headers.iter() {
35        if let Ok(value_str) = value.to_str() {
36            headers.insert(name.to_string(), value_str.to_string());
37        }
38    }
39
40    // Extract API version from headers
41    let api_version = headers
42        .get("keygen-version")
43        .or_else(|| headers.get("x-api-version"))
44        .or_else(|| headers.get("api-version"))
45        .cloned();
46
47    // Extract server timestamp from headers
48    let timestamp = headers
49        .get("date")
50        .or_else(|| headers.get("x-timestamp"))
51        .cloned();
52
53    // Use the ping response text as message
54    let message = Some(response.body.trim().to_string());
55
56    Ok(ServiceInfo {
57        timestamp,
58        api_version,
59        message,
60        headers,
61    })
62}
63
64/// Check if the service supports a specific feature by version
65pub fn supports_feature(service_info: &ServiceInfo, required_version: &str) -> bool {
66    if let Some(version) = &service_info.api_version {
67        // Simple version comparison - can be enhanced with semver crate
68        version.as_str() >= required_version
69    } else {
70        // If we can't determine version, assume latest
71        true
72    }
73}
74
75/// Ping the Keygen service and get basic information
76pub async fn ping() -> Result<PingResponse, Error> {
77    let client = Client::from_global_config()?;
78    let response = client.get_text("ping").await?;
79
80    // The ping endpoint returns plain text (usually "ok")
81    // We'll extract version info from headers if available
82    let message = response.body.trim().to_string();
83
84    let version = response
85        .headers
86        .get("keygen-version")
87        .and_then(|v| v.to_str().ok())
88        .map(|s| s.to_string());
89
90    let timestamp = response
91        .headers
92        .get("date")
93        .and_then(|v| v.to_str().ok())
94        .map(|s| s.to_string());
95
96    Ok(PingResponse {
97        message,
98        version,
99        timestamp,
100    })
101}
102
103/// Check if product code field is supported (requires API v1.8+)
104pub async fn supports_product_code() -> Result<bool, Error> {
105    let service_info = get_service_info().await?;
106    Ok(supports_feature(&service_info, "1.8"))
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn test_supports_feature() {
115        let service_info = ServiceInfo {
116            timestamp: None,
117            api_version: Some("1.8.0".to_string()),
118            message: None,
119            headers: HashMap::new(),
120        };
121
122        assert!(supports_feature(&service_info, "1.7"));
123        assert!(supports_feature(&service_info, "1.8"));
124        assert!(!supports_feature(&service_info, "1.9"));
125    }
126}