use crate::storage::SchemaStorage;
use crate::{error::Error, resolver::SchemaResolver, schema::Schema, scope::SchemaScope, tools};
use serde::ser::SerializeMap;
use serde::Serialize;
use serde_json::Map;
use serde_json::Value;
use super::jsonschema::{add_types, extract_type, JsonSchemaExtractOptions, ModelContainer};
pub mod endpoint;
pub mod parameters;
pub mod requestbody;
pub mod responses;
pub mod security;
pub struct OpenapiExtractOptions {
pub wrappers: bool,
pub nested_arrays_as_models: bool,
pub optional_and_nullable_as_models: bool,
pub keep_schema: tools::Filter,
}
#[derive(Default)]
pub struct EndpointContainer {
endpoints: Vec<endpoint::Endpoint>,
}
impl EndpointContainer {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, endpoint: endpoint::Endpoint) {
self.endpoints.push(endpoint);
}
}
#[derive(Debug, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct MediaModel {
pub model: crate::codegen::jsonschema::types::FlatModel,
pub content_type: String,
pub is_unique: bool,
pub alternative_content_type: bool,
pub vnd: Option<MediaVendorType>,
}
#[derive(Debug, Serialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct MediaVendorType {
base: String,
vnd: String,
}
#[derive(Debug, Clone)]
pub struct MediaModelsContainer {
pub list: Vec<MediaModel>,
pub default_content_type: String,
pub multiple_content_types: bool,
}
impl Serialize for MediaModelsContainer {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut models = self.list.clone();
models.dedup_by(|a, b| a.model == b.model);
match models.len().cmp(&1) {
std::cmp::Ordering::Greater => {
let default = models
.iter()
.find(|m| m.content_type == self.default_content_type);
let with_names: Vec<_> = models.iter().collect();
let mut map = serializer.serialize_map(Some(3))?;
map.serialize_entry("default", &default)?;
map.serialize_entry("all", &with_names)?; map.serialize_entry("multipleContentTypes", &self.multiple_content_types)?;
map.end()
}
std::cmp::Ordering::Equal => {
let mut map = serializer.serialize_map(Some(3))?;
map.serialize_entry("default", models.first().unwrap())?;
map.serialize_entry("all", &models)?;
map.serialize_entry("multipleContentTypes", &self.multiple_content_types)?;
map.end()
}
std::cmp::Ordering::Less => serializer.serialize_none(),
}
}
}
#[derive(Serialize, Clone)]
pub struct Openapi {
pub models: ModelContainer,
pub endpoints: Vec<endpoint::Endpoint>,
pub security: security::SecuritySchemes,
pub tags: Vec<String>,
}
pub fn extract(
schema: &Schema,
storage: &SchemaStorage,
options: OpenapiExtractOptions,
) -> Result<Openapi, Error> {
let mut scope = SchemaScope::default();
let mut mcontainer = ModelContainer::default();
let mut econtainer = EndpointContainer::new();
let mut scontainer = security::SecuritySchemes::new();
let mut tags: Vec<String> = vec![];
let root = schema.get_body();
let resolver = &SchemaResolver::new(schema, storage);
let options = &JsonSchemaExtractOptions {
optional_and_nullable_as_models: options.optional_and_nullable_as_models,
keep_schema: options.keep_schema,
..Default::default()
};
tools::each_node(
root,
&mut scope,
"/any:components/any:securitySchemes/definition:*",
|node, parts, scope| {
if let [scheme_name] = parts {
scope.glue(scheme_name).glue("security_scheme");
let scheme = security::new_scheme(node, scheme_name, scope)?;
scontainer.add(scheme);
scope.reduce(2);
}
Ok(())
},
)?;
tools::each_node(root, &mut scope, "path:security", |node, _parts, scope| {
scope.glue("security");
let schemes = security::extract_defaults(node, scope, &scontainer)?;
for scheme in schemes {
scontainer.add_default(scheme);
}
scope.pop();
Ok(())
})?;
tools::each_node(
root,
&mut scope,
"/any:components/any:schemas/definition:*",
|node, parts, scope| {
if let [key] = parts {
scope.glue(key);
add_types(node, &mut mcontainer, scope, resolver, options)?;
scope.pop();
}
Ok(())
},
)?;
tools::each_node(
root,
&mut scope,
"/any:components/any:parameters/definition:*/any:schema",
|node, parts, scope| {
if let [key] = parts {
scope.glue(key).glue("parameter");
add_types(node, &mut mcontainer, scope, resolver, options)?;
scope.reduce(2);
}
Ok(())
},
)?;
tools::each_node(
root,
&mut scope,
"/any:components/any:responses/definition:*/any:content/any:*/any:schema",
|node, parts, scope| {
if let [key, _] = parts {
scope.glue(key).glue("response");
add_types(node, &mut mcontainer, scope, resolver, options)?;
scope.reduce(2);
}
Ok(())
},
)?;
tools::each_node(
root,
&mut scope,
"/any:components/any:requestBodies/definition:*/any:content/any:*/any:schema",
|node, parts, scope| {
if let [key, _] = parts {
scope.glue(key).glue("request");
add_types(node, &mut mcontainer, scope, resolver, options)?;
scope.reduce(2);
}
Ok(())
},
)?;
tools::each_node(
root,
&mut scope,
"path:paths/any:*",
|node, parts, scope| {
if let [path] = parts {
log::trace!("{}", scope);
let endpoints = endpoint::extract_endpoints(
node,
path,
scope,
&mut mcontainer,
&scontainer,
resolver,
options,
)?;
for endpoint in endpoints.into_iter() {
tags.append(&mut endpoint.get_tags().clone());
econtainer.add(endpoint);
}
}
Ok(())
},
)?;
tags.sort();
tags.dedup();
Ok(Openapi {
models: mcontainer,
endpoints: econtainer.endpoints,
security: scontainer,
tags,
})
}
pub fn get_content(
data: &Map<String, Value>,
scope: &mut SchemaScope,
mcontainer: &mut ModelContainer,
resolver: &SchemaResolver,
options: &JsonSchemaExtractOptions,
) -> Option<Result<MediaModelsContainer, Error>> {
data.get("content").and_then(|content| match content {
Value::Object(o) => {
scope.any("content");
let result = Some(
o.iter()
.filter_map(|(content_type, s)| {
scope.any(content_type);
let result = match s {
Value::Object(o) => o.get("schema").and_then(|s| {
scope.any("schema");
let result = Some(
extract_type(s, mcontainer, scope, resolver, options)
.and_then(|m| m.flatten(mcontainer, scope))
.map(|model| MediaModel {
model,
content_type: content_type.to_string(),
is_unique: false,
alternative_content_type: false,
vnd: None,
}),
);
scope.pop();
result
}),
_ => None,
};
scope.pop();
result
})
.collect::<Result<Vec<_>, _>>()
.map(|list| MediaModelsContainer {
default_content_type: "application/json".to_string(),
multiple_content_types: list.len() > 1,
list,
}),
);
scope.pop();
result
}
_ => None,
})
}
impl Openapi {
pub fn set_content_type(mut self, content_type: &str) -> Self {
self.endpoints.iter_mut().for_each(|f| {
f.responses.all.iter_mut().for_each(|r| {
if let Some(ref mut c) = r.models {
c.default_content_type = content_type.to_string();
}
});
if let Some(ref mut rb) = f.requestbody {
if let Some(ref mut c) = rb.models {
c.default_content_type = content_type.to_string();
}
}
});
self
}
}