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}