Skip to content

Commit ac77c4b

Browse files
committed
use inferred union variant names in more places
1 parent 27d15d9 commit ac77c4b

File tree

5 files changed

+222
-48
lines changed

5 files changed

+222
-48
lines changed

typify-impl/src/enums.rs

Lines changed: 46 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Copyright 2025 Oxide Computer Company
22

3-
use std::collections::{BTreeMap, BTreeSet, HashSet};
3+
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
44

55
use heck::{ToKebabCase, ToPascalCase};
66
use proc_macro2::TokenStream;
@@ -609,33 +609,52 @@ impl TypeSpace {
609609
// account for types such as Uuid whose names come from outside of
610610
// the schema... but you can't win them all.
611611
.map(schema_is_named)
612-
.collect::<Option<Vec<_>>>()
613-
// Prune the common prefixes from all variant names. If this
614-
// results in any of them being empty, we don't use these names.
615-
.and_then(|variant_names| {
616-
let common_prefix = variant_names
617-
.iter()
618-
.cloned()
619-
.reduce(|a, b| get_common_prefix(&a, &b))
620-
.unwrap();
621-
variant_names
622-
.into_iter()
623-
.map(|var_name| {
624-
let var_name = &var_name[common_prefix.len()..];
625-
if var_name.is_empty() {
626-
None
627-
} else {
628-
Some(var_name.to_string())
629-
}
630-
})
631-
.collect::<Option<Vec<_>>>()
612+
.collect::<Vec<_>>();
613+
614+
let common_prefix = variant_names
615+
.iter()
616+
.cloned()
617+
.filter_map(|v| v)
618+
.reduce(|a, b| get_common_prefix(&a, &b))
619+
.unwrap_or_else(String::new);
620+
621+
// Store seen, un-stripped names here. Any item for which their
622+
// name appears more than once, ALL the items with that clashing
623+
// name will be converted to the `VariantN` scheme.
624+
let duplicate_names = variant_names
625+
.iter()
626+
.cloned()
627+
.filter_map(|v| v)
628+
.fold(HashMap::new(), |mut counter: HashMap<_, u8>, name| {
629+
*counter.entry(name).or_default() += 1;
630+
counter
632631
})
633-
// Fall back to `VariantN` naming.
634-
.unwrap_or_else(|| {
635-
(0..subschemas.len())
636-
.map(|idx| format!("Variant{}", idx))
637-
.collect()
638-
});
632+
.into_iter()
633+
.filter_map(|(name, count)| if count > 1 { Some(name) } else { None })
634+
.collect::<HashSet<_>>();
635+
636+
// Prune the common prefixes from all variant names. If this
637+
// results in any of them being empty, we don't use these names.
638+
//
639+
// Fall back to VariantN if the variant name cannot be derived from
640+
// the schema.
641+
let variant_names = variant_names
642+
.into_iter()
643+
.enumerate()
644+
.map(|(idx, var_name)| {
645+
match var_name {
646+
None => None,
647+
var_name => {
648+
var_name.filter(|var_name| !duplicate_names.contains(var_name.as_str()))
649+
}
650+
}
651+
.map(|var_name| var_name[common_prefix.len()..].to_string())
652+
.filter(|var_name| !var_name.is_empty())
653+
.map(|var_name| var_name.to_string())
654+
// Fall back to `VariantN` naming.
655+
.unwrap_or_else(|| format!("Variant{}", idx))
656+
})
657+
.collect::<Vec<_>>();
639658

640659
// Gather the variant details along with its name.
641660
let variant_details = subschemas

typify-impl/src/util.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,20 @@ pub(crate) fn schema_is_named(schema: &Schema) -> Option<String> {
622622
extensions: _,
623623
}) => singleton_subschema(subschemas).and_then(schema_is_named),
624624

625+
// Best-effort fallback for things with raw types that can be easily inferred
626+
Schema::Object(SchemaObject {
627+
instance_type: Some(SingleOrVec::Single(single)),
628+
..
629+
}) => match **single {
630+
InstanceType::Boolean => Some("Bool".to_string()),
631+
InstanceType::Integer => Some("Integer".to_string()),
632+
InstanceType::Number => Some("Number".to_string()),
633+
InstanceType::String => Some("String".to_string()),
634+
InstanceType::Array => Some("Array".to_string()),
635+
InstanceType::Object => Some("Object".to_string()),
636+
InstanceType::Null => Some("Null".to_string()),
637+
},
638+
625639
_ => None,
626640
}?;
627641

typify/tests/schemas/rust-collisions.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ impl ::std::convert::TryFrom<::std::string::String> for FormatCollision {
383383
#[derive(:: serde :: Deserialize, :: serde :: Serialize, Clone, Debug)]
384384
#[serde(untagged)]
385385
pub enum KeywordFieldsEnum {
386-
Variant0 {
386+
Object {
387387
#[serde(rename = "impl")]
388388
impl_: ::std::string::String,
389389
#[serde(rename = "match")]
@@ -393,7 +393,7 @@ pub enum KeywordFieldsEnum {
393393
#[serde(rename = "type")]
394394
type_: ::std::string::String,
395395
},
396-
Variant1([::std::string::String; 2usize]),
396+
Array([::std::string::String; 2usize]),
397397
}
398398
impl ::std::convert::From<&Self> for KeywordFieldsEnum {
399399
fn from(value: &KeywordFieldsEnum) -> Self {
@@ -402,7 +402,7 @@ impl ::std::convert::From<&Self> for KeywordFieldsEnum {
402402
}
403403
impl ::std::convert::From<[::std::string::String; 2usize]> for KeywordFieldsEnum {
404404
fn from(value: [::std::string::String; 2usize]) -> Self {
405-
Self::Variant1(value)
405+
Self::Array(value)
406406
}
407407
}
408408
#[doc = "`MapOfKeywords`"]

typify/tests/schemas/various-enums.json

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,47 @@
2626
}
2727
]
2828
},
29+
"one-of-raw-type": {
30+
"oneOf": [
31+
{
32+
"type": "string"
33+
},
34+
{
35+
"type": "integer"
36+
}
37+
]
38+
},
39+
"one-of-missing-title": {
40+
"type": "object",
41+
"oneOf": [
42+
{
43+
"title": "A",
44+
"properties": {
45+
"foo": {
46+
"type": "string"
47+
}
48+
}
49+
},
50+
{
51+
"title": "B",
52+
"properties": {
53+
"bar": {
54+
"type": "integer"
55+
}
56+
}
57+
},
58+
{
59+
"properties": {
60+
"bar": {
61+
"type": "integer"
62+
},
63+
"baz": {
64+
"type": "integer"
65+
}
66+
}
67+
}
68+
]
69+
},
2970
"IpNet": {
3071
"$comment": "we want to see *nice* variant names in the output",
3172
"oneOf": [

typify/tests/schemas/various-enums.rs

Lines changed: 118 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -863,7 +863,7 @@ impl<'de> ::serde::Deserialize<'de> for Ipv6Net {
863863
pub enum JankNames {
864864
Variant0(::std::string::String),
865865
Variant1(::std::collections::HashMap<::std::string::String, ::std::string::String>),
866-
Variant2(::std::collections::HashMap<::std::string::String, i64>),
866+
Object(::std::collections::HashMap<::std::string::String, i64>),
867867
}
868868
impl ::std::convert::From<&Self> for JankNames {
869869
fn from(value: &JankNames) -> Self {
@@ -881,7 +881,7 @@ impl ::std::convert::From<::std::collections::HashMap<::std::string::String, ::s
881881
}
882882
impl ::std::convert::From<::std::collections::HashMap<::std::string::String, i64>> for JankNames {
883883
fn from(value: ::std::collections::HashMap<::std::string::String, i64>) -> Self {
884-
Self::Variant2(value)
884+
Self::Object(value)
885885
}
886886
}
887887
#[doc = "`Never`"]
@@ -1097,6 +1097,108 @@ impl ::std::convert::TryFrom<::std::string::String> for NullStringEnumWithUnknow
10971097
value.parse()
10981098
}
10991099
}
1100+
#[doc = "`OneOfMissingTitle`"]
1101+
#[doc = r""]
1102+
#[doc = r" <details><summary>JSON schema</summary>"]
1103+
#[doc = r""]
1104+
#[doc = r" ```json"]
1105+
#[doc = "{"]
1106+
#[doc = " \"type\": \"object\","]
1107+
#[doc = " \"oneOf\": ["]
1108+
#[doc = " {"]
1109+
#[doc = " \"title\": \"A\","]
1110+
#[doc = " \"properties\": {"]
1111+
#[doc = " \"foo\": {"]
1112+
#[doc = " \"type\": \"string\""]
1113+
#[doc = " }"]
1114+
#[doc = " }"]
1115+
#[doc = " },"]
1116+
#[doc = " {"]
1117+
#[doc = " \"title\": \"B\","]
1118+
#[doc = " \"properties\": {"]
1119+
#[doc = " \"bar\": {"]
1120+
#[doc = " \"type\": \"integer\""]
1121+
#[doc = " }"]
1122+
#[doc = " }"]
1123+
#[doc = " },"]
1124+
#[doc = " {"]
1125+
#[doc = " \"properties\": {"]
1126+
#[doc = " \"bar\": {"]
1127+
#[doc = " \"type\": \"integer\""]
1128+
#[doc = " },"]
1129+
#[doc = " \"baz\": {"]
1130+
#[doc = " \"type\": \"integer\""]
1131+
#[doc = " }"]
1132+
#[doc = " }"]
1133+
#[doc = " }"]
1134+
#[doc = " ]"]
1135+
#[doc = "}"]
1136+
#[doc = r" ```"]
1137+
#[doc = r" </details>"]
1138+
#[derive(:: serde :: Deserialize, :: serde :: Serialize, Clone, Debug)]
1139+
#[serde(untagged)]
1140+
pub enum OneOfMissingTitle {
1141+
A {
1142+
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
1143+
foo: ::std::option::Option<::std::string::String>,
1144+
},
1145+
B {
1146+
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
1147+
bar: ::std::option::Option<i64>,
1148+
},
1149+
Variant2 {
1150+
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
1151+
bar: ::std::option::Option<i64>,
1152+
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
1153+
baz: ::std::option::Option<i64>,
1154+
},
1155+
}
1156+
impl ::std::convert::From<&Self> for OneOfMissingTitle {
1157+
fn from(value: &OneOfMissingTitle) -> Self {
1158+
value.clone()
1159+
}
1160+
}
1161+
#[doc = "`OneOfRawType`"]
1162+
#[doc = r""]
1163+
#[doc = r" <details><summary>JSON schema</summary>"]
1164+
#[doc = r""]
1165+
#[doc = r" ```json"]
1166+
#[doc = "{"]
1167+
#[doc = " \"oneOf\": ["]
1168+
#[doc = " {"]
1169+
#[doc = " \"type\": \"string\""]
1170+
#[doc = " },"]
1171+
#[doc = " {"]
1172+
#[doc = " \"type\": \"integer\""]
1173+
#[doc = " }"]
1174+
#[doc = " ]"]
1175+
#[doc = "}"]
1176+
#[doc = r" ```"]
1177+
#[doc = r" </details>"]
1178+
#[derive(:: serde :: Deserialize, :: serde :: Serialize, Clone, Debug)]
1179+
#[serde(untagged)]
1180+
pub enum OneOfRawType {
1181+
String(::std::string::String),
1182+
Integer(i64),
1183+
}
1184+
impl ::std::convert::From<&Self> for OneOfRawType {
1185+
fn from(value: &OneOfRawType) -> Self {
1186+
value.clone()
1187+
}
1188+
}
1189+
impl ::std::fmt::Display for OneOfRawType {
1190+
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
1191+
match self {
1192+
Self::String(x) => x.fmt(f),
1193+
Self::Integer(x) => x.fmt(f),
1194+
}
1195+
}
1196+
}
1197+
impl ::std::convert::From<i64> for OneOfRawType {
1198+
fn from(value: i64) -> Self {
1199+
Self::Integer(value)
1200+
}
1201+
}
11001202
#[doc = "`OneOfTypes`"]
11011203
#[doc = r""]
11021204
#[doc = r" <details><summary>JSON schema</summary>"]
@@ -1486,8 +1588,8 @@ impl ::std::fmt::Display for ReferenceDef {
14861588
#[derive(:: serde :: Deserialize, :: serde :: Serialize, Clone, Debug)]
14871589
#[serde(untagged)]
14881590
pub enum References {
1489-
Variant0(::std::vec::Vec<::std::string::String>),
1490-
Variant1(::std::collections::HashMap<::std::string::String, ReferencesVariant1Value>),
1591+
Array(::std::vec::Vec<::std::string::String>),
1592+
Object(::std::collections::HashMap<::std::string::String, ReferencesObjectValue>),
14911593
}
14921594
impl ::std::convert::From<&Self> for References {
14931595
fn from(value: &References) -> Self {
@@ -1496,21 +1598,19 @@ impl ::std::convert::From<&Self> for References {
14961598
}
14971599
impl ::std::convert::From<::std::vec::Vec<::std::string::String>> for References {
14981600
fn from(value: ::std::vec::Vec<::std::string::String>) -> Self {
1499-
Self::Variant0(value)
1601+
Self::Array(value)
15001602
}
15011603
}
1502-
impl
1503-
::std::convert::From<
1504-
::std::collections::HashMap<::std::string::String, ReferencesVariant1Value>,
1505-
> for References
1604+
impl ::std::convert::From<::std::collections::HashMap<::std::string::String, ReferencesObjectValue>>
1605+
for References
15061606
{
15071607
fn from(
1508-
value: ::std::collections::HashMap<::std::string::String, ReferencesVariant1Value>,
1608+
value: ::std::collections::HashMap<::std::string::String, ReferencesObjectValue>,
15091609
) -> Self {
1510-
Self::Variant1(value)
1610+
Self::Object(value)
15111611
}
15121612
}
1513-
#[doc = "`ReferencesVariant1Value`"]
1613+
#[doc = "`ReferencesObjectValue`"]
15141614
#[doc = r""]
15151615
#[doc = r" <details><summary>JSON schema</summary>"]
15161616
#[doc = r""]
@@ -1529,29 +1629,29 @@ impl
15291629
#[doc = r" </details>"]
15301630
#[derive(:: serde :: Deserialize, :: serde :: Serialize, Clone, Debug)]
15311631
#[serde(untagged)]
1532-
pub enum ReferencesVariant1Value {
1632+
pub enum ReferencesObjectValue {
15331633
StringVersion(StringVersion),
15341634
ReferenceDef(ReferenceDef),
15351635
}
1536-
impl ::std::convert::From<&Self> for ReferencesVariant1Value {
1537-
fn from(value: &ReferencesVariant1Value) -> Self {
1636+
impl ::std::convert::From<&Self> for ReferencesObjectValue {
1637+
fn from(value: &ReferencesObjectValue) -> Self {
15381638
value.clone()
15391639
}
15401640
}
1541-
impl ::std::fmt::Display for ReferencesVariant1Value {
1641+
impl ::std::fmt::Display for ReferencesObjectValue {
15421642
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
15431643
match self {
15441644
Self::StringVersion(x) => x.fmt(f),
15451645
Self::ReferenceDef(x) => x.fmt(f),
15461646
}
15471647
}
15481648
}
1549-
impl ::std::convert::From<StringVersion> for ReferencesVariant1Value {
1649+
impl ::std::convert::From<StringVersion> for ReferencesObjectValue {
15501650
fn from(value: StringVersion) -> Self {
15511651
Self::StringVersion(value)
15521652
}
15531653
}
1554-
impl ::std::convert::From<ReferenceDef> for ReferencesVariant1Value {
1654+
impl ::std::convert::From<ReferenceDef> for ReferencesObjectValue {
15551655
fn from(value: ReferenceDef) -> Self {
15561656
Self::ReferenceDef(value)
15571657
}

0 commit comments

Comments
 (0)