Repository: netwo-io/apistos
Pull request #52 (2024-02-20 15:15:48)

Fix generics bound for derive macros


Patch:
diff --git a/apistos-gen/src/lib.rs b/apistos-gen/src/lib.rs
index fe9ceaf..e040f5f 100644
--- a/apistos-gen/src/lib.rs
+++ b/apistos-gen/src/lib.rs
@@ -62,11 +62,11 @@ pub fn derive_api_type(input: TokenStream) -> TokenStream {
     vis: _vis,
   } = input;
 
-  let (_, ty_generics, where_clause) = generics.split_for_impl();
+  let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
   let component_name = quote!(#ident).to_string();
   quote!(
     #[automatically_derived]
-    impl #ty_generics schemars::JsonSchema for #ident #ty_generics #where_clause {
+    impl #impl_generics schemars::JsonSchema for #ident #ty_generics #where_clause {
        fn is_referenceable() -> bool {
         false
       }
@@ -86,7 +86,7 @@ pub fn derive_api_type(input: TokenStream) -> TokenStream {
     }
 
     #[automatically_derived]
-    impl #ty_generics apistos::ApiComponent for #ident #ty_generics #where_clause {
+    impl #impl_generics apistos::ApiComponent for #ident #ty_generics #where_clause {
       fn child_schemas() -> Vec<(String, apistos::reference_or::ReferenceOr<apistos::Schema>)> {
         vec![]
       }
@@ -139,11 +139,11 @@ pub fn derive_api_component(input: TokenStream) -> TokenStream {
     vis: _vis,
   } = input;
 
-  let (_, ty_generics, where_clause) = generics.split_for_impl();
+  let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
   let schema_impl = Schemas { deprecated: false };
   quote!(
     #[automatically_derived]
-    impl #generics apistos::ApiComponent for #ident #ty_generics #where_clause {
+    impl #impl_generics apistos::ApiComponent for #ident #ty_generics #where_clause {
       #schema_impl
     }
   )
@@ -241,10 +241,10 @@ pub fn derive_api_security(input: TokenStream) -> TokenStream {
   );
   let security_name = &openapi_security_attributes.name;
 
-  let (_, ty_generics, where_clause) = generics.split_for_impl();
+  let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
   quote!(
     #[automatically_derived]
-    impl #generics apistos::ApiComponent for #ident #ty_generics #where_clause {
+    impl #impl_generics apistos::ApiComponent for #ident #ty_generics #where_clause {
       fn child_schemas() -> Vec<(String, apistos::reference_or::ReferenceOr<apistos::Schema>)> {
         vec![]
       }
@@ -310,18 +310,18 @@ pub fn derive_api_header(input: TokenStream) -> TokenStream {
   let openapi_header_attributes = parse_openapi_header_attrs(&attrs, deprecated)
     .expect_or_abort("expected #[openapi_header(...)] attribute to be present when used with ApiHeader derive trait");
 
-  let (_, ty_generics, where_clause) = generics.split_for_impl();
+  let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
   let schema_impl = Schemas {
     deprecated: openapi_header_attributes.deprecated.unwrap_or_default(),
   };
   quote!(
     #[automatically_derived]
-    impl #generics apistos::ApiComponent for #ident #ty_generics #where_clause {
+    impl #impl_generics apistos::ApiComponent for #ident #ty_generics #where_clause {
       #schema_impl
     }
 
     #[automatically_derived]
-    impl #generics apistos::ApiHeader for #ident #ty_generics #where_clause {
+    impl #impl_generics apistos::ApiHeader for #ident #ty_generics #where_clause {
       #openapi_header_attributes
     }
   )
@@ -373,10 +373,10 @@ pub fn derive_api_cookie(input: TokenStream) -> TokenStream {
   let openapi_cookie_attributes = parse_openapi_cookie_attrs(&attrs, deprecated)
     .expect_or_abort("expected #[openapi_cookie(...)] attribute to be present when used with ApiCookie derive trait");
 
-  let (_, ty_generics, where_clause) = generics.split_for_impl();
+  let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
   quote!(
     #[automatically_derived]
-    impl #generics apistos::ApiComponent for #ident #ty_generics #where_clause {
+    impl #impl_generics apistos::ApiComponent for #ident #ty_generics #where_clause {
       #openapi_cookie_attributes
     }
   )
@@ -426,10 +426,10 @@ pub fn derive_api_error(input: TokenStream) -> TokenStream {
     "expected #[openapi_error(...)] attribute to be present when used with ApiErrorComponent derive trait",
   );
 
-  let (_, ty_generics, where_clause) = generics.split_for_impl();
+  let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
   quote!(
     #[automatically_derived]
-    impl #generics apistos::ApiErrorComponent for #ident #ty_generics #where_clause {
+    impl #impl_generics apistos::ApiErrorComponent for #ident #ty_generics #where_clause {
       #openapi_error_attributes
     }
   )


Test patch:
diff --git a/apistos-gen-test/src/tests/api_type_derive.rs b/apistos-gen-test/src/tests/api_type_derive.rs
index 48e9f73..0bf344b 100644
--- a/apistos-gen-test/src/tests/api_type_derive.rs
+++ b/apistos-gen-test/src/tests/api_type_derive.rs
@@ -1,6 +1,7 @@
 use assert_json_diff::assert_json_eq;
 use schemars::schema::InstanceType;
 use serde_json::json;
+use std::str::FromStr;
 
 use crate::utils::assert_schema;
 use apistos_core::{ApiComponent, TypedSchema};
@@ -110,6 +111,44 @@ fn api_type_derive_with_default_type_parameter() {
   );
 }
 
+#[test]
+#[allow(dead_code)]
+fn api_type_derive_with_generic_type_parameter() {
+  struct GenericHolder<T: FromStr> {
+    inner: T,
+  }
+
+  #[derive(ApiType)]
+  struct Name<T: FromStr>(GenericHolder<T>);
+
+  impl<T: FromStr> TypedSchema for Name<T> {
+    fn schema_type() -> InstanceType {
+      InstanceType::String
+    }
+
+    fn format() -> Option<String> {
+      // not a real format but not a problem
+      Some("lastname".to_string())
+    }
+  }
+
+  let name_schema = <Name<String> as ApiComponent>::schema();
+  let name_child_schemas = <Name<String> as ApiComponent>::child_schemas();
+  assert!(name_schema.is_some());
+  assert!(name_child_schemas.is_empty());
+  let (schema_name, schema) = name_schema.expect("schema should be defined");
+  assert_eq!(schema_name, "Name");
+  assert_schema(&schema.clone());
+  let json = serde_json::to_value(schema).expect("Unable to serialize as Json");
+  assert_json_eq!(
+    json,
+    json!({
+      "type": "string",
+      "format": "lastname"
+    })
+  );
+}
+
 #[test]
 #[allow(dead_code)]
 fn api_type_derive_with_format_complex_struct() {