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}