openapi-struct-gen 0.1.10

Generate rust structures from an openapi 3.0 definition
Documentation
use heck::ToUpperCamelCase;
use indexmap::IndexMap;
use openapiv3::{
    MediaType, OpenAPI, PathItem, ReferenceOr, RequestBody, Response, Schema, StatusCode,
};
use std::collections::BTreeMap;

pub fn parse_schema(oapi: OpenAPI) -> BTreeMap<String, Schema> {
    let mut schemas: BTreeMap<String, Schema> = BTreeMap::new();
    if let Some(components) = oapi.components {
        gather_from_schemas(&mut schemas, components.schemas);
        gather_from_responses(&mut schemas, components.responses);
        gather_from_bodies(&mut schemas, components.request_bodies);
    }
    gather_from_paths(&mut schemas, oapi.paths.paths);
    schemas
}

fn gather_from_schemas(
    schemas: &mut BTreeMap<String, Schema>,
    map: IndexMap<String, ReferenceOr<Schema>>,
) {
    for (name, refor) in map.into_iter() {
        if let ReferenceOr::Item(i) = refor {
            schemas.insert(name, i);
        }
    }
}

fn from_single_media_type(schemas: &mut BTreeMap<String, Schema>, name: String, mt: MediaType) {
    if let Some(refor) = mt.schema {
        if let ReferenceOr::Item(i) = refor {
            schemas.insert(name, i);
        }
    }
}

fn from_single_response<F>(schemas: &mut BTreeMap<String, Schema>, name_modifier: F, resp: Response)
where
    F: Fn(String) -> String,
{
    for (mtn, mt) in resp.content.into_iter() {
        from_single_media_type(schemas, name_modifier(mtn), mt);
    }
}

fn from_single_body<F>(schemas: &mut BTreeMap<String, Schema>, name_modifier: F, body: RequestBody)
where
    F: Fn(String) -> String,
{
    for (mtn, mt) in body.content.into_iter() {
        from_single_media_type(schemas, name_modifier(mtn), mt);
    }
}

fn gather_from_responses(
    schemas: &mut BTreeMap<String, Schema>,
    map: IndexMap<String, ReferenceOr<Response>>,
) {
    for (name, refor) in map.into_iter() {
        if let ReferenceOr::Item(i) = refor {
            from_single_response(
                schemas,
                |mtn| generate_response_name(name.clone(), None, None, false, mtn),
                i,
            );
        }
    }
}

fn gather_from_bodies(
    schemas: &mut BTreeMap<String, Schema>,
    map: IndexMap<String, ReferenceOr<RequestBody>>,
) {
    for (name, refor) in map.into_iter() {
        if let ReferenceOr::Item(i) = refor {
            from_single_body(
                schemas,
                |mtn| generate_request_body_name(name.clone(), None, mtn),
                i,
            );
        }
    }
}

fn generate_response_name(
    name: String,
    method: Option<&str>,
    status_code: Option<StatusCode>,
    is_default: bool,
    media_type: String,
) -> String {
    format!(
        "{}{}{}{}Response{}",
        name.to_upper_camel_case(),
        method.unwrap_or_default().to_upper_camel_case(),
        status_code.map(|sc| sc.to_string()).unwrap_or_default(),
        media_type
            .split("/")
            .skip(1)
            .next()
            .unwrap()
            .to_upper_camel_case(),
        if is_default { "Default" } else { "" },
    )
}

fn generate_request_body_name(name: String, method: Option<&str>, media_type: String) -> String {
    format!(
        "{}{}{}RequestBody",
        name.to_upper_camel_case(),
        method.unwrap_or_default().to_upper_camel_case(),
        media_type
            .split("/")
            .skip(1)
            .next()
            .unwrap()
            .to_upper_camel_case()
    )
}

fn from_single_path_item(schemas: &mut BTreeMap<String, Schema>, name: String, pi: PathItem) {
    for (method, operation) in pi.into_iter() {
        if let Some(ReferenceOr::Item(body)) = operation.request_body {
            from_single_body(
                schemas,
                |mtn| generate_request_body_name(name.clone(), Some(method), mtn),
                body,
            );
        }

        if let Some(ReferenceOr::Item(resp)) = operation.responses.default {
            from_single_response(
                schemas,
                |mtn| generate_response_name(name.clone(), Some(method), None, true, mtn),
                resp,
            );
        }
        for (status_code, refor_resp) in operation.responses.responses.into_iter() {
            if let ReferenceOr::Item(resp) = refor_resp {
                from_single_response(
                    schemas,
                    |mtn| {
                        generate_response_name(
                            name.clone(),
                            Some(method),
                            Some(status_code.clone()),
                            false,
                            mtn,
                        )
                    },
                    resp,
                );
            }
        }
    }
}

fn gather_from_paths(
    schemas: &mut BTreeMap<String, Schema>,
    map: IndexMap<String, ReferenceOr<PathItem>>,
) {
    for (name, refor) in map.into_iter() {
        if let ReferenceOr::Item(pi) = refor {
            from_single_path_item(schemas, name, pi);
        }
    }
}