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}