1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
use crate::get_add_operation_fn_name;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use syn::{parse::Parser, punctuated::Punctuated, token::Comma, Path, Result};

pub fn parse(routes: TokenStream) -> TokenStream {
    parse_inner(routes)
        .unwrap_or_else(|e| e.to_compile_error())
        .into()
}

fn parse_inner(routes: TokenStream) -> Result<TokenStream2> {
    let paths = <Punctuated<Path, Comma>>::parse_terminated.parse(routes)?;
    let add_operations = create_add_operations(paths.clone())?;
    let openapi_object = get_new_openapi_object();
    Ok(quote! {
        {
            let settings = ::rocket_okapi::settings::OpenApiSettings::new();
            let mut gen = ::rocket_okapi::gen::OpenApiGenerator::new(settings.clone());
            #add_operations
            #openapi_object

            let mut routes = ::rocket::routes![#paths];
            routes.push(::rocket_okapi::handlers::OpenApiHandler::new(spec).into_route(&settings.json_path));
            routes
        }
    })
}

fn get_new_openapi_object() -> TokenStream2 {
    quote! {
        let mut spec = gen.into_openapi();
        let mut info = ::okapi::openapi3::Info {
            title: APPLICATION_NAME.to_string(), // env!("CARGO_PKG_NAME").to_owned(),
            version: env!("CARGO_PKG_VERSION").to_owned(),
            ..Default::default()
        };
        info.description = Some(APPLICATION_DESCRIPTION.to_string());
        // if !env!("CARGO_PKG_DESCRIPTION").is_empty() {
        //     info.description = Some(env!("CARGO_PKG_DESCRIPTION").to_owned());
        // }
        if !env!("CARGO_PKG_REPOSITORY").is_empty() {
            info.contact = Some(::okapi::openapi3::Contact{
                name: Some("Repository".to_owned()),
                url: Some(env!("CARGO_PKG_REPOSITORY").to_owned()),
                ..Default::default()
            });
        }
        if !env!("CARGO_PKG_HOMEPAGE").is_empty() {
            info.contact = Some(::okapi::openapi3::Contact{
                name: Some("Homepage".to_owned()),
                url: Some(env!("CARGO_PKG_HOMEPAGE").to_owned()),
                ..Default::default()
            });
        }
        info.license = Some(::okapi::openapi3::License{
            name: APPLICATION_LICENSE.to_string(),
            url: Some(APPLICATION_LICENSE_URL.to_string()),
            ..Default::default()
        });
        spec.info = info;
        spec.external_docs = Some(::okapi::openapi3::ExternalDocs{
            description: Some(APPLICATION_DOCS_NAME.to_string()),
            url: APPLICATION_DOCS_URL.to_string(),
            ..Default::default()
        });
    }
}

pub fn parse_spec_and_routes(routes: TokenStream) -> TokenStream {
    get_spec_and_routes(routes)
        .unwrap_or_else(|e| e.to_compile_error())
        .into()
}

fn get_spec_and_routes(routes: TokenStream) -> Result<TokenStream2> {
    let paths = <Punctuated<Path, Comma>>::parse_terminated.parse(routes)?;
    let add_operations = create_add_operations(paths.clone())?;
    let openapi_object = get_new_openapi_object();
    Ok(quote! {
        {
            let settings = ::rocket_okapi::settings::OpenApiSettings::new();
            let mut gen = ::rocket_okapi::gen::OpenApiGenerator::new(settings.clone());
            #add_operations
            #openapi_object

            let mut routes = ::rocket::routes![#paths];
            routes.push(::rocket_okapi::handlers::OpenApiHandler::new(spec.clone()).into_route(&settings.json_path));
            (spec, routes)
        }
    })
}

fn create_add_operations(paths: Punctuated<Path, Comma>) -> Result<TokenStream2> {
    let function_calls = paths.into_iter().map(|path| {
        let fn_name = fn_name_for_add_operation(path.clone());
        let operation_id = operation_id(&path);
        quote! {
            #fn_name(&mut gen, #operation_id.to_owned())
                .expect(&format!("Could not generate OpenAPI operation for `{}`.", stringify!(#path)));
        }
    });
    Ok(quote! {
        #(#function_calls)*
    })
}

fn fn_name_for_add_operation(mut fn_path: Path) -> Path {
    let mut last_seg = fn_path.segments.last_mut().expect("syn::Path has segments");
    last_seg.ident = get_add_operation_fn_name(&last_seg.ident);
    fn_path
}

fn operation_id(fn_path: &Path) -> String {
    let idents: Vec<String> = fn_path
        .segments
        .iter()
        .map(|s| s.ident.to_string())
        .collect();
    idents.join("_")
}