Repository: GREsau/schemars
Pull request #291 (2024-05-13 10:29:02)

Use `const` instead of single-valued `enum`
Tests need updating - this probably shouldn't be done until test schemas are regenerated
Tests also need adding to cover `openapi3` (including visitors), since the `schema_for_schema` tests were previously removed


Patch:
diff --git a/schemars/src/_private.rs b/schemars/src/_private.rs
index 0ba27ce..2510401 100644
--- a/schemars/src/_private.rs
+++ b/schemars/src/_private.rs
@@ -53,17 +53,16 @@ impl<T: Serialize> MaybeSerializeWrapper<T> {
     }
 }
 
-/// Create a schema for a unit enum
-pub fn new_unit_enum(variant: &str) -> Schema {
-    // TODO switch from single-valued "enum" to "const"
+/// Create a schema for a unit enum variant
+pub fn new_unit_enum_variant(variant: &str) -> Schema {
     json_schema!({
         "type": "string",
-        "enum": [variant],
+        "const": variant,
     })
 }
 
-/// Create a schema for an externally tagged enum
-pub fn new_externally_tagged_enum(variant: &str, sub_schema: Schema) -> Schema {
+/// Create a schema for an externally tagged enum variant
+pub fn new_externally_tagged_enum_variant(variant: &str, sub_schema: Schema) -> Schema {
     json_schema!({
         "type": "object",
         "properties": {
@@ -74,7 +73,8 @@ pub fn new_externally_tagged_enum(variant: &str, sub_schema: Schema) -> Schema {
     })
 }
 
-pub fn apply_internal_enum_tag(
+/// Update a schema for an internally tagged enum variant
+pub fn apply_internal_enum_variant_tag(
     schema: &mut Schema,
     tag_name: &str,
     variant: &str,
@@ -94,8 +94,7 @@ pub fn apply_internal_enum_tag(
             tag_name.to_string(),
             json!({
                 "type": "string",
-                // TODO switch from single-valued "enum" to "const"
-                "enum": [variant]
+                "const": variant
             }),
         );
     }
@@ -113,34 +112,6 @@ pub fn apply_internal_enum_tag(
     }
 }
 
-/// Create a schema for an internally tagged enum
-pub fn new_internally_tagged_enum(
-    tag_name: &str,
-    variant: &str,
-    deny_unknown_fields: bool,
-) -> Schema {
-    // TODO switch from single-valued "enum" to "const"
-    let mut schema = json_schema!({
-        "type": "object",
-        "properties": {
-            tag_name: {
-                "type": "string",
-                "enum": [variant],
-            }
-        },
-        "required": [tag_name],
-    });
-
-    if deny_unknown_fields {
-        schema
-            .as_object_mut()
-            .unwrap()
-            .insert("additionalProperties".into(), false.into());
-    }
-
-    schema
-}
-
 pub fn insert_object_property<T: ?Sized + JsonSchema>(
     schema: &mut Schema,
     key: &str,
diff --git a/schemars/src/gen.rs b/schemars/src/gen.rs
index 8336ecc..c06f85a 100644
--- a/schemars/src/gen.rs
+++ b/schemars/src/gen.rs
@@ -97,6 +97,7 @@ impl SchemaSettings {
                     skip_additional_properties: true,
                 }),
                 Box::new(SetSingleExample),
+                Box::new(ReplaceConstValue),
             ],
             inline_subschemas: false,
         }
diff --git a/schemars/src/visit.rs b/schemars/src/visit.rs
index cfe11df..5fd4273 100644
--- a/schemars/src/visit.rs
+++ b/schemars/src/visit.rs
@@ -169,3 +169,21 @@ impl Visitor for SetSingleExample {
         }
     }
 }
+
+/// This visitor will replace the `const` schema property with a single-valued `enum` property.
+///
+/// This is useful for dialects of JSON Schema (e.g. OpenAPI 3.0) that do not support the `const` property.
+#[derive(Debug, Clone)]
+pub struct ReplaceConstValue;
+
+impl Visitor for ReplaceConstValue {
+    fn visit_schema(&mut self, schema: &mut Schema) {
+        visit_schema(self, schema);
+
+        if let Some(obj) = schema.as_object_mut() {
+            if let Some(value) = obj.remove("const") {
+                obj.insert("enum".into(), Value::Array(vec![value]));
+            }
+        }
+    }
+}
diff --git a/schemars_derive/src/schema_exprs.rs b/schemars_derive/src/schema_exprs.rs
index f22831e..7a1de81 100644
--- a/schemars_derive/src/schema_exprs.rs
+++ b/schemars_derive/src/schema_exprs.rs
@@ -197,12 +197,12 @@ fn expr_for_external_tagged_enum<'a>(
 
         let mut schema_expr = if variant.is_unit() && variant.attrs.with.is_none() {
             quote! {
-                schemars::_private::new_unit_enum(#name)
+                schemars::_private::new_unit_enum_variant(#name)
             }
         } else {
             let sub_schema = expr_for_untagged_enum_variant(variant, deny_unknown_fields);
             quote! {
-                schemars::_private::new_externally_tagged_enum(#name, #sub_schema)
+                schemars::_private::new_externally_tagged_enum_variant(#name, #sub_schema)
             }
         };
 
@@ -236,7 +236,7 @@ fn expr_for_internal_tagged_enum<'a>(
 
             quote!({
                 let mut schema = #schema_expr;
-                schemars::_private::apply_internal_enum_tag(&mut schema, #tag_name, #name, #deny_unknown_fields);
+                schemars::_private::apply_internal_enum_variant_tag(&mut schema, #tag_name, #name, #deny_unknown_fields);
                 schema
             })
         })


Test patch:
diff --git a/schemars/tests/expected/deprecated-enum.json b/schemars/tests/expected/deprecated-enum.json
index 55a98ef..dab90ec 100644
--- a/schemars/tests/expected/deprecated-enum.json
+++ b/schemars/tests/expected/deprecated-enum.json
@@ -11,10 +11,8 @@
     },
     {
       "type": "string",
-      "deprecated": true,
-      "enum": [
-        "DeprecatedUnitVariant"
-      ]
+      "const": "DeprecatedUnitVariant",
+      "deprecated": true
     },
     {
       "type": "object",
diff --git a/schemars/tests/expected/doc_comments_enum.json b/schemars/tests/expected/doc_comments_enum.json
index c983c1d..fc38db1 100644
--- a/schemars/tests/expected/doc_comments_enum.json
+++ b/schemars/tests/expected/doc_comments_enum.json
@@ -13,9 +13,7 @@
     {
       "description": "This comment is included in the generated schema :)",
       "type": "string",
-      "enum": [
-        "DocumentedUnit"
-      ]
+      "const": "DocumentedUnit"
     },
     {
       "title": "Complex variant",
diff --git a/schemars/tests/expected/enum-internal-duf.json b/schemars/tests/expected/enum-internal-duf.json
index 6db4d31..758bf9b 100644
--- a/schemars/tests/expected/enum-internal-duf.json
+++ b/schemars/tests/expected/enum-internal-duf.json
@@ -7,9 +7,7 @@
       "properties": {
         "typeProperty": {
           "type": "string",
-          "enum": [
-            "UnitOne"
-          ]
+          "const": "UnitOne"
         }
       },
       "additionalProperties": false,
@@ -22,9 +20,7 @@
       "properties": {
         "typeProperty": {
           "type": "string",
-          "enum": [
-            "StringMap"
-          ]
+          "const": "StringMap"
         }
       },
       "additionalProperties": {
@@ -39,9 +35,7 @@
       "properties": {
         "typeProperty": {
           "type": "string",
-          "enum": [
-            "UnitStructNewType"
-          ]
+          "const": "UnitStructNewType"
         }
       },
       "additionalProperties": false,
@@ -61,9 +55,7 @@
         },
         "typeProperty": {
           "type": "string",
-          "enum": [
-            "StructNewType"
-          ]
+          "const": "StructNewType"
         }
       },
       "required": [
@@ -84,9 +76,7 @@
         },
         "typeProperty": {
           "type": "string",
-          "enum": [
-            "Struct"
-          ]
+          "const": "Struct"
         }
       },
       "additionalProperties": false,
@@ -101,9 +91,7 @@
       "properties": {
         "typeProperty": {
           "type": "string",
-          "enum": [
-            "UnitTwo"
-          ]
+          "const": "UnitTwo"
         }
       },
       "additionalProperties": false,
@@ -117,9 +105,7 @@
       "properties": {
         "typeProperty": {
           "type": "string",
-          "enum": [
-            "WithInt"
-          ]
+          "const": "WithInt"
         }
       },
       "required": [
diff --git a/schemars/tests/expected/enum-internal.json b/schemars/tests/expected/enum-internal.json
index 2382f2d..765248f 100644
--- a/schemars/tests/expected/enum-internal.json
+++ b/schemars/tests/expected/enum-internal.json
@@ -7,9 +7,7 @@
       "properties": {
         "typeProperty": {
           "type": "string",
-          "enum": [
-            "UnitOne"
-          ]
+          "const": "UnitOne"
         }
       },
       "required": [
@@ -21,9 +19,7 @@
       "properties": {
         "typeProperty": {
           "type": "string",
-          "enum": [
-            "StringMap"
-          ]
+          "const": "StringMap"
         }
       },
       "additionalProperties": {
@@ -38,9 +34,7 @@
       "properties": {
         "typeProperty": {
           "type": "string",
-          "enum": [
-            "UnitStructNewType"
-          ]
+          "const": "UnitStructNewType"
         }
       },
       "required": [
@@ -59,9 +53,7 @@
         },
         "typeProperty": {
           "type": "string",
-          "enum": [
-            "StructNewType"
-          ]
+          "const": "StructNewType"
         }
       },
       "required": [
@@ -82,9 +74,7 @@
         },
         "typeProperty": {
           "type": "string",
-          "enum": [
-            "Struct"
-          ]
+          "const": "Struct"
         }
       },
       "required": [
@@ -98,9 +88,7 @@
       "properties": {
         "typeProperty": {
           "type": "string",
-          "enum": [
-            "UnitTwo"
-          ]
+          "const": "UnitTwo"
         }
       },
       "required": [
@@ -113,9 +101,7 @@
       "properties": {
         "typeProperty": {
           "type": "string",
-          "enum": [
-            "WithInt"
-          ]
+          "const": "WithInt"
         }
       },
       "required": [
diff --git a/schemars/tests/expected/enum-simple-internal-duf.json b/schemars/tests/expected/enum-simple-internal-duf.json
index 9e6e5d4..4fdf04e 100644
--- a/schemars/tests/expected/enum-simple-internal-duf.json
+++ b/schemars/tests/expected/enum-simple-internal-duf.json
@@ -7,9 +7,7 @@
       "properties": {
         "typeProperty": {
           "type": "string",
-          "enum": [
-            "A"
-          ]
+          "const": "A"
         }
       },
       "additionalProperties": false,
@@ -22,9 +20,7 @@
       "properties": {
         "typeProperty": {
           "type": "string",
-          "enum": [
-            "B"
-          ]
+          "const": "B"
         }
       },
       "additionalProperties": false,
@@ -37,9 +33,7 @@
       "properties": {
         "typeProperty": {
           "type": "string",
-          "enum": [
-            "C"
-          ]
+          "const": "C"
         }
       },
       "additionalProperties": false,
diff --git a/schemars/tests/expected/enum-simple-internal.json b/schemars/tests/expected/enum-simple-internal.json
index a549c8c..050089c 100644
--- a/schemars/tests/expected/enum-simple-internal.json
+++ b/schemars/tests/expected/enum-simple-internal.json
@@ -7,9 +7,7 @@
       "properties": {
         "typeProperty": {
           "type": "string",
-          "enum": [
-            "A"
-          ]
+          "const": "A"
         }
       },
       "required": [
@@ -21,9 +19,7 @@
       "properties": {
         "typeProperty": {
           "type": "string",
-          "enum": [
-            "B"
-          ]
+          "const": "B"
         }
       },
       "required": [
@@ -35,9 +31,7 @@
       "properties": {
         "typeProperty": {
           "type": "string",
-          "enum": [
-            "C"
-          ]
+          "const": "C"
         }
       },
       "required": [
diff --git a/schemars/tests/expected/enum-unit-doc.json b/schemars/tests/expected/enum-unit-doc.json
index 11a5c2a..86160e8 100644
--- a/schemars/tests/expected/enum-unit-doc.json
+++ b/schemars/tests/expected/enum-unit-doc.json
@@ -6,23 +6,17 @@
       "title": "A deer",
       "description": "A female deer",
       "type": "string",
-      "enum": [
-        "Do"
-      ]
+      "const": "Do"
     },
     {
       "description": "A drop of golden sun",
       "type": "string",
-      "enum": [
-        "Re"
-      ]
+      "const": "Re"
     },
     {
       "description": "A name I call myself",
       "type": "string",
-      "enum": [
-        "Mi"
-      ]
+      "const": "Mi"
     }
   ]
 }
\ No newline at end of file
diff --git a/schemars/tests/expected/schema_settings-2019_09.json b/schemars/tests/expected/schema_settings-2019_09.json
index 4e18426..e51f397 100644
--- a/schemars/tests/expected/schema_settings-2019_09.json
+++ b/schemars/tests/expected/schema_settings-2019_09.json
@@ -45,9 +45,7 @@
         {
           "description": "This is a documented unit variant",
           "type": "string",
-          "enum": [
-            "DocumentedUnit"
-          ]
+          "const": "DocumentedUnit"
         },
         {
           "type": "object",
diff --git a/schemars/tests/expected/schema_settings.json b/schemars/tests/expected/schema_settings.json
index 8e70c7c..07cdb0e 100644
--- a/schemars/tests/expected/schema_settings.json
+++ b/schemars/tests/expected/schema_settings.json
@@ -45,9 +45,7 @@
         {
           "description": "This is a documented unit variant",
           "type": "string",
-          "enum": [
-            "DocumentedUnit"
-          ]
+          "const": "DocumentedUnit"
         },
         {
           "type": "object",
diff --git a/schemars/tests/expected/schema_with-enum-internal.json b/schemars/tests/expected/schema_with-enum-internal.json
index 8b6fbeb..45bc8e9 100644
--- a/schemars/tests/expected/schema_with-enum-internal.json
+++ b/schemars/tests/expected/schema_with-enum-internal.json
@@ -10,9 +10,7 @@
         },
         "typeProperty": {
           "type": "string",
-          "enum": [
-            "Struct"
-          ]
+          "const": "Struct"
         }
       },
       "required": [
@@ -25,9 +23,7 @@
       "properties": {
         "typeProperty": {
           "type": "string",
-          "enum": [
-            "NewType"
-          ]
+          "const": "NewType"
         }
       },
       "required": [
@@ -39,9 +35,7 @@
       "properties": {
         "typeProperty": {
           "type": "string",
-          "enum": [
-            "Unit"
-          ]
+          "const": "Unit"
         }
       },
       "required": [