use serde::Serialize;
use serde_json::Value;
use crate::{
codegen::jsonschema::{JsonSchemaExtractOptions, ModelContainer},
error::Error,
process::name::endpoint,
resolver::SchemaResolver,
scope::{SchemaScope, Space},
};
use super::{
parameters::{self, Parameters},
requestbody, responses, security,
};
#[derive(Serialize, Clone)]
pub struct Endpoint {
security: Vec<security::SecurityScheme>,
path: String,
method: String,
operation: String,
description: Option<String>,
tags: Vec<String>,
parameters: parameters::Parameters,
pub requestbody: Option<requestbody::RequestBody>,
pub responses: responses::Responses,
x: std::collections::HashMap<String, Value>,
}
impl Endpoint {
pub fn get_tags(&self) -> &Vec<String> {
&self.tags
}
}
#[allow(clippy::too_many_arguments)]
pub fn extract_endpoints(
node: &Value,
path: &str,
scope: &mut SchemaScope,
mcontainer: &mut ModelContainer,
scontainer: &security::SecuritySchemes,
resolver: &SchemaResolver,
options: &JsonSchemaExtractOptions,
) -> Result<Vec<Endpoint>, Error> {
resolver.resolve(node, scope, |node, scope| match node {
Value::Object(details) => {
scope.any("parameters");
let parameters = if details.contains_key("parameters") {
Some(parameters::extract(
details, scope, mcontainer, resolver, options,
)?)
} else {
None
};
scope.pop();
let mut endpoints = vec![];
for method in &[
"get", "put", "post", "delete", "options", "head", "patch", "trace",
] {
if let Some(method_details) = details.get(*method) {
scope.any(method);
endpoints.push(new_endpoint(
method_details,
parameters.as_ref(),
path,
method,
scope,
mcontainer,
scontainer,
resolver,
options,
)?);
scope.pop();
}
}
Ok(endpoints)
}
_ => Err(Error::EndpointsValidation {
path: path.to_string(),
}),
})
}
#[allow(clippy::too_many_arguments)]
fn new_endpoint(
node: &Value,
parameters: Option<&Parameters>,
path: &str,
method: &str,
scope: &mut SchemaScope,
mcontainer: &mut ModelContainer,
scontainer: &security::SecuritySchemes,
resolver: &SchemaResolver,
options: &JsonSchemaExtractOptions,
) -> Result<Endpoint, Error> {
match node {
Value::Object(data) => {
let security = data
.get("security")
.map(|v| security::extract_defaults(v, scope, scontainer))
.map_or(Ok(None), |v| v.map(Some))?
.unwrap_or_else(|| scontainer.default.clone());
let operation = data
.get("operationId")
.map(|v| v.as_str().unwrap().to_string())
.unwrap_or_else(|| {
endpoint::Endpoint::new(method.to_string(), path.to_string())
.unwrap()
.get_operation_id(true)
});
let description = data.get("description").map(|v| {
v.as_str()
.map(|s| s.lines().collect::<Vec<_>>().join(" "))
.unwrap()
});
let tags = data
.get("tags")
.map(|v| match v {
Value::Array(a) => Ok(a
.iter()
.map(|v| v.as_str().unwrap().to_string())
.collect::<Vec<_>>()),
_ => Err(Error::CodegenInvalidEndpointFormat),
})
.map_or(Ok(None), |v| v.map(Some))?
.unwrap_or_default();
let tags = if tags.is_empty() {
vec!["default".to_string()]
} else {
tags
};
let x = data
.iter()
.filter_map(|(key, val)| {
key.strip_prefix("x-")
.map(|stripped| (stripped.to_string(), val.clone()))
})
.collect::<std::collections::HashMap<String, Value>>();
scope.glue(&operation);
scope.add_spaces(&mut tags.clone().into_iter().map(Space::Tag).collect());
scope.add_spaces(&mut vec![Space::Operation(operation.clone())]);
scope.add_spaces(&mut vec![Space::Operation(operation.clone())]);
let mut endpoint_parameters =
parameters::extract(data, scope, mcontainer, resolver, options)?;
if let Some(shared) = parameters {
endpoint_parameters.merge(shared)
}
let endpoint = Endpoint {
security,
description,
operation,
method: method.to_string(),
path: path.to_string(),
tags,
responses: responses::extract(data, scope, mcontainer, resolver, options)?,
requestbody: requestbody::extract(data, scope, mcontainer, resolver, options)?,
parameters: endpoint_parameters,
x,
};
scope.clear_spaces();
scope.pop();
Ok(endpoint)
}
_ => Err(Error::CodegenInvalidEndpointFormat),
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_extract_of_parameters() {
let schema = json!({
"parameters": [{
"in": "path",
"name": "userId",
"description": "userId",
"required": true,
"schema": { "type": "string" }
}],
"post": {
"summary": "Save something",
"description": "Testing",
"responses": {
"200": {
"description": "Success response",
"content": { "application/json": { "schema" : {"type": "string"} } }
}
}
},
"get": {
"summary": "Get something",
"description": "Testing 2",
"parameters": [{
"in": "query",
"name": "testId",
"description": "testId",
"required": false,
"schema": { "type": "string" }
}],
"responses": {
"200": {
"description": "Success response",
"content": { "application/json": { "schema" : {"type": "string"} } }
}
}
}
});
let mut mcontainer = ModelContainer::default();
let mut scontainer = super::security::SecuritySchemes::new();
let mut scope = SchemaScope::default();
let resolver = SchemaResolver::empty();
let options = JsonSchemaExtractOptions::default();
let result = extract_endpoints(
&schema,
"/users/{userId}",
&mut scope,
&mut mcontainer,
&mut scontainer,
&resolver,
&options,
);
assert!(result.is_ok());
let endpoints = result.unwrap();
let post_endpoint = endpoints.iter().find(|e| e.method == "post").unwrap();
assert_eq!(post_endpoint.parameters.all.len(), 1);
let get_endpoint = endpoints.iter().find(|e| e.method == "get").unwrap();
assert_eq!(get_endpoint.parameters.all.len(), 2);
assert_eq!(get_endpoint.parameters.query.len(), 1);
assert_eq!(get_endpoint.parameters.path.len(), 1);
}
#[test]
fn test_responses() {
let schema = json!({
"get": {
"summary": "Get something",
"description": "Testing 2",
"parameters": [{
"in": "query",
"name": "testId",
"description": "testId",
"required": false,
"schema": { "type": "string" }
}],
"responses": {
"200": {
"description": "Success response",
"content": {
"application/json": { "schema" : {"type": "string"} },
"application/vnd.short+json": { "schema" : {"type": "object", "properties": { "test" : {"type": "string"}}} },
},
},
"400": {
"description": "Fail response",
"content": {
"application/json": { "schema" : {"type": "object", "properties": { "errorCode" : {"type": "number"}}} },
},
}
}
}
});
let mut mcontainer = ModelContainer::default();
let mut scontainer = super::security::SecuritySchemes::new();
let mut scope = SchemaScope::default();
let resolver = SchemaResolver::empty();
let options = JsonSchemaExtractOptions::default();
let result = extract_endpoints(
&schema,
"/users/{userId}",
&mut scope,
&mut mcontainer,
&mut scontainer,
&resolver,
&options,
);
assert!(result.is_ok());
let endpoints = result.unwrap();
let value = serde_json::to_value(&endpoints).unwrap();
let find = value.pointer("/0/responses/success/models").unwrap();
assert_eq!(
find.as_object()
.unwrap()
.get("all")
.unwrap()
.as_array()
.unwrap()
.len(),
2
);
}
}