Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rust-Analyzer fixes and workspace improvements #47

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 43 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,20 +95,55 @@ The paths receives a String which must respect this structure :

You can add several paths by separating them with a coma `","`.

## Usage with workspaces
## `utoipauto` Macro Attributes

All attributes are optional!
The `utoipauto` macro supports several attributes to customize its behavior:

- **`paths`**: Specifies the paths to scan for functions and structs. If no path is specified, the macro will
automatically scan the `src` folder.

```rust
#[utoipauto(paths = "./src/rest")]
```

- **convert_to_full_path**: When set to false, it allows importing from another crate without converting to a full path.
Defaults to `true`.

```rust
#[utoipauto(paths = "./utoipauto/src from utoipauto", convert_to_full_path = false)]
```

- **function_attribute_name**: Specifies a custom attribute to look for instead of the default #[utoipa::path(...)].

If you are using a workspace, you must specify the name of the crate in the path.
<br>
This applies even if you are using `#[utoipauto]` in the same crate.
```rust
#[utoipauto(function_attribute_name = "handler")]
```

- **schema_attribute_name**: Specifies a custom derive attribute for model detection instead of the default ToSchema.

```rust
#[utoipauto(paths = "./utoipauto/src")]
#[utoipauto(schema_attribute_name = "Schema")]
```

You can specify that the specified paths are from another crate by using the from key work.
- **response_attribute_name**: Specifies a custom derive attribute for response detection instead of the default
ToResponse.

```rust
#[utoipauto(response_attribute_name = "Response")]
```

These attributes allow you to tailor the macro's behavior to fit your project's structure and naming conventions

## Usage with workspaces

If you are using a workspace, it just works out of the box.

If you want to import from another crate you have to specify the crate, the path and set `convert_to_full_path` to
`false`.

```rust
#[utoipauto(paths = "./utoipauto/src from utoipauto")]
#[utoipauto(paths = "./utoipauto/src from utoipauto", convert_to_full_path = false)]
```

### Import from src folder
Expand Down Expand Up @@ -266,7 +301,7 @@ pub fn custom_router() {

#[utoipauto(function_attribute_name = "handler")] //Custom attribute
#[derive(OpenApi)]
#[openapi(tags()))]
# [openapi(tags()))]
pub struct ApiDoc;

```
Expand Down
17 changes: 17 additions & 0 deletions acceptance/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion acceptance/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["crate_segment_path", "folder_in_src", "generics", "responses", "utility"]
members = ["crate_segment_path", "folder_in_src", "generics", "import_from_workspace", "lib_of_openapi", "responses", "utility"]
resolver = "2"

[workspace.package]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ 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 )")]
#[utoipauto(
paths = "( ./folder_in_src/crate_folder/new_sub_folder/paths.rs from folder-in-src::new_sub_folder )",
convert_to_full_path = false
)]
#[derive(OpenApi)]
#[openapi(info(title = "Percentage API", version = "1.0.0"))]
#[allow(dead_code)]
Expand Down
2 changes: 1 addition & 1 deletion acceptance/generics/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use routes::*;
use utoipa::OpenApi;
use utoipauto::utoipauto;

#[utoipauto(paths = "./generics/src")]
#[utoipauto]
#[derive(Debug, OpenApi)]
#[openapi(info(title = "Generic Test Api"))]
pub(crate) struct ApiDoc;
Expand Down
20 changes: 20 additions & 0 deletions acceptance/import_from_workspace/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "import_from_workspace"
authors.workspace = true
version.workspace = true
edition.workspace = true
publish.workspace = true
description.workspace = true
readme.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true

[dependencies]
utoipa.workspace = true
utoipauto = { workspace = true }

lib_of_openapi = { path = "../lib_of_openapi" }

[dev-dependencies]
utility.workspace = true
29 changes: 29 additions & 0 deletions acceptance/import_from_workspace/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use utoipa::OpenApi;
use utoipauto::utoipauto;

#[utoipauto(paths = "./lib_of_openapi/src from lib_of_openapi")]
#[derive(Debug, OpenApi)]
#[openapi(info(title = "Import form generics test."))]
pub(crate) struct ApiDoc;

fn main() {
println!(
"Our OpenApi documentation {}",
ApiDoc::openapi().to_pretty_json().unwrap()
);
}

#[cfg(test)]
mod tests {
use super::*;
use utility::assert_json_eq;

pub(crate) const EXPECTED_OPEN_API: &str = include_str!("open_api.expected.json");

#[test]
fn test_openapi() {
let open_api = ApiDoc::openapi().to_json().unwrap();

assert_json_eq(&open_api, EXPECTED_OPEN_API);
}
}
43 changes: 43 additions & 0 deletions acceptance/import_from_workspace/src/open_api.expected.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"openapi": "3.1.0",
"info": {
"title": "Import form generics test.",
"description": "A collection of crates to test utoipauto.",
"contact": {
"name": "ProbablyClem"
},
"license": {
"name": "MIT OR Apache-2.0"
},
"version": "0.1.0"
},
"paths": {},
"components": {
"schemas": {
"Schema1": {
"type": "integer",
"format": "int32",
"minimum": 0
},
"Schema2": {
"type": "integer",
"format": "int32",
"minimum": 0
},
"Schema3": {
"type": "integer",
"format": "int32",
"minimum": 0
},
"Schema4": {
"type": "integer",
"format": "int64",
"minimum": 0
},
"Schema5": {
"type": "integer",
"minimum": 0
}
}
}
}
14 changes: 14 additions & 0 deletions acceptance/lib_of_openapi/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "lib_of_openapi"
authors.workspace = true
version.workspace = true
edition.workspace = true
publish.workspace = true
description.workspace = true
readme.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true

[dependencies]
utoipa.workspace = true
16 changes: 16 additions & 0 deletions acceptance/lib_of_openapi/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use utoipa::ToSchema;

#[derive(Debug, ToSchema)]
pub struct Schema1(pub u8);

#[derive(Debug, ToSchema)]
pub struct Schema2(pub u16);

#[derive(Debug, ToSchema)]
pub struct Schema3(pub u32);

#[derive(Debug, ToSchema)]
pub struct Schema4(pub u64);

#[derive(Debug, ToSchema)]
pub struct Schema5(pub u128);
4 changes: 2 additions & 2 deletions acceptance/responses/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ mod routes;
use utoipa::OpenApi;
use utoipauto::utoipauto;

#[utoipauto(paths = "./responses/src")]
#[utoipauto]
#[derive(Debug, OpenApi)]
#[openapi(info(title = "Responses Test Api"))]
pub(crate) struct ApiDoc;
Expand All @@ -19,8 +19,8 @@ fn main() {
#[cfg(test)]
mod tests {
use crate::ApiDoc;
use utoipa::OpenApi;
use utility::assert_json_eq;
use utoipa::OpenApi;

pub(crate) const EXPECTED_OPEN_API: &str = include_str!("open_api.expected.json");
#[test]
Expand Down
30 changes: 23 additions & 7 deletions utoipauto-core/src/string_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub fn trim_parentheses(str: &str) -> String {
/// vec![
/// "./utoipa-auto-macro/tests/controllers/controller1.rs".to_string(),
/// "./utoipa-auto-macro/tests/controllers/controller2.rs".to_string(),
/// ]
/// ]
/// );
/// ```
pub fn extract_paths(attributes: &str) -> Vec<String> {
Expand Down Expand Up @@ -94,7 +94,7 @@ pub fn discover(paths: Vec<String>, params: &Parameters) -> (String, String, Str
let mut uto_models: String = String::new();
let mut uto_responses: String = String::new();
for p in paths {
let path = extract_crate_name(p);
let path = extract_crate_name(p, params.convert_to_full_path);
let (list_fn, list_model, list_reponse) = discover_from_file(path.paths, path.crate_name, params);
// We need to add a coma after each path
for i in list_fn {
Expand All @@ -116,13 +116,28 @@ struct Path {
crate_name: String,
}

fn extract_crate_name(path: String) -> Path {
fn extract_crate_name(path: String, convert_to_full_path: bool) -> Path {
let convert_to_full_path = convert_to_full_path && !path.contains(" from ");
let mut path = path.split(" from ");

let paths = path.next().unwrap();
let paths = match convert_to_full_path {
true => convert_to_absolute_path(paths),
false => paths.to_string(),
};
let crate_name = path.next().unwrap_or("crate").to_string();
Path {
paths: paths.to_string(),
crate_name,
Path { paths, crate_name }
}

fn convert_to_absolute_path(path: &str) -> String {
match std::env::var("CARGO_MANIFEST_DIR") {
Ok(mut manifest_dir) => {
let path = path.strip_prefix(".").unwrap_or(path).to_string();
manifest_dir.push_str(path.as_str());

manifest_dir
}
Err(_) => path.to_string(),
}
}

Expand All @@ -142,6 +157,7 @@ mod test {
assert_eq!(
super::extract_crate_name(
"utoipa_auto_macro::from::controllers::controller1 from utoipa_auto_macro".to_string(),
false
),
super::Path {
paths: "utoipa_auto_macro::from::controllers::controller1".to_string(),
Expand All @@ -153,7 +169,7 @@ mod test {
#[test]
fn test_extract_crate_name_default() {
assert_eq!(
super::extract_crate_name("utoipa_auto_macro::from::controllers::controller1".to_string()),
super::extract_crate_name("utoipa_auto_macro::from::controllers::controller1".to_string(), false),
super::Path {
paths: "utoipa_auto_macro::from::controllers::controller1".to_string(),
crate_name: "crate".to_string()
Expand Down
24 changes: 23 additions & 1 deletion utoipauto-core/src/token_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub struct Parameters {
pub fn_attribute_name: String,
pub schema_attribute_name: String,
pub response_attribute_name: String,
pub convert_to_full_path: bool,
}

/// Extract the paths string attribute from the proc_macro::TokenStream
Expand All @@ -17,13 +18,15 @@ pub fn extract_attributes(stream: proc_macro2::TokenStream) -> Parameters {
let paths = extract_attribute("paths", stream.clone());
let fn_attribute_name = extract_attribute("function_attribute_name", stream.clone());
let schema_attribute_name = extract_attribute("schema_attribute_name", stream.clone());
let response_attribute_name = extract_attribute("response_attribute_name", stream);
let response_attribute_name = extract_attribute("response_attribute_name", stream.clone());
let convert_to_full_path = extract_bool_attribute("convert_to_full_path", stream);
// if no paths specified, we use the default path "./src"
Parameters {
paths: paths.unwrap_or("./src".to_string()),
fn_attribute_name: fn_attribute_name.unwrap_or("utoipa".to_string()),
schema_attribute_name: schema_attribute_name.unwrap_or("ToSchema".to_string()),
response_attribute_name: response_attribute_name.unwrap_or("ToResponse".to_string()),
convert_to_full_path: convert_to_full_path.unwrap_or(true),
}
}

Expand All @@ -46,6 +49,25 @@ fn extract_attribute(name: &str, stream: proc_macro2::TokenStream) -> Option<Str
None
}

fn extract_bool_attribute(name: &str, stream: proc_macro2::TokenStream) -> Option<bool> {
let mut has_value = false;

for token in stream {
if has_value {
if let proc_macro2::TokenTree::Ident(ident) = token {
let value = ident.to_string();
return Some(value.parse::<bool>().unwrap());
}
}
if let proc_macro2::TokenTree::Ident(ident) = token {
if ident.to_string().eq(name) {
has_value = true;
}
}
}
None
}

fn get_content(lit: Literal) -> String {
let content = lit.to_string();
content[1..content.len() - 1].to_string()
Expand Down
Loading
Loading