diff --git a/README.md b/README.md index 301273a..9702981 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,8 @@ -
+

Utoipauto

Rust Macros to automate the addition of Paths/Schemas to Utoipa crate, simulating Reflection during the compilation phase

-

-

# Crate presentation @@ -111,24 +109,13 @@ If you want to use generics, you have three ways to do it. use path::to::schema; ``` -3. use experimental `generic_full_path` feature +3. use `generic_full_path` feature -Please keep in mind that this is an experimental feature and causes more build-time overhead. -
+Please keep in mind that this is an experimental feature and causes more build-time overhead. Higher RAM usage, longer compile times and excessive disk usage (especially on larger projects) are the consequences. -
-Also we currently do not support "partial" imports. The following is **NOT** supported: - -```rust -use path::to::generic; - -#[aliases(GenericSchema = generic::Schema)] -``` - -Please use the full path instead or the `as` keyword to rename the imported schemas. ```toml -utoipauto = { version = "0.2.0", feature = ["generic_full_path"] } +utoipauto = { version = "*", feature = ["generic_full_path"] } ``` ## Usage with workspaces diff --git a/tests/generic_full_path/even_more_schemas.rs b/tests/generic_full_path/even_more_schemas.rs new file mode 100644 index 0000000..f75f6ba --- /dev/null +++ b/tests/generic_full_path/even_more_schemas.rs @@ -0,0 +1,3 @@ +pub struct EvenMoreSchema; + +pub struct EvenMoreSchema2; diff --git a/tests/generic_full_path/mod.rs b/tests/generic_full_path/mod.rs index e06ced0..9c5f3fc 100644 --- a/tests/generic_full_path/mod.rs +++ b/tests/generic_full_path/mod.rs @@ -1,3 +1,5 @@ +mod even_more_schemas; mod more_schemas; +mod partial_imports; mod schemas; mod test; diff --git a/tests/generic_full_path/partial_imports.rs b/tests/generic_full_path/partial_imports.rs new file mode 100644 index 0000000..305a2e5 --- /dev/null +++ b/tests/generic_full_path/partial_imports.rs @@ -0,0 +1,22 @@ +use utoipa::ToSchema; + +use crate::generic_full_path::even_more_schemas as schemas; +use crate::generic_full_path::more_schemas; + +#[derive(ToSchema)] +#[aliases( + PartialImportGenericSchemaMoreSchema = PartialImportGenericSchema < more_schemas::MoreSchema >, + PartialImportGenericSchemaMoreSchema2 = PartialImportGenericSchema < more_schemas::MoreSchema2 > +)] +pub struct PartialImportGenericSchema { + _data: T, +} + +#[derive(ToSchema)] +#[aliases( + PartialImportEvenMoreGenericSchemaAsImport = PartialImportGenericSchema < schemas::EvenMoreSchema >, + PartialImportEvenMoreGenericSchema2AsImport = PartialImportGenericSchema < schemas::EvenMoreSchema2 > +)] +pub struct PartialImportGenericSchemaAsImport { + _data: T, +} diff --git a/utoipauto-core/src/discover.rs b/utoipauto-core/src/discover.rs index 2d48557..4cad3cd 100644 --- a/utoipauto-core/src/discover.rs +++ b/utoipauto-core/src/discover.rs @@ -144,12 +144,10 @@ fn parse_from_attr( .expect("Failed to parse derive attribute"); for nested_meta in nested { if nested_meta.path().segments.len() == 2 { - if nested_meta.path().segments[0].ident.to_string() == "utoipa" { - if nested_meta.path().segments[1].ident.to_string() == "ToSchema" - && !is_generic - { + if nested_meta.path().segments[0].ident == "utoipa" { + if nested_meta.path().segments[1].ident == "ToSchema" && !is_generic { out.push(DiscoverType::Model(name.to_string())); - } else if nested_meta.path().segments[1].ident.to_string() == "ToResponse" + } else if nested_meta.path().segments[1].ident == "ToResponse" && !is_generic { out.push(DiscoverType::Response(name.to_string())); @@ -197,9 +195,8 @@ fn parse_generic_schema(meta: ParseNestedMeta, name: &str, _imports: Vec } let generics = merge_nested_generics(nested_generics); - let generic_type = name.to_string() + &generics; - generic_type + name.to_string() + &generics } #[cfg(feature = "generic_full_path")] @@ -408,21 +405,17 @@ fn extract_use_statements(file_path: &str, crate_name: &str) -> Vec { #[cfg(feature = "generic_full_path")] fn find_import(imports: Vec, current_module: &str, name: &str) -> String { let name = name.trim(); - for import in imports { + let current_module = current_module.trim(); + for import in imports.iter() { if import.contains(name) { - let import = import - .split(" as ") - .next() - .unwrap_or(&import) - .trim() - .to_string(); - return import; + let full_path = import_to_full_path(import); + return full_path; } } - // If the name contains `::` it means it's already a full path + // If the name contains `::` it means that it's a partial import or a full path if name.contains("::") { - return name.to_string(); + return handle_partial_import(imports, name).unwrap_or_else(|| name.to_string()); } // Only append the module path if the name does not already contain it @@ -433,6 +426,35 @@ fn find_import(imports: Vec, current_module: &str, name: &str) -> String name.to_string() } +#[cfg(feature = "generic_full_path")] +fn handle_partial_import(imports: Vec, name: &str) -> Option { + name.split("::").next().and_then(|first| { + let first = first.trim(); + + for import in imports { + let import = import.trim(); + + if import.ends_with(first) { + let full_path = import_to_full_path(&import); + + let usable_import = format!("{}{}", full_path.trim(), name[first.len()..].trim()); + return Some(usable_import); + } + } + None + }) +} + +#[cfg(feature = "generic_full_path")] +fn import_to_full_path(import: &str) -> String { + import + .split(" as ") + .next() + .unwrap_or(&import) + .trim() + .to_string() +} + #[cfg(feature = "generic_full_path")] fn get_current_module_from_name(name: &str) -> String { let parts: Vec<&str> = name.split("::").collect(); @@ -443,6 +465,9 @@ fn get_current_module_from_name(name: &str) -> String { mod test { use quote::quote; + #[cfg(feature = "generic_full_path")] + use crate::discover::{find_import, get_current_module_from_name, process_one_generic}; + #[test] fn test_parse_function() { let quoted = quote! { @@ -475,6 +500,18 @@ mod test { assert_eq!(result, expected); } + #[test] + #[cfg(feature = "generic_full_path")] + fn test_process_one_generic_partial_import() { + let part = "PartialImportGenericSchema"; + let name = "crate"; + let imports = vec!["crate::generic_full_path::more_schemas".to_string()]; + let expected = + "PartialImportGenericSchema"; + let result = process_one_generic(part, name, imports); + assert_eq!(result, expected); + } + #[test] #[cfg(feature = "generic_full_path")] fn test_process_one_generic_no_nested_generics() { diff --git a/utoipauto-core/src/file_utils.rs b/utoipauto-core/src/file_utils.rs index fa9db3d..81a771b 100644 --- a/utoipauto-core/src/file_utils.rs +++ b/utoipauto-core/src/file_utils.rs @@ -44,13 +44,14 @@ pub fn parse_files>(path: T) -> Result } fn is_rust_file(path: &Path) -> bool { - path.is_file() && match path.extension() { - Some(ext) => match ext.to_str() { - Some(ext) => ext.eq("rs"), + path.is_file() + && match path.extension() { + Some(ext) => match ext.to_str() { + Some(ext) => ext.eq("rs"), + None => false, + }, None => false, - }, - None => false, - } + } } /// Extract the module name from the file path diff --git a/utoipauto-core/src/string_utils.rs b/utoipauto-core/src/string_utils.rs index d6d8414..52c93a0 100644 --- a/utoipauto-core/src/string_utils.rs +++ b/utoipauto-core/src/string_utils.rs @@ -37,12 +37,12 @@ pub fn trim_parentheses(str: &str) -> String { /// paths, /// vec![ /// "./utoipa-auto-macro/tests/controllers/controller1.rs".to_string(), -/// "./utoipa-auto-macro/tests/controllers/controller2.rs".to_string() +/// "./utoipa-auto-macro/tests/controllers/controller2.rs".to_string(), /// ] /// ); /// ``` pub fn extract_paths(attributes: &str) -> Vec { - let attributes = trim_parentheses(&attributes); + let attributes = trim_parentheses(attributes); if attributes.contains('|') { panic!("Please use the new syntax ! paths=\"(MODULE_TREE_PATH => MODULE_SRC_PATH) ;\"")