diff --git a/example/specs/basic.yaml b/example/specs/basic.yaml index 510ecaa..46aad99 100644 --- a/example/specs/basic.yaml +++ b/example/specs/basic.yaml @@ -14,6 +14,9 @@ channels: message: payload: type: object + properties: + name: + type: string publish: operationId: userSingedUp summary: send welcome email to user diff --git a/example/specs/basic_ref.yml b/example/specs/basic_ref.yml index ea71f4f..628946d 100644 --- a/example/specs/basic_ref.yml +++ b/example/specs/basic_ref.yml @@ -10,6 +10,7 @@ servers: channels: user/signedup: subscribe: + operationId: onUserSignup message: $ref: "#/components/messages/userSignUp" components: diff --git a/src/parser/common.rs b/src/parser/common.rs index 161038d..3c8eddc 100644 --- a/src/parser/common.rs +++ b/src/parser/common.rs @@ -5,14 +5,14 @@ use regex::Regex; use crate::asyncapi_model::AsyncAPI; -pub fn parse_spec_to_model(path: &Path) -> Result { +pub fn parse_spec_to_model(path: &Path) -> Result { let string_content = fs::read_to_string(path).expect("file could not be read"); // check if file is yaml or json let parsed = match path.extension() { Some(ext) => match ext.to_str() { - Some("yaml") => serde_yaml::from_str::(&string_content).unwrap(), - Some("yml") => serde_yaml::from_str::(&string_content).unwrap(), - Some("json") => serde_json::from_str::(&string_content).unwrap(), + Some("yaml") => serde_yaml::from_str::(&string_content).unwrap(), + Some("yml") => serde_yaml::from_str::(&string_content).unwrap(), + Some("json") => serde_json::from_str::(&string_content).unwrap(), _ => { panic!("file has no extension"); } @@ -21,7 +21,10 @@ pub fn parse_spec_to_model(path: &Path) -> Result { panic!("file has no extension"); } }; - Ok(parsed) + let with_resolved_references = + crate::parser::resolve_refs::resolve_refs(parsed.clone(), parsed); + let spec = serde_json::from_value::(with_resolved_references)?; + Ok(spec) } fn capitalize_first_char(s: &str) -> String { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 4a5eb81..45bc102 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1,6 +1,8 @@ mod common; mod pubsub; +mod resolve_refs; mod schema_parser; pub use common::parse_spec_to_model; pub use pubsub::spec_to_pubsub_template_type; +pub use resolve_refs::resolve_refs; pub use schema_parser::schema_parser_mapper; diff --git a/src/parser/pubsub.rs b/src/parser/pubsub.rs index 2284185..a482251 100644 --- a/src/parser/pubsub.rs +++ b/src/parser/pubsub.rs @@ -108,6 +108,7 @@ pub fn spec_to_pubsub_template_type<'a>( server, subscribe_channels, publish_channels, + schema: joined_schemas, }; Ok(pubsub_template) } diff --git a/src/parser/resolve_refs.rs b/src/parser/resolve_refs.rs new file mode 100644 index 0000000..9cef3a9 --- /dev/null +++ b/src/parser/resolve_refs.rs @@ -0,0 +1,85 @@ +pub fn resolve_json_path(json: serde_json::Value, path: &str) -> serde_json::Value { + let parts = path.split('/').collect::>(); + let mut current_json = json; + for part in parts { + current_json = current_json[part].clone(); + } + current_json +} + +pub fn resolve_refs(json: serde_json::Value, root_json: serde_json::Value) -> serde_json::Value { + match json { + serde_json::Value::Object(map) => { + let mut new_map = serde_json::Map::new(); + for (key, value) in map { + if key == "$ref" { + if let serde_json::Value::String(string_val) = value { + let correct_json = resolve_json_path( + root_json.clone(), + string_val.trim_start_matches("#/"), + ); + return resolve_refs(correct_json, root_json); + } else { + panic!("$ref value is not a string"); + } + } + let new_value = resolve_refs(value, root_json.clone()); + new_map.insert(key, new_value); + } + serde_json::Value::Object(new_map) + } + serde_json::Value::Array(array) => { + let new_array = array + .into_iter() + .map(|value| resolve_refs(value, root_json.clone())) + .collect(); + serde_json::Value::Array(new_array) + } + _ => json, + } +} + +#[cfg(test)] +mod tests { + use std::{fs, path::Path}; + + use super::*; + use crate::*; + + const SCHEMAS: [&str; 1] = ["./example/specs/basic_ref.yml"]; + + //parse file to json, allowed files are yaml and json + fn parse_test(path: &Path) -> serde_json::Value { + let string_content = fs::read_to_string(path).expect("file could not be read"); + // check if file is yaml or json + let parsed: serde_json::Value = match path.extension() { + Some(ext) => match ext.to_str() { + Some("yaml") => serde_yaml::from_str::(&string_content).unwrap(), + Some("yml") => serde_yaml::from_str::(&string_content).unwrap(), + Some("json") => serde_json::from_str::(&string_content).unwrap(), + _ => { + panic!("file has no extension"); + } + }, + None => { + panic!("file has no extension"); + } + }; + parsed + } + + #[test] + fn resolves_refs() { + for schema_paths in SCHEMAS { + let definition = parse_test(Path::new(schema_paths)); + let resolved: serde_json::Value = resolve_refs(definition.clone(), definition.clone()); + let filename_without_extension = Path::new(schema_paths) + .file_stem() + .unwrap() + .to_str() + .unwrap(); + let out_dir = Path::new("./test_output/{}.rs").join(filename_without_extension); + utils::write_to_path_create_dir(&resolved.to_string(), &out_dir).unwrap(); + } + } +} diff --git a/src/template_model/pubsub_template.rs b/src/template_model/pubsub_template.rs index 45ee4b5..50f70f0 100644 --- a/src/template_model/pubsub_template.rs +++ b/src/template_model/pubsub_template.rs @@ -9,6 +9,7 @@ pub struct PubsubTemplate<'a> { pub server: &'a Server, pub subscribe_channels: Vec<(&'a String, &'a Operation)>, pub publish_channels: Vec<(&'a String, &'a Operation)>, + pub schema: String, } impl<'a> From<&PubsubTemplate<'a>> for gtmpl::Value { diff --git a/templates/handler.rs b/templates/handler.rs index f9f196d..c140c3b 100644 --- a/templates/handler.rs +++ b/templates/handler.rs @@ -2,6 +2,8 @@ use std::time; use async_nats::{Client, Message}; use crate::publish_message; +{{ .schema }} + {{ range .subscribe_channels }} pub fn handler_{{ (index . 1).operationId }}(message: Message) { println!("Received message {:#?}", message)