Repository: ProbablyClem/utoipauto (language: rust)

Issue:
Does discovery account for dash / underscore difference in path vs module name?

I have a module, part of a workspace: this is its `Cargo.toml`
```toml
[package]
name = "child-mod"
```

It's a submodule of `current-crate` where I'm generating Openapi docs.

```
current-crate/
├─ src/
│  ├─ mod.rs
│  ├─ child-mod/
│  │  ├─ src/
│  │  │  ├─ models.rs
```

Hence, I need to both declare 
- current crate (because it's part of the workspace) - that part works fine ✅
- the submodule with `from` keyword, as specified [here](https://docs.rs/crate/utoipauto/latest), like this:


```
#[utoipauto(paths = "./current-crate/src,./src/models.rs from child-mod")]`
```



Problem is, importing from submodule looks like this (note `_` instead of `-`):

```rust
use child_mod::SomeStruct;
```

So you receive a file path in the macro and [transform it to module path](https://github.com/ProbablyClem/utoipauto/blob/main/utoipauto-core/src/file_utils.rs#L70). 

Also, you [recurse through the file tree](https://github.com/ProbablyClem/utoipauto/blob/main/utoipauto-core/src/discover.rs#L11) so I cannot directly input underscored file name in the `#[utoipauto(path` annotation because the file wouldn't be found.

However I couldn't find whether you account for this module naming quirk: difference between file paths containing `-` and module paths containing `_`. 

Is this why my `path` annotation isn't working or something else?



Discussion / hints:
Interesting, I believe we just never encountered this case.
Adding a replace in [transform it to module path](https://github.com/ProbablyClem/utoipauto/blob/main/utoipauto-core/src/file_utils.rs#L70) should do the trick.
Feel free to submit a PR

Fix patch:
diff --git a/acceptance/Cargo.lock b/acceptance/Cargo.lock
index 4ce7c21..89d9f57 100644
--- a/acceptance/Cargo.lock
+++ b/acceptance/Cargo.lock
@@ -8,6 +8,14 @@ version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
 
+[[package]]
+name = "folder-in-src"
+version = "0.1.0"
+dependencies = [
+ "utoipa",
+ "utoipauto",
+]
+
 [[package]]
 name = "hashbrown"
 version = "0.14.5"
@@ -188,6 +196,7 @@ dependencies = [
 name = "utoipauto-lib-test"
 version = "0.1.0"
 dependencies = [
+ "folder-in-src",
  "utoipa",
  "utoipauto",
 ]
diff --git a/acceptance/Cargo.toml b/acceptance/Cargo.toml
index b2ed104..20a69dd 100644
--- a/acceptance/Cargo.toml
+++ b/acceptance/Cargo.toml
@@ -1,5 +1,5 @@
 [workspace]
-members = ["crate_segment_path"]
+members = ["crate_segment_path", "folder_in_src"]
 resolver = "2"
 
 [workspace.package]
diff --git a/acceptance/crate_segment_path/Cargo.toml b/acceptance/crate_segment_path/Cargo.toml
index 8d9c875..516da3d 100644
--- a/acceptance/crate_segment_path/Cargo.toml
+++ b/acceptance/crate_segment_path/Cargo.toml
@@ -15,4 +15,5 @@ path = "crate_folder/lib.rs"
 
 [dependencies]
 utoipa.workspace = true
-utoipauto.workspace = true
\ No newline at end of file
+utoipauto.workspace = true
+folder-in-src = { path = "../folder_in_src" }
\ No newline at end of file
diff --git a/acceptance/folder_in_src/Cargo.toml b/acceptance/folder_in_src/Cargo.toml
new file mode 100644
index 0000000..d7afbee
--- /dev/null
+++ b/acceptance/folder_in_src/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "folder-in-src"
+description = "Test library that tests folders in the src directory."
+authors.workspace = true
+version.workspace = true
+edition.workspace = true
+publish.workspace = true
+readme.workspace = true
+license.workspace = true
+repository.workspace = true
+homepage.workspace = true
+
+[lib]
+path = "crate_folder/lib.rs"
+
+[dependencies]
+utoipa.workspace = true
+utoipauto.workspace = true
\ No newline at end of file
diff --git a/acceptance/folder_in_src/crate_folder/lib.rs b/acceptance/folder_in_src/crate_folder/lib.rs
new file mode 100644
index 0000000..ce0d782
--- /dev/null
+++ b/acceptance/folder_in_src/crate_folder/lib.rs
@@ -0,0 +1,1 @@
+pub mod new_sub_folder;
diff --git a/acceptance/folder_in_src/crate_folder/new_sub_folder/mod.rs b/acceptance/folder_in_src/crate_folder/new_sub_folder/mod.rs
new file mode 100644
index 0000000..8120252
--- /dev/null
+++ b/acceptance/folder_in_src/crate_folder/new_sub_folder/mod.rs
@@ -0,0 +1,2 @@
+pub mod paths;
+mod test;
diff --git a/acceptance/folder_in_src/crate_folder/new_sub_folder/paths.rs b/acceptance/folder_in_src/crate_folder/new_sub_folder/paths.rs
new file mode 100644
index 0000000..fec992d
--- /dev/null
+++ b/acceptance/folder_in_src/crate_folder/new_sub_folder/paths.rs
@@ -0,0 +1,7 @@
+#![allow(dead_code)] // This code is used in the tests
+
+#[utoipa::path(post, path = "/route1new")]
+pub fn route1new() {}
+
+#[utoipa::path(post, path = "/route2new")]
+pub fn route2new() {}
diff --git a/utoipauto-core/src/file_utils.rs b/utoipauto-core/src/file_utils.rs
index 9e2ddbe..8a5c43e 100644
--- a/utoipauto-core/src/file_utils.rs
+++ b/utoipauto-core/src/file_utils.rs
@@ -87,6 +87,7 @@ pub fn extract_module_name_from_path(path: &str, crate_name: &str) -> String {
     // Also skip fragments that are already out of the crate name. For example,
     // `./src/lib/my/module/name from crate::my::module` should turn into `crate::my::module:name`,
     // and not into `crate::lib::my::module::name`.
+    let crate_name = crate_name.replace("-", "_");
     let mut crate_segments = crate_name.split("::");
     let first_crate_fragment = crate_segments.next().expect("Crate should not be empty");
     let segments_inside_crate = match crate_segments.next() {
@@ -192,4 +193,12 @@ mod tests {
             "crate::routes::asset"
         );
     }
+
+    #[test]
+    fn test_extract_module_name_from_workspace_with_external_crate_and_underscore() {
+        assert_eq!(
+            extract_module_name_from_path("./src/applications/src/retail-api/controllers/mod.rs", "other-crate"),
+            "other_crate::retail-api::controllers"
+        );
+    }
 }


Test patch:
diff --git a/acceptance/crate_segment_path/crate_folder/sub_folder/test.rs b/acceptance/crate_segment_path/crate_folder/sub_folder/test.rs
index 2ac8996..6ddd6db 100644
--- a/acceptance/crate_segment_path/crate_folder/sub_folder/test.rs
+++ b/acceptance/crate_segment_path/crate_folder/sub_folder/test.rs
@@ -7,7 +7,20 @@ use utoipauto::utoipauto;
 #[allow(dead_code)]
 pub struct CrateInAnotherPath {}
 
+#[utoipauto(
+    paths = "( ./folder_in_src/crate_folder/new_sub_folder/paths.rs from folder-in-src::new_sub_folder )"
+)]
+#[derive(OpenApi)]
+#[openapi(info(title = "Percentage API", version = "1.0.0"))]
+#[allow(dead_code)]
+pub struct CrateInAnotherCrate {}
+
 #[test]
 fn test_crate_in_another_path() {
     assert_eq!(CrateInAnotherPath::openapi().paths.paths.len(), 2)
 }
+
+#[test]
+fn test_crate_in_another_crate() {
+    assert_eq!(CrateInAnotherCrate::openapi().paths.paths.len(), 2)
+}
diff --git a/acceptance/folder_in_src/crate_folder/new_sub_folder/test.rs b/acceptance/folder_in_src/crate_folder/new_sub_folder/test.rs
new file mode 100644
index 0000000..0a5aeed
--- /dev/null
+++ b/acceptance/folder_in_src/crate_folder/new_sub_folder/test.rs
@@ -0,0 +1,13 @@
+use utoipa::OpenApi;
+use utoipauto::utoipauto;
+
+#[utoipauto(paths = "( ./folder_in_src/crate_folder/new_sub_folder/paths.rs from crate::new_sub_folder )")]
+#[derive(OpenApi)]
+#[openapi(info(title = "Percentage API", version = "1.0.0"))]
+#[allow(dead_code)]
+pub struct CrateInAnotherPath {}
+
+#[test]
+fn test_crate_in_another_path() {
+    assert_eq!(CrateInAnotherPath::openapi().paths.paths.len(), 2)
+}