rocket_okapi 0.3.6

OpenAPI (AKA Swagger) document generation for Rocket applications
Documentation
use crate::settings::OpenApiSettings;
use crate::OperationInfo;
use okapi::openapi3::*;
use okapi::Map;
use rocket::http::Method;
use schemars::gen::SchemaGenerator;
use schemars::schema::SchemaObject;
use schemars::JsonSchema;
use std::collections::{hash_map::Entry as HashEntry, HashMap};
use std::iter::FromIterator;

/// A struct that visits all `rocket::Route`s, and aggregates information about them.
#[derive(Debug, Clone)]
pub struct OpenApiGenerator {
    settings: OpenApiSettings,
    schema_generator: SchemaGenerator,
    operations: HashMap<(String, Method), Operation>,
}

impl OpenApiGenerator {
    /// Create a new `OpenApiGenerator` from the settings provided.
    pub fn new(settings: OpenApiSettings) -> Self {
        OpenApiGenerator {
            schema_generator: settings.schema_settings.clone().into_generator(),
            settings,
            operations: Default::default(),
        }
    }

    /// Add a new `HTTP Method` to the collection of endpoints in the `OpenApiGenerator`.
    pub fn add_operation(&mut self, mut op: OperationInfo) {
        if let Some(op_id) = op.operation.operation_id {
            // TODO do this outside add_operation
            op.operation.operation_id = Some(op_id.trim_start_matches(':').replace("::", "_"));
        }
        match self.operations.entry((op.path, op.method)) {
            HashEntry::Occupied(e) => {
                let (path, method) = e.key();
                panic!(
                    "An OpenAPI operation has already been added for {} {}",
                    method, path
                );
            }
            HashEntry::Vacant(e) => e.insert(op.operation),
        };
    }

    /// Returns a JSON Schema object for the type `T`.
    pub fn json_schema<T: ?Sized + JsonSchema>(&mut self) -> SchemaObject {
        self.schema_generator.subschema_for::<T>().into()
    }

    /// Obtain the internal `SchemaGenerator` object.
    pub fn schema_generator(&self) -> &SchemaGenerator {
        &self.schema_generator
    }

    /// Generate an `OpenApi` specification for all added operations.
    pub fn into_openapi(self) -> OpenApi {
        OpenApi {
            openapi: "3.0.0".to_owned(),
            paths: {
                let mut paths = Map::new();
                for ((path, method), op) in self.operations {
                    let path_item = paths.entry(path).or_default();
                    set_operation(path_item, method, op);
                }
                paths
            },
            components: Some(Components {
                schemas: Map::from_iter(
                    self.schema_generator
                        .into_definitions()
                        .into_iter()
                        .map(|(k, v)| (k, v.into())),
                ),
                ..Default::default()
            }),
            ..Default::default()
        }
    }
}

fn set_operation(path_item: &mut PathItem, method: Method, op: Operation) {
    use Method::*;
    let option = match method {
        Get => &mut path_item.get,
        Put => &mut path_item.put,
        Post => &mut path_item.post,
        Delete => &mut path_item.delete,
        Options => &mut path_item.options,
        Head => &mut path_item.head,
        Patch => &mut path_item.patch,
        Trace => &mut path_item.trace,
        // Connect not available in OpenAPI3. Maybe should set in extensions?
        Connect => return,
    };
    assert!(option.is_none());
    option.replace(op);
}