openapi-codegen 0.1.0

Automatically generate rust HTTP clients from OpenAPI files.
Documentation
use crate::{RustPascalIdentifier, RustSnakeIdentifier, RustType};
use openapiv3::Schema;
use openapiv3::{AnySchema, ReferenceOr, SchemaVariant};
use serde_derive::Serialize;
use std::borrow::Borrow;

#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum DataType {
    Struct(Struct),
    Enum(Enum),
    NewType(NewType),
}

impl From<(String, ReferenceOr<Schema>)> for DataType {
    fn from((name, reference_or_schema): (String, ReferenceOr<Schema>)) -> Self {
        match reference_or_schema {
            ReferenceOr::Reference { .. } => unimplemented!(),
            ReferenceOr::Item(item) => match item {
                Schema::Any(any_schema) => {
                    DataType::Struct((name, any_schema.borrow() as &AnySchema).into())
                }
                Schema::Schema(schema_variant) => match schema_variant.borrow() {
                    SchemaVariant::String { .. } => {
                        DataType::Enum((name, schema_variant.borrow()).into())
                    }
                    SchemaVariant::Object { .. } => {
                        DataType::Struct((name, schema_variant.borrow() as &SchemaVariant).into())
                    }
                    SchemaVariant::Array { .. } => {
                        DataType::NewType((name, schema_variant.borrow() as &SchemaVariant).into())
                    }

                    e => {
                        dbg!(e);
                        unimplemented!()
                    }
                },
                _ => unimplemented!(),
            },
        }
    }
}

#[derive(Debug, Serialize)]
pub struct Struct {
    pub pascal_id: RustPascalIdentifier,
    pub snake_id: RustSnakeIdentifier,
    pub optional_fields: Vec<Field>,
    pub required_fields: Vec<Field>,
}

impl From<(String, &SchemaVariant)> for Struct {
    fn from((name, schema_variant): (String, &SchemaVariant)) -> Self {
        if let SchemaVariant::Object {
            properties,
            required,
            ..
        } = schema_variant
        {
            Struct {
                pascal_id: name.clone().into(),
                snake_id: name.into(),
                optional_fields: properties
                    .into_iter()
                    .filter(|(name, _)| !required.contains(name))
                    .map(|entry| entry.into())
                    .collect::<Vec<Field>>(),
                required_fields: properties
                    .into_iter()
                    .filter(|(name, _)| required.contains(name))
                    .map(|entry| entry.into())
                    .collect::<Vec<Field>>(),
            }
        } else {
            unimplemented!()
        }
    }
}

impl From<(String, &AnySchema)> for Struct {
    fn from((name, any_schema): (String, &AnySchema)) -> Self {
        Struct {
            pascal_id: name.clone().into(),
            snake_id: name.into(),
            optional_fields: any_schema
                .properties
                .iter()
                .filter(|(name, _)| !any_schema.required.contains(name))
                .map(|entry| entry.into())
                .collect::<Vec<Field>>(),
            required_fields: any_schema
                .properties
                .iter()
                .filter(|(name, _)| any_schema.required.contains(name))
                .map(|entry| entry.into())
                .collect::<Vec<Field>>(),
        }
    }
}

#[derive(Debug, Serialize)]
pub struct NewType {
    pub pascal_id: RustPascalIdentifier,
    pub snake_id: RustSnakeIdentifier,
    pub inner: Field,
}

impl From<(String, &SchemaVariant)> for NewType {
    fn from((name, schema_variant): (String, &SchemaVariant)) -> Self {
        if let SchemaVariant::Array { items, .. } = schema_variant {
            let mut inner: Field = (&String::from("inner"), items).into();
            inner.r#type = RustType(format!("Vec<{}>", inner.r#type.0));
            inner.borrowed_type = inner.r#type.borrowed();

            NewType {
                pascal_id: name.clone().into(),
                snake_id: name.into(),
                inner,
            }
        } else {
            unimplemented!()
        }
    }
}

#[derive(Debug, Serialize)]
pub struct Field {
    pub api_id: String,
    pub snake_id: RustSnakeIdentifier,
    pub r#type: RustType,
    pub borrowed_type: RustType,
    pub nullable: bool,
    pub default: String,
}

impl From<(&String, &ReferenceOr<Box<Schema>>)> for Field {
    fn from((name, reference_or_schema): (&String, &ReferenceOr<Box<Schema>>)) -> Self {
        let r#type: RustType = reference_or_schema.into();

        Field {
            api_id: name.to_owned(),
            snake_id: name.to_owned().into(),
            borrowed_type: r#type.borrowed(),
            nullable: match reference_or_schema {
                ReferenceOr::Reference { .. } => false,
                ReferenceOr::Item(schema) => match schema.borrow() {
                    Schema::Schema(schema_variant) => match schema_variant.borrow() {
                        SchemaVariant::String { schema_data, .. }
                        | SchemaVariant::Number { schema_data, .. }
                        | SchemaVariant::Integer { schema_data, .. }
                        | SchemaVariant::Object { schema_data, .. }
                        | SchemaVariant::Array { schema_data, .. }
                        | SchemaVariant::Boolean { schema_data, .. } => schema_data.nullable,
                    },
                    Schema::OneOf { one_of } => one_of.is_empty(),
                    _ => unimplemented!(),
                },
            },
            default: match r#type.0.borrow() {
                "String" => format!("\"{}\".into()", name),
                t => format!("{}::default()", t),
            },
            r#type,
        }
    }
}

#[derive(Debug, Serialize)]
pub struct Enum {
    pub pascal_id: RustPascalIdentifier,
    pub snake_id: RustSnakeIdentifier,
    pub variants: Vec<Variant>,
}

impl From<(String, &SchemaVariant)> for Enum {
    fn from((name, schema_variant): (String, &SchemaVariant)) -> Self {
        if let SchemaVariant::String { enumeration, .. } = schema_variant {
            Enum {
                pascal_id: name.clone().into(),
                snake_id: name.into(),
                variants: enumeration
                    .into_iter()
                    .map(|value| value.into())
                    .collect::<Vec<Variant>>(),
            }
        } else {
            unimplemented!()
        }
    }
}

#[derive(Debug, Serialize)]
pub struct Variant {
    pub api_id: String,
    pub pascal_id: RustPascalIdentifier,
}

impl From<String> for Variant {
    fn from(name: String) -> Self {
        Variant {
            api_id: name.clone(),
            pascal_id: name.into(),
        }
    }
}

impl From<&String> for Variant {
    fn from(name: &String) -> Self {
        Variant {
            api_id: name.clone(),
            pascal_id: name.to_owned().into(),
        }
    }
}