use std::ascii::AsciiExt;
use valico::json_dsl;
use std::collections;
use jsonway::{self};
use json::{self, JsonValue, ToJson};
use framework::{self, Nesting};
use server::mime;
use server::header;
use server::method;
#[allow(dead_code)]
pub enum Scheme {
Http,
Https,
Ws,
Wss,
}
impl ToString for Scheme {
fn to_string(&self) -> String {
match self {
&Scheme::Http => "http",
&Scheme::Https => "https",
&Scheme::Ws => "ws",
&Scheme::Wss => "wss"
}.to_string()
}
}
#[allow(dead_code)]
#[derive(Default)]
pub struct Contact {
pub name: String,
pub url: Option<String>,
pub email: Option<String>
}
#[allow(dead_code)]
#[derive(Default)]
pub struct Spec {
pub info: Info,
pub host: Option<String>,
pub base_path: Option<String>,
pub schemes: Option<Vec<Scheme>>,
pub consumes: Option<Vec<mime::Mime>>,
pub produces: Option<Vec<mime::Mime>>,
}
#[allow(dead_code)]
#[derive(Default)]
pub struct Info {
pub title: String,
pub description: Option<String>,
pub terms_of_service: Option<String>,
pub contact: Option<Contact>,
pub license: Option<License>,
pub version: Option<String>
}
#[allow(dead_code)]
#[derive(Default)]
pub struct License {
pub name: String,
pub url: String
}
#[allow(dead_code)]
#[derive(Clone)]
enum Place {
Query,
Header,
Path,
FormData,
Body
}
impl ToString for Place {
fn to_string(&self) -> String {
match self {
&Place::Query => "query",
&Place::Header => "header",
&Place::Path => "path",
&Place::FormData => "formData",
&Place::Body => "body"
}.to_string()
}
}
#[allow(dead_code)]
#[derive(Clone)]
enum ParamType {
String,
Number,
Integer,
Boolean,
Array,
File
}
impl ToString for ParamType {
fn to_string(&self) -> String {
match self {
&ParamType::String => "string",
&ParamType::Number => "number",
&ParamType::Integer => "integer",
&ParamType::Boolean => "boolean",
&ParamType::Array => "array",
&ParamType::File => "file"
}.to_string()
}
}
#[allow(dead_code)]
#[derive(Clone)]
enum ItemType {
String,
Number,
Integer,
Boolean,
Array
}
impl ToString for ItemType {
fn to_string(&self) -> String {
match self {
&ItemType::String => "string",
&ItemType::Number => "number",
&ItemType::Integer => "integer",
&ItemType::Boolean => "boolean",
&ItemType::Array => "array"
}.to_string()
}
}
#[allow(dead_code)]
#[derive(Clone)]
struct ItemParams {
pub type_: ItemType,
pub format: Option<String>,
pub items: Option<Vec<ItemParams>>
}
#[allow(dead_code)]
#[derive(Clone)]
enum ParamExt {
BodyParam(json::Object),
NormalParam {
type_: ParamType,
format: Option<String>,
items: Option<Vec<ItemParams>>,
}
}
#[allow(dead_code)]
#[derive(Clone)]
struct Param {
pub name: String,
pub place: Place,
pub description: Option<String>,
pub required: bool,
pub ext: ParamExt
}
impl ToJson for Param {
fn to_json(&self) -> JsonValue {
jsonway::object(|param| {
param.set("name", self.name.clone());
param.set("in", self.place.to_string());
if self.description.is_some() {
param.set("description", self.description.clone().unwrap());
}
param.set("required", self.required);
match &self.ext {
&ParamExt::BodyParam(ref schema) => param.set("schema", schema.clone()),
&ParamExt::NormalParam{ref type_, ref format, ..} => {
param.set("type", type_.to_string());
if format.is_some() {
param.set("format", format.clone().unwrap())
}
}
}
}).to_json()
}
}
pub struct SwaggerSpecKey;
impl ::typemap::Key for SwaggerSpecKey {
type Value = JsonValue;
}
pub fn enable(app: &mut framework::Application, spec: Spec) {
let spec = build_spec(app, spec);
app.ext.insert::<SwaggerSpecKey>(spec);
}
#[allow(unused_variables)]
pub fn build_spec(app: &framework::Application, spec: Spec) -> JsonValue {
jsonway::object(|json| {
json.set("swagger", "2.0".to_string());
json.object("info", |info| {
info.set("title", spec.info.title.clone());
if spec.info.description.is_some() {
info.set("description", spec.info.description.as_ref().unwrap().clone());
}
if spec.info.terms_of_service.is_some() {
info.set("termsOfService", spec.info.terms_of_service.as_ref().unwrap().clone());
}
if spec.info.contact.is_some() {
let contact_spec = spec.info.contact.as_ref().unwrap();
info.object("contact", |contact| {
contact.set("name", contact_spec.name.clone());
if contact_spec.url.is_some() {
contact.set("url", contact_spec.url.as_ref().unwrap().clone());
}
if contact_spec.email.is_some() {
contact.set("email", contact_spec.email.as_ref().unwrap().clone());
}
});
}
if spec.info.license.is_some() {
let license_spec = spec.info.license.as_ref().unwrap();
info.object("license", |license| {
license.set("name", license_spec.name.clone());
license.set("url", license_spec.url.clone());
});
}
info.set("version", spec.info.version.clone()
.or(app.root_api.version.clone().map(|v| v.version))
.unwrap_or_else(|| "0.0.0".to_string()));
});
if spec.host.is_some() {
json.set("host", spec.host.as_ref().unwrap().clone());
}
json.set("basePath", spec.base_path.clone().unwrap_or_else(|| {
let mut base_path = "/".to_string();
if app.root_api.prefix.is_some() {
base_path.push_str(&app.root_api.prefix.as_ref().unwrap());
}
if app.root_api.version.is_some() {
match app.root_api.version.as_ref().unwrap() {
&framework::Version{ref version, versioning: framework::Versioning::Path} => {
if base_path.len() > 1 {
base_path.push_str("/")
}
base_path.push_str(&version);
},
_ => ()
}
}
base_path
}));
if spec.schemes.is_some() {
let schemes_spec = spec.schemes.as_ref().unwrap();
json.array("schemes", |schemes| {
for scheme in schemes_spec.iter() {
schemes.push(scheme.to_string())
}
})
}
if spec.consumes.is_some() {
let consumes_spec = spec.consumes.as_ref().unwrap();
json.array("consumes", |consumes| {
for mime in consumes_spec.iter() {
consumes.push(mime.to_string())
}
});
}
if spec.produces.is_some() {
let produces_spec = spec.produces.as_ref().unwrap();
json.array("produces", |produces| {
for mime in produces_spec.iter() {
produces.push(mime.to_string())
}
});
}
json.object("paths", |paths| {
fill_paths(WalkContext {
path: "",
params: vec![]
}, paths, &app.root_api.handlers);
});
}).unwrap()
}
#[allow(unused_variables)]
pub fn create_api(path: &str) -> framework::Api {
framework::Api::build(|api| {
api.namespace(path, |docs| {
docs.get("", |endpoint| {
endpoint.summary("Get Swagger 2.0 specification of this API");
endpoint.handle(|mut client, _params| {
client.set_header(header::AccessControlAllowOrigin::Any);
let swagger_spec = client.app.ext.get::<SwaggerSpecKey>();
if swagger_spec.is_some() {
client.json(swagger_spec.unwrap())
} else {
client.empty()
}
})
})
})
})
}
#[allow(dead_code)]
struct WalkContext<'a> {
pub path: &'a str,
pub params: Vec<Param>
}
fn fill_paths<'a>(mut context: WalkContext<'a>, paths: &mut jsonway::ObjectBuilder, handlers: &framework::ApiHandlers) {
for handler_ in handlers.iter() {
let handler = &**handler_ as &framework::ApiHandler;
if handler.is::<framework::Api>() {
let mut path = context.path.to_string();
let api = handler.downcast::<framework::Api>().unwrap();
if api.prefix.is_some() {
path.push_str(&api.prefix.as_ref().unwrap());
}
if api.version.is_some() {
match api.version.as_ref().unwrap() {
&framework::Version{ref version, versioning: framework::Versioning::Path} => {
path.push_str(&version);
path.push_str("/");
},
_ => ()
}
}
fill_paths(WalkContext{
path: &path,
params: context.params.clone()
}, paths, &api.handlers);
} else if handler.is::<framework::Namespace>() {
let mut path = context.path.to_string();
let namespace = handler.downcast::<framework::Namespace>().unwrap();
path.push_str(&("/".to_string() + &encode_path_string(&namespace.path)));
let mut params = context.params.clone();
params.extend(extract_params(&namespace.coercer, &namespace.path));
fill_paths(WalkContext{
path: &path,
params: params,
}, paths, &namespace.handlers);
} else if handler.is::<framework::Endpoint>() {
let mut path = context.path.to_string();
let endpoint = handler.downcast::<framework::Endpoint>().unwrap();
if endpoint.path.path.len() > 0 {
path.push_str(&("/".to_string() + &encode_path_string(&endpoint.path)));
}
let definition = build_endpoint_definition(endpoint, &mut context);
let method = format!("{:?}", endpoint.method).to_ascii_lowercase();
let exists = {
let maybe_path_obj = paths.object.get_mut(&path);
if maybe_path_obj.is_some() {
let path_obj = maybe_path_obj.unwrap().as_object_mut().unwrap();
path_obj.insert(method.to_string(), definition.to_json());
true
} else {
false
}
};
if !exists {
paths.object(path.to_string(), |path_item| {
path_item.set(method.clone(), definition.to_json());
path_item.array("parameters", |parameters| {
for param in context.params.iter() {
parameters.push(param.to_json())
}
})
});
}
}
}
}
#[allow(unused_variables)]
fn build_endpoint_definition(endpoint: &framework::Endpoint, context: &mut WalkContext) -> JsonValue {
jsonway::object(|def| {
if endpoint.summary.is_some() {
def.set("summary", endpoint.summary.as_ref().unwrap().clone());
}
if endpoint.desc.is_some() {
def.set("description", endpoint.desc.as_ref().unwrap().clone());
}
def.object("responses", |responses| {
responses.object("200", |default| {
default.set("description", "Default response".to_string());
default.object("schema", |schema| {});
default.object("headers", |headers| {});
default.object("examples", |examples| {});
})
});
if endpoint.consumes.is_some() {
def.array("consumes", |consumes| {
let consumes_spec = endpoint.consumes.as_ref().unwrap();
for mime in consumes_spec.iter() {
consumes.push(mime.to_string())
}
});
}
if endpoint.produces.is_some() {
def.array("produces", |produces| {
let produces_spec = endpoint.produces.as_ref().unwrap();
for mime in produces_spec.iter() {
produces.push(mime.to_string())
}
});
}
def.array("parameters", |parameters| {
let params = extract_params(&endpoint.coercer, &endpoint.path);
let mut final_params = vec![];
for param in context.params.iter() {
final_params.push(param.clone())
}
for param in params.iter() {
final_params.push(param.clone())
}
match endpoint.method {
method::Method::Post |
method::Method::Put => {
for param in final_params.iter_mut() {
match param.place {
Place::Query => param.place = Place::FormData,
_ => ()
}
}
},
_ => ()
};
parameters.map(final_params.iter(), |param| param.to_json());
});
}).unwrap()
}
fn encode_path_string(path: &framework::Path) -> String {
let ref original_path = path.path;
return framework::path::MATCHER.replace_all(&original_path, "{$1}");
}
fn param_type(param: &json_dsl::Param) -> ParamType {
match ¶m.coercer {
&Some(ref coercer) => {
match coercer.get_primitive_type() {
json_dsl::PrimitiveType::String => ParamType::String,
json_dsl::PrimitiveType::I64 => ParamType::Integer,
json_dsl::PrimitiveType::F64 => ParamType::Number,
json_dsl::PrimitiveType::Array => ParamType::Array,
json_dsl::PrimitiveType::Boolean => ParamType::Boolean,
json_dsl::PrimitiveType::File => ParamType::File,
_ => ParamType::String
}
},
&None => ParamType::String
}
}
fn build_param_from_coercer(param: &json_dsl::Param, required: bool) -> Param {
let swagger_param = Param {
name: param.name.clone(),
place: Place::Query,
description: param.description.clone(),
required: required,
ext: ParamExt::NormalParam {
type_: param_type(param),
format: None,
items: None
}
};
swagger_param
}
fn extract_params(coercer: &Option<json_dsl::Builder>, path: &framework::Path) -> Vec<Param> {
let mut params = collections::BTreeMap::new();
if coercer.is_some() {
let coercer = coercer.as_ref().unwrap();
for param in coercer.get_required().iter() {
params.insert(param.name.clone(), build_param_from_coercer(param, true));
}
for param in coercer.get_optional().iter() {
params.insert(param.name.clone(), build_param_from_coercer(param, false));
}
}
for param_name in path.params.iter() {
let exists = {
let mut existing_param = params.get_mut(param_name);
if existing_param.is_some() {
let param = existing_param.as_mut().unwrap();
param.place = Place::Path;
param.required = true;
true
} else {
false
}
};
if !exists {
let param = Param {
name: param_name.clone(),
place: Place::Path,
description: None,
required: true,
ext: ParamExt::NormalParam {
type_: ParamType::String,
format: None,
items: None
}
};
params.insert(param_name.clone(), param);
}
}
params.into_iter().map(|(_key, value)| value).collect::<Vec<Param>>()
}