Repository: wafflespeanut/paperclip
Pull request #234 (2020-10-01 13:29:27)

Allow schema binding by dynamically generated name
Currently it is not possible to generate schema name for container types, except the wrapped type `T::NAME`. But there might be different wrappers for various purposes. For instance we have wrapper for paginated results and since Rust does not support concatenation of const str I am not able to assign NAME to custom Apiv2Schema impl:
```
    impl<T: Apiv2Schema> Apiv2Schema for Paginated<T> {
        const NAME: Option<&'static str> = None;

        fn raw_schema() -> DefaultSchemaRaw {
            let mut schema = DefaultSchemaRaw::default();
            schema.name = Some(format!("Paginated{}", T::NAME.unwrap_or("Unknown")));
            schema.description = Some(format!("Paginated results.\n<br/>Item description: {}", T::DESCRIPTION));
            {
                let s = Vec::<T>::raw_schema();
                schema.properties.insert("data".into(), s.into());
            }
            {
                let mut s = Paging::raw_schema();
                s.description = Some("Paging information".to_string());
                schema.properties.insert("paging".into(), s.into());
            }
            schema
        }
    }
```

with this change generated schema for Payload wrapper will be `Paginated<TypeName>`


Patch:
diff --git a/Cargo.toml b/Cargo.toml
index 7fbbd062..b140f77d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -51,7 +51,7 @@ futures = "0.3"
 uuid_dev = { version = "0.8", features = ["serde"], package = "uuid" }
 reqwest = { version = "0.10", features = ["blocking", "json"] }
 log = { version = "0.4", features = ["kv_unstable"] }
-insta = "0.16"
+insta = "1.0"
 env_logger = "0.7"
 
 [features]
diff --git a/core/src/v2/schema.rs b/core/src/v2/schema.rs
index 9ca8a89b..27205510 100644
--- a/core/src/v2/schema.rs
+++ b/core/src/v2/schema.rs
@@ -182,8 +182,7 @@ impl_type_simple!(isize, DataType::Integer, DataTypeFormat::Int64);
 impl_type_simple!(u64, DataType::Integer, DataTypeFormat::Int64);
 impl_type_simple!(u128, DataType::Integer, DataTypeFormat::Int64);
 impl_type_simple!(usize, DataType::Integer, DataTypeFormat::Int64);
-impl_type_simple!(serde_json::Value, DataType::Object);
-impl_type_simple!(serde_yaml::Value, DataType::Object);
+
 #[cfg(feature = "actix-multipart")]
 impl_type_simple!(
     actix_multipart::Multipart,
@@ -196,6 +195,8 @@ impl_type_simple!(
     DataType::String,
     DataTypeFormat::DateTime
 );
+#[cfg(feature = "chrono")]
+impl_type_simple!(chrono::NaiveDate, DataType::String, DataTypeFormat::Date);
 #[cfg(feature = "rust_decimal")]
 impl_type_simple!(
     rust_decimal::Decimal,
@@ -216,6 +217,16 @@ impl<T: chrono::offset::TimeZone> TypedData for chrono::DateTime<T> {
     }
 }
 
+#[cfg(feature = "chrono")]
+impl<T: chrono::offset::TimeZone> TypedData for chrono::Date<T> {
+    fn data_type() -> DataType {
+        DataType::String
+    }
+    fn format() -> Option<DataTypeFormat> {
+        Some(DataTypeFormat::Date)
+    }
+}
+
 /// Represents a OpenAPI v2 schema object convertible. This is auto-implemented by
 /// framework-specific macros:
 ///
@@ -254,6 +265,8 @@ pub trait Apiv2Schema {
         let mut def = Self::raw_schema();
         if let Some(n) = Self::NAME {
             def.reference = Some(String::from("#/definitions/") + n);
+        } else if let Some(n) = def.name.as_ref() {
+            def.reference = Some(String::from("#/definitions/") + n);
         }
         if !Self::DESCRIPTION.is_empty() {
             def.description = Some(Self::DESCRIPTION.to_owned());
@@ -269,6 +282,8 @@ pub trait Apiv2Schema {
 }
 
 impl Apiv2Schema for () {}
+impl Apiv2Schema for serde_json::Value {}
+impl Apiv2Schema for serde_yaml::Value {}
 
 impl<T: TypedData> Apiv2Schema for T {
     fn raw_schema() -> DefaultSchemaRaw {


Test patch:
diff --git a/tests/test_app.rs b/tests/test_app.rs
index cf25d494..b243bdc2 100644
--- a/tests/test_app.rs
+++ b/tests/test_app.rs
@@ -39,6 +39,7 @@ struct Pet {
     name: String,
     class: PetClass,
     id: Option<u64>,
+    birthday: chrono_dev::NaiveDate,
     updated_on: Option<chrono_dev::NaiveDateTime>,
     #[serde(rename = "uuid")]
     uid: Option<uuid_dev::Uuid>,
@@ -49,6 +50,7 @@ impl Default for Pet {
         Self {
             name: "".to_string(),
             class: PetClass::EverythingElse,
+            birthday: chrono_dev::NaiveDate::from_ymd(2012, 3, 10),
             id: None,
             updated_on: None,
             uid: None,
@@ -138,6 +140,10 @@ fn test_simple_app() {
                           "description": "Pick a good one.",
                           "type": "string"
                         },
+                        "birthday": {
+                          "format": "date",
+                          "type": "string"
+                        },
                         "updatedOn": {
                           "format": "date-time",
                           "type": "string"
@@ -147,7 +153,7 @@ fn test_simple_app() {
                           "type": "string"
                         }
                       },
-                      "required":["class", "name"]
+                      "required":["birthday", "class", "name"]
                     }
                   },
                   "paths": {
@@ -443,10 +449,8 @@ fn test_params() {
                             "properties": {
                                 "json": {
                                     "description": "JSON value",
-                                    "type": "object"
                                 },
                                 "yaml": {
-                                    "type": "object"
                                 }
                             }
                         },
@@ -454,7 +458,6 @@ fn test_params() {
                             "properties": {
                                 "json": {
                                     "description": "JSON value",
-                                    "type": "object"
                                 }
                             }
                         }
@@ -1038,6 +1041,10 @@ fn test_list_in_out() {
                           "description": "Pick a good one.",
                           "type": "string"
                         },
+                        "birthday": {
+                          "format": "date",
+                          "type": "string"
+                        },
                         "updatedOn": {
                           "format": "date-time",
                           "type": "string"
@@ -1047,7 +1054,7 @@ fn test_list_in_out() {
                           "type": "string"
                         }
                       },
-                      "required":["class", "name"]
+                      "required":["birthday", "class", "name"]
                     }
                   },
                   "paths": {
@@ -1158,6 +1165,10 @@ fn test_impl_traits() {
                           "description": "Pick a good one.",
                           "type": "string"
                         },
+                        "birthday": {
+                          "format": "date",
+                          "type": "string"
+                        },
                         "updatedOn": {
                           "format": "date-time",
                           "type": "string"
@@ -1167,7 +1178,7 @@ fn test_impl_traits() {
                           "type": "string"
                         }
                       },
-                      "required":["class", "name"]
+                      "required":["birthday", "class", "name"]
                     }
                   },
                   "paths": {
@@ -1273,6 +1284,10 @@ fn test_operation_with_generics() {
                                  "description":"Pick a good one.",
                                  "type":"string"
                               },
+                              "birthday": {
+                                "format": "date",
+                                "type": "string"
+                              },
                               "updatedOn":{
                                  "format":"date-time",
                                  "type":"string"
@@ -1283,6 +1298,7 @@ fn test_operation_with_generics() {
                               }
                            },
                            "required":[
+                             "birthday",
                               "class",
                               "name"
                            ]
@@ -1430,6 +1446,10 @@ fn test_operations_documentation() {
                           "description": "Pick a good one.",
                           "type": "string"
                         },
+                        "birthday": {
+                          "format": "date",
+                          "type": "string"
+                        },
                         "updatedOn": {
                           "format": "date-time",
                           "type": "string"
@@ -1439,7 +1459,7 @@ fn test_operations_documentation() {
                           "type": "string"
                         }
                       },
-                      "required":["class", "name"]
+                      "required":["birthday", "class", "name"]
                     }
                   },
                   "paths": {
@@ -1565,6 +1585,10 @@ fn test_operations_macro_attributes() {
                                     "description": "Pick a good one.",
                                     "type": "string"
                                 },
+                                "birthday": {
+                                  "format": "date",
+                                  "type": "string"
+                                },
                                 "updatedOn": {
                                     "format": "date-time",
                                     "type": "string"
@@ -1575,6 +1599,7 @@ fn test_operations_macro_attributes() {
                                 }
                             },
                             "required":[
+                                "birthday",
                                 "class",
                                 "name"
                             ]
@@ -1871,6 +1896,10 @@ fn test_errors_app() {
                           "description": "Pick a good one.",
                           "type": "string"
                         },
+                        "birthday": {
+                          "format": "date",
+                          "type": "string"
+                        },
                         "updatedOn": {
                           "format": "date-time",
                           "type": "string"
@@ -1880,7 +1909,7 @@ fn test_errors_app() {
                           "type": "string"
                         }
                       },
-                      "required":["class", "name"]
+                      "required":["birthday", "class", "name"]
                     }
                   },
                   "paths": {
@@ -2028,6 +2057,10 @@ fn test_security_app() {
                           "description": "Pick a good one.",
                           "type": "string"
                         },
+                        "birthday": {
+                          "format": "date",
+                          "type": "string"
+                        },
                         "updatedOn": {
                           "format": "date-time",
                           "type": "string"
@@ -2037,7 +2070,7 @@ fn test_security_app() {
                           "type": "string"
                         }
                       },
-                      "required":["class", "name"]
+                      "required":["birthday", "class", "name"]
                     }
                   },
                   "paths": {