Repository: oxidecomputer/typify (rust)

Issue:
Merge oneOf with many compatible enums of a single value
https://github.com/OAI/OpenAPI-Specification/issues/348#issuecomment-1484163189 is apparently the best way to describe enum values, like

```json
    "enum-value-descriptions": {
      "type": "object",
      "description": "https://github.com/OAI/OpenAPI-Specification/issues/348#issuecomment-1484163189",
      "oneOf": [
        {
          "enum": ["A"],
          "description": "An A"
        },
        {
          "enum": ["B"],
          "description": "A B"
        }
      ]
    }
```

Typify generates a bunch of variants for the above, when they could be collapsed into a single Rust enum. 


Pull request description:
be more permissive of simple enum variants
Fixes #497.

Patch:
diff --git a/typify-impl/src/enums.rs b/typify-impl/src/enums.rs
index ff4c682..cc35a0e 100644
--- a/typify-impl/src/enums.rs
+++ b/typify-impl/src/enums.rs
@@ -89,10 +89,10 @@ impl TypeSpace {
                     // variant that we'd never use... I guess.
                     Schema::Bool(false) => todo!(),
 
-                    // Strings must be simple enumerations.
+                    // Strings must be simple enumerations or constants.
                     Schema::Object(SchemaObject {
                         metadata,
-                        instance_type: Some(SingleOrVec::Single(single)),
+                        instance_type,
                         format: None,
                         enum_values: Some(values),
                         const_value: None,
@@ -103,7 +103,13 @@ impl TypeSpace {
                         object: _,
                         reference: None,
                         extensions: _,
-                    }) if single.as_ref() == &InstanceType::String => {
+                    }) => {
+                        match instance_type {
+                            Some(SingleOrVec::Single(single))
+                                if single.as_ref() == &InstanceType::String => {}
+                            None => {}
+                            _ => return None,
+                        };
                         // Confirm that all values are, in fact, simple strings.
                         // Simple strings become simple variants. If any is not
                         // a string, we'll end up returning None
@@ -117,6 +123,33 @@ impl TypeSpace {
                             })
                             .collect()
                     }
+                    Schema::Object(SchemaObject {
+                        metadata,
+                        instance_type,
+                        format: None,
+                        enum_values: None,
+                        const_value: Some(value),
+                        subschemas: None,
+                        number: _,
+                        string: _,
+                        array: _,
+                        object: _,
+                        reference: None,
+                        extensions: _,
+                    }) => {
+                        match instance_type {
+                            Some(SingleOrVec::Single(single))
+                                if single.as_ref() == &InstanceType::String => {}
+                            None => {}
+                            _ => return None,
+                        };
+                        std::iter::once(value.as_str().map(|variant_name| ProtoVariant::Simple {
+                            name: variant_name,
+                            description: metadata_description(metadata),
+                        }))
+                        .collect()
+                    }
+
                     other => match get_object(other) {
                         // Objects must have a single property, and that
                         // property must be required. The type of that lone


Test patch:
diff --git a/typify/tests/schemas/various-enums.json b/typify/tests/schemas/various-enums.json
index 874b156..772318b 100644
--- a/typify/tests/schemas/various-enums.json
+++ b/typify/tests/schemas/various-enums.json
@@ -258,6 +258,26 @@
           }
         }
       ]
+    },
+    "commented-variants": {
+      "oneOf": [
+        {
+          "enum": [
+            "A"
+          ],
+          "description": "An A"
+        },
+        {
+          "enum": [
+            "B"
+          ],
+          "description": "A B"
+        },
+        {
+          "const": "C",
+          "description": "a pirate's favorite letter"
+        }
+      ]
     }
   }
 }
\ No newline at end of file
diff --git a/typify/tests/schemas/various-enums.rs b/typify/tests/schemas/various-enums.rs
index 4934278..8a22181 100644
--- a/typify/tests/schemas/various-enums.rs
+++ b/typify/tests/schemas/various-enums.rs
@@ -96,6 +96,85 @@ impl Default for AlternativeEnum {
         AlternativeEnum::Choice2
     }
 }
+#[doc = "CommentedVariants"]
+#[doc = r""]
+#[doc = r" <details><summary>JSON schema</summary>"]
+#[doc = r""]
+#[doc = r" ```json"]
+#[doc = "{"]
+#[doc = "  \"oneOf\": ["]
+#[doc = "    {"]
+#[doc = "      \"description\": \"An A\","]
+#[doc = "      \"enum\": ["]
+#[doc = "        \"A\""]
+#[doc = "      ]"]
+#[doc = "    },"]
+#[doc = "    {"]
+#[doc = "      \"description\": \"A B\","]
+#[doc = "      \"enum\": ["]
+#[doc = "        \"B\""]
+#[doc = "      ]"]
+#[doc = "    },"]
+#[doc = "    {"]
+#[doc = "      \"description\": \"a pirate's favorite letter\","]
+#[doc = "      \"const\": \"C\""]
+#[doc = "    }"]
+#[doc = "  ]"]
+#[doc = "}"]
+#[doc = r" ```"]
+#[doc = r" </details>"]
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
+pub enum CommentedVariants {
+    #[doc = "An A"]
+    A,
+    #[doc = "A B"]
+    B,
+    #[doc = "a pirate's favorite letter"]
+    C,
+}
+impl From<&CommentedVariants> for CommentedVariants {
+    fn from(value: &CommentedVariants) -> Self {
+        value.clone()
+    }
+}
+impl ToString for CommentedVariants {
+    fn to_string(&self) -> String {
+        match *self {
+            Self::A => "A".to_string(),
+            Self::B => "B".to_string(),
+            Self::C => "C".to_string(),
+        }
+    }
+}
+impl std::str::FromStr for CommentedVariants {
+    type Err = self::error::ConversionError;
+    fn from_str(value: &str) -> Result<Self, self::error::ConversionError> {
+        match value {
+            "A" => Ok(Self::A),
+            "B" => Ok(Self::B),
+            "C" => Ok(Self::C),
+            _ => Err("invalid value".into()),
+        }
+    }
+}
+impl std::convert::TryFrom<&str> for CommentedVariants {
+    type Error = self::error::ConversionError;
+    fn try_from(value: &str) -> Result<Self, self::error::ConversionError> {
+        value.parse()
+    }
+}
+impl std::convert::TryFrom<&String> for CommentedVariants {
+    type Error = self::error::ConversionError;
+    fn try_from(value: &String) -> Result<Self, self::error::ConversionError> {
+        value.parse()
+    }
+}
+impl std::convert::TryFrom<String> for CommentedVariants {
+    type Error = self::error::ConversionError;
+    fn try_from(value: String) -> Result<Self, self::error::ConversionError> {
+        value.parse()
+    }
+}
 #[doc = "DiskAttachment"]
 #[doc = r""]
 #[doc = r" <details><summary>JSON schema</summary>"]