Repository: juhaku/utoipa
Pull request #15 (2022-01-28 15:08:50)

Add support for lifetimes
* Add support for lifetimes in types
* Improve generic support 


Patch:
diff --git a/utoipa-gen/src/component.rs b/utoipa-gen/src/component.rs
index 43716fc..537d633 100644
--- a/utoipa-gen/src/component.rs
+++ b/utoipa-gen/src/component.rs
@@ -297,7 +297,11 @@ impl<'a> ComponentPart<'a> {
         ComponentPart::from_type_path(
             match ty {
                 Type::Path(path) => path,
-                _ => abort_call_site!("Unexpected type, expected Type::Path"),
+                Type::Reference(reference) => match reference.elem.as_ref() {
+                    Type::Path(path) => path,
+                    _ => abort_call_site!("unexpected type in reference, expected Type:Path"),
+                },
+                _ => abort_call_site!("unexpected type, expected Type::Path"),
             },
             ComponentPart::convert,
             ComponentPart::resolve_component_type,
diff --git a/utoipa-gen/src/component_type.rs b/utoipa-gen/src/component_type.rs
index 78a5e81..deca1ae 100644
--- a/utoipa-gen/src/component_type.rs
+++ b/utoipa-gen/src/component_type.rs
@@ -15,9 +15,7 @@ impl<'a> ComponentType<'a> {
             name,
             "String"
                 | "str"
-                | "&str"
                 | "char"
-                | "&char"
                 | "bool"
                 | "usize"
                 | "u8"
diff --git a/utoipa-gen/src/openapi.rs b/utoipa-gen/src/openapi.rs
index af92ebe..13bd0af 100644
--- a/utoipa-gen/src/openapi.rs
+++ b/utoipa-gen/src/openapi.rs
@@ -4,7 +4,7 @@ use syn::{
     bracketed,
     parse::{Parse, ParseStream},
     punctuated::Punctuated,
-    Attribute, Error, ExprPath, Generics, Token,
+    Attribute, Error, ExprPath, GenericParam, Generics, Token,
 };
 
 use proc_macro2::TokenStream;
@@ -29,6 +29,15 @@ struct Component {
     generics: Generics,
 }
 
+impl Component {
+    fn has_lifetime_generics(&self) -> bool {
+        self.generics
+            .params
+            .iter()
+            .any(|generic| matches!(generic, GenericParam::Lifetime(_)))
+    }
+}
+
 pub fn parse_openapi_attributes_from_attributes(attrs: &[Attribute]) -> Option<OpenApiAttr> {
     attrs
         .iter()
@@ -140,11 +149,21 @@ impl ToTokens for OpenApi {
                 let component_name = &*ident.to_string();
                 let (_, ty_generics, _) = component.generics.split_for_impl();
 
+                let assert_ty_generics = if component.has_lifetime_generics() {
+                    Some(quote! {<'static>})
+                } else {
+                    Some(ty_generics.to_token_stream())
+                };
                 let assert_component = format_ident!("_AssertComponent{}", component_name);
                 tokens.extend(quote_spanned! {span=>
-                    struct #assert_component where #ident #ty_generics: utoipa::Component;
+                    struct #assert_component where #ident #assert_ty_generics: utoipa::Component;
                 });
 
+                let ty_generics = if component.has_lifetime_generics() {
+                    None
+                } else {
+                    Some(ty_generics)
+                };
                 schema.extend(quote! {
                     .with_component(#component_name, <#ident #ty_generics>::component())
                 });


Test patch:
diff --git a/tests/component_derive_test.rs b/tests/component_derive_test.rs
index 00a5970..8e4a44f 100644
--- a/tests/component_derive_test.rs
+++ b/tests/component_derive_test.rs
@@ -26,13 +26,13 @@ macro_rules! api_doc {
         api_doc!(@doc $name)
     }};
 
-    ( $( #[$attr:meta] )* $key:ident $name:ident<$generic:ident> $body:tt ) => {{
+    ( $( #[$attr:meta] )* $key:ident $name:ident< $($life:lifetime)? $($generic:ident)? > $body:tt ) => {{
         #[allow(dead_code)]
         #[derive(Component)]
         $(#[$attr])*
-        $key $name<$generic> $body
+        $key $name<$($life)? $($generic)?> $body
 
-        api_doc!(@doc $name <$generic> )
+        api_doc!(@doc $name < $($life)? $($generic)?> )
     }};
 
     ( @doc $name:ident $( $generic:tt )* ) => {{
@@ -438,3 +438,17 @@ fn derive_struct_with_generics() {
         "properties.t.$ref" = r###""#/components/schemas/Type""###, "Status t field"
     };
 }
+
+#[test]
+fn derive_struct_with_lifetime_generics() {
+    #[allow(unused)]
+    let greeting = api_doc! {
+        struct Greeting<'a> {
+            greeting: &'a str
+        }
+    };
+
+    assert_value! {greeting=>
+        "properties.greeting.type" = r###""string""###, "Greeting greeting field type"
+    };
+}
diff --git a/tests/utoipa_gen_test.rs b/tests/utoipa_gen_test.rs
index 458fc24..b8178ae 100644
--- a/tests/utoipa_gen_test.rs
+++ b/tests/utoipa_gen_test.rs
@@ -1,4 +1,5 @@
 #![cfg(feature = "actix_extras")]
+
 use serde::{Deserialize, Serialize};
 use utoipa::{Component, OpenApi};
 
@@ -21,6 +22,11 @@ enum StatusType {
     NotOk,
 }
 
+#[derive(Serialize, Deserialize, Component)]
+struct Simple {
+    greeting: &'static str,
+}
+
 mod pet_api {
     use super::*;
 
@@ -49,7 +55,7 @@ mod pet_api {
 }
 
 #[derive(OpenApi, Default)]
-#[openapi(handlers = [pet_api::get_pet_by_id], components = [Pet, StatusType, Status<StatusType>])]
+#[openapi(handlers = [pet_api::get_pet_by_id], components = [Pet, StatusType, Status<StatusType>, Simple])]
 struct ApiDoc;
 
 #[test]