use crate::generative_schema::{EntityDefinition, RouteDefinition};
use crate::OpenApiSpec;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
#[derive(Debug, Clone)]
pub struct SchemaPreview {
pub spec: OpenApiSpec,
pub spec_json: Value,
pub entities: Vec<EntityDefinition>,
pub routes: Vec<RouteDefinition>,
pub metadata: GenerationMetadata,
}
impl Serialize for SchemaPreview {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeStruct;
let mut state = serializer.serialize_struct("SchemaPreview", 5)?;
state.serialize_field("spec_json", &self.spec_json)?;
state.serialize_field("entities", &self.entities)?;
state.serialize_field("routes", &self.routes)?;
state.serialize_field("metadata", &self.metadata)?;
state.end()
}
}
impl<'de> Deserialize<'de> for SchemaPreview {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::{self, MapAccess, Visitor};
use std::fmt;
struct SchemaPreviewVisitor;
impl<'de> Visitor<'de> for SchemaPreviewVisitor {
type Value = SchemaPreview;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("struct SchemaPreview")
}
fn visit_map<V>(self, mut map: V) -> Result<SchemaPreview, V::Error>
where
V: MapAccess<'de>,
{
let mut spec_json: Option<Value> = None;
let mut entities: Option<Vec<EntityDefinition>> = None;
let mut routes: Option<Vec<RouteDefinition>> = None;
let mut metadata: Option<GenerationMetadata> = None;
while let Some(key) = map.next_key()? {
match key {
"spec_json" => {
if spec_json.is_some() {
return Err(de::Error::duplicate_field("spec_json"));
}
spec_json = Some(map.next_value()?);
}
"entities" => {
if entities.is_some() {
return Err(de::Error::duplicate_field("entities"));
}
entities = Some(map.next_value()?);
}
"routes" => {
if routes.is_some() {
return Err(de::Error::duplicate_field("routes"));
}
routes = Some(map.next_value()?);
}
"metadata" => {
if metadata.is_some() {
return Err(de::Error::duplicate_field("metadata"));
}
metadata = Some(map.next_value()?);
}
_ => {
let _ = map.next_value::<de::IgnoredAny>()?;
}
}
}
let spec_json = spec_json.ok_or_else(|| de::Error::missing_field("spec_json"))?;
let entities = entities.ok_or_else(|| de::Error::missing_field("entities"))?;
let routes = routes.ok_or_else(|| de::Error::missing_field("routes"))?;
let metadata = metadata.ok_or_else(|| de::Error::missing_field("metadata"))?;
let spec = OpenApiSpec::from_json(spec_json.clone()).map_err(|e| {
de::Error::custom(format!("Failed to parse OpenAPI spec: {}", e))
})?;
Ok(SchemaPreview {
spec,
spec_json,
entities,
routes,
metadata,
})
}
}
deserializer.deserialize_map(SchemaPreviewVisitor)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GenerationMetadata {
pub entity_count: usize,
pub route_count: usize,
pub relationship_count: usize,
pub generated_at: String,
}
pub struct SchemaBuilder;
impl SchemaBuilder {
pub fn build_spec(
entities: Vec<EntityDefinition>,
routes: Vec<RouteDefinition>,
title: Option<String>,
version: Option<String>,
) -> Result<OpenApiSpec, crate::Error> {
let mut spec_json = json!({
"openapi": "3.0.0",
"info": {
"title": title.unwrap_or_else(|| "Generated API".to_string()),
"version": version.unwrap_or_else(|| "1.0.0".to_string()),
},
"paths": {},
"components": {
"schemas": {}
}
});
if let Some(paths) = spec_json.get_mut("paths").and_then(|p| p.as_object_mut()) {
for route in &routes {
let path_entry = paths.entry(route.path.clone()).or_insert_with(|| json!({}));
if let Some(path_obj) = path_entry.as_object_mut() {
let method = route.method.to_lowercase();
path_obj.insert(method.clone(), json!({
"summary": route.description,
"operationId": format!("{}_{}", method, route.entity.to_lowercase()),
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": format!("#/components/schemas/{}", route.entity)
}
}
}
}
}
}));
}
}
}
if let Some(schemas) = spec_json
.get_mut("components")
.and_then(|c| c.as_object_mut())
.and_then(|c| c.get_mut("schemas"))
.and_then(|s| s.as_object_mut())
{
for entity in &entities {
schemas.insert(entity.name.clone(), entity.schema.clone());
}
}
OpenApiSpec::from_json(spec_json)
}
pub fn create_preview(
entities: Vec<EntityDefinition>,
routes: Vec<RouteDefinition>,
title: Option<String>,
version: Option<String>,
) -> Result<SchemaPreview, crate::Error> {
let spec = Self::build_spec(entities.clone(), routes.clone(), title, version)?;
let relationship_count = entities.iter().map(|e| e.relationships.len()).sum::<usize>();
let metadata = GenerationMetadata {
entity_count: entities.len(),
route_count: routes.len(),
relationship_count,
generated_at: chrono::Utc::now().to_rfc3339(),
};
let spec_json = if let Some(ref raw) = spec.raw_document {
raw.clone()
} else {
serde_json::to_value(&spec.spec).map_err(|e| {
crate::Error::config(format!("Failed to serialize OpenAPI spec: {}", e))
})?
};
Ok(SchemaPreview {
spec,
spec_json,
entities,
routes,
metadata,
})
}
}