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) ;\"")