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