use crate::core::api::ApiError;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SpecVersion {
Swagger2_0,
OpenAPI3_0(String),
OpenAPI3_1(String),
}
impl fmt::Display for SpecVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SpecVersion::Swagger2_0 => write!(f, "Swagger 2.0"),
SpecVersion::OpenAPI3_0(v) => write!(f, "OpenAPI {}", v),
SpecVersion::OpenAPI3_1(v) => write!(f, "OpenAPI {}", v),
}
}
}
impl SpecVersion {}
pub fn detect_spec_version(content: &str) -> Result<SpecVersion> {
let value: Value = serde_json::from_str(content)
.or_else(|_| serde_yaml::from_str(content))
.map_err(|e| ApiError::ValidationError(format!("Failed to parse spec: {}", e)))?;
detect_version_from_value(&value)
}
pub fn detect_version_from_value(value: &Value) -> Result<SpecVersion> {
if let Some(swagger_version) = value.get("swagger").and_then(|v| v.as_str()) {
if swagger_version == "2.0" {
return Ok(SpecVersion::Swagger2_0);
} else {
return Err(ApiError::ValidationError(format!(
"Unsupported Swagger version: {}. Only 2.0 is supported.",
swagger_version
))
.into());
}
}
if let Some(openapi_version) = value.get("openapi").and_then(|v| v.as_str()) {
let parts: Vec<&str> = openapi_version.split('.').collect();
if parts.len() < 2 {
return Err(ApiError::ValidationError(format!(
"Invalid OpenAPI version format: {}",
openapi_version
))
.into());
}
match parts[0] {
"3" => {
match parts[1] {
"0" => {
if parts.len() == 3 && parts[2].chars().all(|c| c.is_ascii_digit()) {
Ok(SpecVersion::OpenAPI3_0(openapi_version.to_string()))
} else {
Err(ApiError::ValidationError(format!(
"Invalid OpenAPI 3.0.x version format: {}. Expected format: 3.0.X where X is a number",
openapi_version
)).into())
}
}
"1" => {
if parts.len() == 3 && parts[2].chars().all(|c| c.is_ascii_digit()) {
Ok(SpecVersion::OpenAPI3_1(openapi_version.to_string()))
} else {
Err(ApiError::ValidationError(format!(
"Invalid OpenAPI 3.1.x version format: {}. Expected format: 3.1.X where X is a number",
openapi_version
)).into())
}
}
_ => Err(ApiError::ValidationError(format!(
"Unsupported OpenAPI version: {}. Supported: 3.0.x and 3.1.x",
openapi_version
))
.into()),
}
}
_ => Err(ApiError::ValidationError(format!(
"Unsupported OpenAPI major version: {}. Only version 3.x is supported.",
openapi_version
))
.into()),
}
} else {
Err(ApiError::ValidationError(
"No 'swagger' or 'openapi' field found. This doesn't appear to be a valid OpenAPI/Swagger specification.".to_string()
).into())
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_detect_swagger_2_0() {
let spec = json!({
"swagger": "2.0",
"info": {
"title": "Test API",
"version": "1.0.0"
}
});
let version = detect_version_from_value(&spec).unwrap();
assert_eq!(version, SpecVersion::Swagger2_0);
}
#[test]
fn test_detect_openapi_3_0() {
let spec = json!({
"openapi": "3.0.2",
"info": {
"title": "Test API",
"version": "1.0.0"
}
});
let version = detect_version_from_value(&spec).unwrap();
assert_eq!(version, SpecVersion::OpenAPI3_0("3.0.2".to_string()));
}
#[test]
fn test_detect_openapi_3_0_4() {
let spec = json!({
"openapi": "3.0.4",
"info": {
"title": "Test API",
"version": "1.0.0"
}
});
let version = detect_version_from_value(&spec).unwrap();
assert_eq!(version, SpecVersion::OpenAPI3_0("3.0.4".to_string()));
}
#[test]
fn test_detect_openapi_3_0_10() {
let spec = json!({
"openapi": "3.0.10",
"info": {
"title": "Test API",
"version": "1.0.0"
}
});
let version = detect_version_from_value(&spec).unwrap();
assert_eq!(version, SpecVersion::OpenAPI3_0("3.0.10".to_string()));
}
#[test]
fn test_detect_openapi_3_1() {
let spec = json!({
"openapi": "3.1.0",
"info": {
"title": "Test API",
"version": "1.0.0"
}
});
let version = detect_version_from_value(&spec).unwrap();
assert_eq!(version, SpecVersion::OpenAPI3_1("3.1.0".to_string()));
}
#[test]
fn test_detect_openapi_3_1_1() {
let spec = json!({
"openapi": "3.1.1",
"info": {
"title": "Test API",
"version": "1.0.0"
}
});
let version = detect_version_from_value(&spec).unwrap();
assert_eq!(version, SpecVersion::OpenAPI3_1("3.1.1".to_string()));
}
#[test]
fn test_reject_invalid_3_0_format() {
let spec = json!({"openapi": "3.0"});
assert!(detect_version_from_value(&spec).is_err());
let spec = json!({"openapi": "3.0.x"});
assert!(detect_version_from_value(&spec).is_err());
let spec = json!({"openapi": "3.0.1.2"});
assert!(detect_version_from_value(&spec).is_err());
}
#[test]
fn test_reject_invalid_3_1_format() {
let spec = json!({"openapi": "3.1"});
assert!(detect_version_from_value(&spec).is_err());
let spec = json!({"openapi": "3.1.x"});
assert!(detect_version_from_value(&spec).is_err());
let spec = json!({"openapi": "3.1.0-beta"});
assert!(detect_version_from_value(&spec).is_err());
}
#[test]
fn test_invalid_version() {
let spec = json!({
"openapi": "2.0",
"info": {
"title": "Test API",
"version": "1.0.0"
}
});
let result = detect_version_from_value(&spec);
assert!(result.is_err());
}
#[test]
fn test_missing_version() {
let spec = json!({
"info": {
"title": "Test API",
"version": "1.0.0"
}
});
let result = detect_version_from_value(&spec);
assert!(result.is_err());
}
}