xui-rs 1.0.2

A modern, typed async Rust SDK for the 3x-ui panel API
Documentation
use std::fmt;

use serde::{Deserialize, Serialize};
use serde_json::Value;

/// Runtime `OpenAPI` document embedded in the connected panel.
///
/// The model stays open-ended so extensions and schemas from future panel
/// versions are preserved exactly.
#[derive(Clone, Deserialize, PartialEq, Serialize)]
#[serde(transparent)]
pub struct OpenApiDocument(Value);

impl OpenApiDocument {
    /// Borrows the complete `OpenAPI` JSON document.
    pub const fn as_value(&self) -> &Value {
        &self.0
    }

    /// Consumes the wrapper and returns the complete JSON document.
    pub fn into_value(self) -> Value {
        self.0
    }

    /// Returns the declared `OpenAPI` version string.
    pub fn version(&self) -> Option<&str> {
        self.0.get("openapi").and_then(Value::as_str)
    }

    /// Counts real HTTP operations, excluding documentation-only WebSocket
    /// message pseudo-operations.
    pub fn http_operation_count(&self) -> usize {
        const METHODS: &[&str] = &[
            "get", "head", "post", "put", "patch", "delete", "options", "trace",
        ];
        self.0
            .get("paths")
            .and_then(Value::as_object)
            .into_iter()
            .flat_map(|paths| paths.values())
            .filter_map(Value::as_object)
            .map(|item| {
                METHODS
                    .iter()
                    .filter(|method| item.contains_key(**method))
                    .count()
            })
            .sum()
    }
}

impl From<Value> for OpenApiDocument {
    fn from(value: Value) -> Self {
        Self(value)
    }
}

impl fmt::Debug for OpenApiDocument {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("OpenApiDocument")
            .field("version", &self.version())
            .field("http_operation_count", &self.http_operation_count())
            .finish_non_exhaustive()
    }
}

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

    #[test]
    fn openapi_document_counts_only_http_operations() {
        let value = json!({
            "openapi": "3.0.3",
            "paths": {
                "/one": {"get": {}, "post": {}, "parameters": []},
                "/two": {"delete": {}, "websocket": {}}
            }
        });
        let document = OpenApiDocument::from(value.clone());
        assert_eq!(document.version(), Some("3.0.3"));
        assert_eq!(document.http_operation_count(), 3);
        assert!(format!("{document:?}").contains("http_operation_count: 3"));
        assert_eq!(document.as_value(), &value);
        assert_eq!(document.into_value(), value);

        let empty = OpenApiDocument::from(json!({}));
        assert_eq!(empty.version(), None);
        assert_eq!(empty.http_operation_count(), 0);
    }
}