use crate::{
dtos::{items_type::ItemsType, schema_type::SchemaType},
entities::{DepthTracker, ReferenceResolver},
};
use mycelium_base::utils::errors::{execution_err, MappedErrors};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use utoipa::ToSchema;
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct Schema {
#[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
pub reference: Option<String>,
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub r#type: Option<SchemaType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub format: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub nullable: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub required: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[schema(no_recursion)]
pub properties: Option<HashMap<String, Schema>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub items: Option<Box<ItemsType>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enum_values: Option<Vec<serde_json::Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default: Option<serde_json::Value>,
}
impl ReferenceResolver for Schema {
fn resolve_ref(
&self,
components: &serde_json::Value,
depth_tracker: &mut DepthTracker,
) -> Result<serde_json::Value, MappedErrors> {
if depth_tracker.should_stop() {
return Ok(depth_tracker.empty_value());
}
depth_tracker.increment();
let mut value = serde_json::to_value(self).map_err(|e| {
execution_err(format!("Failed to serialize schema: {e}"))
})?;
if let Some(reference) = &self.reference {
let ref_path = reference.split('/').collect::<Vec<&str>>();
let element_definition = if ref_path.len() > 2 {
ref_path[ref_path.len() - 2]
} else {
return Ok(depth_tracker.empty_value());
};
let element_name = ref_path.last().ok_or(execution_err(format!(
"Failed to resolve schema ref. Unable to get the component name from reference: {reference}"
)))?;
let ref_value = components
.get(element_definition)
.and_then(|schema| schema.get(element_name))
.ok_or(
execution_err(format!(
"Failed to resolve schema ref: {element_name}"
))
.with_exp_true(),
)?;
return Ok(ref_value.clone());
}
if let Some(properties) = &self.properties {
for (key, schema) in properties {
let resolved_value =
schema.resolve_ref(components, depth_tracker)?;
value["properties"][key] = resolved_value;
}
}
if let Some(items) = &self.items {
let resolved_value =
items.resolve_ref(components, depth_tracker)?;
value["items"] = resolved_value;
}
Ok(value)
}
}