Skip to main content

specta_openapi/
lib.rs

1//! [OpenAPI](https://www.openapis.org) language exporter for [Specta](specta).
2//!
3//! <div class="warning">
4//! This crate is still in active development and is not yet ready for general purpose use!
5//! </div>
6//!
7#![cfg_attr(docsrs, feature(doc_cfg))]
8#![doc(
9    html_logo_url = "https://github.com/specta-rs/specta/raw/main/.github/logo-128.png",
10    html_favicon_url = "https://github.com/specta-rs/specta/raw/main/.github/logo-128.png"
11)]
12#![allow(warnings)] // TODO: This crate is still in development
13
14use openapiv3::{
15    ArrayType, BooleanType, NumberType, ReferenceOr, Schema, SchemaData, SchemaKind, StringType,
16    Type,
17};
18use specta::datatype::{DataType, Primitive};
19
20// pub fn to_openapi_export(def: &DataType) -> Result<openapiv3::Schema, String> {
21//     Ok(match &def {
22//         // Named struct
23//         // DataType::Struct(StructType {
24//         //     name,
25//         //     generics,
26//         //     fields,
27//         //     ..
28//         // }) => match fields.len() {
29//         //     0 => format!("export type {name} = {inline_ts}"),
30//         //     _ => {
31//         //         let generics = match generics.len() {
32//         //             0 => "".into(),
33//         //             _ => format!("<{}>", generics.to_vec().join(", ")),
34//         //         };
35
36//         //         format!("export interface {name}{generics} {inline_ts}")
37//         //     }
38//         // },
39//         // // Enum
40//         // DataType::Enum(EnumType { name, generics, .. }) => {
41//         //     let generics = match generics.len() {
42//         //         0 => "".into(),
43//         //         _ => format!("<{}>", generics.to_vec().join(", ")),
44//         //     };
45
46//         //     format!("export type {name}{generics} = {inline_ts}")
47//         // }
48//         // // Unnamed struct
49//         // DataType::Tuple(TupleType { name, .. }) => {
50//         //     format!("export type {name} = {inline_ts}")
51//         // }
52//         _ => todo!(), // return Err(format!("Type cannot be exported: {:?}", def)),
53//     })
54// }
55
56macro_rules! primitive_def {
57    ($($t:ident)+) => {
58        $(DataType::Primitive(Primitive::$t))|+
59    }
60}
61
62pub fn to_openapi(typ: &DataType) -> ReferenceOr<Schema> {
63    let mut schema_data = SchemaData {
64        nullable: false,
65        deprecated: false, // TODO
66        example: None,     // TODO
67        title: None,       // TODO
68        description: None, // TODO
69        default: None,     // TODO
70        ..Default::default()
71    };
72
73    match &typ {
74        // DataType::Any => ReferenceOr::Item(Schema {
75        //     schema_data,
76        //     schema_kind: SchemaKind::Type(Type::Object(openapiv3::ObjectType::default())), // TODO: Use official "Any Type"
77        // }),
78        primitive_def!(i8 i16 i32 isize u8 u16 u32 usize f32 f64) => ReferenceOr::Item(Schema {
79            schema_data,
80            schema_kind: SchemaKind::Type(Type::Number(NumberType::default())), // TODO: Configure number type. Ts: `number`
81        }),
82        primitive_def!(i64 u64 i128 u128) => ReferenceOr::Item(Schema {
83            schema_data,
84            schema_kind: SchemaKind::Type(Type::Number(NumberType::default())), // TODO: Configure number type. Ts: `bigint`
85        }),
86        primitive_def!(String char) => ReferenceOr::Item(Schema {
87            schema_data,
88            schema_kind: SchemaKind::Type(Type::String(StringType::default())), // TODO: Configure string type. Ts: `string`
89        }),
90        primitive_def!(bool) => ReferenceOr::Item(Schema {
91            schema_data,
92            schema_kind: SchemaKind::Type(Type::Boolean(BooleanType::default())),
93        }),
94        // primitive_def!(Never) => "never".into(),
95        DataType::Nullable(def) => {
96            to_openapi(def)
97            // schema.schema_data.nullable = true; // TODO
98        }
99        // DataType::Map(def) => {
100        //     format!("Record<{}, {}>", to_openapi(&def.0), to_openapi(&def.1))
101        // }
102        DataType::List(def) => ReferenceOr::Item(Schema {
103            schema_data,
104            schema_kind: SchemaKind::Type(Type::Array(ArrayType {
105                items: Some(match to_openapi(def.ty()) {
106                    ReferenceOr::Item(schema) => ReferenceOr::Item(Box::new(schema)),
107                    ReferenceOr::Reference { reference } => ReferenceOr::Reference { reference },
108                }),
109                // TODO: This type is missing `Default`
110                min_items: None,
111                max_items: None,
112                unique_items: false,
113            })),
114        }),
115        DataType::Tuple(tuple) => match tuple.elements() {
116            [] => {
117                schema_data.nullable = true;
118                ReferenceOr::Item(Schema {
119                    schema_data,
120                    schema_kind: SchemaKind::Type(Type::Object(openapiv3::ObjectType::default())), // TODO: This should be `null` type
121                })
122            }
123            [ty] => to_openapi(ty),
124            _tys => todo!(),
125        },
126        DataType::Struct(s) => {
127            let _fields = s.fields();
128
129            // match &fields[..] {
130            //     [] => todo!(), // "null".to_string(),
131            //     fields => {
132            //         // let mut out = match tag {
133            //         //     Some(tag) => vec![format!("{tag}: \"{name}\"")],
134            //         //     None => vec![],
135            //         // };
136
137            //         // let field_defs = object_fields(fields);
138
139            //         // out.extend(field_defs);
140
141            //         // format!("{{ {} }}", out.join(", "))
142
143            //         ReferenceOr::Item(Schema {
144            //             schema_data,
145            //             schema_kind: SchemaKind::Type(Type::Object(openapiv3::ObjectType {
146            //                 properties: fields
147            //                     .iter()
148            //                     .map(
149            //                         |ObjectField {
150            //                              name, ty, optional, ..
151            //                          }| {
152            //                             (
153            //                                 name.clone(),
154            //                                 match to_openapi(ty) {
155            //                                     ReferenceOr::Item(v) => {
156            //                                         ReferenceOr::Item(Box::new(v))
157            //                                     }
158            //                                     ReferenceOr::Reference { reference } => {
159            //                                         ReferenceOr::Reference { reference }
160            //                                     }
161            //                                 },
162            //                             )
163            //                         },
164            //                     )
165            //                     .collect(),
166            //                 ..Default::default()
167            //             })),
168            //         })
169            //     }
170            // }
171            todo!();
172        }
173        DataType::Enum(_e) => {
174            // let variants = e.variants();
175
176            // match &variants[..] {
177            //     [] => todo!(), // "never".to_string(),
178            //     variants => {
179            //         // variants
180            //         // .iter()
181            //         // .map(|variant| {
182            //         //     let sanitised_name = sanitise_name(variant.name());
183
184            //         //     match (repr, variant) {
185            //         //         (EnumRepr::Internal { tag }, EnumVariant::Unit(_)) => {
186            //         //             format!("{{ {tag}: \"{sanitised_name}\" }}")
187            //         //         }
188            //         //         (EnumRepr::Internal { tag }, EnumVariant::Unnamed(tuple)) => {
189            //         //             let typ = to_openapi(&DataType::Tuple(tuple.clone()));
190
191            //         //             format!("{{ {tag}: \"{sanitised_name}\" }} & {typ}")
192            //         //         }
193            //         //         (EnumRepr::Internal { tag }, EnumVariant::Named(obj)) => {
194            //         //             let mut fields = vec![format!("{tag}: \"{sanitised_name}\"")];
195
196            //         //             fields.extend(object_fields(&obj.fields));
197
198            //         //             format!("{{ {} }}", fields.join(", "))
199            //         //         }
200            //         //         (EnumRepr::External, EnumVariant::Unit(_)) => {
201            //         //             format!("\"{sanitised_name}\"")
202            //         //         }
203            //         //         (EnumRepr::External, v) => {
204            //         //             let ts_values = to_openapi(&v.data_type());
205
206            //         //             format!("{{ {sanitised_name}: {ts_values} }}")
207            //         //         }
208            //         //         (EnumRepr::Untagged, EnumVariant::Unit(_)) => "null".to_string(),
209            //         //         (EnumRepr::Untagged, v) => to_openapi(&v.data_type()),
210            //         //         (EnumRepr::Adjacent { tag, .. }, EnumVariant::Unit(_)) => {
211            //         //             format!("{{ {tag}: \"{sanitised_name}\" }}")
212            //         //         }
213            //         //         (EnumRepr::Adjacent { tag, content }, v) => {
214            //         //             let ts_values = to_openapi(&v.data_type());
215
216            //         //             format!("{{ {tag}: \"{sanitised_name}\", {content}: {ts_values} }}")
217            //         //         }
218            //         //     }
219            //         // })
220            //         // .collect::<Vec<_>>()
221            //         // .join(" | ");
222
223            //         ReferenceOr::Item(Schema {
224            //             schema_data,
225            //             schema_kind: SchemaKind::AnyOf {
226            //                 any_of: variants
227            //                     .iter()
228            //                     .map(|variant| match variant {
229            //                         EnumVariants::Unit(_) => ReferenceOr::Item(Schema {
230            //                             schema_data: Default::default(),
231            //                             schema_kind: SchemaKind::Type(Type::Object(
232            //                                 openapiv3::ObjectType::default(), // TODO: Is this correct?
233            //                             )),
234            //                         }),
235            //                         EnumVariants::Unnamed(tuple) => {
236            //                             to_openapi(&DataType::Tuple(tuple.clone()))
237            //                         }
238            //                         EnumVariant::Named(obj) => {
239            //                             to_openapi(&DataType::Struct(obj.clone()))
240            //                         }
241            //                     })
242            //                     .collect(),
243            //             },
244            //         })
245            //     }
246            // }
247
248            todo!();
249        }
250        DataType::Reference(_reference) => {
251            todo!();
252            // match &reference.generics()[..] {
253            //     [] => {
254            //         todo!();
255            //         // ReferenceOr::Item(Schema {
256            //         //     schema_data,
257            //         //     schema_kind: SchemaKind::OneOf {
258            //         //         one_of: vec![ReferenceOr::Reference {
259            //         //             reference: format!("#/components/schemas/{}", reference.name()),
260            //         //         }],
261            //         //     },
262            //         // })
263            //     }
264            //     generics => {
265            //         // let generics = generics
266            //         //     .iter()
267            //         //     .map(to_openapi)
268            //         //     .collect::<Vec<_>>()
269            //         //     .join(", ");
270
271            //         // format!("{name}<{generics}>")
272            //         todo!();
273            //     }
274            // }
275        }
276        // DataType::Generic(ident) => ident.to_string(),
277        x => {
278            println!("{:?} {:?}", x, typ);
279            todo!();
280        }
281    }
282}