openapi_model_generator/
parser.rs

1use crate::{models::{Model, Field, RequestModel, ResponseModel}, Result};
2use openapiv3::{OpenAPI, Schema, ReferenceOr, SchemaKind, Type, VariantOrUnknownOrEmpty, StringFormat};
3
4pub fn parse_openapi(openapi: &OpenAPI) -> Result<(Vec<Model>, Vec<RequestModel>, Vec<ResponseModel>)> {
5    let mut models = Vec::new();
6    let mut requests = Vec::new();
7    let mut responses = Vec::new();
8
9    // Parse components/schemas
10    if let Some(components) = &openapi.components {
11        for (name, schema) in &components.schemas {
12            if let Some(model) = parse_schema(name, schema)? {
13                models.push(model);
14            }
15        }
16    }
17
18    // Parse paths
19    for (_path, path_item) in openapi.paths.iter() {
20        let path_item = match path_item {
21            ReferenceOr::Item(item) => item,
22            ReferenceOr::Reference { .. } => continue,
23        };
24
25        if let Some(op) = &path_item.get {
26            process_operation(op, &mut requests, &mut responses)?;
27        }
28        if let Some(op) = &path_item.post {
29            process_operation(op, &mut requests, &mut responses)?;
30        }
31        if let Some(op) = &path_item.put {
32            process_operation(op, &mut requests, &mut responses)?;
33        }
34        if let Some(op) = &path_item.delete {
35            process_operation(op, &mut requests, &mut responses)?;
36        }
37        if let Some(op) = &path_item.patch {
38            process_operation(op, &mut requests, &mut responses)?;
39        }
40    }
41
42    Ok((models, requests, responses))
43}
44
45fn process_operation(
46    operation: &openapiv3::Operation,
47    requests: &mut Vec<RequestModel>,
48    responses: &mut Vec<ResponseModel>,
49) -> Result<()> {
50    // Parse request body
51    if let Some(request_body_ref) = &operation.request_body {
52        if let ReferenceOr::Item(request_body) = request_body_ref {
53            for (content_type, media_type) in &request_body.content {
54                if let Some(schema) = &media_type.schema {
55                    let request = RequestModel {
56                        name: format!("{}Request", operation.operation_id.as_deref().unwrap_or("Unknown")),
57                        content_type: content_type.clone(),
58                        schema: extract_type(schema)?,
59                        is_required: request_body.required,
60                    };
61                    requests.push(request);
62                }
63            }
64        }
65    }
66
67    // Parse responses
68    for (status, response_ref) in operation.responses.responses.iter() {
69        if let ReferenceOr::Item(response) = response_ref {
70            for (content_type, media_type) in &response.content {
71                if let Some(schema) = &media_type.schema {
72                    let response = ResponseModel {
73                        name: format!("{}Response", operation.operation_id.as_deref().unwrap_or("Unknown")),
74                        status_code: status.to_string(),
75                        content_type: content_type.clone(),
76                        schema: extract_type(schema)?,
77                        description: Some(response.description.clone()),
78                    };
79                    responses.push(response);
80                }
81            }
82        }
83    }
84
85    Ok(())
86}
87
88fn parse_schema(name: &str, schema: &ReferenceOr<Schema>) -> Result<Option<Model>> {
89    match schema {
90        ReferenceOr::Reference { .. } => Ok(None),
91        ReferenceOr::Item(schema) => {
92            if let SchemaKind::Type(Type::Object(obj)) = &schema.schema_kind {
93                let mut fields = Vec::new();
94                for (field_name, field_schema) in &obj.properties {
95                    let field_type = match field_schema {
96                        ReferenceOr::Item(boxed_schema) => {
97                            extract_type(&ReferenceOr::Item((**boxed_schema).clone()))?
98                        },
99                        ReferenceOr::Reference { reference } => {
100                            extract_type(&ReferenceOr::Reference { reference: reference.clone() })?
101                        }
102                    };
103                    let is_required = obj.required.contains(field_name);
104                    fields.push(Field {
105                        name: field_name.clone(),
106                        field_type,
107                        is_required,
108                    });
109                }
110                Ok(Some(Model {
111                    name: name.to_string(),
112                    fields,
113                }))
114            } else {
115                Ok(None)
116            }
117        }
118    }
119}
120
121fn extract_type(schema: &ReferenceOr<Schema>) -> Result<String> {
122    match schema {
123        ReferenceOr::Reference { reference } => {
124            let type_name = reference.split('/').last().unwrap_or("Unknown");
125            Ok(type_name.to_string())
126        }
127        ReferenceOr::Item(schema) => {
128            match &schema.schema_kind {
129                SchemaKind::Type(Type::String(string_type)) => {
130                    match &string_type.format {
131                        VariantOrUnknownOrEmpty::Item(fmt) => match fmt {
132                            StringFormat::DateTime => Ok("DateTime<Utc>".to_string()),
133                            StringFormat::Date => Ok("NaiveDate".to_string()),
134                            _ => Ok("String".to_string()),
135                        },
136                        _ => Ok("String".to_string()),
137                    }
138                }
139                SchemaKind::Type(Type::Integer(_)) => Ok("i64".to_string()),
140                SchemaKind::Type(Type::Number(_)) => Ok("f64".to_string()),
141                SchemaKind::Type(Type::Boolean {}) => Ok("bool".to_string()),
142                SchemaKind::Type(Type::Array(arr)) => {
143                    if let Some(items) = &arr.items {
144                        let items_ref: &ReferenceOr<Box<Schema>> = &*items;
145                        let inner_type = match items_ref {
146                            ReferenceOr::Item(boxed_schema) => extract_type(&ReferenceOr::Item((**boxed_schema).clone()))?,
147                            ReferenceOr::Reference { reference } => extract_type(&ReferenceOr::Reference { reference: reference.clone() })?,
148                        };
149                        Ok(format!("Vec<{}>", inner_type))
150                    } else {
151                        Ok("Vec<serde_json::Value>".to_string())
152                    }
153                }
154                SchemaKind::Type(Type::Object(obj)) => {
155                    if obj.properties.is_empty() {
156                        Ok("()".to_string())
157                    } else {
158                        Ok("serde_json::Value".to_string())
159                    }
160                }
161                _ => Ok("serde_json::Value".to_string()),
162            }
163        }
164    }
165}