specta-openapi 0.0.3

Export your Rust types to OpenAPI
Documentation
//! [OpenAPI](https://www.openapis.org) language exporter for [Specta](specta).
//!
//! <div class="warning">
//! This crate is still in active development and is not yet ready for general purpose use!
//! </div>
//!
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc(
    html_logo_url = "https://github.com/specta-rs/specta/raw/main/.github/logo-128.png",
    html_favicon_url = "https://github.com/specta-rs/specta/raw/main/.github/logo-128.png"
)]
#![allow(warnings)] // TODO: This crate is still in development

use openapiv3::{
    ArrayType, BooleanType, NumberType, ReferenceOr, Schema, SchemaData, SchemaKind, StringType,
    Type,
};
use specta::datatype::{DataType, Primitive};

// pub fn to_openapi_export(def: &DataType) -> Result<openapiv3::Schema, String> {
//     Ok(match &def {
//         // Named struct
//         // DataType::Struct(StructType {
//         //     name,
//         //     generics,
//         //     fields,
//         //     ..
//         // }) => match fields.len() {
//         //     0 => format!("export type {name} = {inline_ts}"),
//         //     _ => {
//         //         let generics = match generics.len() {
//         //             0 => "".into(),
//         //             _ => format!("<{}>", generics.to_vec().join(", ")),
//         //         };

//         //         format!("export interface {name}{generics} {inline_ts}")
//         //     }
//         // },
//         // // Enum
//         // DataType::Enum(EnumType { name, generics, .. }) => {
//         //     let generics = match generics.len() {
//         //         0 => "".into(),
//         //         _ => format!("<{}>", generics.to_vec().join(", ")),
//         //     };

//         //     format!("export type {name}{generics} = {inline_ts}")
//         // }
//         // // Unnamed struct
//         // DataType::Tuple(TupleType { name, .. }) => {
//         //     format!("export type {name} = {inline_ts}")
//         // }
//         _ => todo!(), // return Err(format!("Type cannot be exported: {:?}", def)),
//     })
// }

macro_rules! primitive_def {
    ($($t:ident)+) => {
        $(DataType::Primitive(Primitive::$t))|+
    }
}

pub fn to_openapi(typ: &DataType) -> ReferenceOr<Schema> {
    let mut schema_data = SchemaData {
        nullable: false,
        deprecated: false, // TODO
        example: None,     // TODO
        title: None,       // TODO
        description: None, // TODO
        default: None,     // TODO
        ..Default::default()
    };

    match &typ {
        // DataType::Any => ReferenceOr::Item(Schema {
        //     schema_data,
        //     schema_kind: SchemaKind::Type(Type::Object(openapiv3::ObjectType::default())), // TODO: Use official "Any Type"
        // }),
        primitive_def!(i8 i16 i32 isize u8 u16 u32 usize f16 f32 f64 f128) => {
            ReferenceOr::Item(Schema {
                schema_data,
                schema_kind: SchemaKind::Type(Type::Number(NumberType::default())), // TODO: Configure number type. Ts: `number`
            })
        }
        primitive_def!(i64 u64 i128 u128) => ReferenceOr::Item(Schema {
            schema_data,
            schema_kind: SchemaKind::Type(Type::Number(NumberType::default())), // TODO: Configure number type. Ts: `bigint`
        }),
        primitive_def!(str char) => ReferenceOr::Item(Schema {
            schema_data,
            schema_kind: SchemaKind::Type(Type::String(StringType::default())), // TODO: Configure string type. Ts: `string`
        }),
        primitive_def!(bool) => ReferenceOr::Item(Schema {
            schema_data,
            schema_kind: SchemaKind::Type(Type::Boolean(BooleanType::default())),
        }),
        // primitive_def!(Never) => "never".into(),
        DataType::Nullable(def) => match to_openapi(def) {
            ReferenceOr::Item(mut schema) => {
                schema.schema_data.nullable = true;
                ReferenceOr::Item(schema)
            }
            reference => reference,
        },
        // DataType::Map(def) => {
        //     format!("Record<{}, {}>", to_openapi(&def.0), to_openapi(&def.1))
        // }
        DataType::List(def) => ReferenceOr::Item(Schema {
            schema_data,
            schema_kind: SchemaKind::Type(Type::Array(ArrayType {
                items: Some(match to_openapi(&def.ty) {
                    ReferenceOr::Item(schema) => ReferenceOr::Item(Box::new(schema)),
                    ReferenceOr::Reference { reference } => ReferenceOr::Reference { reference },
                }),
                // TODO: This type is missing `Default`
                min_items: None,
                max_items: None,
                unique_items: false,
            })),
        }),
        DataType::Tuple(tuple) => match tuple.elements.as_slice() {
            [] => {
                schema_data.nullable = true;
                ReferenceOr::Item(Schema {
                    schema_data,
                    schema_kind: SchemaKind::Type(Type::Object(openapiv3::ObjectType::default())), // TODO: This should be `null` type
                })
            }
            [ty] => to_openapi(ty),
            _tys => todo!(),
        },
        DataType::Struct(s) => {
            let _fields = &s.fields;

            // match &fields[..] {
            //     [] => todo!(), // "null".to_string(),
            //     fields => {
            //         // let mut out = match tag {
            //         //     Some(tag) => vec![format!("{tag}: \"{name}\"")],
            //         //     None => vec![],
            //         // };

            //         // let field_defs = object_fields(fields);

            //         // out.extend(field_defs);

            //         // format!("{{ {} }}", out.join(", "))

            //         ReferenceOr::Item(Schema {
            //             schema_data,
            //             schema_kind: SchemaKind::Type(Type::Object(openapiv3::ObjectType {
            //                 properties: fields
            //                     .iter()
            //                     .map(
            //                         |ObjectField {
            //                              name, ty, optional, ..
            //                          }| {
            //                             (
            //                                 name.clone(),
            //                                 match to_openapi(ty) {
            //                                     ReferenceOr::Item(v) => {
            //                                         ReferenceOr::Item(Box::new(v))
            //                                     }
            //                                     ReferenceOr::Reference { reference } => {
            //                                         ReferenceOr::Reference { reference }
            //                                     }
            //                                 },
            //                             )
            //                         },
            //                     )
            //                     .collect(),
            //                 ..Default::default()
            //             })),
            //         })
            //     }
            // }
            todo!();
        }
        DataType::Enum(_e) => {
            // let variants = e.variants();

            // match &variants[..] {
            //     [] => todo!(), // "never".to_string(),
            //     variants => {
            //         // variants
            //         // .iter()
            //         // .map(|variant| {
            //         //     let sanitised_name = sanitise_name(variant.name());

            //         //     match (repr, variant) {
            //         //         (EnumRepr::Internal { tag }, Variant::Unit(_)) => {
            //         //             format!("{{ {tag}: \"{sanitised_name}\" }}")
            //         //         }
            //         //         (EnumRepr::Internal { tag }, Variant::Unnamed(tuple)) => {
            //         //             let typ = to_openapi(&DataType::Tuple(tuple.clone()));

            //         //             format!("{{ {tag}: \"{sanitised_name}\" }} & {typ}")
            //         //         }
            //         //         (EnumRepr::Internal { tag }, Variant::Named(obj)) => {
            //         //             let mut fields = vec![format!("{tag}: \"{sanitised_name}\"")];

            //         //             fields.extend(object_fields(&obj.fields));

            //         //             format!("{{ {} }}", fields.join(", "))
            //         //         }
            //         //         (EnumRepr::External, Variant::Unit(_)) => {
            //         //             format!("\"{sanitised_name}\"")
            //         //         }
            //         //         (EnumRepr::External, v) => {
            //         //             let ts_values = to_openapi(&v.data_type());

            //         //             format!("{{ {sanitised_name}: {ts_values} }}")
            //         //         }
            //         //         (EnumRepr::Untagged, Variant::Unit(_)) => "null".to_string(),
            //         //         (EnumRepr::Untagged, v) => to_openapi(&v.data_type()),
            //         //         (EnumRepr::Adjacent { tag, .. }, Variant::Unit(_)) => {
            //         //             format!("{{ {tag}: \"{sanitised_name}\" }}")
            //         //         }
            //         //         (EnumRepr::Adjacent { tag, content }, v) => {
            //         //             let ts_values = to_openapi(&v.data_type());

            //         //             format!("{{ {tag}: \"{sanitised_name}\", {content}: {ts_values} }}")
            //         //         }
            //         //     }
            //         // })
            //         // .collect::<Vec<_>>()
            //         // .join(" | ");

            //         ReferenceOr::Item(Schema {
            //             schema_data,
            //             schema_kind: SchemaKind::AnyOf {
            //                 any_of: variants
            //                     .iter()
            //                     .map(|variant| match variant {
            //                         EnumVariants::Unit(_) => ReferenceOr::Item(Schema {
            //                             schema_data: Default::default(),
            //                             schema_kind: SchemaKind::Type(Type::Object(
            //                                 openapiv3::ObjectType::default(), // TODO: Is this correct?
            //                             )),
            //                         }),
            //                         EnumVariants::Unnamed(tuple) => {
            //                             to_openapi(&DataType::Tuple(tuple.clone()))
            //                         }
            //                         Variant::Named(obj) => {
            //                             to_openapi(&DataType::Struct(obj.clone()))
            //                         }
            //                     })
            //                     .collect(),
            //             },
            //         })
            //     }
            // }

            todo!();
        }
        DataType::Reference(_reference) => {
            todo!();
            // match &reference.generics()[..] {
            //     [] => {
            //         todo!();
            //         // ReferenceOr::Item(Schema {
            //         //     schema_data,
            //         //     schema_kind: SchemaKind::OneOf {
            //         //         one_of: vec![ReferenceOr::Reference {
            //         //             reference: format!("#/components/schemas/{}", reference.name()),
            //         //         }],
            //         //     },
            //         // })
            //     }
            //     generics => {
            //         // let generics = generics
            //         //     .iter()
            //         //     .map(to_openapi)
            //         //     .collect::<Vec<_>>()
            //         //     .join(", ");

            //         // format!("{name}<{generics}>")
            //         todo!();
            //     }
            // }
        }
        // DataType::Generic(ident) => ident.to_string(),
        x => {
            println!("{:?} {:?}", x, typ);
            todo!();
        }
    }
}