Skip to content

Commit

Permalink
fix(cli-ng): correct mustache templates and misc
Browse files Browse the repository at this point in the history
Seems the current mustache crate doesn't support things like:
{{#blob.name}}
{{#blob.1}}
{{#blob}}{{{.}}}
Worked around this by rewriting the templates slightly
Fixed a few bugs, likely induced after latest refactoring to get
compatible with openapiv3.

Signed-off-by: Tiago Castro <[email protected]>
  • Loading branch information
tiagolobocastro committed Oct 23, 2024
1 parent b779766 commit 7c0db9a
Show file tree
Hide file tree
Showing 9 changed files with 230 additions and 40 deletions.
8 changes: 8 additions & 0 deletions cli-ng/src/v3/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,14 @@ struct OperationsTpl<'a> {
pub(super) struct OperationsApiTpl<'a> {
classname: &'a str,
class_filename: &'a str,
has_auth_methods: bool,

operations: OperationsTpl<'a>,
}
pub(super) struct OperationsApi {
classname: String,
class_filename: String,
has_auth_methods: bool,

operations: Vec<Operation>,
}
Expand All @@ -122,6 +124,7 @@ impl OpenApiV3 {
.map(|o| OperationsApiTpl {
classname: o.classname(),
class_filename: o.class_filename(),
has_auth_methods: o.has_auth_methods,
operations: OperationsTpl {
operation: &o.operations,
},
Expand Down Expand Up @@ -327,10 +330,14 @@ impl OpenApiV3 {
.api
.operations()
.map(|(path, method, operation)| Operation::new(self, path, method, operation))
.sorted_by(Self::sort_op_id)
.collect::<Vec<Operation>>();

Ok(operation)
}
fn sort_op_id(a: &Operation, b: &Operation) -> std::cmp::Ordering {
a.operation_id_original.clone().unwrap_or_default().cmp(&b.operation_id_original.clone().unwrap())
}
fn apis(&self, operations: &Vec<Operation>) -> Result<Vec<OperationsApi>, std::io::Error> {
let mut tags = std::collections::HashMap::<String, OperationsApi>::new();
for op in operations {
Expand Down Expand Up @@ -490,6 +497,7 @@ impl From<&Operation> for OperationsApi {
class_filename: src.class_filename().into(),
classname: src.classname().into(),
operations: vec![src.clone()],
has_auth_methods: src.has_auth_methods,
}
}
}
Expand Down
79 changes: 68 additions & 11 deletions cli-ng/src/v3/operation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::collections::HashMap;
use super::{OpenApiV3, Parameter, Property};

use heck::{ToLowerCamelCase, ToSnakeCase, ToUpperCamelCase};
use itertools::Itertools;
use ramhorns_derive::Content;

use log::debug;
Expand Down Expand Up @@ -38,7 +39,7 @@ pub(crate) struct Operation {

path: String,
operation_id: Option<String>,
return_type: String,
return_type: Option<String>,
return_format: String,
http_method: String,
return_base_type: String,
Expand All @@ -54,8 +55,6 @@ pub(crate) struct Operation {
has_produces: bool,
prioritized_content_types: Vec<std::collections::HashMap<String, String>>,

body_param: Parameter,

all_params: Vec<Parameter>,
has_params: bool,
path_params: Vec<Parameter>,
Expand All @@ -64,6 +63,8 @@ pub(crate) struct Operation {
has_query_params: bool,
header_params: Vec<Parameter>,
has_header_params: bool,
has_body_param: bool,
body_param: Option<Parameter>,
implicit_headers_params: Vec<Parameter>,
has_implicit_headers_params: bool,
form_params: Vec<Parameter>,
Expand All @@ -72,8 +73,8 @@ pub(crate) struct Operation {
has_required_params: bool,
optional_params: Vec<Parameter>,
has_optional_params: bool,
auth_methods: Vec<Parameter>,
has_auth_methods: bool,
auth_methods: Vec<AuthMethod>,
pub(crate) has_auth_methods: bool,

tags: Vec<String>,
responses: Vec<()>,
Expand All @@ -84,11 +85,13 @@ pub(crate) struct Operation {

vendor_extensions: HashMap<String, String>,

operation_id_original: Option<String>,
pub(crate) operation_id_original: Option<String>,
operation_id_camel_case: Option<String>,
operation_id_lower_case: Option<String>,
support_multiple_responses: bool,

description: Option<String>,

api_doc_path: &'static str,
model_doc_path: &'static str,
}
Expand Down Expand Up @@ -121,6 +124,9 @@ fn header_param(api: &OpenApiV3, value: &openapiv3::Parameter) -> Option<Paramet
_ => None,
}
}
fn body_param(api: &OpenApiV3, value: &openapiv3::RequestBody) -> Option<Parameter> {
Parameter::from_body(api, value)
}

impl Operation {
/// Create an Operation based on the deserialized openapi operation.
Expand Down Expand Up @@ -165,22 +171,32 @@ impl Operation {
openapiv3::ReferenceOr::Item(item) => path_param(root, item),
}
})
.sorted_by(|a, b| b.required().cmp(&a.required()))
.collect::<Vec<_>>();
let body_param = operation
.request_body
.as_ref()
.and_then(|p| {
match p {
// todo: need to handle this
openapiv3::ReferenceOr::Reference { .. } => todo!(),
openapiv3::ReferenceOr::Item(item) => body_param(root, item),
}
});

let mut ext_path = path.to_string();
for param in &path_params {
if param.data_format() == "url" {
//info!("path: {path}");
//info!("path_params: {param:?}");
ext_path = path.replace(param.name(), &format!("{}:.*", param.base_name()));
vendor_extensions.insert("x-actix-query-string".into(), "true".into());
}
}
vendor_extensions.insert("x-actixPath".into(), ext_path);

let all_params = query_params
let all_params = path_params
.iter()
.chain(&path_params)
.chain(query_params.iter().sorted_by(|a, b| b.required().cmp(&a.required())))
.chain(&body_param)
.cloned()
.collect::<Vec<_>>();
// todo: support multiple responses
Expand All @@ -204,6 +220,7 @@ impl Operation {
None => (String::new(), String::new()),
};
Self {
description: operation.description.as_ref().map(|d| d.replace("\n", " ")),
classname: class,
class_filename: class_file,
summary: operation.summary.clone(),
Expand All @@ -227,11 +244,42 @@ impl Operation {
query_params,
header_params: vec![],
has_header_params: false,
has_body_param: body_param.is_some(),
body_param,
path: path.to_string(),
http_method: method.to_upper_camel_case(),
support_multiple_responses: false,
return_type: return_model.data_type(),
return_type: {
let data_type = return_model.data_type();
if data_type == "()" {
None
} else {
Some(data_type)
}},
has_auth_methods: operation.security.is_some(),
auth_methods: match &operation.security {
None => vec![],
Some(sec) => {
sec.iter().flat_map(|a| {
a.iter().map(|(key, _)| {
match key.as_str() {
"JWT" => AuthMethod {
scheme: "JWT".to_string(),
is_basic: true,
is_basic_bearer: true,
},
scheme => AuthMethod {
scheme: scheme.to_string(),
is_basic: false,
is_basic_bearer: false,
}
}
})
.collect::<Vec<_>>()
})
.collect::<Vec<_>>()
}
},
vendor_extensions,
api_doc_path: "docs/apis/",
model_doc_path: "docs/models/",
Expand All @@ -251,3 +299,12 @@ impl Operation {
&self.class_filename
}
}

#[derive(Default, Content, Clone, Debug)]
#[ramhorns(rename_all = "camelCase")]
struct AuthMethod {
scheme: String,

is_basic: bool,
is_basic_bearer: bool,
}
87 changes: 83 additions & 4 deletions cli-ng/src/v3/parameter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ pub(crate) struct Parameter {
param_name: String,
base_name: String,
example: Option<String>,
description: Option<String>,
examples: Vec<String>,
required: bool,
deprecated: Option<bool>,
is_nullable: bool,
is_string: bool,
is_array: bool,
is_uuid: bool,
is_uri: bool,
is_primitive_type: bool,
is_container: bool,
data_type: String,
Expand All @@ -26,16 +28,87 @@ pub(crate) struct Parameter {
}

impl Parameter {
/// Create a new Parameter from a request body.
pub(super) fn from_body(api: &OpenApiV3, body: &openapiv3::RequestBody) -> Option<Self> {
let (_, media) = body.content.first()?;

let schema_back;
let mut body_type = None;
let schema = match media.schema.as_ref()? {
openapiv3::ReferenceOr::Reference { reference } => {
let schema_ref = reference.replace("#/components/schemas/", "");
let schema = api.api.components.as_ref().and_then(|c| {
c.schemas
.get(&schema_ref)
});
body_type = Some(schema_ref);
match schema {
None => {
api.missing_schema_ref(reference);
schema_back = openapiv3::Schema {
schema_data: Default::default(),
schema_kind: openapiv3::SchemaKind::Any(
openapiv3::AnySchema::default(),
),
};
&schema_back
}
Some(ref_or) => match ref_or {
openapiv3::ReferenceOr::Reference { .. } => {
panic!("double reference not supported");
}
openapiv3::ReferenceOr::Item(schema) => schema,
},
}
}
openapiv3::ReferenceOr::Item(schema) => schema,
};
let property = Property::from_schema(api, None, schema, None, body_type.as_deref());
if property.is_any_type() {
body_type = Some("body".to_string());
}
let body_type = body_type.as_deref().unwrap_or("unknown");
let property = super::OpenApiV3::post_process(property);
Some(Self {
// todo: should have snake case param
param_name: body_type.to_snake_case(),
base_name: body_type.to_snake_case(),
example: media.example.as_ref().map(|v| v.to_string()),
examples: vec![],
description: body.description.as_ref().map(|s| s.replace("\n", " ")),
required: body.required,
deprecated: None,
is_nullable: schema.schema_data.nullable,
is_string: property.is_string(),
is_array: property.is_array(),
is_uuid: property.is_uuid(),
is_uri: property.is_uri(),
is_primitive_type: property.is_primitive_type(),
is_container: property.is_container(),
items: property.items().clone(),
data_type: property.data_type(),
data_format: property.data_format(),
vendor_extensions: body
.extensions
.iter()
.map(|(k, v)| (k.clone(), v.to_string()))
.collect(),
})
}
/// Create a new Parameter based on the deserialized parameter data.
pub(super) fn new(api: &OpenApiV3, param: &openapiv3::ParameterData) -> Self {
let schema_back;
let mut schema_type = None;
let schema = match &param.format {
openapiv3::ParameterSchemaOrContent::Schema(ref_s) => match ref_s {
openapiv3::ReferenceOr::Reference { reference } => {
match api.api.components.as_ref().and_then(|c| {
let schema_ref = reference.replace("#/components/schemas/", "");
let schema = api.api.components.as_ref().and_then(|c| {
c.schemas
.get(&reference.replace("#/components/schemas/", ""))
}) {
.get(&schema_ref)
});
schema_type = Some(schema_ref);
match schema {
None => {
api.missing_schema_ref(reference);
schema_back = openapiv3::Schema {
Expand All @@ -60,20 +133,22 @@ impl Parameter {
todo!()
}
};
let property = Property::from_schema(api, None, schema, Some(&param.name), None);
let property = Property::from_schema(api, None, schema, Some(&param.name), schema_type.as_deref());
let property = super::OpenApiV3::post_process(property);
Self {
// todo: should have snake case param
param_name: param.name.to_snake_case(),
base_name: param.name.clone(),
example: param.example.as_ref().map(|v| v.to_string()),
examples: vec![],
description: param.description.as_ref().map(|s| s.replace("\n", " ")),
required: param.required,
deprecated: param.deprecated,
is_nullable: schema.schema_data.nullable,
is_string: property.is_string(),
is_array: property.is_array(),
is_uuid: property.is_uuid(),
is_uri: property.is_uri(),
is_primitive_type: property.is_primitive_type(),
is_container: property.is_container(),
items: property.items().clone(),
Expand All @@ -98,4 +173,8 @@ impl Parameter {
pub fn name(&self) -> &str {
&self.param_name
}
/// Is the parameter required or optional.
pub fn required(&self) -> bool {
self.required
}
}
Loading

0 comments on commit 7c0db9a

Please sign in to comment.