openapi_codegen/
lib.rs

1pub mod client;
2
3pub use client::client;
4use heck::{CamelCase, SnakeCase};
5use lazy_static::lazy_static;
6use openapiv3::ParameterData;
7use openapiv3::RequestBody;
8use openapiv3::Schema as SchemaV3;
9use openapiv3::SchemaVariant;
10use openapiv3::{IntegerFormat, NumberFormat, ReferenceOr, StringFormat, VariantOrUnknownOrEmpty};
11use regex::Regex;
12use serde_derive::Serialize;
13use std::borrow::Borrow;
14use std::fmt;
15
16lazy_static! {
17    static ref STRICT_KEYWORDS: [&'static str; 36] = [
18        "as", "break", "const", "continue", "crate", "dyn", "else", "enum", "extern", "false",
19        "fn", "for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub",
20        "ref", "return", "Self", "self", "static", "struct", "super", "trait", "true", "type",
21        "unsafe", "use", "where", "while",
22    ];
23}
24
25lazy_static! {
26    static ref RESERVED_KEYWORDS: [&'static str; 15] = [
27        "abstract", "async", "await", "become", "box", "do", "final", "macro", "override", "priv",
28        "try", "typeof", "unsized", "virtual", "yield",
29    ];
30}
31
32lazy_static! {
33    static ref RAW_INCOMPATIBLE_KEYWORDS: [&'static str; 5] =
34        ["crate", "extern", "self", "Self", "super"];
35}
36
37lazy_static! {
38    static ref INVALID_PATTERNS: Regex = Regex::new(r"[^a-zA-Z0-9_]").unwrap();
39}
40
41/// These are potentically keywords, so should be prefixed with r# for safety
42#[derive(Debug, Serialize)]
43pub struct RustSnakeIdentifier(String);
44
45impl From<String> for RustSnakeIdentifier {
46    fn from(s: String) -> Self {
47        let identifier = INVALID_PATTERNS.replace_all(&s, " ").to_snake_case();
48
49        if RAW_INCOMPATIBLE_KEYWORDS.contains(&identifier.borrow()) {
50            RustSnakeIdentifier(format!("{}_", identifier))
51        } else {
52            RustSnakeIdentifier(identifier)
53        }
54    }
55}
56
57impl fmt::Display for RustSnakeIdentifier {
58    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
59        write!(f, "{}", self.0)
60    }
61}
62
63/// These are potentically keywords, so should be prefixed with r# for safety
64#[derive(Debug, Serialize)]
65pub struct RustPascalIdentifier(String);
66
67impl From<String> for RustPascalIdentifier {
68    fn from(s: String) -> Self {
69        let identifier = INVALID_PATTERNS.replace_all(&s, "_").to_camel_case();
70
71        if RAW_INCOMPATIBLE_KEYWORDS.contains(&identifier.borrow()) {
72            RustPascalIdentifier(format!("{}_", identifier))
73        } else {
74            RustPascalIdentifier(identifier)
75        }
76    }
77}
78
79impl fmt::Display for RustPascalIdentifier {
80    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
81        write!(f, "{}", self.0)
82    }
83}
84
85#[derive(Debug, Serialize)]
86pub struct RustType(String);
87
88impl From<&SchemaVariant> for RustType {
89    fn from(schema_variant: &SchemaVariant) -> RustType {
90        // https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#dataTypes
91        RustType(match schema_variant {
92            SchemaVariant::String { format, .. } => match format {
93                VariantOrUnknownOrEmpty::Item(StringFormat::Date) => "String",
94                VariantOrUnknownOrEmpty::Item(StringFormat::DateTime) => "String",
95                VariantOrUnknownOrEmpty::Item(StringFormat::Password) => "String",
96                VariantOrUnknownOrEmpty::Item(StringFormat::Byte) => "String",
97                VariantOrUnknownOrEmpty::Item(StringFormat::Binary) => "String",
98                VariantOrUnknownOrEmpty::Unknown(_) => "String",
99                VariantOrUnknownOrEmpty::Empty => "String",
100            }
101            .into(),
102            SchemaVariant::Number { format, .. } => match format {
103                VariantOrUnknownOrEmpty::Item(NumberFormat::Float) => "f32",
104                VariantOrUnknownOrEmpty::Item(NumberFormat::Double) => "f64",
105                VariantOrUnknownOrEmpty::Unknown(_) => "f32",
106                VariantOrUnknownOrEmpty::Empty => "f32",
107            }
108            .into(),
109            SchemaVariant::Integer { format, .. } => match format {
110                VariantOrUnknownOrEmpty::Item(IntegerFormat::Int32) => "i32",
111                VariantOrUnknownOrEmpty::Item(IntegerFormat::Int64) => "i64",
112                VariantOrUnknownOrEmpty::Unknown(_) => "i32",
113                VariantOrUnknownOrEmpty::Empty => "i32",
114            }
115            .into(),
116            SchemaVariant::Object { .. } => "Value".into(),
117            SchemaVariant::Array { items, .. } => format!("Vec<{}>", RustType::from(items)),
118            SchemaVariant::Boolean { .. } => "bool".into(),
119        })
120    }
121}
122
123impl From<&ReferenceOr<Box<SchemaV3>>> for RustType {
124    fn from(reference_or_schema: &ReferenceOr<Box<SchemaV3>>) -> RustType {
125        match reference_or_schema {
126            ReferenceOr::Reference { reference } => {
127                let type_name = reference
128                    .trim_start_matches("#/components/schemas/")
129                    .trim_start_matches("#/components/requestBodies/")
130                    .to_camel_case();
131                RustType(format!("{}", type_name))
132            }
133            ReferenceOr::Item(schema) => match schema.borrow() {
134                SchemaV3::Schema(schema_variant) => {
135                    (schema_variant.borrow() as &SchemaVariant).into()
136                }
137                SchemaV3::Any(any_schema) => {
138                    // struct
139                    dbg!(any_schema);
140                    unimplemented!()
141                }
142                SchemaV3::OneOf { one_of } => {
143                    if one_of.is_empty() {
144                        RustType("Value".into())
145                    } else {
146                        unimplemented!()
147                    }
148                }
149                _ => unimplemented!(),
150            },
151        }
152    }
153}
154
155impl From<&ReferenceOr<SchemaV3>> for RustType {
156    fn from(reference_or_schema: &ReferenceOr<SchemaV3>) -> RustType {
157        match reference_or_schema {
158            ReferenceOr::Reference { reference } => {
159                let type_name = reference
160                    .trim_start_matches("#/components/schemas/")
161                    .trim_start_matches("#/components/requestBodies/")
162                    .to_camel_case();
163                RustType(format!("{}", type_name))
164            }
165            ReferenceOr::Item(schema) => match schema {
166                SchemaV3::Schema(schema_variant) => {
167                    (schema_variant.borrow() as &SchemaVariant).into()
168                }
169                SchemaV3::Any(any_schema) => {
170                    // struct
171                    dbg!(any_schema);
172                    unimplemented!()
173                }
174                SchemaV3::OneOf { one_of } => {
175                    if one_of.is_empty() {
176                        RustType("Value".into())
177                    } else {
178                        unimplemented!()
179                    }
180                }
181                _ => unimplemented!(),
182            },
183        }
184    }
185}
186
187impl From<&ParameterData> for RustType {
188    fn from(parameter_data: &ParameterData) -> RustType {
189        match &parameter_data.format {
190            openapiv3::ParameterSchemaOrContent::Content(_) => unimplemented!(),
191            openapiv3::ParameterSchemaOrContent::Schema(ref reference_or_schema) => {
192                reference_or_schema.into()
193            }
194        }
195    }
196}
197
198impl From<&ReferenceOr<RequestBody>> for RustType {
199    fn from(reference_or_requestbody: &ReferenceOr<RequestBody>) -> RustType {
200        match reference_or_requestbody {
201            ReferenceOr::Reference { reference } => {
202                let type_name = reference
203                    .trim_start_matches("#/components/schemas/")
204                    .trim_start_matches("#/components/requestBodies/")
205                    .to_camel_case();
206                RustType(format!("{}", type_name))
207            }
208            ReferenceOr::Item(requestbody) => match requestbody.content.get("application/json") {
209                Some(mediatype) => match mediatype.schema {
210                    Some(ref reference_or_schema) => reference_or_schema.into(),
211                    None => unimplemented!(),
212                },
213                None => {
214                    dbg!(requestbody);
215                    unimplemented!()
216                }
217            },
218        }
219    }
220}
221
222impl RustType {
223    pub fn borrowed(&self) -> RustType {
224        RustType(match self.0.as_str() {
225            "String" => "&str".into(),
226            x => format!("&{}", x),
227        })
228    }
229}
230
231impl fmt::Display for RustType {
232    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
233        write!(f, "{}", self.0)
234    }
235}