Skip to content

Commit 7cb780b

Browse files
authored
Support for array types, format (#41)
* rebase * remove pubsub file
1 parent b43b25f commit 7cb780b

11 files changed

Lines changed: 145 additions & 39 deletions

File tree

example/specs/rpc-client.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
asyncapi: '2.6.0'
1+
asyncapi: '2.1.0'
22
id: 'urn:example:rpcclient'
33
defaultContentType: application/json
44

example/specs/rpc-server.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
asyncapi: '2.6.0'
1+
asyncapi: '2.1.0'
22
id: 'urn:example:rpcserver'
33
defaultContentType: application/json
44

src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,12 @@ fn main() {
3434

3535
// render template and write
3636
template_render_write(
37-
&template_path.join("main.rs"),
37+
&template_path.join("main.rstmpl"),
3838
&async_config,
3939
&output_path.join("src/main.rs"),
4040
);
4141
template_render_write(
42-
&template_path.join("handler.rs"),
42+
&template_path.join("handler.rstmpl"),
4343
&async_config,
4444
&output_path.join("src/handler.rs"),
4545
);

src/parser/common.rs

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
use std::{collections::HashSet, fs, path::Path};
22

3-
use inflector::Inflector;
4-
use proc_macro2::Ident;
53
use regex::Regex;
64

75
use crate::asyncapi_model::AsyncAPI;
@@ -54,24 +52,27 @@ fn parse_string_to_serde_json_value(file_path: &Path) -> serde_json::Value {
5452
parsed_value
5553
}
5654

57-
fn capitalize_first_char(s: &str) -> String {
58-
let mut c = s.chars();
59-
match c.next() {
60-
None => String::new(),
61-
Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
55+
pub fn validate_identifier_string(s: &str, camel_case: bool) -> String {
56+
let re = Regex::new(r"[^\w\s]").unwrap();
57+
let mut sanitized = re.replace_all(s, "").to_string();
58+
59+
// split into words and process each word
60+
let words: Vec<&str> = sanitized.split_whitespace().collect();
61+
62+
if camel_case {
63+
sanitized = words
64+
.into_iter()
65+
.map(|word| {
66+
let mut chars = word.chars();
67+
match chars.next() {
68+
None => String::new(),
69+
Some(f) => f.to_uppercase().chain(chars).collect(),
70+
}
71+
})
72+
.collect();
73+
} else {
74+
sanitized = words.join("_").to_lowercase();
6275
}
63-
}
6476

65-
pub fn validate_identifier_string(s: &str) -> String {
66-
// Remove special chars, capitalize words, remove spaces
67-
let re = Regex::new(r"[^\w\s]").unwrap();
68-
let sanitized_identifier = re.replace_all(s, " ").to_title_case().replace(' ', "");
69-
let capitalized_sanitized_identifier = capitalize_first_char(sanitized_identifier.as_str());
70-
// Create a new identifier
71-
// This acts as validation for the message name, panics when the name is invalid
72-
Ident::new(
73-
&capitalized_sanitized_identifier,
74-
proc_macro2::Span::call_site(),
75-
);
76-
capitalized_sanitized_identifier
77+
sanitized
7778
}

src/parser/preprocessor.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ pub fn sanitize_operation_ids_and_check_duplicate(
2222
for (key, value) in map {
2323
if key == "operationId" {
2424
if let serde_json::Value::String(string_val) = &value {
25-
let sanitized_val = validate_identifier_string(string_val.as_str());
25+
let sanitized_val = validate_identifier_string(string_val.as_str(), false);
2626
if seen_operation_ids.contains(&sanitized_val) {
2727
panic!("Duplicate operationId found: {}", sanitized_val);
2828
} else {

src/parser/schema_parser.rs

Lines changed: 118 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use crate::asyncapi_model::{
2-
schema::{ObjectType, SchemaKind, Type},
3-
ReferenceOr, Schema,
2+
schema::{ArrayType, IntegerFormat, NumberFormat, ObjectType, SchemaKind, StringFormat, Type},
3+
ReferenceOr, Schema, VariantOrUnknownOrEmpty,
44
};
55
use core::fmt;
6-
use std::{collections::HashMap, format};
6+
use std::{collections::HashMap, format, panic};
77

88
use super::common::validate_identifier_string;
99

@@ -38,7 +38,7 @@ fn object_schema_to_string(
3838
) -> Result<String, SchemaParserError> {
3939
let before_string: String = format!(
4040
"#[derive(Clone, Debug, Deserialize, Serialize)]\npub struct {} {{\n",
41-
validate_identifier_string(property_name)
41+
validate_identifier_string(property_name, true)
4242
);
4343
let after_string = String::from("\n}\n");
4444
let property_string_iterator: Vec<Result<String, SchemaParserError>> = schema
@@ -68,18 +68,122 @@ fn object_schema_to_string(
6868
Ok(property_name.to_string())
6969
}
7070

71+
fn format_to_rust_type(schema_type: &Type) -> String {
72+
match schema_type {
73+
//TODO: Add suggested validators for each string format
74+
Type::String(_var) => {
75+
match &_var.format {
76+
VariantOrUnknownOrEmpty::Item(item) => {
77+
match item {
78+
StringFormat::Date => {
79+
// Date format is usually "yyyy-mm-dd"
80+
// You could validate it with a regex
81+
// if date_regex.is_match(_var) {
82+
// return "String".to_string();
83+
// }
84+
// panic!("Invalid Date format for string");
85+
"String".to_string()
86+
},
87+
StringFormat::DateTime => {
88+
// DateTime format could be ISO 8601: "2023-01-16T23:28:56.782Z"
89+
// You could validate it using chrono's parse function
90+
// if let Ok(_dt) = chrono::DateTime::parse_from_rfc3339(_var) {
91+
// return "String".to_string();
92+
// }
93+
// panic!("Invalid DateTime format for string");
94+
"String".to_string()
95+
},
96+
StringFormat::Password => {
97+
// Password might not need specific validation, just keep as String
98+
"String".to_string()
99+
},
100+
StringFormat::Byte => {
101+
// Bytes could be base64 encoded string
102+
// if base64::decode(_var).is_ok() {
103+
// return "String".to_string();
104+
// }
105+
// panic!("Invalid Byte format for string");
106+
"String".to_string()
107+
},
108+
StringFormat::Binary => {
109+
// Binary data might be just represented as a String
110+
"String".to_string()
111+
},
112+
}
113+
},
114+
VariantOrUnknownOrEmpty::Unknown(_unknown) => "String".to_string(),
115+
VariantOrUnknownOrEmpty::Empty => "String".to_string(),
116+
}
117+
},
118+
Type::Number(_var) => {
119+
match &_var.format {
120+
VariantOrUnknownOrEmpty::Item(item) => {
121+
match item {
122+
NumberFormat::Float => "f32".to_string(),
123+
NumberFormat::Double => "f64".to_string(),
124+
}
125+
}
126+
VariantOrUnknownOrEmpty::Unknown(_unknown) => "f64".to_string(),
127+
VariantOrUnknownOrEmpty::Empty => "f64".to_string(),
128+
}
129+
},
130+
Type::Integer(_var) => {
131+
match &_var.format {
132+
VariantOrUnknownOrEmpty::Item(item) => {
133+
match item {
134+
IntegerFormat::Int32 => "i32".to_string(),
135+
IntegerFormat::Int64 => "i64".to_string(),
136+
}
137+
}
138+
VariantOrUnknownOrEmpty::Unknown(_unknown) => "i64".to_string(),
139+
VariantOrUnknownOrEmpty::Empty => "i64".to_string(),
140+
}
141+
},
142+
Type::Boolean{} => "bool".to_string(),
143+
_type => panic!("Unsupported primitive type: Currently only supports string, number, integer and boolean types"),
144+
}
145+
}
146+
71147
fn primitive_type_to_string(
72148
schema_type: Type,
73149
property_name: &str,
74150
) -> Result<String, SchemaParserError> {
75-
// TODO: Add support for arrays
76-
match schema_type {
77-
Type::String(_var) => Ok(format!("pub {}: String", validate_identifier_string(property_name))),
78-
Type::Number(_var) => Ok(format!("pub {}: f64", validate_identifier_string(property_name))),
79-
Type::Integer(_var) => Ok(format!("pub {}: int64", validate_identifier_string(property_name)) ),
80-
Type::Boolean{} => Ok(format!("pub {}: bool", validate_identifier_string(property_name))),
81-
_type => Err(SchemaParserError::GenericError("Unsupported primitive type: Currently only supports string, number, integer and boolean types".to_string(), Some(property_name.into()))),
82-
}
151+
Ok(format!(
152+
"pub {}: {}",
153+
validate_identifier_string(property_name, false),
154+
format_to_rust_type(&schema_type)
155+
))
156+
}
157+
158+
fn array_type_to_string(
159+
array_type: &ArrayType,
160+
property_name: &str,
161+
) -> Result<String, SchemaParserError> {
162+
let item_type = match &array_type.items {
163+
Some(type_box) => match type_box {
164+
ReferenceOr::Item(schema) => match &schema.schema_kind {
165+
SchemaKind::Type(schema_type) => format_to_rust_type(schema_type),
166+
_ => panic!("Unsupported schema kind"),
167+
},
168+
ReferenceOr::Reference { reference: _ } => {
169+
return Err(SchemaParserError::GenericError(
170+
"References are not supported yet".to_string(),
171+
property_name.to_string().into(),
172+
))
173+
}
174+
},
175+
None => {
176+
return Err(SchemaParserError::GenericError(
177+
"Array type without item type".into(),
178+
None,
179+
))
180+
}
181+
};
182+
Ok(format!(
183+
"pub {}: Vec<{}>",
184+
validate_identifier_string(property_name, false),
185+
item_type
186+
))
83187
}
84188

85189
pub fn schema_parser_mapper(
@@ -95,9 +199,10 @@ pub fn schema_parser_mapper(
95199
Ok(format!(
96200
"pub {}: {}",
97201
struct_name,
98-
validate_identifier_string(struct_name.as_str()).as_str()
202+
validate_identifier_string(struct_name.as_str(), false).as_str()
99203
))
100204
}
205+
Type::Array(array_type) => array_type_to_string(array_type, property_name),
101206
_primitive_type => primitive_type_to_string(_primitive_type.clone(), property_name),
102207
},
103208
_other_schema_kind => {

src/template_model/simplified_operation.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ pub fn simplify_operation(operation: &Operation, channel_name: &str) -> Simplifi
3434
let unique_id = operation
3535
.operation_id
3636
.clone()
37-
.unwrap_or_else(|| validate_identifier_string(channel_name));
37+
.unwrap_or_else(|| validate_identifier_string(channel_name, false));
3838

3939
let messages: Vec<SimplifiedMessage> = match &operation.message {
4040
Some(operation_message) => match operation_message {
File renamed without changes.

0 commit comments

Comments
 (0)