use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ApiEndpoint {
pub path: String,
pub methods: Vec<String>,
pub operation_id: Option<String>,
pub summary: Option<String>,
}
#[derive(Debug, Clone)]
pub struct ProcessedSpec {
pub version: Option<String>,
pub endpoints: Option<Vec<ApiEndpoint>>,
pub content_hash: String,
}
pub fn extract_version(spec: &serde_json::Value) -> Option<String> {
spec.get("info")
.and_then(|info| info.get("version"))
.and_then(|v| v.as_str())
.map(String::from)
}
#[must_use]
pub fn extract_endpoints(spec: &serde_json::Value) -> Option<Vec<ApiEndpoint>> {
let paths_obj = spec.get("paths").and_then(|p| p.as_object())?;
let endpoints: Vec<ApiEndpoint> = paths_obj
.iter()
.flat_map(|(path, path_item)| {
let methods = path_item.as_object();
methods
.into_iter()
.flat_map(|obj| {
["get", "post", "put", "patch", "delete"]
.iter()
.filter_map(|method| {
let operation = obj.get(*method)?;
Some(ApiEndpoint {
path: path.clone(),
methods: vec![method.to_uppercase()],
operation_id: operation
.get("operationId")
.and_then(|v| v.as_str())
.map(String::from),
summary: operation
.get("summary")
.and_then(|v| v.as_str())
.map(String::from),
})
})
.collect::<Vec<_>>()
})
.collect::<Vec<_>>()
})
.collect();
if endpoints.is_empty() {
None
} else {
Some(endpoints)
}
}
#[must_use]
pub fn compute_content_hash(content: &str) -> String {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
content.hash(&mut hasher);
format!("{:016x}", hasher.finish())
}
#[must_use]
pub fn process_spec(spec: &serde_json::Value) -> ProcessedSpec {
let version = extract_version(spec);
let endpoints = extract_endpoints(spec);
let content_hash = compute_content_hash(&spec.to_string());
ProcessedSpec {
version,
endpoints,
content_hash,
}
}