use serde::Deserialize;
use std::collections::BTreeMap;
#[derive(Debug, Deserialize, Clone, Default)]
pub struct JsonSchema {
#[serde(default, rename = "$ref")]
pub ref_path: Option<String>,
#[serde(default)]
pub properties: Option<BTreeMap<String, JsonSchema>>,
#[serde(default, rename = "allOf")]
pub all_of: Option<Vec<JsonSchema>>,
#[serde(default, rename = "oneOf")]
pub one_of: Option<Vec<JsonSchema>>,
#[serde(default, rename = "anyOf")]
pub any_of: Option<Vec<JsonSchema>>,
}
#[derive(Debug, Deserialize, Default)]
pub struct ResponseContent {
pub content: Option<BTreeMap<String, MediaType>>,
}
#[derive(Debug, Deserialize, Default)]
pub struct MediaType {
pub schema: Option<JsonSchema>,
}
#[must_use]
pub fn extract_response_type(
responses: &BTreeMap<String, ResponseContent>,
components_schemas: Option<&BTreeMap<String, JsonSchema>>,
) -> Option<String> {
for status in &["200", "201", "202", "204", "default"] {
if let Some(response) = responses.get(*status) {
if *status == "204" {
return Some("()".to_string());
}
if let Some(content) = &response.content {
if let Some(media) = content.get("application/json") {
if let Some(schema) = &media.schema {
let schema_to_check = if let Some(ref_path) = &schema.ref_path {
components_schemas
.and_then(|schemas| {
let type_name =
ref_path.trim_start_matches("#/components/schemas/");
schemas.get(type_name)
})
.unwrap_or(schema)
} else {
schema
};
let is_generatable = schema_to_check.properties.is_some()
|| (schema_to_check.all_of.is_none()
&& schema_to_check.any_of.is_none()
&& schema_to_check.one_of.is_none());
if is_generatable {
return extract_type_name_from_schema(
schema_to_check,
components_schemas,
);
}
return Some("serde_json::Value".to_string());
}
}
}
}
}
None
}
pub fn extract_type_name_from_ref(ref_path: &str) -> Option<String> {
if ref_path.starts_with("#/") {
return ref_path.split('/').next_back().map(String::from);
}
ref_path.split('.').next_back().map(String::from)
}
fn extract_type_name_from_schema(
schema: &JsonSchema,
_components_schemas: Option<&BTreeMap<String, JsonSchema>>,
) -> Option<String> {
if let Some(ref_path) = &schema.ref_path {
let type_name = extract_type_name_from_ref(ref_path)?;
Some(normalize_type_name(&type_name))
} else {
None
}
}
#[must_use]
pub fn normalize_type_name(name: &str) -> String {
name.split(['.', '-', '@', '_'])
.map(|part| {
let mut chars = part.chars();
chars
.next()
.map(|c| c.to_uppercase().collect::<String>())
.unwrap_or_default()
+ chars.as_str()
})
.collect()
}
#[must_use]
pub fn extract_path_params(path: &str) -> Vec<String> {
let re = regex::Regex::new(r"\{([^}]+)\}").unwrap();
re.captures_iter(path)
.map(|cap| cap[1].to_string())
.collect()
}