From b119632b9fdf28f505ce015d1fcecd4b436b53f0 Mon Sep 17 00:00:00 2001 From: Cameron Taggart Date: Mon, 26 Oct 2020 07:26:42 -0600 Subject: [PATCH 1/6] add error report --- Cargo.lock | 68 +++++++++++++++++++++- codegen/Cargo.toml | 6 +- codegen/examples/gen_mgmt.rs | 107 ++++++++++++++++++++++++++--------- codegen/src/codegen.rs | 76 +++++++++++++------------ codegen/src/lib.rs | 17 +++--- codegen/src/lib_rs.rs | 20 ++++--- codegen/src/path.rs | 1 - 7 files changed, 213 insertions(+), 82 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4e43ee5..1954ee0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,20 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "addr2line" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6a2d3371669ab3ca9797670853d61402b03d0b4b9ebf33d677dfa720203072" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" + [[package]] name = "aho-corasick" version = "0.7.14" @@ -74,6 +89,20 @@ dependencies = [ "serde_json", ] +[[package]] +name = "backtrace" +version = "0.3.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707b586e0e2f247cbde68cdd2c3ce69ea7b7be43e1c5b426e37c9319c4b9838e" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "bitflags" version = "1.2.1" @@ -119,6 +148,12 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "clap" version = "2.33.3" @@ -233,6 +268,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "gimli" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724" + [[package]] name = "hashbrown" version = "0.9.1" @@ -293,9 +334,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743" +checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" [[package]] name = "linked-hash-map" @@ -315,6 +356,22 @@ version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" +[[package]] +name = "miniz_oxide" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "object" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37fd5004feb2ce328a52b0b3d01dbf4ffff72583493900ed15f22d4111c51693" + [[package]] name = "opaque-debug" version = "0.2.3" @@ -442,6 +499,12 @@ version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cab7a364d15cde1e505267766a2d3c4e22a843e1a601f0fa7564c0f82ced11c" +[[package]] +name = "rustc-demangle" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" + [[package]] name = "ryu" version = "1.0.5" @@ -515,6 +578,7 @@ version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c4e6046e4691afe918fd1b603fd6e515bcda5388a1092a9edbada307d159f09" dependencies = [ + "backtrace", "doc-comment", "snafu-derive", ] diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index bcdb5fb..35d34f8 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -15,10 +15,10 @@ serde_json = "*" serde_yaml = "*" heck = "*" regex = "*" -indexmap = {version = "*", features = ["serde-1"]} +indexmap = { version = "*", features = ["serde-1"] } path_abs = "*" comrak = "0.8" serde = "1.0" -snafu = "0.6" +snafu = { version = "0.6", features = ["backtraces"] } http = "0.2" -lazy_static = "1.4" \ No newline at end of file +lazy_static = "1.4" diff --git a/codegen/examples/gen_mgmt.rs b/codegen/examples/gen_mgmt.rs index 86f26ac..1ee2661 100644 --- a/codegen/examples/gen_mgmt.rs +++ b/codegen/examples/gen_mgmt.rs @@ -1,20 +1,17 @@ // cargo run --example gen_mgmt // https://github.com/Azure/azure-rest-api-specs/blob/master/specification/compute/resource-manager - use autorust_codegen::{ - cargo_toml, + self, cargo_toml, config_parser::{self, to_api_version, to_mod_name}, - lib_rs, path, run, Config, + lib_rs, path, Config, }; use heck::SnakeCase; +use snafu::{ErrorCompat, OptionExt, ResultExt, Snafu}; use std::{ collections::{HashMap, HashSet}, fs, }; -pub type Error = Box; -pub type Result = std::result::Result; - const SPEC_FOLDER: &str = "../azure-rest-api-specs/specification"; const OUTPUT_FOLDER: &str = "../azure-sdk-for-rust/services/mgmt"; @@ -25,7 +22,8 @@ const SERVICE_NAMES: &[(&str, &str)] = &[ const ONLY_SERVICES: &[&str] = &[ // "datafactory", - ]; + // "network", +]; const SKIP_SERVICES: &[&str] = &[ "apimanagement", // missing properties, all preview apis @@ -65,14 +63,69 @@ const SKIP_SERVICE_TAGS: &[(&str, &str)] = &[ ("recoveryservicesbackup", "package-2020-07"), // duplicate fn get_operation_status ]; -fn main() -> Result<()> { - let paths = fs::read_dir(SPEC_FOLDER)?; +pub type Result = std::result::Result; + +#[derive(Debug, Snafu)] +// #[snafu(visibility(pub(crate)))] +pub enum Error { + #[snafu(display("file name was not utf-8"))] + FileNameNotUtf8Error {}, + IoError { + source: std::io::Error, + }, + PathError { + source: path::Error, + }, + CodegenError { + #[snafu(backtrace)] + source: autorust_codegen::Error, + }, + CargoTomlError { + source: cargo_toml::Error, + }, + LibRsError { + source: lib_rs::Error, + }, +} + +fn main() { + match run() { + Ok(_) => {} + Err(err) => { + report(&err); + } + } +} + +fn report(err: &E) +where + E: std::error::Error, + E: snafu::ErrorCompat, + E: Send + Sync, +{ + eprintln!("[ERROR] {}", err); + if let Some(source) = err.source() { + eprintln!(); + eprintln!("Caused by:"); + for (i, e) in std::iter::successors(Some(source), |e| e.source()).enumerate() { + eprintln!(" {}: {}", i, e); + } + } + + if let Some(backtrace) = ErrorCompat::backtrace(err) { + eprintln!("Backtrace:"); + eprintln!("{}", backtrace); + } +} + +fn run() -> Result<()> { + let paths = fs::read_dir(SPEC_FOLDER).context(IoError)?; let mut spec_folders = Vec::new(); for path in paths { - let path = path?; - if path.file_type()?.is_dir() { + let path = path.context(IoError)?; + if path.file_type().context(IoError)?.is_dir() { let file_name = path.file_name(); - let spec_folder = file_name.to_str().ok_or("file name was not utf-8")?; + let spec_folder = file_name.to_str().context(FileNameNotUtf8Error)?; spec_folders.push(spec_folder.to_owned()); } } @@ -95,8 +148,8 @@ fn main() -> Result<()> { } fn gen_crate(spec_folder: &str) -> Result<()> { - let spec_folder_full = path::join(SPEC_FOLDER, spec_folder)?; - let readme = &path::join(spec_folder_full, "resource-manager/readme.md")?; + let spec_folder_full = path::join(SPEC_FOLDER, spec_folder).context(PathError)?; + let readme = &path::join(spec_folder_full, "resource-manager/readme.md").context(PathError)?; if !readme.exists() { println!("readme not found at {:?}", readme); return Ok(()); @@ -105,14 +158,13 @@ fn gen_crate(spec_folder: &str) -> Result<()> { let service_name = &get_service_name(spec_folder); // println!("{} -> {}", spec_folder, service_name); let crate_name = &format!("azure_mgmt_{}", service_name); - let output_folder = &path::join(OUTPUT_FOLDER, service_name)?; + let output_folder = &path::join(OUTPUT_FOLDER, service_name).context(PathError)?; - let src_folder = path::join(output_folder, "src")?; + let src_folder = path::join(output_folder, "src").context(PathError)?; if src_folder.exists() { - fs::remove_dir_all(&src_folder)?; + fs::remove_dir_all(&src_folder).context(IoError)?; } - // fs::create_dir_all(&src_folder)?; let packages = config_parser::parse_configurations_from_autorest_config_file(&readme); let mut feature_mod_names = Vec::new(); let skip_service_tags: HashSet<&(&str, &str)> = SKIP_SERVICE_TAGS.iter().collect(); @@ -128,24 +180,26 @@ fn gen_crate(spec_folder: &str) -> Result<()> { let mod_name = &to_mod_name(tag); feature_mod_names.push((tag.to_string(), mod_name.clone())); // println!(" {}", mod_name); - let mod_output_folder = path::join(&src_folder, mod_name)?; + let mod_output_folder = path::join(&src_folder, mod_name).context(PathError)?; // println!(" {:?}", mod_output_folder); // for input_file in &package.input_files { // println!(" {}", input_file); // } - let input_files: Vec<_> = package + let input_files: Result> = package .input_files .iter() - .map(|input_file| path::join(readme, input_file).unwrap()) + .map(|input_file| Ok(path::join(readme, input_file).context(PathError)?)) .collect(); + let input_files = input_files?; // for input_file in &input_files { // println!(" {:?}", input_file); // } - run(Config { + autorust_codegen::run(Config { api_version: Some(api_version), output_folder: mod_output_folder.into(), input_files, - })?; + }) + .context(CodegenError)?; } } if feature_mod_names.len() == 0 { @@ -154,9 +208,10 @@ fn gen_crate(spec_folder: &str) -> Result<()> { cargo_toml::create( crate_name, &feature_mod_names, - &path::join(output_folder, "Cargo.toml").map_err(|_| "Cargo.toml")?, - )?; - lib_rs::create(&feature_mod_names, &path::join(src_folder, "lib.rs").map_err(|_| "lib.rs")?)?; + &path::join(output_folder, "Cargo.toml").context(PathError)?, + ) + .context(CargoTomlError)?; + lib_rs::create(&feature_mod_names, &path::join(src_folder, "lib.rs").context(PathError)?).context(LibRsError)?; Ok(()) } diff --git a/codegen/src/codegen.rs b/codegen/src/codegen.rs index bec54f6..3438b8a 100644 --- a/codegen/src/codegen.rs +++ b/codegen/src/codegen.rs @@ -24,6 +24,7 @@ pub enum Error { SpecError { source: spec::Error }, ArrayExpectedToHaveItems, NoNameForRef, + EmtpyIdent, } /// Whether or not to pass a type is a reference. @@ -123,7 +124,7 @@ impl CodeGen { file.extend(self.create_vec_alias(doc_file, schema_name, schema)?); } else if is_local_enum(schema) { let no_namespace = TokenStream::new(); - let (_tp_name, tp) = create_enum(&no_namespace, schema_name, schema); + let (_tp_name, tp) = create_enum(&no_namespace, schema_name, schema)?; file.extend(tp); } else { for stream in self.create_struct(doc_file, schema_name, schema)? { @@ -173,7 +174,7 @@ impl CodeGen { for (module_name, module) in modules { match module_name { Some(module_name) => { - let name = ident(&module_name); + let name = ident(&module_name)?; file.extend(quote! { pub mod #name { use crate::models::*; @@ -193,7 +194,7 @@ impl CodeGen { fn create_vec_alias(&self, doc_file: &Path, alias_name: &str, schema: &ResolvedSchema) -> Result { let items = get_schema_array_items(&schema.schema.common)?; - let typ = ident(&alias_name.to_camel_case()); + let typ = ident(&alias_name.to_camel_case())?; let items_typ = get_type_name_for_schema_ref(&items, AsReference::False)?; Ok(quote! { pub type #typ = Vec<#items_typ>; }) } @@ -203,13 +204,13 @@ impl CodeGen { let mut streams = Vec::new(); let mut local_types = Vec::new(); let mut props = TokenStream::new(); - let ns = ident(&struct_name.to_snake_case()); - let nm = ident(&struct_name.to_camel_case()); + let ns = ident(&struct_name.to_snake_case())?; + let nm = ident(&struct_name.to_camel_case())?; let required: HashSet<&str> = schema.schema.required.iter().map(String::as_str).collect(); for schema in &schema.schema.all_of { let type_name = get_type_name_for_schema_ref(schema, AsReference::False)?; - let field_name = ident(&type_name.to_string().to_snake_case()); + let field_name = ident(&type_name.to_string().to_snake_case())?; props.extend(quote! { #[serde(flatten)] pub #field_name: #type_name, @@ -221,7 +222,7 @@ impl CodeGen { .resolve_schema_map(doc_file, &schema.schema.properties) .context(SpecError)?; for (property_name, property) in &properties { - let nm = ident(&property_name.to_snake_case()); + let nm = ident(&property_name.to_snake_case())?; let (mut field_tp_name, field_tp) = self.create_struct_field_type(doc_file, &ns, property_name, property)?; let is_required = required.contains(property_name.as_str()); let is_vec = is_vec(&field_tp_name); @@ -288,15 +289,15 @@ impl CodeGen { ) -> Result<(TokenStream, Vec)> { match &property.ref_key { Some(ref_key) => { - let tp = ident(&ref_key.name.to_camel_case()); + let tp = ident(&ref_key.name.to_camel_case())?; Ok((tp, Vec::new())) } None => { if is_local_enum(property) { - let (tp_name, tp) = create_enum(namespace, property_name, property); + let (tp_name, tp) = create_enum(namespace, property_name, property)?; Ok((tp_name, vec![tp])) } else if is_local_struct(property) { - let id = ident(&property_name.to_camel_case()); + let id = ident(&property_name.to_camel_case())?; let tp_name = quote! {#namespace::#id}; let tps = self.create_struct(doc_file, property_name, property)?; // println!("creating local struct {:?} {}", tp_name, tps.len()); @@ -394,13 +395,13 @@ fn is_local_struct(property: &ResolvedSchema) -> bool { property.schema.properties.len() > 0 } -fn create_enum(namespace: &TokenStream, property_name: &str, property: &ResolvedSchema) -> (TokenStream, TokenStream) { +fn create_enum(namespace: &TokenStream, property_name: &str, property: &ResolvedSchema) -> Result<(TokenStream, TokenStream)> { let schema_type = property.schema.common.type_.as_ref(); let enum_values = enum_values_as_strings(&property.schema.common.enum_); - let id = ident(&property_name.to_camel_case()); + let id = ident(&property_name.to_camel_case())?; let mut values = TokenStream::new(); - enum_values.iter().for_each(|name| { - let nm = ident(&name.to_camel_case()); + for name in enum_values { + let nm = ident(&name.to_camel_case())?; let rename = if &nm.to_string() == name { quote! {} } else { @@ -411,8 +412,8 @@ fn create_enum(namespace: &TokenStream, property_name: &str, property: &Resolved #nm, }; values.extend(value); - }); - let nm = ident(&property_name.to_camel_case()); + } + let nm = ident(&property_name.to_camel_case())?; let tp = quote! { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum #nm { @@ -420,7 +421,7 @@ fn create_enum(namespace: &TokenStream, property_name: &str, property: &Resolved } }; let tp_name = quote! {#namespace::#id}; - (tp_name, tp) + Ok((tp_name, tp)) } /// Wraps a type in an Option if is not required. @@ -432,10 +433,10 @@ fn require(is_required: bool, tp: TokenStream) -> TokenStream { } } -pub fn ident(text: &str) -> TokenStream { +pub fn ident(text: &str) -> Result { let text = text.replace(".", "_"); // prefix with underscore if starts with invalid character - let text = match text.chars().next().unwrap() { + let text = match text.chars().next().context(EmtpyIdent)? { '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '0' => format!("_{}", text), _ => text.to_owned(), }; @@ -444,7 +445,7 @@ pub fn ident(text: &str) -> TokenStream { } else { format_ident!("{}", text) }; - idt.into_token_stream() + Ok(idt.into_token_stream()) } fn enum_values_as_strings(values: &Vec) -> Vec<&str> { @@ -478,7 +479,7 @@ fn get_param_type(param: &Parameter) -> Result { Ok(require(is_required || is_array, tp)) } -fn get_param_name(param: &Parameter) -> TokenStream { +fn get_param_name(param: &Parameter) -> Result { ident(¶m.name.to_snake_case()) } @@ -494,7 +495,7 @@ fn format_path(param_re: &Regex, path: &str) -> String { fn create_function_params(cg: &CodeGen, doc_file: &Path, parameters: &Vec) -> Result { let mut params: Vec = Vec::new(); for param in parameters { - let name = get_param_name(param); + let name = get_param_name(param)?; let tp = get_param_type(param)?; params.push(quote! { #name: #tp }); } @@ -557,7 +558,7 @@ fn get_type_name_for_schema_ref(schema: &ReferenceOr, as_ref: AsReferenc ReferenceOr::Reference { reference, .. } => { let rf = Reference::parse(&reference); let name = &rf.name.context(NoNameForRef)?; - let idt = ident(&name.to_camel_case()); + let idt = ident(&name.to_camel_case())?; match as_ref { AsReference::True => Ok(quote! { &#idt }), AsReference::False => Ok(quote! { #idt }), @@ -584,11 +585,12 @@ fn create_function( param_re: &Regex, function_name: &str, ) -> Result { - let fname = ident(function_name); + let fname = ident(function_name)?; let params = parse_params(param_re, path); // println!("path params {:#?}", params); - let params: Vec<_> = params.iter().map(|s| ident(&s.to_snake_case())).collect(); + let params: Result> = params.iter().map(|s| Ok(ident(&s.to_snake_case())?)).collect(); + let params = params?; let uri_str_args = quote! { #(#params),* }; let fpath = format!("{{}}{}", &format_path(param_re, path)); @@ -641,7 +643,7 @@ fn create_function( // params for param in ¶meters { let param_name = ¶m.name; - let param_name_var = get_param_name(¶m); + let param_name_var = get_param_name(¶m)?; let required = param.required.unwrap_or(false); match param.in_ { ParameterType::Path => {} // handled above @@ -741,7 +743,7 @@ fn create_function( Some(tp) => quote! { (#tp) }, None => quote! {}, }; - let enum_type_name = ident(&get_response_type_name(status_code)); + let enum_type_name = ident(&get_response_type_name(status_code))?; success_responses_ts.extend(quote! { #enum_type_name#tp, }) } response_enum.extend(quote! { @@ -763,7 +765,7 @@ fn create_function( if response_type == "DefaultResponse" { error_responses_ts.extend(quote! { DefaultResponse { status_code: StatusCode, #tp }, }); } else { - let response_type = ident(response_type); + let response_type = ident(response_type)?; error_responses_ts.extend(quote! { #response_type { #tp }, }); } } @@ -776,8 +778,8 @@ fn create_function( match status_code { autorust_openapi::StatusCode::Code(_) => { let tp = create_response_type(rsp)?; - let status_code_name = ident(&get_status_code_name(status_code)); - let response_type_name = ident(&get_response_type_name(status_code)); + let status_code_name = ident(&get_status_code_name(status_code))?; + let response_type_name = ident(&get_response_type_name(status_code))?; if is_single_response { match tp { Some(tp) => { @@ -825,8 +827,8 @@ fn create_function( match status_code { autorust_openapi::StatusCode::Code(_) => { let tp = create_response_type(rsp)?; - let status_code_name = ident(&get_status_code_name(status_code)); - let response_type_name = ident(&get_response_type_name(status_code)); + let status_code_name = ident(&get_status_code_name(status_code))?; + let response_type_name = ident(&get_response_type_name(status_code))?; match tp { Some(tp) => { match_status.extend(quote! { @@ -933,16 +935,18 @@ mod tests { use super::*; #[test] - fn test_ident_odata_next_link() { + fn test_ident_odata_next_link() -> Result<()> { let idt = "odata.nextLink".to_snake_case(); assert_eq!(idt, "odata.next_link"); - let idt = ident(&idt); + let idt = ident(&idt)?; assert_eq!(idt.to_string(), "odata_next_link"); + Ok(()) } #[test] - fn test_ident_three_dot_two() { - let idt = ident("3.2"); + fn test_ident_three_dot_two() -> Result<()> { + let idt = ident("3.2")?; assert_eq!(idt.to_string(), "_3_2"); + Ok(()) } } diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index f1bcc5e..3f2447d 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -12,7 +12,7 @@ pub use self::{ spec::{OperationVerb, ResolvedSchema, Spec}, }; use proc_macro2::TokenStream; -use snafu::{ResultExt, Snafu}; +use snafu::{Backtrace, ResultExt, Snafu}; use std::{ fs::{self, File}, io::prelude::*, @@ -22,25 +22,28 @@ use std::{ extern crate lazy_static; pub type Result = std::result::Result; + #[derive(Debug, Snafu)] +// #[snafu(visibility(pub(crate)))] pub enum Error { #[snafu(display("Could not create output directory {}: {}", directory.display(), source))] - CreateOutputDirectory { + CreateOutputDirectoryError { directory: PathBuf, source: std::io::Error, }, #[snafu(display("Could not create file {}: {}", file.display(), source))] - CreateFile { + CreateFileError { file: PathBuf, source: std::io::Error, }, #[snafu(display("Could not write file {}: {}", file.display(), source))] - WriteFile { + WriteFileError { file: PathBuf, source: std::io::Error, }, CodeGenError { source: codegen::Error, + backtrace: Backtrace, }, PathError { source: path::Error, @@ -56,7 +59,7 @@ pub struct Config { pub fn run(config: Config) -> Result<()> { let directory = &config.output_folder; - fs::create_dir_all(directory).context(CreateOutputDirectory { directory })?; + fs::create_dir_all(directory).context(CreateOutputDirectoryError { directory })?; let cg = &CodeGen::new(config.clone()).context(CodeGenError)?; // create models from schemas @@ -81,7 +84,7 @@ pub fn write_file>(file: P, tokens: &TokenStream) -> Result<()> let file: PathBuf = file.into(); println!("writing file {}", &file.display()); let code = tokens.to_string(); - let mut buffer = File::create(&file).context(CreateFile { file: file.clone() })?; - buffer.write_all(&code.as_bytes()).context(WriteFile { file })?; + let mut buffer = File::create(&file).context(CreateFileError { file: file.clone() })?; + buffer.write_all(&code.as_bytes()).context(WriteFileError { file })?; Ok(()) } diff --git a/codegen/src/lib_rs.rs b/codegen/src/lib_rs.rs index c043a7c..26009ae 100644 --- a/codegen/src/lib_rs.rs +++ b/codegen/src/lib_rs.rs @@ -4,19 +4,26 @@ use crate::{ }; use proc_macro2::TokenStream; use quote::quote; +use snafu::{ResultExt, Snafu}; use std::path::Path; -use crate::Result; +pub type Result = std::result::Result; + +#[derive(Debug, Snafu)] +pub enum Error { + CodeGenError { source: crate::codegen::Error }, + WriteFileError { source: crate::Error }, +} pub fn create(feature_mod_names: &Vec<(String, String)>, path: &Path) -> Result<()> { - write_file(path, &create_body(feature_mod_names))?; + write_file(path, &create_body(feature_mod_names)?).context(WriteFileError)?; Ok(()) } -fn create_body(feature_mod_names: &Vec<(String, String)>) -> TokenStream { +fn create_body(feature_mod_names: &Vec<(String, String)>) -> Result { let mut cfgs = TokenStream::new(); for (feature_name, mod_name) in feature_mod_names { - let mod_name = ident(mod_name); + let mod_name = ident(mod_name).context(CodeGenError)?; cfgs.extend(quote! { #[cfg(feature = #feature_name)] mod #mod_name; @@ -25,7 +32,7 @@ fn create_body(feature_mod_names: &Vec<(String, String)>) -> TokenStream { }); } let generated_by = create_generated_by_header(); - quote! { + Ok(quote! { #generated_by #cfgs @@ -55,6 +62,5 @@ fn create_body(feature_mod_names: &Vec<(String, String)>) -> TokenStream { } } } - - } + }) } diff --git a/codegen/src/path.rs b/codegen/src/path.rs index 2f64ac4..3e63ae2 100644 --- a/codegen/src/path.rs +++ b/codegen/src/path.rs @@ -2,7 +2,6 @@ use path_abs::PathMut; use snafu::{ResultExt, Snafu}; use std::path::{Path, PathBuf}; -// pub type StdError = std:error::Error; pub type Result = std::result::Result; #[derive(Debug, Snafu)] pub enum Error { From 0826457223a49bf0870bb7f51451765dd435c543 Mon Sep 17 00:00:00 2001 From: Cameron Taggart Date: Wed, 28 Oct 2020 22:05:02 -0600 Subject: [PATCH 2/6] parse Ident --- Cargo.lock | 7 +- codegen/Cargo.toml | 3 +- codegen/examples/gen_mgmt.rs | 12 +- codegen/src/codegen.rs | 228 ++++++++++++++++++++++---- codegen/src/lib.rs | 19 ++- codegen/src/lib_rs.rs | 15 +- codegen/src/spec.rs | 24 ++- codegen/tests/azure_rest_api_specs.rs | 2 +- 8 files changed, 250 insertions(+), 60 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1954ee0..2ac73bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,12 +77,13 @@ dependencies = [ "serde_json", "serde_yaml", "snafu", + "syn", ] [[package]] name = "autorust_openapi" version = "0.2.0" -source = "git+https://github.com/ctaggart/autorust_openapi#181baf2b89d32c013258e40b59d3e00bb501c9c4" +source = "git+https://github.com/ctaggart/autorust_openapi?rev=181baf2b89d32c013258e40b59d3e00bb501c9c4#181baf2b89d32c013258e40b59d3e00bb501c9c4" dependencies = [ "indexmap", "serde", @@ -352,9 +353,9 @@ checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" [[package]] name = "memchr" -version = "2.3.3" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" [[package]] name = "miniz_oxide" diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 35d34f8..da931c1 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" [lib] [dependencies] -autorust_openapi = { git = "https://github.com/ctaggart/autorust_openapi" } +autorust_openapi = { rev = "181baf2b89d32c013258e40b59d3e00bb501c9c4", git = "https://github.com/ctaggart/autorust_openapi" } # autorust_openapi = { path = "../autorust_openapi" } quote = "*" proc-macro2 = { version = "*", default-features = false } @@ -22,3 +22,4 @@ serde = "1.0" snafu = { version = "0.6", features = ["backtraces"] } http = "0.2" lazy_static = "1.4" +syn = { version = "1.0", features = ["parsing"] } diff --git a/codegen/examples/gen_mgmt.rs b/codegen/examples/gen_mgmt.rs index 1ee2661..45555fe 100644 --- a/codegen/examples/gen_mgmt.rs +++ b/codegen/examples/gen_mgmt.rs @@ -6,7 +6,7 @@ use autorust_codegen::{ lib_rs, path, Config, }; use heck::SnakeCase; -use snafu::{ErrorCompat, OptionExt, ResultExt, Snafu}; +use snafu::{OptionExt, ResultExt, Snafu}; use std::{ collections::{HashMap, HashSet}, fs, @@ -21,7 +21,6 @@ const SERVICE_NAMES: &[(&str, &str)] = &[ ]; const ONLY_SERVICES: &[&str] = &[ - // "datafactory", // "network", ]; @@ -61,6 +60,7 @@ const SKIP_SERVICE_TAGS: &[(&str, &str)] = &[ ("azureactivedirectory", "package-preview-2020-07"), ("resources", "package-policy-2020-03"), ("recoveryservicesbackup", "package-2020-07"), // duplicate fn get_operation_status + ("network", "package-2017-03-30-only"), // SchemaNotFound 2017-09-01/network.json SubResource ]; pub type Result = std::result::Result; @@ -112,10 +112,10 @@ where } } - if let Some(backtrace) = ErrorCompat::backtrace(err) { - eprintln!("Backtrace:"); - eprintln!("{}", backtrace); - } + // if let Some(backtrace) = ErrorCompat::backtrace(err) { + // eprintln!("Backtrace:"); + // eprintln!("{}", backtrace); + // } } fn run() -> Result<()> { diff --git a/codegen/src/codegen.rs b/codegen/src/codegen.rs index ac2021f..a6beba0 100644 --- a/codegen/src/codegen.rs +++ b/codegen/src/codegen.rs @@ -8,7 +8,7 @@ use autorust_openapi::{CollectionFormat, DataType, Parameter, ParameterType, Pat use heck::{CamelCase, SnakeCase}; use indexmap::IndexMap; use proc_macro2::TokenStream; -use quote::{format_ident, quote, ToTokens}; +use quote::{quote, ToTokens}; use regex::Regex; use serde_json::Value; use snafu::{OptionExt, ResultExt, Snafu}; @@ -21,10 +21,32 @@ use std::{ pub type Result = std::result::Result; #[derive(Debug, Snafu)] pub enum Error { - SpecError { source: spec::Error }, + SpecError { + source: spec::Error, + }, ArrayExpectedToHaveItems, NoNameForRef, - EmtpyIdent, + EmtpyIdentError, + #[snafu(display("ParseIdentError {} {}", text, source))] + ParseIdentError { + source: syn::Error, + text: String, + }, + #[snafu(display("IdentError at {}:{} {} ", file, line, source))] + IdentError { + #[snafu(source(from(Error, Box::new)))] + source: Box, + file: &'static str, + line: u32, + }, + #[snafu(display("CreateEnumIdentError {} {} {}", property_name, enum_value, source))] + CreateEnumIdentError { + #[snafu(source(from(Error, Box::new)))] + source: Box, + property_name: String, + enum_value: String, + }, + // CreateEnumIdentError{ property_name: property_name.to_owned(), enum_value: name.to_owned() } } /// Whether or not to pass a type is a reference. @@ -174,7 +196,10 @@ impl CodeGen { for (module_name, module) in modules { match module_name { Some(module_name) => { - let name = ident(&module_name)?; + let name = ident(&module_name).context(IdentError { + file: file!(), + line: line!(), + })?; file.extend(quote! { pub mod #name { use crate::models::*; @@ -194,7 +219,10 @@ impl CodeGen { fn create_vec_alias(&self, doc_file: &Path, alias_name: &str, schema: &ResolvedSchema) -> Result { let items = get_schema_array_items(&schema.schema.common)?; - let typ = ident(&alias_name.to_camel_case())?; + let typ = ident(&alias_name.to_camel_case()).context(IdentError { + file: file!(), + line: line!(), + })?; let items_typ = get_type_name_for_schema_ref(&items, AsReference::False)?; Ok(quote! { pub type #typ = Vec<#items_typ>; }) } @@ -204,13 +232,22 @@ impl CodeGen { let mut streams = Vec::new(); let mut local_types = Vec::new(); let mut props = TokenStream::new(); - let ns = ident(&struct_name.to_snake_case())?; - let nm = ident(&struct_name.to_camel_case())?; + let ns = ident(&struct_name.to_snake_case()).context(IdentError { + file: file!(), + line: line!(), + })?; + let nm = ident(&struct_name.to_camel_case()).context(IdentError { + file: file!(), + line: line!(), + })?; let required: HashSet<&str> = schema.schema.required.iter().map(String::as_str).collect(); for schema in &schema.schema.all_of { let type_name = get_type_name_for_schema_ref(schema, AsReference::False)?; - let field_name = ident(&type_name.to_string().to_snake_case())?; + let field_name = ident(&type_name.to_string().to_snake_case()).context(IdentError { + file: file!(), + line: line!(), + })?; props.extend(quote! { #[serde(flatten)] pub #field_name: #type_name, @@ -222,7 +259,10 @@ impl CodeGen { .resolve_schema_map(doc_file, &schema.schema.properties) .context(SpecError)?; for (property_name, property) in &properties { - let nm = ident(&property_name.to_snake_case())?; + let nm = ident(&property_name.to_snake_case()).context(IdentError { + file: file!(), + line: line!(), + })?; let (mut field_tp_name, field_tp) = self.create_struct_field_type(doc_file, &ns, property_name, property)?; let is_required = required.contains(property_name.as_str()); let is_vec = is_vec(&field_tp_name); @@ -289,7 +329,10 @@ impl CodeGen { ) -> Result<(TokenStream, Vec)> { match &property.ref_key { Some(ref_key) => { - let tp = ident(&ref_key.name.to_camel_case())?; + let tp = ident(&ref_key.name.to_camel_case()).context(IdentError { + file: file!(), + line: line!(), + })?; Ok((tp, Vec::new())) } None => { @@ -297,7 +340,10 @@ impl CodeGen { let (tp_name, tp) = create_enum(namespace, property_name, property)?; Ok((tp_name, vec![tp])) } else if is_local_struct(property) { - let id = ident(&property_name.to_camel_case())?; + let id = ident(&property_name.to_camel_case()).context(IdentError { + file: file!(), + line: line!(), + })?; let tp_name = quote! {#namespace::#id}; let tps = self.create_struct(doc_file, property_name, property)?; // println!("creating local struct {:?} {}", tp_name, tps.len()); @@ -398,10 +444,22 @@ fn is_local_struct(property: &ResolvedSchema) -> bool { fn create_enum(namespace: &TokenStream, property_name: &str, property: &ResolvedSchema) -> Result<(TokenStream, TokenStream)> { let schema_type = property.schema.common.type_.as_ref(); let enum_values = enum_values_as_strings(&property.schema.common.enum_); - let id = ident(&property_name.to_camel_case())?; + let id = ident(&property_name.to_camel_case()).context(IdentError { + file: file!(), + line: line!(), + })?; let mut values = TokenStream::new(); for name in enum_values { - let nm = ident(&name.to_camel_case())?; + let nm = name + .to_camel_case_ident() + .context(IdentError { + file: file!(), + line: line!(), + }) + .context(CreateEnumIdentError { + property_name: property_name.to_owned(), + enum_value: name.to_owned(), + })?; let rename = if &nm.to_string() == name { quote! {} } else { @@ -413,7 +471,10 @@ fn create_enum(namespace: &TokenStream, property_name: &str, property: &Resolved }; values.extend(value); } - let nm = ident(&property_name.to_camel_case())?; + let nm = ident(&property_name.to_camel_case()).context(IdentError { + file: file!(), + line: line!(), + })?; let tp = quote! { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum #nm { @@ -434,20 +495,48 @@ fn require(is_required: bool, tp: TokenStream) -> TokenStream { } pub fn ident(text: &str) -> Result { - let text = text.replace(".", "_"); + let mut txt = text.replace(".", "_"); + txt = txt.replace(",", "_"); + txt = txt.replace("-", "_"); + txt = txt.replace("/", "_"); + txt = txt.replace(" ", ""); // prefix with underscore if starts with invalid character - let text = match text.chars().next().context(EmtpyIdent)? { - '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '0' => format!("_{}", text), - _ => text.to_owned(), + txt = match txt.chars().next().context(EmtpyIdentError)? { + '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '0' => format!("_{}", txt), + _ => txt.to_owned(), }; - let idt = if is_keyword(&text) { - format_ident!("{}_", text) - } else { - format_ident!("{}", text) + if is_keyword(&txt) { + txt = format!("{}_", txt); + }; + // let idt = syn::parse_str::(&txt).context(ParseIdentError{ text: text.to_owned() })?; + let idt = match syn::parse_str::(&txt) { + Ok(idt) => idt, + Err(_) => { + // replace certain unicode charaters with their unicode names + txt = txt.replace("*", "Asterisk"); + syn::parse_str::(&txt).context(ParseIdentError { text: text.to_owned() })? + } }; Ok(idt.into_token_stream()) } +pub trait CamelCaseIdent: ToOwned { + fn to_camel_case_ident(&self) -> Result; +} + +impl CamelCaseIdent for str { + fn to_camel_case_ident(&self) -> Result { + let mut txt = ident(self)?.to_string().to_camel_case(); + // prefix with underscore if starts with invalid character + txt = match txt.chars().next().context(EmtpyIdentError)? { + '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '0' => format!("_{}", txt), + _ => txt.to_owned(), + }; + let idt = syn::parse_str::(&txt).context(ParseIdentError { text: self.to_owned() })?; + Ok(idt.into_token_stream()) + } +} + fn enum_values_as_strings(values: &Vec) -> Vec<&str> { values .iter() @@ -480,7 +569,10 @@ fn get_param_type(param: &Parameter) -> Result { } fn get_param_name(param: &Parameter) -> Result { - ident(¶m.name.to_snake_case()) + ident(¶m.name.to_snake_case()).context(IdentError { + file: file!(), + line: line!(), + }) } fn parse_params(param_re: &Regex, path: &str) -> Vec { @@ -558,7 +650,10 @@ fn get_type_name_for_schema_ref(schema: &ReferenceOr, as_ref: AsReferenc ReferenceOr::Reference { reference, .. } => { let rf = Reference::parse(&reference); let name = &rf.name.context(NoNameForRef)?; - let idt = ident(&name.to_camel_case())?; + let idt = ident(&name.to_camel_case()).context(IdentError { + file: file!(), + line: line!(), + })?; match as_ref { AsReference::True => Ok(quote! { &#idt }), AsReference::False => Ok(quote! { #idt }), @@ -585,11 +680,22 @@ fn create_function( param_re: &Regex, function_name: &str, ) -> Result { - let fname = ident(function_name)?; + let fname = ident(function_name).context(IdentError { + file: file!(), + line: line!(), + })?; let params = parse_params(param_re, path); // println!("path params {:#?}", params); - let params: Result> = params.iter().map(|s| Ok(ident(&s.to_snake_case())?)).collect(); + let params: Result> = params + .iter() + .map(|s| { + Ok(ident(&s.to_snake_case()).context(IdentError { + file: file!(), + line: line!(), + })?) + }) + .collect(); let params = params?; let uri_str_args = quote! { #(#params),* }; @@ -746,7 +852,10 @@ fn create_function( Some(tp) => quote! { (#tp) }, None => quote! {}, }; - let enum_type_name = ident(&get_response_type_name(status_code))?; + let enum_type_name = ident(&get_response_type_name(status_code)).context(IdentError { + file: file!(), + line: line!(), + })?; success_responses_ts.extend(quote! { #enum_type_name#tp, }) } response_enum.extend(quote! { @@ -768,7 +877,10 @@ fn create_function( if response_type == "DefaultResponse" { error_responses_ts.extend(quote! { DefaultResponse { status_code: StatusCode, #tp }, }); } else { - let response_type = ident(response_type)?; + let response_type = ident(response_type).context(IdentError { + file: file!(), + line: line!(), + })?; error_responses_ts.extend(quote! { #response_type { #tp }, }); } } @@ -781,8 +893,14 @@ fn create_function( match status_code { autorust_openapi::StatusCode::Code(_) => { let tp = create_response_type(rsp)?; - let status_code_name = ident(&get_status_code_name(status_code))?; - let response_type_name = ident(&get_response_type_name(status_code))?; + let status_code_name = ident(&get_status_code_name(status_code)).context(IdentError { + file: file!(), + line: line!(), + })?; + let response_type_name = ident(&get_response_type_name(status_code)).context(IdentError { + file: file!(), + line: line!(), + })?; if is_single_response { match tp { Some(tp) => { @@ -830,13 +948,19 @@ fn create_function( match status_code { autorust_openapi::StatusCode::Code(_) => { let tp = create_response_type(rsp)?; - let status_code_name = ident(&get_status_code_name(status_code))?; - let response_type_name = ident(&get_response_type_name(status_code))?; + let status_code_name = ident(&get_status_code_name(status_code)).context(IdentError { + file: file!(), + line: line!(), + })?; + let response_type_name = ident(&get_response_type_name(status_code)).context(IdentError { + file: file!(), + line: line!(), + })?; match tp { Some(tp) => { match_status.extend(quote! { StatusCode::#status_code_name => { - let body: bytes::Bytes = rsp.bytes().await.context(#fname::ResponseBytesError)?; + let body: bytes::Bytes = rsp.bytes().await.context(#fname::ResponseBytesError).context(IdentError{file: file!(), line: line!()})?; let rsp_value: #tp = serde_json::from_slice(&body).context(#fname::DeserializeError { body })?; #fname::#response_type_name{value: rsp_value}.fail() } @@ -953,4 +1077,42 @@ mod tests { assert_eq!(idt.to_string(), "_3_2"); Ok(()) } + + #[test] + fn test_ident_asterisk() -> Result<()> { + assert_eq!(ident("*")?.to_string(), "Asterisk"); + assert_eq!("*".to_camel_case(), ""); + assert_eq!("*".to_camel_case_ident()?.to_string(), "Asterisk"); + Ok(()) + } + + #[test] + fn test_ident_system_assigned_user_assigned() -> Result<()> { + assert_eq!( + "SystemAssigned, UserAssigned".to_camel_case_ident()?.to_string(), + "SystemAssignedUserAssigned" + ); + Ok(()) + } + + #[test] + fn test_ident_gcm_aes_128() -> Result<()> { + assert_eq!("gcm-aes-128".to_camel_case_ident()?.to_string(), "GcmAes128"); + Ok(()) + } + + #[test] + fn test_ident_5() -> Result<()> { + assert_eq!("5".to_camel_case_ident()?.to_string(), "_5"); + Ok(()) + } + + #[test] + fn test_ident_app_configuration() -> Result<()> { + assert_eq!( + "Microsoft.AppConfiguration/configurationStores".to_camel_case_ident()?.to_string(), + "MicrosoftAppConfigurationConfigurationStores" + ); + Ok(()) + } } diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 3f2447d..f7fbe11 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -12,7 +12,7 @@ pub use self::{ spec::{OperationVerb, ResolvedSchema, Spec}, }; use proc_macro2::TokenStream; -use snafu::{Backtrace, ResultExt, Snafu}; +use snafu::{ResultExt, Snafu}; use std::{ fs::{self, File}, io::prelude::*, @@ -41,9 +41,16 @@ pub enum Error { file: PathBuf, source: std::io::Error, }, - CodeGenError { + CodeGenNewError { + source: codegen::Error, + }, + #[snafu(display("CreateModelsError {} {}", config.output_folder.display(), source))] + CreateModelsError { + source: codegen::Error, + config: Config, + }, + CreateOperationsError { source: codegen::Error, - backtrace: Backtrace, }, PathError { source: path::Error, @@ -60,15 +67,15 @@ pub struct Config { pub fn run(config: Config) -> Result<()> { let directory = &config.output_folder; fs::create_dir_all(directory).context(CreateOutputDirectoryError { directory })?; - let cg = &CodeGen::new(config.clone()).context(CodeGenError)?; + let cg = &CodeGen::new(config.clone()).context(CodeGenNewError)?; // create models from schemas - let models = cg.create_models().context(CodeGenError)?; + let models = cg.create_models().context(CreateModelsError { config: config.clone() })?; let models_path = path::join(&config.output_folder, "models.rs").context(PathError)?; write_file(&models_path, &models)?; // create api client from operations - let operations = cg.create_operations().context(CodeGenError)?; + let operations = cg.create_operations().context(CreateOperationsError)?; let operations_path = path::join(&config.output_folder, "operations.rs").context(PathError)?; write_file(&operations_path, &operations)?; diff --git a/codegen/src/lib_rs.rs b/codegen/src/lib_rs.rs index a4832ac..50c3c2e 100644 --- a/codegen/src/lib_rs.rs +++ b/codegen/src/lib_rs.rs @@ -11,8 +11,14 @@ pub type Result = std::result::Result; #[derive(Debug, Snafu)] pub enum Error { - CodeGenError { source: crate::codegen::Error }, - WriteFileError { source: crate::Error }, + IdentModNameError { + source: crate::codegen::Error, + feature_name: String, + mod_name: String, + }, + WriteFileError { + source: crate::Error, + }, } pub fn create(feature_mod_names: &Vec<(String, String)>, path: &Path) -> Result<()> { @@ -23,7 +29,10 @@ pub fn create(feature_mod_names: &Vec<(String, String)>, path: &Path) -> Result< fn create_body(feature_mod_names: &Vec<(String, String)>) -> Result { let mut cfgs = TokenStream::new(); for (feature_name, mod_name) in feature_mod_names { - let mod_name = ident(mod_name).context(CodeGenError)?; + let mod_name = ident(mod_name).context(IdentModNameError { + feature_name: feature_name.to_owned(), + mod_name: mod_name.to_owned(), + })?; cfgs.extend(quote! { #[cfg(feature = #feature_name)] mod #mod_name; diff --git a/codegen/src/spec.rs b/codegen/src/spec.rs index 0f515fc..b371b5f 100644 --- a/codegen/src/spec.rs +++ b/codegen/src/spec.rs @@ -12,14 +12,25 @@ use std::{ pub type Result = std::result::Result; #[derive(Debug, Snafu)] pub enum Error { - PathJoin { source: path::Error }, - SchemaNotFound, + PathJoin { + source: path::Error, + }, + #[snafu(display("SchemaNotFound {} {}", ref_key.file.display(), ref_key.name))] + SchemaNotFound { + ref_key: RefKey, + }, NoNameInReference, ParameterNotFound, NotImplemented, - ReadFile { source: std::io::Error }, - DeserializeYaml { source: serde_yaml::Error }, - DeserializeJson { source: serde_json::Error }, + ReadFile { + source: std::io::Error, + }, + DeserializeYaml { + source: serde_yaml::Error, + }, + DeserializeJson { + source: serde_json::Error, + }, } /// An API specification @@ -110,7 +121,6 @@ impl Spec { Some(file) => path::join(doc_file, &file).context(PathJoin)?, }; match rf.name { - // None => Err(format!("no name in reference {}", &reference))?, None => NoNameInReference.fail(), Some(nm) => { let ref_key = RefKey { @@ -121,7 +131,7 @@ impl Spec { .schemas .get(&ref_key) // .ok_or_else(|| format!("schema not found {} {}", &file.display(), &nm))? - .context(SchemaNotFound)? + .context(SchemaNotFound { ref_key: ref_key.clone() })? .clone(); Ok(ResolvedSchema { ref_key: Some(ref_key), diff --git a/codegen/tests/azure_rest_api_specs.rs b/codegen/tests/azure_rest_api_specs.rs index 56925f8..3e10141 100644 --- a/codegen/tests/azure_rest_api_specs.rs +++ b/codegen/tests/azure_rest_api_specs.rs @@ -23,7 +23,7 @@ fn refs_count_avs() -> Result<()> { "../../azure-rest-api-specs/specification/vmware/resource-manager/Microsoft.AVS/stable/2020-03-20/vmware.json", )?; let refs = spec::get_refs(api); - assert_eq!(190, refs.len()); + assert_eq!(197, refs.len()); Ok(()) } From 12e171e33560f8e4a21061f396aae2a1cc1a00f2 Mon Sep 17 00:00:00 2001 From: Cameron Taggart Date: Thu, 29 Oct 2020 17:32:36 -0600 Subject: [PATCH 3/6] fix merge --- codegen/src/spec.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codegen/src/spec.rs b/codegen/src/spec.rs index ff9345e..1ebd2c4 100644 --- a/codegen/src/spec.rs +++ b/codegen/src/spec.rs @@ -112,7 +112,7 @@ impl Spec { let name = reference.name.ok_or_else(|| Error::NoNameInReference)?; let ref_key = RefKey { file: full_path, name }; - let schema = self.schemas.get(&ref_key).context(SchemaNotFound)?.clone(); + let schema = self.schemas.get(&ref_key).context(SchemaNotFound { ref_key: ref_key.clone() })?.clone(); Ok(ResolvedSchema { ref_key: Some(ref_key), schema, From 84cf88f6a70cda74c164bcea133661126c5d6d88 Mon Sep 17 00:00:00 2001 From: Cameron Taggart Date: Thu, 29 Oct 2020 20:05:06 -0600 Subject: [PATCH 4/6] create identifier mod --- Cargo.lock | 64 ---------- codegen/Cargo.toml | 2 +- codegen/examples/gen_mgmt.rs | 2 +- codegen/src/codegen.rs | 199 ++----------------------------- codegen/src/identifier.rs | 223 +++++++++++++++++++++++++++++++++++ codegen/src/lib.rs | 1 + codegen/src/lib_rs.rs | 7 +- codegen/src/spec.rs | 6 +- 8 files changed, 244 insertions(+), 260 deletions(-) create mode 100644 codegen/src/identifier.rs diff --git a/Cargo.lock b/Cargo.lock index 2ac73bb..25d2044 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,20 +1,5 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -[[package]] -name = "addr2line" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b6a2d3371669ab3ca9797670853d61402b03d0b4b9ebf33d677dfa720203072" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" - [[package]] name = "aho-corasick" version = "0.7.14" @@ -90,20 +75,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "backtrace" -version = "0.3.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707b586e0e2f247cbde68cdd2c3ce69ea7b7be43e1c5b426e37c9319c4b9838e" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - [[package]] name = "bitflags" version = "1.2.1" @@ -149,12 +120,6 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - [[package]] name = "clap" version = "2.33.3" @@ -269,12 +234,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "gimli" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724" - [[package]] name = "hashbrown" version = "0.9.1" @@ -357,22 +316,6 @@ version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" -[[package]] -name = "miniz_oxide" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d" -dependencies = [ - "adler", - "autocfg", -] - -[[package]] -name = "object" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37fd5004feb2ce328a52b0b3d01dbf4ffff72583493900ed15f22d4111c51693" - [[package]] name = "opaque-debug" version = "0.2.3" @@ -500,12 +443,6 @@ version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cab7a364d15cde1e505267766a2d3c4e22a843e1a601f0fa7564c0f82ced11c" -[[package]] -name = "rustc-demangle" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" - [[package]] name = "ryu" version = "1.0.5" @@ -579,7 +516,6 @@ version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c4e6046e4691afe918fd1b603fd6e515bcda5388a1092a9edbada307d159f09" dependencies = [ - "backtrace", "doc-comment", "snafu-derive", ] diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index da931c1..1899446 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -19,7 +19,7 @@ indexmap = { version = "*", features = ["serde-1"] } path_abs = "*" comrak = "0.8" serde = "1.0" -snafu = { version = "0.6", features = ["backtraces"] } +snafu = "0.6" http = "0.2" lazy_static = "1.4" syn = { version = "1.0", features = ["parsing"] } diff --git a/codegen/examples/gen_mgmt.rs b/codegen/examples/gen_mgmt.rs index 45555fe..ff3e375 100644 --- a/codegen/examples/gen_mgmt.rs +++ b/codegen/examples/gen_mgmt.rs @@ -45,7 +45,7 @@ const SKIP_SERVICES: &[&str] = &[ "migrateprojects", // recursive type has infinite size "mixedreality", // &AccountKeyRegenerateRequest not found in scope "netapp", // codegen wrong, missing operation params in function - "network", // thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', codegen/src/codegen.rs:419:42 + "network", // TODO #73 recursive types "powerplatform", // Error: "parameter not found ../azure-rest-api-specs/specification/powerplatform/resource-manager/Microsoft.PowerPlatform/common/v1/definitions.json ResourceGroupNameParameter" "recoveryservicessiterecovery", // duplicate package-2016-08 https://github.com/Azure/azure-rest-api-specs/pull/11287 "redis", // map_type diff --git a/codegen/src/codegen.rs b/codegen/src/codegen.rs index c06ef1b..8895084 100644 --- a/codegen/src/codegen.rs +++ b/codegen/src/codegen.rs @@ -1,5 +1,5 @@ -#![allow(unused_variables, dead_code)] use crate::{ + identifier::{ident, CamelCaseIdent}, spec, status_codes::{get_error_responses, get_response_type_name, get_status_code_name, get_success_responses, has_default_response}, Config, OperationVerb, Reference, ResolvedSchema, Spec, @@ -8,7 +8,7 @@ use autorust_openapi::{CollectionFormat, DataType, Parameter, ParameterType, Pat use heck::{CamelCase, SnakeCase}; use indexmap::IndexMap; use proc_macro2::TokenStream; -use quote::{quote, ToTokens}; +use quote::quote; use regex::Regex; use serde_json::Value; use snafu::{OptionExt, ResultExt, Snafu}; @@ -26,16 +26,10 @@ pub enum Error { }, ArrayExpectedToHaveItems, NoNameForRef, - EmtpyIdentError, - #[snafu(display("ParseIdentError {} {}", text, source))] - ParseIdentError { - source: syn::Error, - text: String, - }, #[snafu(display("IdentError at {}:{} {} ", file, line, source))] IdentError { - #[snafu(source(from(Error, Box::new)))] - source: Box, + // #[snafu(source(from(Error, Box::new)))] + source: crate::identifier::Error, file: &'static str, line: u32, }, @@ -46,7 +40,6 @@ pub enum Error { property_name: String, enum_value: String, }, - // CreateEnumIdentError{ property_name: property_name.to_owned(), enum_value: name.to_owned() } } /// Whether or not to pass a type is a reference. @@ -119,7 +112,7 @@ impl CodeGen { for (ref_key, schema) in &all_schemas { let doc_file = &ref_key.file; let schema_name = &ref_key.name; - if let Some(first_doc_file) = schema_names.insert(schema_name, doc_file) { + if let Some(_first_doc_file) = schema_names.insert(schema_name, doc_file) { // eprintln!( // "WARN schema {} already created from {:?}, duplicate from {:?}", // schema_name, first_doc_file, doc_file @@ -217,7 +210,7 @@ impl CodeGen { Ok(()) } - fn create_vec_alias(&self, doc_file: &Path, alias_name: &str, schema: &ResolvedSchema) -> Result { + fn create_vec_alias(&self, _doc_file: &Path, alias_name: &str, schema: &ResolvedSchema) -> Result { let items = get_schema_array_items(&schema.schema.common)?; let typ = ident(&alias_name.to_camel_case()).context(IdentError { file: file!(), @@ -374,65 +367,6 @@ pub fn create_generated_by_header() -> TokenStream { quote! { #![doc = #comment] } } -fn is_keyword(word: &str) -> bool { - matches!( - word, - // https://doc.rust-lang.org/grammar.html#keywords - "abstract" - | "alignof" - | "as" - | "become" - | "box" - | "break" - | "const" - | "continue" - | "crate" - | "do" - | "else" - | "enum" - | "extern" - | "false" - | "final" - | "fn" - | "for" - | "if" - | "impl" - | "in" - | "let" - | "loop" - | "macro" - | "match" - | "mod" - | "move" - | "mut" - | "offsetof" - | "override" - | "priv" - | "proc" - | "pub" - | "pure" - | "ref" - | "return" - | "Self" - | "self" - | "sizeof" - | "static" - | "struct" - | "super" - | "trait" - | "true" - | "type" - | "typeof" - | "unsafe" - | "unsized" - | "use" - | "virtual" - | "where" - | "while" - | "yield" - ) -} - fn is_local_enum(property: &ResolvedSchema) -> bool { property.schema.common.enum_.len() > 0 } @@ -442,7 +376,6 @@ fn is_local_struct(property: &ResolvedSchema) -> bool { } fn create_enum(namespace: &TokenStream, property_name: &str, property: &ResolvedSchema) -> Result<(TokenStream, TokenStream)> { - let schema_type = property.schema.common.type_.as_ref(); let enum_values = enum_values_as_strings(&property.schema.common.enum_); let id = ident(&property_name.to_camel_case()).context(IdentError { file: file!(), @@ -494,49 +427,6 @@ fn require(is_required: bool, tp: TokenStream) -> TokenStream { } } -pub fn ident(text: &str) -> Result { - let mut txt = text.replace(".", "_"); - txt = txt.replace(",", "_"); - txt = txt.replace("-", "_"); - txt = txt.replace("/", "_"); - txt = txt.replace(" ", ""); - // prefix with underscore if starts with invalid character - txt = match txt.chars().next().context(EmtpyIdentError)? { - '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '0' => format!("_{}", txt), - _ => txt.to_owned(), - }; - if is_keyword(&txt) { - txt = format!("{}_", txt); - }; - // let idt = syn::parse_str::(&txt).context(ParseIdentError{ text: text.to_owned() })?; - let idt = match syn::parse_str::(&txt) { - Ok(idt) => idt, - Err(_) => { - // replace certain unicode charaters with their unicode names - txt = txt.replace("*", "Asterisk"); - syn::parse_str::(&txt).context(ParseIdentError { text: text.to_owned() })? - } - }; - Ok(idt.into_token_stream()) -} - -pub trait CamelCaseIdent: ToOwned { - fn to_camel_case_ident(&self) -> Result; -} - -impl CamelCaseIdent for str { - fn to_camel_case_ident(&self) -> Result { - let mut txt = ident(self)?.to_string().to_camel_case(); - // prefix with underscore if starts with invalid character - txt = match txt.chars().next().context(EmtpyIdentError)? { - '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '0' => format!("_{}", txt), - _ => txt.to_owned(), - }; - let idt = syn::parse_str::(&txt).context(ParseIdentError { text: self.to_owned() })?; - Ok(idt.into_token_stream()) - } -} - fn enum_values_as_strings(values: &Vec) -> Vec<&str> { values .iter() @@ -547,17 +437,10 @@ fn enum_values_as_strings(values: &Vec) -> Vec<&str> { .collect() } -/// example: pub type Pets = Vec; -fn trim_ref(path: &str) -> String { - let pos = path.rfind('/').map_or(0, |i| i + 1); - path[pos..].to_string() -} - fn get_param_type(param: &Parameter) -> Result { let is_required = param.required.unwrap_or(false); let is_array = is_array(¶m.common); - let format = param.common.format.as_deref(); - let tp = if let Some(param_type) = ¶m.common.type_ { + let tp = if let Some(_param_type) = ¶m.common.type_ { get_type_name_for_schema(¶m.common, AsReference::True)? } else if let Some(schema) = ¶m.schema { get_type_name_for_schema_ref(schema, AsReference::True)? @@ -584,7 +467,7 @@ fn format_path(param_re: &Regex, path: &str) -> String { param_re.replace_all(path, "{}").to_string() } -fn create_function_params(cg: &CodeGen, doc_file: &Path, parameters: &Vec) -> Result { +fn create_function_params(_cg: &CodeGen, _doc_file: &Path, parameters: &Vec) -> Result { let mut params: Vec = Vec::new(); for param in parameters { let name = get_param_name(param)?; @@ -675,7 +558,7 @@ fn create_function( cg: &CodeGen, doc_file: &Path, path: &str, - item: &PathItem, + _item: &PathItem, operation_verb: &OperationVerb, param_re: &Regex, function_name: &str, @@ -742,7 +625,7 @@ fn create_function( // api-version param if has_param_api_version { - if let Some(api_version) = cg.api_version() { + if let Some(_api_version) = cg.api_version() { ts_request_builder.extend(quote! { req_builder = req_builder.query(&[("api-version", &operation_config.api_version)]); }); @@ -960,7 +843,7 @@ fn create_function( Some(tp) => { match_status.extend(quote! { StatusCode::#status_code_name => { - let body: bytes::Bytes = rsp.bytes().await.context(#fname::ResponseBytesError).context(IdentError{file: file!(), line: line!()})?; + let body: bytes::Bytes = rsp.bytes().await.context(#fname::ResponseBytesError)?; let rsp_value: #tp = serde_json::from_slice(&body).context(#fname::DeserializeError { body })?; #fname::#response_type_name{value: rsp_value}.fail() } @@ -985,7 +868,6 @@ fn create_function( autorust_openapi::StatusCode::Code(_) => {} autorust_openapi::StatusCode::Default => { let tp = create_response_type(rsp)?; - let response_type_name = ident(&get_response_type_name(status_code)); match tp { Some(tp) => { match_status.extend(quote! { @@ -1057,62 +939,3 @@ pub fn create_mod(api_version: &str) -> TokenStream { pub const API_VERSION: &str = #api_version; } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_ident_odata_next_link() -> Result<()> { - let idt = "odata.nextLink".to_snake_case(); - assert_eq!(idt, "odata.next_link"); - let idt = ident(&idt)?; - assert_eq!(idt.to_string(), "odata_next_link"); - Ok(()) - } - - #[test] - fn test_ident_three_dot_two() -> Result<()> { - let idt = ident("3.2")?; - assert_eq!(idt.to_string(), "_3_2"); - Ok(()) - } - - #[test] - fn test_ident_asterisk() -> Result<()> { - assert_eq!(ident("*")?.to_string(), "Asterisk"); - assert_eq!("*".to_camel_case(), ""); - assert_eq!("*".to_camel_case_ident()?.to_string(), "Asterisk"); - Ok(()) - } - - #[test] - fn test_ident_system_assigned_user_assigned() -> Result<()> { - assert_eq!( - "SystemAssigned, UserAssigned".to_camel_case_ident()?.to_string(), - "SystemAssignedUserAssigned" - ); - Ok(()) - } - - #[test] - fn test_ident_gcm_aes_128() -> Result<()> { - assert_eq!("gcm-aes-128".to_camel_case_ident()?.to_string(), "GcmAes128"); - Ok(()) - } - - #[test] - fn test_ident_5() -> Result<()> { - assert_eq!("5".to_camel_case_ident()?.to_string(), "_5"); - Ok(()) - } - - #[test] - fn test_ident_app_configuration() -> Result<()> { - assert_eq!( - "Microsoft.AppConfiguration/configurationStores".to_camel_case_ident()?.to_string(), - "MicrosoftAppConfigurationConfigurationStores" - ); - Ok(()) - } -} diff --git a/codegen/src/identifier.rs b/codegen/src/identifier.rs new file mode 100644 index 0000000..0c6bcf2 --- /dev/null +++ b/codegen/src/identifier.rs @@ -0,0 +1,223 @@ +use heck::CamelCase; +use proc_macro2::TokenStream; +use quote::ToTokens; +use snafu::{ResultExt, Snafu}; + +pub type Result = std::result::Result; +#[derive(Debug, Snafu)] +pub enum Error { + #[snafu(display("ParseIdentError {} {}", text, source))] + ParseIdentError { source: syn::Error, text: String }, +} + +pub trait CamelCaseIdent: ToOwned { + fn to_camel_case_ident(&self) -> Result; +} + +impl CamelCaseIdent for str { + fn to_camel_case_ident(&self) -> Result { + let mut txt = replace_chars_with_unicode_names(self); + txt = replace_chars_with_underscore(&txt); + txt = if starts_with_number(&txt) { + prefix_with_underscore_if_starts_with_number(&txt) + } else { + txt.to_camel_case() + }; + let idt = syn::parse_str::(&txt).context(ParseIdentError { text: txt.to_owned() })?; + Ok(idt.into_token_stream()) + } +} + +pub fn ident(text: &str) -> Result { + let mut txt = replace_chars_with_underscore(text); + txt = remove_spaces(&txt); + txt = prefix_with_underscore_if_starts_with_number(&txt); + txt = prefix_with_underscore_keywords(&txt); + let idt = syn::parse_str::(&txt).context(ParseIdentError { text: txt.to_owned() })?; + Ok(idt.into_token_stream()) +} + +fn remove_spaces(text: &str) -> String { + text.replace(" ", "") +} + +fn replace_chars_with_underscore(text: &str) -> String { + let mut txt = text.replace(".", "_"); + txt = txt.replace(",", "_"); + txt = txt.replace("-", "_"); + txt = txt.replace("/", "_"); + txt +} + +/// Replace some special charaters with their unicode names +fn replace_chars_with_unicode_names(text: &str) -> String { + text.replace("*", "Asterisk") +} + +fn starts_with_number(text: &str) -> bool { + match text.chars().next() { + Some(ch) => match ch { + '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '0' => true, + _ => false, + }, + None => false, + } +} + +fn prefix_with_underscore_if_starts_with_number(text: &str) -> String { + if starts_with_number(text) { + format!("_{}", text) + } else { + text.to_owned() + } +} + +fn prefix_with_underscore_keywords(text: &str) -> String { + if is_keyword(&text) { + format!("{}_", text) + } else { + text.to_owned() + } +} + +fn is_keyword(word: &str) -> bool { + matches!( + word, + // https://doc.rust-lang.org/grammar.html#keywords + "abstract" + | "alignof" + | "as" + | "become" + | "box" + | "break" + | "const" + | "continue" + | "crate" + | "do" + | "else" + | "enum" + | "extern" + | "false" + | "final" + | "fn" + | "for" + | "if" + | "impl" + | "in" + | "let" + | "loop" + | "macro" + | "match" + | "mod" + | "move" + | "mut" + | "offsetof" + | "override" + | "priv" + | "proc" + | "pub" + | "pure" + | "ref" + | "return" + | "Self" + | "self" + | "sizeof" + | "static" + | "struct" + | "super" + | "trait" + | "true" + | "type" + | "typeof" + | "unsafe" + | "unsized" + | "use" + | "virtual" + | "where" + | "while" + | "yield" + ) +} + +#[cfg(test)] +mod tests { + use super::*; + use heck::SnakeCase; + + #[test] + fn test_odata_next_link() -> Result<()> { + let idt = "odata.nextLink".to_snake_case(); + assert_eq!(idt, "odata.next_link"); + let idt = ident(&idt)?; + assert_eq!(idt.to_string(), "odata_next_link"); + Ok(()) + } + + #[test] + fn test_three_dot_two() -> Result<()> { + let idt = ident("3.2")?; + assert_eq!(idt.to_string(), "_3_2"); + Ok(()) + } + + #[test] + fn test_asterisk() -> Result<()> { + assert_eq!("*".to_camel_case(), ""); + assert_eq!("*".to_camel_case_ident()?.to_string(), "Asterisk"); + Ok(()) + } + + #[test] + fn test_system_assigned_user_assigned() -> Result<()> { + assert_eq!( + "SystemAssigned, UserAssigned".to_camel_case_ident()?.to_string(), + "SystemAssignedUserAssigned" + ); + Ok(()) + } + + #[test] + fn test_gcm_aes_128() -> Result<()> { + assert_eq!("gcm-aes-128".to_camel_case_ident()?.to_string(), "GcmAes128"); + Ok(()) + } + + #[test] + fn test_5() -> Result<()> { + assert_eq!("5".to_camel_case_ident()?.to_string(), "_5"); + Ok(()) + } + + #[test] + fn test_app_configuration() -> Result<()> { + assert_eq!( + "Microsoft.AppConfiguration/configurationStores".to_camel_case_ident()?.to_string(), + "MicrosoftAppConfigurationConfigurationStores" + ); + Ok(()) + } + + #[test] + fn test_microsoft_key_vault_vaults() -> Result<()> { + assert_eq!( + "Microsoft.KeyVault/vaults".to_camel_case_ident()?.to_string(), + "MicrosoftKeyVaultVaults" + ); + Ok(()) + } + + #[test] + fn test_azure_virtual_machine_best_practices() -> Result<()> { + assert_eq!( + "Azure virtual machine best practices – Dev/Test".to_camel_case_ident()?.to_string(), + "AzureVirtualMachineBestPracticesDevTest" + ); + Ok(()) + } + + #[test] + fn test_1_0() -> Result<()> { + assert_eq!("1.0".to_camel_case_ident()?.to_string(), "_1_0"); + Ok(()) + } +} diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index f7fbe11..b3c1ec4 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -1,6 +1,7 @@ pub mod cargo_toml; mod codegen; pub mod config_parser; +pub mod identifier; pub mod lib_rs; pub mod path; mod reference; diff --git a/codegen/src/lib_rs.rs b/codegen/src/lib_rs.rs index 50c3c2e..3d65112 100644 --- a/codegen/src/lib_rs.rs +++ b/codegen/src/lib_rs.rs @@ -1,7 +1,4 @@ -use crate::{ - codegen::{create_generated_by_header, ident}, - write_file, -}; +use crate::{codegen::create_generated_by_header, identifier::ident, write_file}; use proc_macro2::TokenStream; use quote::quote; use snafu::{ResultExt, Snafu}; @@ -12,7 +9,7 @@ pub type Result = std::result::Result; #[derive(Debug, Snafu)] pub enum Error { IdentModNameError { - source: crate::codegen::Error, + source: crate::identifier::Error, feature_name: String, mod_name: String, }, diff --git a/codegen/src/spec.rs b/codegen/src/spec.rs index 1ebd2c4..b4ebb32 100644 --- a/codegen/src/spec.rs +++ b/codegen/src/spec.rs @@ -112,7 +112,11 @@ impl Spec { let name = reference.name.ok_or_else(|| Error::NoNameInReference)?; let ref_key = RefKey { file: full_path, name }; - let schema = self.schemas.get(&ref_key).context(SchemaNotFound { ref_key: ref_key.clone() })?.clone(); + let schema = self + .schemas + .get(&ref_key) + .context(SchemaNotFound { ref_key: ref_key.clone() })? + .clone(); Ok(ResolvedSchema { ref_key: Some(ref_key), schema, From 650bfb70e2203d16d04d431997662b899dcb016c Mon Sep 17 00:00:00 2001 From: Cameron Taggart Date: Thu, 29 Oct 2020 22:54:17 -0600 Subject: [PATCH 5/6] begin redis testing --- codegen/examples/gen_mgmt.rs | 39 ++------------ codegen/src/lib.rs | 1 - codegen/src/reference.rs | 13 +++++ codegen/src/spec.rs | 15 +++--- codegen/tests/azure_rest_api_specs.rs | 29 ++++------- codegen/tests/redis_spec.rs | 75 +++++++++++++++++++++++++++ 6 files changed, 111 insertions(+), 61 deletions(-) create mode 100644 codegen/tests/redis_spec.rs diff --git a/codegen/examples/gen_mgmt.rs b/codegen/examples/gen_mgmt.rs index ff3e375..b5c137d 100644 --- a/codegen/examples/gen_mgmt.rs +++ b/codegen/examples/gen_mgmt.rs @@ -22,6 +22,7 @@ const SERVICE_NAMES: &[(&str, &str)] = &[ const ONLY_SERVICES: &[&str] = &[ // "network", + // "redis", ]; const SKIP_SERVICES: &[&str] = &[ @@ -29,7 +30,7 @@ const SKIP_SERVICES: &[&str] = &[ "automation", // Error: Error("data did not match any variant of untagged enum ReferenceOr", line: 90, column: 5) "cosmos-db", // get_gremlin_graph_throughput defined twice "cost-management", // use of undeclared crate or module `definition` - "databox", // recursive type has infinite size + "databox", // TODO #73 recursive types "databoxedge", // duplicate model pub struct SkuCost { "datamigration", // Error: "schema not found ../azure-rest-api-specs/specification/datamigration/resource-manager/Microsoft.DataMigration/preview/2018-07-15-preview/definitions/MigrateSqlServerSqlDbTask.json ValidationStatus" "deploymentmanager", // missing params @@ -38,7 +39,7 @@ const SKIP_SERVICES: &[&str] = &[ "hardwaresecuritymodules", // recursive without indirection on Error "healthcareapis", // Error: "schema not found ../azure-rest-api-specs/specification/common-types/resource-management/v1/types.json Resource" "hybridcompute", // use of undeclared crate or module `status` - "logic", // recursive type has infinite size + "logic", // TODO #73 recursive types "machinelearning", // missing params "managedservices", // registration_definition "mediaservices", // Error: Error("invalid unicode code point", line: 1380, column: 289) @@ -66,7 +67,6 @@ const SKIP_SERVICE_TAGS: &[(&str, &str)] = &[ pub type Result = std::result::Result; #[derive(Debug, Snafu)] -// #[snafu(visibility(pub(crate)))] pub enum Error { #[snafu(display("file name was not utf-8"))] FileNameNotUtf8Error {}, @@ -77,7 +77,6 @@ pub enum Error { source: path::Error, }, CodegenError { - #[snafu(backtrace)] source: autorust_codegen::Error, }, CargoTomlError { @@ -88,37 +87,7 @@ pub enum Error { }, } -fn main() { - match run() { - Ok(_) => {} - Err(err) => { - report(&err); - } - } -} - -fn report(err: &E) -where - E: std::error::Error, - E: snafu::ErrorCompat, - E: Send + Sync, -{ - eprintln!("[ERROR] {}", err); - if let Some(source) = err.source() { - eprintln!(); - eprintln!("Caused by:"); - for (i, e) in std::iter::successors(Some(source), |e| e.source()).enumerate() { - eprintln!(" {}: {}", i, e); - } - } - - // if let Some(backtrace) = ErrorCompat::backtrace(err) { - // eprintln!("Backtrace:"); - // eprintln!("{}", backtrace); - // } -} - -fn run() -> Result<()> { +fn main() -> Result<()> { let paths = fs::read_dir(SPEC_FOLDER).context(IoError)?; let mut spec_folders = Vec::new(); for path in paths { diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index b3c1ec4..782a251 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -25,7 +25,6 @@ extern crate lazy_static; pub type Result = std::result::Result; #[derive(Debug, Snafu)] -// #[snafu(visibility(pub(crate)))] pub enum Error { #[snafu(display("Could not create output directory {}: {}", directory.display(), source))] CreateOutputDirectoryError { diff --git a/codegen/src/reference.rs b/codegen/src/reference.rs index 60ed202..2b24f34 100644 --- a/codegen/src/reference.rs +++ b/codegen/src/reference.rs @@ -74,4 +74,17 @@ mod tests { ); Ok(()) } + + #[test] + fn can_parse_types() -> Result<()> { + assert_eq!( + Reference::parse("./types.json#/definitions/Resource"), + Reference { + file: Some("./types.json".to_owned()), + path: vec!["definitions".to_owned()], + name: Some("Resource".to_owned()), + } + ); + Ok(()) + } } diff --git a/codegen/src/spec.rs b/codegen/src/spec.rs index b4ebb32..5907156 100644 --- a/codegen/src/spec.rs +++ b/codegen/src/spec.rs @@ -68,13 +68,12 @@ impl Spec { match schema { ReferenceOr::Reference { .. } => {} ReferenceOr::Item(schema) => { - schemas.insert( - RefKey { - file: path.clone(), - name: name.clone(), - }, - schema.clone(), - ); + let ref_key = RefKey { + file: path.clone(), + name: name.clone(), + }; + // println!("{:?}", ref_key); + schemas.insert(ref_key, schema.clone()); } } } @@ -105,10 +104,12 @@ impl Spec { /// Find the schema for a given doc path and reference pub fn resolve_schema_ref>(&self, doc_path: P, reference: Reference) -> Result { let doc_path: PathBuf = doc_path.into(); + // println!("{:?} {:?}", doc_path, reference); let full_path = match reference.file { None => doc_path, Some(file) => path::join(doc_path, &file).context(PathJoin)?, }; + // println!("{:?}", full_path); let name = reference.name.ok_or_else(|| Error::NoNameInReference)?; let ref_key = RefKey { file: full_path, name }; diff --git a/codegen/tests/azure_rest_api_specs.rs b/codegen/tests/azure_rest_api_specs.rs index 6511274..f55d3df 100644 --- a/codegen/tests/azure_rest_api_specs.rs +++ b/codegen/tests/azure_rest_api_specs.rs @@ -6,12 +6,14 @@ use autorust_codegen::*; use spec::RefString; use std::path::PathBuf; -pub type Error = Box; -pub type Result = std::result::Result; +type Result = std::result::Result>; + +const COMMON_TYPES_SPEC: &str = "../../azure-rest-api-specs/specification/security/resource-manager/common/v1/types.json"; +const VMWARE_SPEC: &str = "../../azure-rest-api-specs/specification/vmware/resource-manager/Microsoft.AVS/stable/2020-03-20/vmware.json"; #[test] fn refs_count_security_common() -> Result<()> { - let api = &spec::openapi::parse("../../azure-rest-api-specs/specification/security/resource-manager/common/v1/types.json")?; + let api = &spec::openapi::parse(COMMON_TYPES_SPEC)?; let refs = spec::get_refs(api); assert_eq!(13, refs.len()); Ok(()) @@ -19,9 +21,7 @@ fn refs_count_security_common() -> Result<()> { #[test] fn refs_count_avs() -> Result<()> { - let api = &spec::openapi::parse( - "../../azure-rest-api-specs/specification/vmware/resource-manager/Microsoft.AVS/stable/2020-03-20/vmware.json", - )?; + let api = &spec::openapi::parse(VMWARE_SPEC)?; let refs = spec::get_refs(api); assert_eq!(197, refs.len()); Ok(()) @@ -29,9 +29,7 @@ fn refs_count_avs() -> Result<()> { #[test] fn ref_files() -> Result<()> { - let api = &spec::openapi::parse( - "../../azure-rest-api-specs/specification/vmware/resource-manager/Microsoft.AVS/stable/2020-03-20/vmware.json", - )?; + let api = &spec::openapi::parse(VMWARE_SPEC)?; let files = spec::openapi::get_ref_files(api); assert_eq!(1, files.len()); assert!(files.contains("../../../../../common-types/resource-management/v1/types.json")); @@ -40,9 +38,7 @@ fn ref_files() -> Result<()> { #[test] fn read_spec_avs() -> Result<()> { - let spec = &Spec::read_files(&[ - "../../azure-rest-api-specs/specification/vmware/resource-manager/Microsoft.AVS/stable/2020-03-20/vmware.json", - ])?; + let spec = &Spec::read_files(&[VMWARE_SPEC])?; assert_eq!(2, spec.docs.len()); assert!(spec.docs.contains_key(std::path::Path::new( "../../azure-rest-api-specs/specification/common-types/resource-management/v1/types.json" @@ -52,8 +48,7 @@ fn read_spec_avs() -> Result<()> { #[test] fn test_resolve_schema_ref() -> Result<()> { - let file = - PathBuf::from("../../azure-rest-api-specs/specification/vmware/resource-manager/Microsoft.AVS/stable/2020-03-20/vmware.json"); + let file = PathBuf::from(VMWARE_SPEC); let spec = &Spec::read_files(&[&file])?; spec.resolve_schema_ref(&file, Reference::parse("#/definitions/OperationList"))?; spec.resolve_schema_ref( @@ -65,8 +60,7 @@ fn test_resolve_schema_ref() -> Result<()> { #[test] fn test_resolve_parameter_ref() -> Result<()> { - let file = - PathBuf::from("../../azure-rest-api-specs/specification/vmware/resource-manager/Microsoft.AVS/stable/2020-03-20/vmware.json"); + let file = PathBuf::from(VMWARE_SPEC); let spec = &Spec::read_files(&[&file])?; spec.resolve_parameter_ref( &file, @@ -77,8 +71,7 @@ fn test_resolve_parameter_ref() -> Result<()> { #[test] fn test_resolve_all_refs() -> Result<()> { - let doc_file = - PathBuf::from("../../azure-rest-api-specs/specification/vmware/resource-manager/Microsoft.AVS/stable/2020-03-20/vmware.json"); + let doc_file = PathBuf::from(VMWARE_SPEC); let spec = &Spec::read_files(&[&doc_file])?; for (doc_file, doc) in &spec.docs { let refs = spec::get_refs(doc); diff --git a/codegen/tests/redis_spec.rs b/codegen/tests/redis_spec.rs new file mode 100644 index 0000000..765a75d --- /dev/null +++ b/codegen/tests/redis_spec.rs @@ -0,0 +1,75 @@ +// cargo test --test redis_specs +// These tests require cloning azure-rest-api-specs. +// git clone git@github.com:Azure/azure-rest-api-specs.git ../azure-rest-api-specs + +use std::path::PathBuf; +use autorust_codegen::{ + spec::{self, RefString}, + Reference, Spec, +}; + +type Result = std::result::Result>; + +const REDIS_SPEC: &str = "../../azure-rest-api-specs/specification/redis/resource-manager/Microsoft.Cache/stable/2020-06-01/redis.json"; +const LINKS_SPEC: &str = "../../azure-rest-api-specs/specification/common-types/resource-management/v1/privatelinks.json"; + +#[test] +fn test_redis_ref_files() -> Result<()> { + let api = &spec::openapi::parse(REDIS_SPEC)?; + let files = spec::openapi::get_ref_files(api); + println!("{:#?}", files); + assert_eq!(2, files.len()); + assert!(files.contains("../../../../../common-types/resource-management/v2/types.json")); + Ok(()) +} + +#[test] +fn test_redis_read_spec() -> Result<()> { + let spec = &Spec::read_files(&[REDIS_SPEC])?; + println!("{:#?}", spec.docs.keys()); + assert_eq!(3, spec.docs.len()); + assert!(spec.docs.contains_key(std::path::Path::new( + "../../azure-rest-api-specs/specification/common-types/resource-management/v2/types.json" + ))); + Ok(()) +} + +#[test] +fn test_links_ref_files() -> Result<()> { + let api = &spec::openapi::parse(LINKS_SPEC)?; + let files = spec::openapi::get_ref_files(api); + println!("{:#?}", files); + assert_eq!(1, files.len()); + assert!(files.contains("./types.json")); + Ok(()) +} + +#[test] +fn test_links_refs_count() -> Result<()> { + let api = &spec::openapi::parse(LINKS_SPEC)?; + let refs = spec::get_refs(api); + assert_eq!(10, refs.len()); + Ok(()) +} + +// #[test] +// fn test_redis_resolve_all_refs() -> Result<()> { +// let doc_file = PathBuf::from(REDIS_SPEC); +// let spec = &Spec::read_files(&[&doc_file])?; +// for (doc_file, doc) in &spec.docs { +// let refs = spec::get_refs(doc); +// for rs in refs { +// match rs { +// RefString::PathItem(_) => {} +// RefString::Example(_) => {} +// RefString::Parameter(reference) => { +// spec.resolve_parameter_ref(&doc_file, Reference::parse(&reference))?; +// } +// RefString::Schema(reference) => { +// spec.resolve_schema_ref(&doc_file, Reference::parse(&reference))?; +// } +// } +// } +// } +// Ok(()) +// } From bf792488dd5675b316f634d1d4dd42df9d57a99a Mon Sep 17 00:00:00 2001 From: Cameron Taggart Date: Thu, 29 Oct 2020 22:56:30 -0600 Subject: [PATCH 6/6] fmt --- codegen/tests/redis_spec.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codegen/tests/redis_spec.rs b/codegen/tests/redis_spec.rs index 765a75d..c08168f 100644 --- a/codegen/tests/redis_spec.rs +++ b/codegen/tests/redis_spec.rs @@ -2,11 +2,11 @@ // These tests require cloning azure-rest-api-specs. // git clone git@github.com:Azure/azure-rest-api-specs.git ../azure-rest-api-specs -use std::path::PathBuf; use autorust_codegen::{ spec::{self, RefString}, Reference, Spec, }; +use std::path::PathBuf; type Result = std::result::Result>;