smart-tree 8.0.0

Smart Tree - An intelligent, AI-friendly directory visualization tool
Documentation
//! OpenAPI/Swagger specification input adapter
//!
//! Transforms API specs into navigable context trees

use super::*;
use anyhow::Result;
use async_trait::async_trait;
use serde_json;

pub struct OpenApiAdapter;

#[async_trait]
impl InputAdapter for OpenApiAdapter {
    fn name(&self) -> &'static str {
        "OpenAPI"
    }

    fn supported_formats(&self) -> Vec<&'static str> {
        vec!["openapi", "swagger", "oas", "api"]
    }

    async fn can_handle(&self, input: &InputSource) -> bool {
        match input {
            InputSource::Path(path) => {
                if let Some(ext) = path.extension() {
                    let ext = ext.to_string_lossy().to_lowercase();
                    return ext == "yaml" || ext == "yml" || ext == "json";
                }
                false
            }
            InputSource::Url(url) => {
                url.contains("swagger") || url.contains("openapi") || url.ends_with("/api-docs")
            }
            InputSource::Raw { format_hint, .. } => format_hint
                .as_ref()
                .map(|h| h == "openapi" || h == "swagger")
                .unwrap_or(false),
            _ => false,
        }
    }

    async fn parse(&self, input: InputSource) -> Result<ContextNode> {
        let spec = match input {
            InputSource::Path(path) => {
                let content = std::fs::read_to_string(&path)?;
                if path
                    .extension()
                    .map(|e| e == "yaml" || e == "yml")
                    .unwrap_or(false)
                {
                    serde_yaml::from_str(&content)?
                } else {
                    serde_json::from_str(&content)?
                }
            }
            InputSource::Url(url) => {
                let response = reqwest::get(url).await?;
                response.json::<serde_json::Value>().await?
            }
            InputSource::Raw { data, .. } => serde_json::from_slice(&data)?,
            _ => anyhow::bail!("Invalid input for OpenAPI adapter"),
        };

        self.parse_openapi_spec(&spec)
    }
}

impl OpenApiAdapter {
    fn parse_openapi_spec(&self, spec: &serde_json::Value) -> Result<ContextNode> {
        let title = spec
            .get("info")
            .and_then(|i| i.get("title"))
            .and_then(|t| t.as_str())
            .unwrap_or("API");

        let version = spec
            .get("info")
            .and_then(|i| i.get("version"))
            .and_then(|v| v.as_str())
            .unwrap_or("1.0.0");

        let mut root = ContextNode {
            id: "api_root".to_string(),
            name: format!("{} v{}", title, version),
            node_type: NodeType::ApiSchema,
            quantum_state: None,
            children: vec![],
            metadata: spec.get("info").cloned().unwrap_or_default(),
            entanglements: vec![],
        };

        // Parse paths
        if let Some(paths) = spec.get("paths").and_then(|p| p.as_object()) {
            let mut path_nodes = Vec::new();

            for (path, methods) in paths {
                let mut path_node = ContextNode {
                    id: path.clone(),
                    name: path.clone(),
                    node_type: NodeType::ApiEndpoint,
                    quantum_state: None,
                    children: vec![],
                    metadata: serde_json::json!({}),
                    entanglements: vec![],
                };

                // Parse methods
                if let Some(methods_obj) = methods.as_object() {
                    for (method, details) in methods_obj {
                        if ["get", "post", "put", "delete", "patch", "head", "options"]
                            .contains(&method.as_str())
                        {
                            let operation_id = details
                                .get("operationId")
                                .and_then(|o| o.as_str())
                                .unwrap_or(method);

                            let mut method_node = ContextNode {
                                id: format!("{}_{}", path, method),
                                name: format!("{} {}", method.to_uppercase(), operation_id),
                                node_type: NodeType::ApiEndpoint,
                                quantum_state: Some(
                                    self.calculate_endpoint_quantum_state(method, details),
                                ),
                                children: vec![],
                                metadata: details.clone(),
                                entanglements: self
                                    .find_endpoint_entanglements(path, method, details),
                            };

                            // Add parameter nodes
                            if let Some(params) =
                                details.get("parameters").and_then(|p| p.as_array())
                            {
                                for param in params {
                                    let param_name = param
                                        .get("name")
                                        .and_then(|n| n.as_str())
                                        .unwrap_or("param");

                                    method_node.children.push(ContextNode {
                                        id: format!("{}_{}_{}", path, method, param_name),
                                        name: format!("param: {}", param_name),
                                        node_type: NodeType::ApiSchema,
                                        quantum_state: None,
                                        children: vec![],
                                        metadata: param.clone(),
                                        entanglements: vec![],
                                    });
                                }
                            }

                            path_node.children.push(method_node);
                        }
                    }
                }

                path_nodes.push(path_node);
            }

            // Group by path segments
            root.children.push(ContextNode {
                id: "endpoints".to_string(),
                name: "Endpoints".to_string(),
                node_type: NodeType::Directory,
                quantum_state: None,
                children: self.organize_by_path_segments(path_nodes),
                metadata: serde_json::json!({}),
                entanglements: vec![],
            });
        }

        // Parse schemas/components
        if let Some(components) = spec.get("components").or_else(|| spec.get("definitions")) {
            root.children.push(self.parse_schemas(components)?);
        }

        Ok(root)
    }

    fn calculate_endpoint_quantum_state(
        &self,
        method: &str,
        details: &serde_json::Value,
    ) -> QuantumState {
        // Calculate quantum properties based on endpoint characteristics
        let complexity = details
            .get("parameters")
            .and_then(|p| p.as_array())
            .map(|a| a.len())
            .unwrap_or(0) as f64;

        let has_security = details.get("security").is_some();

        QuantumState {
            amplitude: match method {
                "get" => 0.9,    // READ operations are stable
                "post" => 0.7,   // CREATE operations
                "put" => 0.6,    // UPDATE operations
                "delete" => 0.5, // DELETE operations are disruptive
                _ => 0.8,
            },
            frequency: 1.0 + complexity, // More params = higher frequency
            phase: if has_security {
                std::f64::consts::PI / 2.0
            } else {
                0.0
            },
            collapse_probability: if method == "delete" { 0.9 } else { 0.3 },
        }
    }

    fn find_endpoint_entanglements(
        &self,
        path: &str,
        _method: &str,
        details: &serde_json::Value,
    ) -> Vec<Entanglement> {
        let mut entanglements = Vec::new();

        // Find schema references
        if let Some(schema_ref) = details
            .get("requestBody")
            .and_then(|r| r.get("content"))
            .and_then(|c| c.get("application/json"))
            .and_then(|j| j.get("schema"))
            .and_then(|s| s.get("$ref"))
            .and_then(|r| r.as_str())
        {
            if let Some(schema_name) = schema_ref.split('/').next_back() {
                entanglements.push(Entanglement {
                    target_id: format!("schema_{}", schema_name),
                    strength: 0.9,
                    relationship: "uses_schema".to_string(),
                });
            }
        }

        // Find related endpoints (same resource)
        let resource = path.split('/').nth(1).unwrap_or("");
        if !resource.is_empty() {
            entanglements.push(Entanglement {
                target_id: format!("resource_{}", resource),
                strength: 0.7,
                relationship: "same_resource".to_string(),
            });
        }

        entanglements
    }

    fn organize_by_path_segments(&self, paths: Vec<ContextNode>) -> Vec<ContextNode> {
        // For simplicity, return as-is
        // In real implementation, would group by common path prefixes
        paths
    }

    fn parse_schemas(&self, components: &serde_json::Value) -> Result<ContextNode> {
        let mut schemas_node = ContextNode {
            id: "schemas".to_string(),
            name: "Schemas".to_string(),
            node_type: NodeType::Directory,
            quantum_state: None,
            children: vec![],
            metadata: serde_json::json!({}),
            entanglements: vec![],
        };

        if let Some(schemas) = components.get("schemas").and_then(|s| s.as_object()) {
            for (name, schema) in schemas {
                schemas_node.children.push(ContextNode {
                    id: format!("schema_{}", name),
                    name: name.clone(),
                    node_type: NodeType::ApiSchema,
                    quantum_state: None,
                    children: self.parse_schema_properties(schema),
                    metadata: schema.clone(),
                    entanglements: vec![],
                });
            }
        }

        Ok(schemas_node)
    }

    fn parse_schema_properties(&self, schema: &serde_json::Value) -> Vec<ContextNode> {
        let mut props = Vec::new();

        if let Some(properties) = schema.get("properties").and_then(|p| p.as_object()) {
            for (prop_name, prop_schema) in properties {
                props.push(ContextNode {
                    id: format!("prop_{}", prop_name),
                    name: prop_name.clone(),
                    node_type: NodeType::ApiSchema,
                    quantum_state: None,
                    children: vec![],
                    metadata: prop_schema.clone(),
                    entanglements: vec![],
                });
            }
        }

        props
    }
}