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 {
36 return schema_or_ref_to_ir_type(&schema.all_of[0]);
37 }
38 let parts: Vec<IrType> = schema
39 .all_of
40 .iter()
41 .map(|sub| match sub {
42 SchemaOrRef::Ref { .. } => schema_or_ref_to_ir_type(sub),
43 SchemaOrRef::Schema(s) => {
44 if s.properties.is_empty() {
45 schema_to_ir_type(s)
46 } else {
47 let fields: Vec<(String, IrType, bool)> = s
48 .properties
49 .iter()
50 .map(|(name, prop)| {
51 (
52 name.clone(),
53 schema_or_ref_to_ir_type(prop),
54 s.required.contains(name),
55 )
56 })
57 .collect();
58 IrType::Object(fields)
59 }
60 }
61 })
62 .collect();
63 return IrType::Intersection(parts);
64 }
65
66 if !schema.enum_values.is_empty() {
68 let string_variants: Vec<String> = schema
69 .enum_values
70 .iter()
71 .filter_map(|v| v.as_str().map(|s| s.to_string()))
72 .collect();
73 if string_variants.len() == 1 {
74 return IrType::StringLiteral(string_variants.into_iter().next().unwrap());
75 }
76 if string_variants.len() > 1 {
77 return IrType::Union(
78 string_variants
79 .into_iter()
80 .map(IrType::StringLiteral)
81 .collect(),
82 );
83 }
84 return IrType::String; }
86
87 if let Some(ref val) = schema.const_value {
89 if let Some(s) = val.as_str() {
90 return IrType::StringLiteral(s.to_string());
91 }
92 return IrType::String;
93 }
94
95 match &schema.schema_type {
97 Some(TypeSet::Single(t)) => match t {
98 SchemaType::String => match schema.format.as_deref() {
99 Some("date-time" | "date") => IrType::DateTime,
100 Some("binary" | "byte") => IrType::Binary,
101 _ => IrType::String,
102 },
103 SchemaType::Number => IrType::Number,
104 SchemaType::Integer => IrType::Integer,
105 SchemaType::Boolean => IrType::Boolean,
106 SchemaType::Null => IrType::Null,
107 SchemaType::Array => match &schema.items {
108 Some(items) => IrType::Array(Box::new(schema_or_ref_to_ir_type(items))),
109 None => IrType::Array(Box::new(IrType::Any)),
110 },
111 SchemaType::Object => resolve_object_type(schema),
112 },
113 Some(TypeSet::Multiple(types)) => {
114 let non_null: Vec<_> = types.iter().filter(|t| **t != SchemaType::Null).collect();
115 let has_null = types.contains(&SchemaType::Null);
116 if non_null.len() == 1 {
117 let single = Schema {
118 schema_type: Some(TypeSet::Single(non_null[0].clone())),
119 ..schema.clone()
120 };
121 let base = schema_to_ir_type(&single);
122 if has_null {
123 IrType::Union(vec![base, IrType::Null])
124 } else {
125 base
126 }
127 } else if non_null.is_empty() && has_null {
128 IrType::Null
129 } else {
130 let mut variants: Vec<IrType> = non_null
132 .iter()
133 .map(|t| {
134 let s = Schema {
135 schema_type: Some(TypeSet::Single((*t).clone())),
136 ..schema.clone()
137 };
138 schema_to_ir_type(&s)
139 })
140 .collect();
141 if has_null {
142 variants.push(IrType::Null);
143 }
144 IrType::Union(variants)
145 }
146 }
147 None => {
148 if !schema.properties.is_empty() {
150 resolve_object_type(schema)
151 } else if schema.items.is_some() {
152 match &schema.items {
153 Some(items) => IrType::Array(Box::new(schema_or_ref_to_ir_type(items))),
154 None => IrType::Array(Box::new(IrType::Any)),
155 }
156 } else {
157 IrType::Any
158 }
159 }
160 }
161}
162
163fn resolve_object_type(schema: &Schema) -> IrType {
164 if schema.properties.is_empty() {
165 match &schema.additional_properties {
166 Some(AdditionalProperties::Schema(s)) => {
167 IrType::Map(Box::new(schema_or_ref_to_ir_type(s)))
168 }
169 Some(AdditionalProperties::Bool(true)) => IrType::Map(Box::new(IrType::Any)),
170 Some(AdditionalProperties::Bool(false)) | None => IrType::Any,
171 }
172 } else {
173 let fields: Vec<(String, IrType, bool)> = schema
174 .properties
175 .iter()
176 .map(|(name, prop)| {
177 let required = schema.required.contains(name);
178 (name.clone(), schema_or_ref_to_ir_type(prop), required)
179 })
180 .collect();
181 IrType::Object(fields)
182 }
183}
184
185pub fn schema_or_ref_to_ir_schema(
187 name: &str,
188 schema_or_ref: &SchemaOrRef,
189) -> Result<IrSchema, TransformError> {
190 match schema_or_ref {
191 SchemaOrRef::Ref { ref_path } => {
192 let target = ref_path.rsplit('/').next().unwrap_or("Unknown");
193 Ok(IrSchema::Alias(IrAliasSchema {
194 name: normalize_name(name),
195 description: None,
196 target: IrType::Ref(normalize_name(target).pascal_case),
197 }))
198 }
199 SchemaOrRef::Schema(schema) => schema_to_ir_schema(name, schema),
200 }
201}
202
203pub fn schema_to_ir_schema(name: &str, schema: &Schema) -> Result<IrSchema, TransformError> {
205 let normalized = normalize_name(name);
206
207 if !schema.enum_values.is_empty() {
209 let variants: Vec<String> = schema
210 .enum_values
211 .iter()
212 .filter_map(|v| v.as_str().map(|s| s.to_string()))
213 .collect();
214 return Ok(IrSchema::Enum(IrEnumSchema {
215 name: normalized,
216 description: schema.description.clone(),
217 variants,
218 }));
219 }
220
221 if !schema.one_of.is_empty() || !schema.any_of.is_empty() {
223 let variants_src = if !schema.one_of.is_empty() {
224 &schema.one_of
225 } else {
226 &schema.any_of
227 };
228 let variants: Vec<IrType> = variants_src.iter().map(schema_or_ref_to_ir_type).collect();
229 let discriminator = schema.discriminator.as_ref().map(|d| IrDiscriminator {
230 property_name: d.property_name.clone(),
231 mapping: d
232 .mapping
233 .iter()
234 .map(|(k, v)| (k.clone(), v.clone()))
235 .collect(),
236 });
237 return Ok(IrSchema::Union(IrUnionSchema {
238 name: normalized,
239 description: schema.description.clone(),
240 variants,
241 discriminator,
242 }));
243 }
244
245 if !schema.all_of.is_empty() {
247 let has_refs = schema
248 .all_of
249 .iter()
250 .any(|s| matches!(s, SchemaOrRef::Ref { .. }));
251 if has_refs {
252 let mut parts: Vec<IrType> = schema
254 .all_of
255 .iter()
256 .map(|sub| match sub {
257 SchemaOrRef::Ref { .. } => schema_or_ref_to_ir_type(sub),
258 SchemaOrRef::Schema(s) => {
259 let fields = build_fields(&s.properties, &s.required);
260 if fields.is_empty() {
261 schema_to_ir_type(s)
262 } else {
263 let inline_fields: Vec<(String, IrType, bool)> = fields
264 .into_iter()
265 .map(|f| (f.original_name, f.field_type, f.required))
266 .collect();
267 IrType::Object(inline_fields)
268 }
269 }
270 })
271 .collect();
272 if !schema.properties.is_empty() {
274 let extra_fields = build_fields(&schema.properties, &schema.required);
275 let inline_fields: Vec<(String, IrType, bool)> = extra_fields
276 .into_iter()
277 .map(|f| (f.original_name, f.field_type, f.required))
278 .collect();
279 parts.push(IrType::Object(inline_fields));
280 }
281 return Ok(IrSchema::Alias(IrAliasSchema {
282 name: normalized,
283 description: schema.description.clone(),
284 target: IrType::Intersection(parts),
285 }));
286 }
287 let merged = merge_all_of(&schema.all_of, &schema.properties, &schema.required);
289 return Ok(IrSchema::Object(IrObjectSchema {
290 name: normalized,
291 description: schema.description.clone(),
292 fields: merged,
293 additional_properties: None,
294 }));
295 }
296
297 match &schema.schema_type {
299 Some(TypeSet::Single(SchemaType::Object)) | None if !schema.properties.is_empty() => {
300 let fields = build_fields(&schema.properties, &schema.required);
302 let additional = schema
303 .additional_properties
304 .as_ref()
305 .and_then(|ap| match ap {
306 AdditionalProperties::Schema(s) => Some(schema_or_ref_to_ir_type(s)),
307 AdditionalProperties::Bool(true) => Some(IrType::Any),
308 _ => None,
309 });
310 Ok(IrSchema::Object(IrObjectSchema {
311 name: normalized,
312 description: schema.description.clone(),
313 fields,
314 additional_properties: additional,
315 }))
316 }
317 _ => {
318 let target = schema_to_ir_type(schema);
320 Ok(IrSchema::Alias(IrAliasSchema {
321 name: normalized,
322 description: schema.description.clone(),
323 target,
324 }))
325 }
326 }
327}
328
329fn build_fields(properties: &IndexMap<String, SchemaOrRef>, required: &[String]) -> Vec<IrField> {
330 properties
331 .iter()
332 .map(|(name, prop)| {
333 let (description, read_only, write_only) = match prop {
334 SchemaOrRef::Schema(s) => (
335 s.description.clone(),
336 s.read_only.unwrap_or(false),
337 s.write_only.unwrap_or(false),
338 ),
339 _ => (None, false, false),
340 };
341 IrField {
342 name: normalize_name(name),
343 original_name: name.clone(),
344 field_type: schema_or_ref_to_ir_type(prop),
345 required: required.contains(name),
346 description,
347 read_only,
348 write_only,
349 }
350 })
351 .collect()
352}
353
354fn merge_all_of(
355 all_of: &[SchemaOrRef],
356 extra_properties: &IndexMap<String, SchemaOrRef>,
357 extra_required: &[String],
358) -> Vec<IrField> {
359 let mut fields = Vec::new();
360
361 for item in all_of {
362 if let SchemaOrRef::Schema(schema) = item {
363 fields.extend(build_fields(&schema.properties, &schema.required));
364 if !schema.all_of.is_empty() {
366 fields.extend(merge_all_of(&schema.all_of, &IndexMap::new(), &[]));
367 }
368 }
369 }
370
371 fields.extend(build_fields(extra_properties, extra_required));
373
374 fields
375}