1use indexmap::IndexMap;
2
3use crate::error::TransformError;
4use crate::ir::{
5 IrAliasSchema, IrDiscriminator, IrEnumSchema, IrField, IrObjectSchema, IrSchema, IrType,
6 IrUnionSchema,
7};
8use crate::parse::schema::{AdditionalProperties, Schema, SchemaOrRef, SchemaType, TypeSet};
9
10use super::name_normalizer::normalize_name;
11
12pub fn schema_or_ref_to_ir_type(schema_or_ref: &SchemaOrRef) -> IrType {
14 match schema_or_ref {
15 SchemaOrRef::Ref { ref_path } => {
16 let name = ref_path.rsplit('/').next().unwrap_or("Unknown");
17 IrType::Ref(normalize_name(name).pascal_case)
18 }
19 SchemaOrRef::Schema(schema) => schema_to_ir_type(schema),
20 }
21}
22
23pub fn schema_to_ir_type(schema: &Schema) -> IrType {
25 if !schema.one_of.is_empty() {
27 let variants: Vec<IrType> = schema.one_of.iter().map(schema_or_ref_to_ir_type).collect();
28 return IrType::Union(variants);
29 }
30 if !schema.any_of.is_empty() {
31 let variants: Vec<IrType> = schema.any_of.iter().map(schema_or_ref_to_ir_type).collect();
32 return IrType::Union(variants);
33 }
34 if !schema.all_of.is_empty() {
35 if schema.all_of.len() == 1 {
37 return schema_or_ref_to_ir_type(&schema.all_of[0]);
38 }
39 let mut merged_fields = Vec::new();
41 for sub in &schema.all_of {
42 match sub {
43 SchemaOrRef::Schema(s) => {
44 for (name, prop) in &s.properties {
45 let required = s.required.contains(name);
46 merged_fields.push((
47 name.clone(),
48 schema_or_ref_to_ir_type(prop),
49 required,
50 ));
51 }
52 }
53 SchemaOrRef::Ref { .. } => {
54 return schema_or_ref_to_ir_type(sub);
56 }
57 }
58 }
59 if !merged_fields.is_empty() {
60 return IrType::Object(merged_fields);
61 }
62 }
63
64 if !schema.enum_values.is_empty() {
66 return IrType::String; }
68
69 if schema.const_value.is_some() {
71 return IrType::String;
72 }
73
74 match &schema.schema_type {
76 Some(TypeSet::Single(t)) => match t {
77 SchemaType::String => match schema.format.as_deref() {
78 Some("date-time" | "date") => IrType::DateTime,
79 Some("binary" | "byte") => IrType::Binary,
80 _ => IrType::String,
81 },
82 SchemaType::Number => IrType::Number,
83 SchemaType::Integer => IrType::Integer,
84 SchemaType::Boolean => IrType::Boolean,
85 SchemaType::Null => IrType::Null,
86 SchemaType::Array => match &schema.items {
87 Some(items) => IrType::Array(Box::new(schema_or_ref_to_ir_type(items))),
88 None => IrType::Array(Box::new(IrType::Any)),
89 },
90 SchemaType::Object => resolve_object_type(schema),
91 },
92 Some(TypeSet::Multiple(types)) => {
93 let non_null: Vec<_> = types.iter().filter(|t| **t != SchemaType::Null).collect();
94 if non_null.len() == 1 {
95 let single = Schema {
97 schema_type: Some(TypeSet::Single(non_null[0].clone())),
98 ..schema.clone()
99 };
100 schema_to_ir_type(&single)
101 } else {
102 IrType::Any
103 }
104 }
105 None => {
106 if !schema.properties.is_empty() {
108 resolve_object_type(schema)
109 } else if schema.items.is_some() {
110 match &schema.items {
111 Some(items) => IrType::Array(Box::new(schema_or_ref_to_ir_type(items))),
112 None => IrType::Array(Box::new(IrType::Any)),
113 }
114 } else {
115 IrType::Any
116 }
117 }
118 }
119}
120
121fn resolve_object_type(schema: &Schema) -> IrType {
122 if schema.properties.is_empty() {
123 match &schema.additional_properties {
125 Some(AdditionalProperties::Schema(s)) => {
126 IrType::Map(Box::new(schema_or_ref_to_ir_type(s)))
127 }
128 Some(AdditionalProperties::Bool(true)) | None => {
129 if schema.properties.is_empty() && schema.additional_properties.is_some() {
130 IrType::Map(Box::new(IrType::Any))
131 } else {
132 IrType::Any
133 }
134 }
135 Some(AdditionalProperties::Bool(false)) => IrType::Any,
136 }
137 } else {
138 let fields: Vec<(String, IrType, bool)> = schema
139 .properties
140 .iter()
141 .map(|(name, prop)| {
142 let required = schema.required.contains(name);
143 (name.clone(), schema_or_ref_to_ir_type(prop), required)
144 })
145 .collect();
146 IrType::Object(fields)
147 }
148}
149
150pub fn schema_or_ref_to_ir_schema(
152 name: &str,
153 schema_or_ref: &SchemaOrRef,
154) -> Result<IrSchema, TransformError> {
155 match schema_or_ref {
156 SchemaOrRef::Ref { ref_path } => {
157 let target = ref_path.rsplit('/').next().unwrap_or("Unknown");
158 Ok(IrSchema::Alias(IrAliasSchema {
159 name: normalize_name(name),
160 description: None,
161 target: IrType::Ref(normalize_name(target).pascal_case),
162 }))
163 }
164 SchemaOrRef::Schema(schema) => schema_to_ir_schema(name, schema),
165 }
166}
167
168pub fn schema_to_ir_schema(name: &str, schema: &Schema) -> Result<IrSchema, TransformError> {
170 let normalized = normalize_name(name);
171
172 if !schema.enum_values.is_empty() {
174 let variants: Vec<String> = schema
175 .enum_values
176 .iter()
177 .filter_map(|v| v.as_str().map(|s| s.to_string()))
178 .collect();
179 return Ok(IrSchema::Enum(IrEnumSchema {
180 name: normalized,
181 description: schema.description.clone(),
182 variants,
183 }));
184 }
185
186 if !schema.one_of.is_empty() || !schema.any_of.is_empty() {
188 let variants_src = if !schema.one_of.is_empty() {
189 &schema.one_of
190 } else {
191 &schema.any_of
192 };
193 let variants: Vec<IrType> = variants_src.iter().map(schema_or_ref_to_ir_type).collect();
194 let discriminator = schema.discriminator.as_ref().map(|d| IrDiscriminator {
195 property_name: d.property_name.clone(),
196 mapping: d
197 .mapping
198 .iter()
199 .map(|(k, v)| (k.clone(), v.clone()))
200 .collect(),
201 });
202 return Ok(IrSchema::Union(IrUnionSchema {
203 name: normalized,
204 description: schema.description.clone(),
205 variants,
206 discriminator,
207 }));
208 }
209
210 if !schema.all_of.is_empty() {
212 let merged = merge_all_of(&schema.all_of, &schema.properties, &schema.required);
213 return Ok(IrSchema::Object(IrObjectSchema {
214 name: normalized,
215 description: schema.description.clone(),
216 fields: merged,
217 additional_properties: None,
218 }));
219 }
220
221 match &schema.schema_type {
223 Some(TypeSet::Single(SchemaType::Object)) | None if !schema.properties.is_empty() => {
224 let fields = build_fields(&schema.properties, &schema.required);
226 let additional = schema
227 .additional_properties
228 .as_ref()
229 .and_then(|ap| match ap {
230 AdditionalProperties::Schema(s) => Some(schema_or_ref_to_ir_type(s)),
231 AdditionalProperties::Bool(true) => Some(IrType::Any),
232 _ => None,
233 });
234 Ok(IrSchema::Object(IrObjectSchema {
235 name: normalized,
236 description: schema.description.clone(),
237 fields,
238 additional_properties: additional,
239 }))
240 }
241 _ => {
242 let target = schema_to_ir_type(schema);
244 Ok(IrSchema::Alias(IrAliasSchema {
245 name: normalized,
246 description: schema.description.clone(),
247 target,
248 }))
249 }
250 }
251}
252
253fn build_fields(properties: &IndexMap<String, SchemaOrRef>, required: &[String]) -> Vec<IrField> {
254 properties
255 .iter()
256 .map(|(name, prop)| {
257 let (description, read_only, write_only) = match prop {
258 SchemaOrRef::Schema(s) => (
259 s.description.clone(),
260 s.read_only.unwrap_or(false),
261 s.write_only.unwrap_or(false),
262 ),
263 _ => (None, false, false),
264 };
265 IrField {
266 name: normalize_name(name),
267 original_name: name.clone(),
268 field_type: schema_or_ref_to_ir_type(prop),
269 required: required.contains(name),
270 description,
271 read_only,
272 write_only,
273 }
274 })
275 .collect()
276}
277
278fn merge_all_of(
279 all_of: &[SchemaOrRef],
280 extra_properties: &IndexMap<String, SchemaOrRef>,
281 extra_required: &[String],
282) -> Vec<IrField> {
283 let mut fields = Vec::new();
284
285 for item in all_of {
286 if let SchemaOrRef::Schema(schema) = item {
287 fields.extend(build_fields(&schema.properties, &schema.required));
288 if !schema.all_of.is_empty() {
290 fields.extend(merge_all_of(&schema.all_of, &IndexMap::new(), &[]));
291 }
292 }
293 }
294
295 fields.extend(build_fields(extra_properties, extra_required));
297
298 fields
299}