Skip to main content
Glama
8b-is
by 8b-is
openapi.rs11.1 kB
//! 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 } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/8b-is/smart-tree'

If you have feedback or need assistance with the MCP directory API, please join our Discord server