amalgam_parser/
openapi.rs

1//! OpenAPI/JSON Schema parser
2
3use crate::{Parser, ParserError};
4use amalgam_core::{
5    ir::{IRBuilder, IR},
6    types::{Field, Type},
7};
8use openapiv3::{OpenAPI, Schema, SchemaKind, Type as OpenAPIType};
9use std::collections::BTreeMap;
10
11pub struct OpenAPIParser;
12
13impl Parser for OpenAPIParser {
14    type Input = OpenAPI;
15
16    fn parse(&self, input: Self::Input) -> Result<IR, ParserError> {
17        let mut builder = IRBuilder::new().module("openapi");
18
19        // Parse components/schemas
20        if let Some(components) = input.components {
21            for (name, schema_ref) in components.schemas {
22                if let openapiv3::ReferenceOr::Item(schema) = schema_ref {
23                    let ty = self.schema_to_type(&schema)?;
24                    builder = builder.add_type(name, ty);
25                }
26            }
27        }
28
29        Ok(builder.build())
30    }
31}
32
33impl OpenAPIParser {
34    pub fn new() -> Self {
35        Self
36    }
37
38    #[allow(clippy::only_used_in_recursion)]
39    fn schema_to_type(&self, schema: &Schema) -> Result<Type, ParserError> {
40        match &schema.schema_kind {
41            SchemaKind::Type(OpenAPIType::String(_)) => Ok(Type::String),
42            SchemaKind::Type(OpenAPIType::Number(_)) => Ok(Type::Number),
43            SchemaKind::Type(OpenAPIType::Integer(_)) => Ok(Type::Integer),
44            SchemaKind::Type(OpenAPIType::Boolean(_)) => Ok(Type::Bool),
45            SchemaKind::Type(OpenAPIType::Array(array_type)) => {
46                let item_type = array_type
47                    .items
48                    .as_ref()
49                    .and_then(|i| i.as_item())
50                    .map(|s| self.schema_to_type(s))
51                    .transpose()?
52                    .unwrap_or(Type::Any);
53                Ok(Type::Array(Box::new(item_type)))
54            }
55            SchemaKind::Type(OpenAPIType::Object(object_type)) => {
56                let mut fields = BTreeMap::new();
57                for (field_name, field_schema_ref) in &object_type.properties {
58                    if let openapiv3::ReferenceOr::Item(field_schema) = field_schema_ref {
59                        let field_type = self.schema_to_type(field_schema)?;
60                        let required = object_type.required.contains(field_name);
61                        fields.insert(
62                            field_name.clone(),
63                            Field {
64                                ty: field_type,
65                                required,
66                                description: field_schema.schema_data.description.clone(),
67                                default: None,
68                            },
69                        );
70                    }
71                }
72                Ok(Type::Record {
73                    fields,
74                    open: object_type.additional_properties.is_some(),
75                })
76            }
77            SchemaKind::OneOf { one_of } => {
78                let mut types = Vec::new();
79                for schema_ref in one_of {
80                    if let openapiv3::ReferenceOr::Item(schema) = schema_ref {
81                        types.push(self.schema_to_type(schema)?);
82                    }
83                }
84                Ok(Type::Union(types))
85            }
86            SchemaKind::AllOf { all_of: _ } => {
87                // For now, treat as Any - would need more complex merging
88                Ok(Type::Any)
89            }
90            SchemaKind::AnyOf { any_of } => {
91                let mut types = Vec::new();
92                for schema_ref in any_of {
93                    if let openapiv3::ReferenceOr::Item(schema) = schema_ref {
94                        types.push(self.schema_to_type(schema)?);
95                    }
96                }
97                Ok(Type::Union(types))
98            }
99            SchemaKind::Not { .. } => {
100                Err(ParserError::UnsupportedFeature("'not' schema".to_string()))
101            }
102            SchemaKind::Any(_) => Ok(Type::Any),
103        }
104    }
105}
106
107impl Default for OpenAPIParser {
108    fn default() -> Self {
109        Self::new()
110    }
111}