use std::collections::{BTreeMap};
use json::{object, JsonValue};
use crate::action::Action;
#[derive(Clone)]
pub struct Swagger {
openapi: String,
info: Info,
servers: Vec<Server>,
components: JsonValue,
tags: BTreeMap<String, Tag>,
security: Vec<JsonValue>,
paths: BTreeMap<String, BTreeMap<String, Api>>,
}
impl Swagger {
pub fn new(version: &str, title: &str, description: &str) -> Swagger {
Swagger {
openapi: "3.0.0".to_string(),
info: Info {
title: title.to_string(),
description: description.to_string(),
version: version.to_string(),
},
servers: vec![],
components: object! {},
tags: Default::default(),
security: vec![],
paths: Default::default(),
}
}
pub fn add_server(&mut self, url: &str, description: &str) -> Server {
Server { url: url.to_string(), description: description.to_string(), variables: Default::default() }
}
pub fn set_server(&mut self, server: Server) {
self.servers.push(server);
}
pub fn add_header(&mut self, key: &str, description: &str, example: &str) {
self.components["parameters"]["GlobalHeader"] = object! {
"name":key,
"in": "header",
"description": description,
"required": true,
"schema": {
"type": "string",
"example":example
}
}
}
pub fn add_components_bearer_token(&mut self) {
self.components["securitySchemes"]["BearerToken"] = object! {
"type": "http",
"scheme": "bearer",
"bearerFormat": "Token"
};
self.security.push(object! {
"BearerToken":[]
})
}
pub fn add_components_header(&mut self, key: &str, description: &str, example: &str) {
self.components["securitySchemes"]["ApiKeyAuth"] = object! {
"type": "apiKey",
"in": "header",
"name": key,
"description": description,
};
self.security.push(object! {
"ApiKeyAuth":example
})
}
pub fn add_authorization_header(&mut self, token: &str) {
self.components["parameters"]["AuthorizationHeader"] = object! {
"name": "Authorization",
"in": "header",
"required": true,
"description": "Bearer token for authentication",
"schema":{
"type": "string",
"example":format!("Bearer {}",token)
}
};
}
pub fn set_global(&mut self, key: &str, example: &str, description: &str) {
self.components["schemas"][key]["type"] = "string".into();
self.components["schemas"][key]["description"] = description.into();
self.components["schemas"][key]["example"] = example.into();
}
pub fn add_tags(&mut self, name: &str, description: &str) {
self.tags.insert(name.to_string(), Tag { name: name.to_string(), description: description.to_string() });
}
pub fn add_paths(&mut self, tag: &str, mut action: Box<dyn Action>) {
let path = format!("/{}/{tag}/{}", action.version(), action.api().replace(".", "/"));
let mut t = BTreeMap::new();
t.insert(action.method().str().to_lowercase().clone(), Api::new(tag, action, self.components.clone()));
self.paths.insert(path, t);
}
pub fn json(&mut self) -> JsonValue {
let mut paths = BTreeMap::new();
for (key, value) in self.paths.iter_mut() {
let mut t = BTreeMap::new();
for (x, y) in value.iter_mut() {
t.insert(x.clone(), y.json().clone());
}
paths.insert(key.clone(), t.clone());
}
object! {
openapi: self.openapi.clone(),
info: self.info.json(),
servers: self.servers.iter().map(|x|x.json()).collect::<Vec<JsonValue>>().clone(),
components: self.components.clone(),
security:self.security.clone(),
tags: self.tags.values().map(|y| y.json()).collect::<Vec<JsonValue>>().clone(),
paths: paths.clone()
}
}
}
#[derive(Clone)]
struct Info {
title: String,
description: String,
version: String,
}
impl Info {
pub fn json(&self) -> JsonValue {
object! {
title: self.title.clone(),
description: self.description.clone(),
version: self.version.clone()
}
}
}
#[derive(Clone, Debug)]
pub struct Server {
url: String,
description: String,
variables: BTreeMap<String, JsonValue>,
}
impl Server {
pub fn json(&self) -> JsonValue {
object! {
url: self.url.clone(),
description: self.description.clone(),
variables: self.variables.clone()
}
}
pub fn set_variable(&mut self, key: &str, value: JsonValue, description: &str) {
self.variables.insert(key.to_string(), object! {
default:value,
description:description
});
}
}
#[derive(Clone)]
struct Tag {
name: String,
description: String,
}
impl Tag {
pub fn json(&self) -> JsonValue {
object! {
name: self.name.clone(),
description: self.description.clone(),
}
}
}
#[derive(Clone)]
struct Api {
tags: Vec<String>,
summary: String,
description: String,
request_body: RequestBody,
responses: JsonValue,
}
impl Api {
pub fn new(tag: &str, mut action: Box<dyn Action>, components: JsonValue) -> Api {
let apis = action.api();
let binding = apis.clone();
let mut apis = binding.split(".");
let mut t = Self {
tags: vec![format!("{tag}.{}.{}", apis.next().unwrap().to_string(), apis.next().unwrap().to_string())],
summary: action.title().to_string(),
description: action.description().to_string(),
request_body: RequestBody::new(components.clone()),
responses: object! {},
};
if !action.params().is_empty() {
t.request_body.set_required(true);
t.request_body.set_content(action.content_type().str().as_str(), action.params());
}
t
}
pub fn json(&self) -> JsonValue {
let mut t = object! {
tags:self.tags.clone(),
summary: self.summary.clone(),
description: self.description.clone(),
requestBody: self.request_body.json(),
responses: self.responses.clone(),
};
if !t["requestBody"]["required"].as_bool().unwrap() {
t.remove("requestBody");
}
t
}
}
#[derive(Clone)]
struct RequestBody {
required: bool,
content: JsonValue,
components: JsonValue,
}
impl RequestBody {
pub fn new(components: JsonValue) -> RequestBody {
Self { required: false, content: object! {}, components }
}
pub fn set_required(&mut self, state: bool) {
self.required = state;
}
pub fn set_content(&mut self, content_type: &str, params: JsonValue) {
self.content[content_type] = object! {
schema:object! {"type":if params.is_array() {"array"}else{"object"}}
};
match self.content[content_type]["schema"]["type"].as_str().unwrap_or("") {
"object" => self.clone().set_schema_object(&mut self.content[content_type]["schema"], params.clone()),
"array" => RequestBody::set_schema_array(&mut self.content[content_type]["schema"], params.clone()),
_ => {}
}
for (field, data) in params.entries() {
if self.clone().components["schemas"][field].is_empty() {
self.content[content_type]["example"][field] = data["example"].clone();
} else {
self.content[content_type]["example"][field] = self.clone().components["schemas"][field]["example"].clone();
}
}
}
fn set_schema_object(self, data: &mut JsonValue, params: JsonValue) {
for (key, value) in params.entries() {
data["properties"][key]["type"] = RequestBody::mode(value["mode"].as_str().unwrap_or(""));
match value["mode"].as_str().unwrap_or("") {
"radio" | "select" => {
data["properties"][key]["enum"] = value["option"].clone();
}
_ => {}
}
match data["properties"][key]["type"].as_str().unwrap_or("") {
"object" => self.clone().set_sub_object(&mut data["properties"][key], value["items"].clone()),
"array" => {
data["properties"][key]["example"] = value["example"].clone();
data["properties"][key]["default"] = value["example"].clone();
RequestBody::set_sub_array(&mut data["properties"][key], value["items"].clone())
}
_ => {
RequestBody::set_schema_str(&mut data["properties"][key], value.clone());
}
}
}
}
fn set_schema_str(data: &mut JsonValue, params: JsonValue) {
data["type"] = RequestBody::mode(params["mode"].as_str().unwrap_or(""));
data["example"] = params["example"].clone();
data["default"] = params["example"].clone();
}
fn set_schema_array(data: &mut JsonValue, params: JsonValue) {
data["items"] = params;
}
fn set_sub_array(data: &mut JsonValue, params: JsonValue) {
data["items"]["type"] = params["mode"].as_str().unwrap_or("string").into();
}
fn set_sub_object(self, data: &mut JsonValue, params: JsonValue) {
for (key, value) in params.entries() {
data["properties"][key]["type"] = RequestBody::mode(value["mode"].as_str().unwrap_or(""));
match value["mode"].as_str().unwrap_or("") {
"radio" | "select" => {
data["properties"][key]["enum"] = value["option"].clone();
}
_ => {}
}
match data["properties"][key]["type"].as_str().unwrap_or("") {
"object" => self.clone().set_sub_object(&mut data["properties"][key], value["items"].clone()),
"array" => RequestBody::set_sub_array(&mut data["properties"][key], value["items"].clone()),
_ => {
RequestBody::set_schema_str(&mut data["properties"][key], value.clone());
}
}
}
}
fn mode(name: &str) -> JsonValue {
match name {
"array" => "array",
"int" => "integer",
"switch" => "boolean",
"radio" => "string",
"select" => "array",
_ => name
}.into()
}
pub fn json(&self) -> JsonValue {
object! {
required: self.required,
content: self.content.clone()
}
}
}