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 f16 f32 f64 f128) => {
79            ReferenceOr::Item(Schema {
80                schema_data,
81                schema_kind: SchemaKind::Type(Type::Number(NumberType::default())), // TODO: Configure number type. Ts: `number`
82            })
83        }
84        primitive_def!(i64 u64 i128 u128) => ReferenceOr::Item(Schema {
85            schema_data,
86            schema_kind: SchemaKind::Type(Type::Number(NumberType::default())), // TODO: Configure number type. Ts: `bigint`
87        }),
88        primitive_def!(str char) => ReferenceOr::Item(Schema {
89            schema_data,
90            schema_kind: SchemaKind::Type(Type::String(StringType::default())), // TODO: Configure string type. Ts: `string`
91        }),
92        primitive_def!(bool) => ReferenceOr::Item(Schema {
93            schema_data,
94            schema_kind: SchemaKind::Type(Type::Boolean(BooleanType::default())),
95        }),
96        // primitive_def!(Never) => "never".into(),
97        DataType::Nullable(def) => {
98            to_openapi(def)
99            // schema.schema_data.nullable = true; // TODO
100        }
101        // DataType::Map(def) => {
102        //     format!("Record<{}, {}>", to_openapi(&def.0), to_openapi(&def.1))
103        // }
104        DataType::List(def) => ReferenceOr::Item(Schema {
105            schema_data,
106            schema_kind: SchemaKind::Type(Type::Array(ArrayType {
107                items: Some(match to_openapi(def.ty()) {
108                    ReferenceOr::Item(schema) => ReferenceOr::Item(Box::new(schema)),
109                    ReferenceOr::Reference { reference } => ReferenceOr::Reference { reference },
110                }),
111                // TODO: This type is missing `Default`
112                min_items: None,
113                max_items: None,
114                unique_items: false,
115            })),
116        }),
117        DataType::Tuple(tuple) => match tuple.elements() {
118            [] => {
119                schema_data.nullable = true;
120                ReferenceOr::Item(Schema {
121                    schema_data,
122                    schema_kind: SchemaKind::Type(Type::Object(openapiv3::ObjectType::default())), // TODO: This should be `null` type
123                })
124            }
125            [ty] => to_openapi(ty),
126            _tys => todo!(),
127        },
128        DataType::Struct(s) => {
129            let _fields = s.fields();
130
131            // match &fields[..] {
132            //     [] => todo!(), // "null".to_string(),
133            //     fields => {
134            //         // let mut out = match tag {
135            //         //     Some(tag) => vec![format!("{tag}: \"{name}\"")],
136            //         //     None => vec![],
137            //         // };
138
139            //         // let field_defs = object_fields(fields);
140
141            //         // out.extend(field_defs);
142
143            //         // format!("{{ {} }}", out.join(", "))
144
145            //         ReferenceOr::Item(Schema {
146            //             schema_data,
147            //             schema_kind: SchemaKind::Type(Type::Object(openapiv3::ObjectType {
148            //                 properties: fields
149            //                     .iter()
150            //                     .map(
151            //                         |ObjectField {
152            //                              name, ty, optional, ..
153            //                          }| {
154            //                             (
155            //                                 name.clone(),
156            //                                 match to_openapi(ty) {
157            //                                     ReferenceOr::Item(v) => {
158            //                                         ReferenceOr::Item(Box::new(v))
159            //                                     }
160            //                                     ReferenceOr::Reference { reference } => {
161            //                                         ReferenceOr::Reference { reference }
162            //                                     }
163            //                                 },
164            //                             )
165            //                         },
166            //                     )
167            //                     .collect(),
168            //                 ..Default::default()
169            //             })),
170            //         })
171            //     }
172            // }
173            todo!();
174        }
175        DataType::Enum(_e) => {
176            // let variants = e.variants();
177
178            // match &variants[..] {
179            //     [] => todo!(), // "never".to_string(),
180            //     variants => {
181            //         // variants
182            //         // .iter()
183            //         // .map(|variant| {
184            //         //     let sanitised_name = sanitise_name(variant.name());
185
186            //         //     match (repr, variant) {
187            //         //         (EnumRepr::Internal { tag }, Variant::Unit(_)) => {
188            //         //             format!("{{ {tag}: \"{sanitised_name}\" }}")
189            //         //         }
190            //         //         (EnumRepr::Internal { tag }, Variant::Unnamed(tuple)) => {
191            //         //             let typ = to_openapi(&DataType::Tuple(tuple.clone()));
192
193            //         //             format!("{{ {tag}: \"{sanitised_name}\" }} & {typ}")
194            //         //         }
195            //         //         (EnumRepr::Internal { tag }, Variant::Named(obj)) => {
196            //         //             let mut fields = vec![format!("{tag}: \"{sanitised_name}\"")];
197
198            //         //             fields.extend(object_fields(&obj.fields));
199
200            //         //             format!("{{ {} }}", fields.join(", "))
201            //         //         }
202            //         //         (EnumRepr::External, Variant::Unit(_)) => {
203            //         //             format!("\"{sanitised_name}\"")
204            //         //         }
205            //         //         (EnumRepr::External, v) => {
206            //         //             let ts_values = to_openapi(&v.data_type());
207
208            //         //             format!("{{ {sanitised_name}: {ts_values} }}")
209            //         //         }
210            //         //         (EnumRepr::Untagged, Variant::Unit(_)) => "null".to_string(),
211            //         //         (EnumRepr::Untagged, v) => to_openapi(&v.data_type()),
212            //         //         (EnumRepr::Adjacent { tag, .. }, Variant::Unit(_)) => {
213            //         //             format!("{{ {tag}: \"{sanitised_name}\" }}")
214            //         //         }
215            //         //         (EnumRepr::Adjacent { tag, content }, v) => {
216            //         //             let ts_values = to_openapi(&v.data_type());
217
218            //         //             format!("{{ {tag}: \"{sanitised_name}\", {content}: {ts_values} }}")
219            //         //         }
220            //         //     }
221            //         // })
222            //         // .collect::<Vec<_>>()
223            //         // .join(" | ");
224
225            //         ReferenceOr::Item(Schema {
226            //             schema_data,
227            //             schema_kind: SchemaKind::AnyOf {
228            //                 any_of: variants
229            //                     .iter()
230            //                     .map(|variant| match variant {
231            //                         EnumVariants::Unit(_) => ReferenceOr::Item(Schema {
232            //                             schema_data: Default::default(),
233            //                             schema_kind: SchemaKind::Type(Type::Object(
234            //                                 openapiv3::ObjectType::default(), // TODO: Is this correct?
235            //                             )),
236            //                         }),
237            //                         EnumVariants::Unnamed(tuple) => {
238            //                             to_openapi(&DataType::Tuple(tuple.clone()))
239            //                         }
240            //                         Variant::Named(obj) => {
241            //                             to_openapi(&DataType::Struct(obj.clone()))
242            //                         }
243            //                     })
244            //                     .collect(),
245            //             },
246            //         })
247            //     }
248            // }
249
250            todo!();
251        }
252        DataType::Reference(_reference) => {
253            todo!();
254            // match &reference.generics()[..] {
255            //     [] => {
256            //         todo!();
257            //         // ReferenceOr::Item(Schema {
258            //         //     schema_data,
259            //         //     schema_kind: SchemaKind::OneOf {
260            //         //         one_of: vec![ReferenceOr::Reference {
261            //         //             reference: format!("#/components/schemas/{}", reference.name()),
262            //         //         }],
263            //         //     },
264            //         // })
265            //     }
266            //     generics => {
267            //         // let generics = generics
268            //         //     .iter()
269            //         //     .map(to_openapi)
270            //         //     .collect::<Vec<_>>()
271            //         //     .join(", ");
272
273            //         // format!("{name}<{generics}>")
274            //         todo!();
275            //     }
276            // }
277        }
278        // DataType::Generic(ident) => ident.to_string(),
279        x => {
280            println!("{:?} {:?}", x, typ);
281            todo!();
282        }
283    }
284}