use super::EndpointContextBuilder;
use crate::core::openapi::OpenApiOperation;
use crate::core::templates::{ParameterKind, TemplateParameterInfo};
use crate::core::utils::{to_proper_case, to_snake_case};
use serde::{Deserialize, Serialize};
use serde_json::{Map as JsonMap, Value as JsonValue};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RustPropertyInfo {
pub name: String,
pub rust_type: String,
pub title: Option<String>,
pub description: Option<String>,
pub example: Option<JsonValue>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RustEndpointContext {
pub endpoint: String,
pub endpoint_cap: String,
pub endpoint_fs: String,
pub path: String,
pub fn_name: String,
pub parameters_type: String,
pub properties_type: String,
pub response_type: String,
pub envelope_properties: JsonValue,
pub properties: Vec<RustPropertyInfo>,
pub properties_for_handler: Vec<String>,
pub parameters: Vec<TemplateParameterInfo>,
pub summary: String,
pub description: String,
pub tags: Vec<String>,
pub properties_schema: JsonMap<String, JsonValue>,
pub response_schema: JsonValue,
pub spec_file_name: Option<String>,
pub valid_fields: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct RustEndpointContextBuilder;
impl EndpointContextBuilder for RustEndpointContextBuilder {
fn build(&self, op: &OpenApiOperation) -> crate::core::error::Result<JsonValue> {
let context = RustEndpointContext {
fn_name: to_snake_case(&op.id),
parameters_type: to_proper_case(&format!("{}_params", op.id)),
endpoint: to_snake_case(&op.id),
endpoint_cap: to_proper_case(&op.id),
endpoint_fs: to_snake_case(&op.id),
path: op.path.clone(),
properties_type: to_proper_case(&format!("{}_properties", op.id)),
response_type: to_proper_case(&format!("{}_response", op.id)),
envelope_properties: extract_envelope_properties(op),
properties: extract_response_properties(op),
properties_for_handler: extract_handler_properties(op),
parameters: op
.parameters
.clone()
.unwrap_or_default()
.into_iter()
.map(|p| TemplateParameterInfo {
name: p.name,
target_type: map_openapi_schema_to_rust_type(p.schema.as_ref()),
description: p.description,
example: p.example,
kind: match p.in_.as_str() {
"path" => ParameterKind::Path,
"query" => ParameterKind::Query,
"header" => ParameterKind::Header,
"cookie" => ParameterKind::Cookie,
_ => ParameterKind::Query, },
})
.collect(),
summary: op.summary.clone().unwrap_or_default(),
description: op.description.clone().unwrap_or_default(),
tags: op.tags.clone().unwrap_or_default(),
properties_schema: extract_properties_schema(op),
response_schema: extract_response_schema(op),
spec_file_name: extract_spec_file_name(op),
valid_fields: extract_valid_fields(op),
};
Ok(serde_json::to_value(&context)?)
}
}
fn map_openapi_schema_to_rust_type(schema: Option<&JsonValue>) -> String {
if let Some(sch) = schema {
if let Some(typ) = sch.get("type").and_then(|v| v.as_str()) {
match typ {
"string" => "String".to_string(),
"integer" => "i32".to_string(),
"boolean" => "bool".to_string(),
"number" => "f64".to_string(),
other => other.to_string(),
}
} else {
"String".to_string()
}
} else {
"String".to_string()
}
}
fn extract_envelope_properties(op: &OpenApiOperation) -> JsonValue {
for (status_code, response) in &op.responses {
if status_code.starts_with('2') {
if let Some(content) = response.content.as_ref() {
if let Some(json_content) = content.get("application/json") {
if let Some(schema) = json_content.get("schema") {
return extract_schema_envelope_properties(schema);
}
}
}
}
}
serde_json::json!({})
}
fn extract_response_properties(op: &OpenApiOperation) -> Vec<RustPropertyInfo> {
let mut properties = Vec::new();
for (status_code, response) in &op.responses {
if status_code.starts_with('2') {
if let Some(content) = response.content.as_ref() {
if let Some(json_content) = content.get("application/json") {
if let Some(schema) = json_content.get("schema") {
properties.extend(extract_schema_properties_as_rust(schema));
}
}
}
}
}
properties
}
fn extract_handler_properties(op: &OpenApiOperation) -> Vec<String> {
extract_response_properties(op)
.into_iter()
.map(|prop| prop.name)
.collect()
}
fn extract_schema_envelope_properties(schema: &JsonValue) -> JsonValue {
if let Some(_ref_str) = schema.get("$ref").and_then(JsonValue::as_str) {
return serde_json::json!({});
}
if let Some(properties) = schema.get("properties") {
return properties.clone();
}
if schema.get("type").and_then(JsonValue::as_str) == Some("array") {
if let Some(items) = schema.get("items") {
return extract_schema_envelope_properties(items);
}
}
serde_json::json!({})
}
fn extract_schema_properties_as_rust(schema: &JsonValue) -> Vec<RustPropertyInfo> {
let mut rust_properties = Vec::new();
if let Some(_ref_str) = schema.get("$ref").and_then(JsonValue::as_str) {
return rust_properties;
}
if let Some(properties) = schema.get("properties").and_then(JsonValue::as_object) {
for (prop_name, prop_schema) in properties {
let rust_type = map_openapi_schema_to_rust_type(Some(prop_schema));
let title = prop_schema
.get("title")
.and_then(JsonValue::as_str)
.map(String::from);
let description = prop_schema
.get("description")
.and_then(JsonValue::as_str)
.map(String::from);
let example = prop_schema.get("example").cloned();
rust_properties.push(RustPropertyInfo {
name: to_snake_case(prop_name),
rust_type,
title,
description,
example,
});
}
}
if schema.get("type").and_then(JsonValue::as_str) == Some("array") {
if let Some(items) = schema.get("items") {
rust_properties.extend(extract_schema_properties_as_rust(items));
}
}
rust_properties
}
fn extract_properties_schema(op: &OpenApiOperation) -> JsonMap<String, JsonValue> {
for (status_code, response) in &op.responses {
if status_code.starts_with('2') {
if let Some(content) = response.content.as_ref() {
if let Some(json_content) = content.get("application/json") {
if let Some(schema) = json_content.get("schema") {
if let Some(properties) = extract_schema_properties_map(schema) {
return properties;
}
}
}
}
}
}
JsonMap::new()
}
fn extract_response_schema(op: &OpenApiOperation) -> JsonValue {
for (status_code, response) in &op.responses {
if status_code.starts_with('2') {
if let Some(content) = response.content.as_ref() {
if let Some(json_content) = content.get("application/json") {
if let Some(schema) = json_content.get("schema") {
return schema.clone();
}
}
}
}
}
serde_json::json!({})
}
fn extract_spec_file_name(_op: &OpenApiOperation) -> Option<String> {
None
}
fn extract_valid_fields(op: &OpenApiOperation) -> Vec<String> {
extract_response_properties(op)
.into_iter()
.map(|prop| prop.name)
.collect()
}
fn extract_schema_properties_map(schema: &JsonValue) -> Option<JsonMap<String, JsonValue>> {
if let Some(_ref_str) = schema.get("$ref").and_then(JsonValue::as_str) {
return None;
}
if let Some(properties) = schema.get("properties").and_then(JsonValue::as_object) {
return Some(properties.clone());
}
if schema.get("type").and_then(JsonValue::as_str) == Some("array") {
if let Some(items) = schema.get("items") {
return extract_schema_properties_map(items);
}
}
None
}