threexui-rs 2.9.5

Async Rust SDK for the 3x-ui panel API (compatible with 3x-ui v2.9.2 and v2.9.3)
Documentation
use serde::{Deserialize, Deserializer, Serialize};

fn null_or_vec<'de, D>(de: D) -> Result<Vec<String>, D::Error>
where
    D: Deserializer<'de>,
{
    Ok(Option::<Vec<String>>::deserialize(de)?.unwrap_or_default())
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CustomGeoResource {
    pub id: i64,
    #[serde(rename = "type")]
    pub geo_type: String,
    pub alias: String,
    pub url: String,
    #[serde(default)]
    pub local_path: String,
    #[serde(default)]
    pub last_updated_at: i64,
    #[serde(default)]
    pub created_at: i64,
    #[serde(default)]
    pub updated_at: i64,
}

#[derive(Debug, Clone, Serialize)]
pub struct CreateCustomGeo {
    #[serde(rename = "type")]
    pub geo_type: String,
    pub alias: String,
    pub url: String,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct CustomGeoAliases {
    #[serde(default, deserialize_with = "null_or_vec")]
    pub geosite: Vec<String>,
    #[serde(default, deserialize_with = "null_or_vec")]
    pub geoip: Vec<String>,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn custom_geo_deserializes() {
        let raw = r#"{"id":1,"type":"geoip","alias":"myip","url":"https://example.com/ip.dat","localPath":"","lastUpdatedAt":0,"createdAt":0,"updatedAt":0}"#;
        let r: CustomGeoResource = serde_json::from_str(raw).unwrap();
        assert_eq!(r.id, 1);
        assert_eq!(r.geo_type, "geoip");
        assert_eq!(r.alias, "myip");
    }
}