oasgen-core 0.1.9

Dependency of oasgen. Generates OpenAPI 3.0 spec based on Rust code. Works with actix-web, but architected to easily extend to other frameworks (or no framework).
Documentation
#![allow(unused)]

use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use openapiv3::{MediaType, Operation, ReferenceOr, RequestBody, Response, Responses, Schema, StatusCode};
use crate::OaSchema;
use pin_project_lite::pin_project;


fn type_name_to_operation_id(type_name: &str) -> Option<String> {
    Some(type_name.split("::").skip(1).collect::<Vec<_>>().join("_"))
}

pub trait OaOperation<Signature> {
    fn referenced_schema(schema: &str) -> Schema;
    fn references() -> Vec<&'static str>;
    fn operation() -> Operation;
}


/// Not meant for public use. This is autogenerated for any function annotated with #[openapi]
/// It allows function metadata (docstring, arg schemas.) to be defined at runtime (instead of lost during compilation)
pub trait FunctionMetadata {
    /// This is Option<static str> because it basically just comes from function annotation attributes.
    /// If that isn't defined, and this is none, then we compute it from the function name at runtime.
    fn operation_id() -> Option<&'static str>;

    fn summary() -> Option<&'static str>;

    fn description() -> Option<&'static str>;
}

pin_project! {
    /// Every openapi function has its return type wrapped in this return type.
    /// That gives us a PhantomData marker which stores the function metadata to make it available at runtime.
    #[repr(transparent)]
    pub struct TypedResponseFuture<F, Signature> {
        #[pin]
        inner: F,
        _marker: std::marker::PhantomData<Signature>,
    }
}

impl<F, Signature> TypedResponseFuture<F, Signature>
{
    pub fn new(inner: F) -> Self {
        Self { inner, _marker: Default::default() }
    }
}

impl<F, Signature> Future for TypedResponseFuture<F, Signature>
    where
        F: Future,
{
    type Output = F::Output;

    #[inline]
    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        let this = self.as_mut().project();
        this.inner.poll(cx)
    }
}

impl<F, A0, Fut, FuncMetadata> OaOperation<(A0, Fut, FuncMetadata)> for F
    where
        F: Fn(A0) -> TypedResponseFuture<Fut, FuncMetadata>,
        Fut: Future,
        A0: OaSchema,
        Fut::Output: OaSchema,
        FuncMetadata: FunctionMetadata,
{
    fn referenced_schema(schema: &str) -> Schema {
        if A0::schema_name().map(|s| s == schema).unwrap_or(false) {
            A0::schema().unwrap()
        } else if Fut::Output::schema_name().map(|s| s == schema).unwrap_or(false) {
            Fut::Output::schema().unwrap()
        } else {
            panic!("Unknown schema: {}", schema)
        }
    }

    fn references() -> Vec<&'static str> {
        vec![A0::schema_name(), Fut::Output::schema_name()].into_iter().flatten().collect()
    }

    fn operation() -> Operation {
        let a0 = A0::schema_ref();
        let request_body = if let Some(a0) = a0 {
            let mut content = indexmap::IndexMap::new();
            content.insert("application/json".to_string(), MediaType {
                schema: Some(a0),
                ..MediaType::default()
            });
            let body = RequestBody {
                content,
                required: true,
                ..RequestBody::default()
            };
            Some(ReferenceOr::Item(body))
        } else {
            None
        };

        let mut responses = Responses::default();

        responses.responses.insert(StatusCode::Code(200), ReferenceOr::Item({
            let mut content = indexmap::IndexMap::new();
            content.insert("application/json".to_string(), MediaType {
                schema: Fut::Output::schema_ref(),
                ..MediaType::default()
            });
            Response {
                content,
                ..Response::default()
            }
        }));

        Operation {
            operation_id: type_name_to_operation_id(std::any::type_name::<F>()),
            request_body,
            summary: FuncMetadata::summary().map(str::to_string),
            responses,
            ..Operation::default()
        }
    }
}

impl<F, A0, A1, Fut, FuncMetadata> OaOperation<(A0, A1, Fut, FuncMetadata)> for F
    where
        F: Fn(A0, A1) -> TypedResponseFuture<Fut, FuncMetadata>,
        Fut: Future,
        A0: OaSchema,
        A1: OaSchema,
        Fut::Output: OaSchema,
        FuncMetadata: FunctionMetadata,
{
    fn referenced_schema(schema: &str) -> Schema {
        if A0::schema_name().map(|s| s == schema).unwrap_or(false) {
            A0::schema().unwrap()
        } else if A1::schema_name().map(|s| s == schema).unwrap_or(false) {
                A1::schema().unwrap()
        } else if Fut::Output::schema_name().map(|s| s == schema).unwrap_or(false) {
            Fut::Output::schema().unwrap()
        } else {
            panic!("Unknown schema: {}", schema)
        }
    }

    fn references() -> Vec<&'static str> {
        vec![
            A0::schema_name(),
            A1::schema_name(),
            Fut::Output::schema_name()
        ].into_iter().flatten().collect()
    }

    fn operation() -> Operation {
        let body = A0::schema_ref().or_else(
            || A1::schema_ref()
        ).unwrap();
        let mut content = indexmap::IndexMap::new();
        content.insert("application/json".to_string(), MediaType {
            schema: Some(body),
            ..MediaType::default()
        });
        let body = RequestBody {
            content,
            required: true,
            ..RequestBody::default()
        };
        let mut responses = Responses::default();

        responses.responses.insert(StatusCode::Code(200), ReferenceOr::Item({
            let mut content = indexmap::IndexMap::new();
            content.insert("application/json".to_string(), MediaType {
                schema: Fut::Output::schema_ref(),
                ..MediaType::default()
            });
            Response {
                content,
                ..Response::default()
            }
        }));

        Operation {
            operation_id: type_name_to_operation_id(std::any::type_name::<F>()),
            request_body: Some(ReferenceOr::Item(body)),
            summary: FuncMetadata::summary().map(str::to_string),
            responses,
            ..Operation::default()
        }
    }
}


impl<F, A0, A1, A2, Fut, FuncMetadata> OaOperation<(A0, A1, A2, Fut, FuncMetadata)> for F
    where
        F: Fn(A0, A1, A2) -> TypedResponseFuture<Fut, FuncMetadata>,
        Fut: Future,
        A0: OaSchema,
        A1: OaSchema,
        A2: OaSchema,
        Fut::Output: OaSchema,
        FuncMetadata: FunctionMetadata,
{
    fn referenced_schema(schema: &str) -> Schema {
        if A0::schema_name().map(|s| s == schema).unwrap_or(false) {
            A0::schema().unwrap()
        } else if A1::schema_name().map(|s| s == schema).unwrap_or(false) {
            A1::schema().unwrap()
        } else if A2::schema_name().map(|s| s == schema).unwrap_or(false) {
            A2::schema().unwrap()
        } else if Fut::Output::schema_name().map(|s| s == schema).unwrap_or(false) {
            Fut::Output::schema().unwrap()
        } else {
            panic!("Unknown schema: {}", schema)
        }
    }

    fn references() -> Vec<&'static str> {
        vec![
            A0::schema_name(),
            A1::schema_name(),
            A2::schema_name(),
            Fut::Output::schema_name()
        ].into_iter().flatten().collect()
    }

    fn operation() -> Operation {
        let body = A0::schema_ref().or_else(
            || A1::schema_ref()
        ).or_else(
            || A2::schema_ref()
        )
            .unwrap();
        let mut content = indexmap::IndexMap::new();
        content.insert("application/json".to_string(), MediaType {
            schema: Some(body),
            ..MediaType::default()
        });
        let body = RequestBody {
            content,
            required: true,
            ..RequestBody::default()
        };
        let mut responses = Responses::default();

        responses.responses.insert(StatusCode::Code(200), ReferenceOr::Item({
            let mut content = indexmap::IndexMap::new();
            content.insert("application/json".to_string(), MediaType {
                schema: Fut::Output::schema_ref(),
                ..MediaType::default()
            });
            Response {
                content,
                ..Response::default()
            }
        }));

        Operation {
            operation_id: type_name_to_operation_id(std::any::type_name::<F>()),
            request_body: Some(ReferenceOr::Item(body)),
            summary: FuncMetadata::summary().map(str::to_string),
            responses,
            ..Operation::default()
        }
    }
}


// fn type_name_to_operation_id(type_name: &str) -> Option<String> {
//     Some(type_name.split("::").skip(1).collect::<Vec<_>>().join("_"))
// }

// impl<F, A, Fut, Output> OaOperation<(A, Fut, Output)> for F
//     where
//         F: Fn(A) -> Fut,
//         Fut: Future<Output=Output>,
// impl<F, A, Fut, Output> OaOperation<(A, ), Fut, Output> for F
//     where
//         F: Fn(A) -> Fut,
//         A: OaSchema,
//         Fut: Future<Output=Output>,
//         Output: OaSchema,
// {
//     // type Args = Args;
//     fn operation() -> Operation {
//         let s = A::schema_ref().unwrap();
//         let mut content = indexmap::IndexMap::new();
//         content.insert("application/json".to_string(), MediaType {
//             schema: Some(s),
//             ..MediaType::default()
//         });
//         let body = RequestBody {
//             content,
//             required: true,
//             ..RequestBody::default()
//         };
//         let mut responses = Responses::default();
//
//         responses.responses.insert(StatusCode::Code(200), ReferenceOr::Item({
//             let mut content = indexmap::IndexMap::new();
//             content.insert("application/json".to_string(), MediaType {
//                 schema: Some(Output::schema_ref().unwrap()),
//                 ..MediaType::default()
//             });
//             Response {
//                 content,
//                 ..Response::default()
//             }
//         }));
//
//         Operation {
//             operation_id: type_name_to_operation_id(std::any::type_name::<F>()),
//             request_body: Some(ReferenceOr::Item(body)),
//             responses,
//             ..Operation::default()
//         }
//     }
// }