Skip to content

Commit 7fe410a

Browse files
authored
fix untagged enum automatic ordering (#991)
1 parent 1de2bbe commit 7fe410a

File tree

3 files changed

+100
-17
lines changed

3 files changed

+100
-17
lines changed

typify-impl/src/convert.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::type_entry::{
77
EnumTagType, TypeEntry, TypeEntryDetails, TypeEntryEnum, TypeEntryNewtype, TypeEntryStruct,
88
Variant, VariantDetails,
99
};
10-
use crate::util::{all_mutually_exclusive, ref_key, StringValidator};
10+
use crate::util::{all_mutually_exclusive, ref_key, ReorderedInstanceType, StringValidator};
1111
use log::{debug, info};
1212
use schemars::schema::{
1313
ArrayValidation, InstanceType, Metadata, ObjectValidation, Schema, SchemaObject, SingleOrVec,
@@ -672,7 +672,14 @@ impl TypeSpace {
672672
} => {
673673
// Eliminate duplicates (they hold no significance); they
674674
// aren't supposed to be there, but we can still handle it.
675-
let unique_types = instance_types.iter().collect::<BTreeSet<_>>();
675+
// Convert the types into a form that puts integers before
676+
// numbers to ensure that integer get matched before numbers in
677+
// untagged enum generation.
678+
let unique_types = instance_types
679+
.iter()
680+
.copied()
681+
.map(ReorderedInstanceType::from)
682+
.collect::<BTreeSet<_>>();
676683

677684
// Massage the data into labeled subschemas with the following
678685
// format:
@@ -695,8 +702,9 @@ impl TypeSpace {
695702
// but why do tomorrow what we could easily to today?
696703
let subschemas = unique_types
697704
.into_iter()
705+
.map(InstanceType::from)
698706
.map(|it| {
699-
let instance_type = Some(SingleOrVec::Single(Box::new(*it)));
707+
let instance_type = Some(SingleOrVec::Single(Box::new(it)));
700708
let (label, inner_schema) = match it {
701709
InstanceType::Null => (
702710
"null",

typify-impl/src/util.rs

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -933,6 +933,63 @@ impl StringValidator {
933933
}
934934
}
935935

936+
/// A re-ordering of JSON schema instance types that puts integer values before
937+
/// number values.
938+
///
939+
/// This is used for untagged enum generation to ensure that integer values
940+
/// are matched before number values.
941+
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
942+
pub(crate) enum ReorderedInstanceType {
943+
/// The JSON schema instance type `null`.
944+
Null,
945+
946+
/// The JSON schema instance type `boolean`.
947+
Boolean,
948+
949+
/// The JSON schema instance type `integer`.
950+
Integer,
951+
952+
/// The JSON schema instance type `number`.
953+
Number,
954+
955+
/// The JSON schema instance type `string`.
956+
String,
957+
958+
/// The JSON schema instance type `array`.
959+
Array,
960+
961+
/// The JSON schema instance type `object`.
962+
Object,
963+
}
964+
965+
impl From<InstanceType> for ReorderedInstanceType {
966+
fn from(instance_type: InstanceType) -> Self {
967+
match instance_type {
968+
InstanceType::Null => Self::Null,
969+
InstanceType::Boolean => Self::Boolean,
970+
InstanceType::Object => Self::Object,
971+
InstanceType::Array => Self::Array,
972+
InstanceType::Integer => Self::Integer,
973+
InstanceType::Number => Self::Number,
974+
InstanceType::String => Self::String,
975+
}
976+
}
977+
}
978+
979+
impl From<ReorderedInstanceType> for InstanceType {
980+
fn from(instance_type: ReorderedInstanceType) -> Self {
981+
match instance_type {
982+
ReorderedInstanceType::Null => Self::Null,
983+
ReorderedInstanceType::Boolean => Self::Boolean,
984+
ReorderedInstanceType::Object => Self::Object,
985+
ReorderedInstanceType::Array => Self::Array,
986+
ReorderedInstanceType::Integer => Self::Integer,
987+
ReorderedInstanceType::Number => Self::Number,
988+
ReorderedInstanceType::String => Self::String,
989+
}
990+
}
991+
}
992+
936993
#[cfg(test)]
937994
mod tests {
938995
use std::collections::BTreeMap;
@@ -944,7 +1001,7 @@ mod tests {
9441001
};
9451002

9461003
use crate::{
947-
util::{decode_segment, sanitize, schemas_mutually_exclusive, Case},
1004+
util::{decode_segment, sanitize, schemas_mutually_exclusive, Case, ReorderedInstanceType},
9481005
Name,
9491006
};
9501007

@@ -1121,4 +1178,22 @@ mod tests {
11211178
assert!(ach.is_valid("Meshach"));
11221179
assert!(!ach.is_valid("Abednego"));
11231180
}
1181+
1182+
#[test]
1183+
fn test_instance_type_ordering() {
1184+
let null = ReorderedInstanceType::Null;
1185+
let boolean = ReorderedInstanceType::Boolean;
1186+
let integer = ReorderedInstanceType::Integer;
1187+
let number = ReorderedInstanceType::Number;
1188+
let string = ReorderedInstanceType::String;
1189+
let array = ReorderedInstanceType::Array;
1190+
let object = ReorderedInstanceType::Object;
1191+
1192+
assert!(null < boolean);
1193+
assert!(boolean < integer);
1194+
assert!(integer < number);
1195+
assert!(number < string);
1196+
assert!(string < array);
1197+
assert!(array < object);
1198+
}
11241199
}

typify/tests/schemas/multiple-instance-types.rs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,14 @@ pub mod error {
4141
#[derive(:: serde :: Deserialize, :: serde :: Serialize, Clone, Debug)]
4242
#[serde(untagged)]
4343
pub enum IntOrStr {
44-
String(::std::string::String),
4544
Integer(i64),
45+
String(::std::string::String),
4646
}
4747
impl ::std::fmt::Display for IntOrStr {
4848
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
4949
match self {
50-
Self::String(x) => x.fmt(f),
5150
Self::Integer(x) => x.fmt(f),
51+
Self::String(x) => x.fmt(f),
5252
}
5353
}
5454
}
@@ -79,31 +79,31 @@ impl ::std::convert::From<i64> for IntOrStr {
7979
pub enum OneOfSeveral {
8080
Null,
8181
Boolean(bool),
82-
Object(::serde_json::Map<::std::string::String, ::serde_json::Value>),
83-
Array(::std::vec::Vec<::serde_json::Value>),
84-
String(::std::string::String),
8582
Integer(i64),
83+
String(::std::string::String),
84+
Array(::std::vec::Vec<::serde_json::Value>),
85+
Object(::serde_json::Map<::std::string::String, ::serde_json::Value>),
8686
}
8787
impl ::std::convert::From<bool> for OneOfSeveral {
8888
fn from(value: bool) -> Self {
8989
Self::Boolean(value)
9090
}
9191
}
92-
impl ::std::convert::From<::serde_json::Map<::std::string::String, ::serde_json::Value>>
93-
for OneOfSeveral
94-
{
95-
fn from(value: ::serde_json::Map<::std::string::String, ::serde_json::Value>) -> Self {
96-
Self::Object(value)
92+
impl ::std::convert::From<i64> for OneOfSeveral {
93+
fn from(value: i64) -> Self {
94+
Self::Integer(value)
9795
}
9896
}
9997
impl ::std::convert::From<::std::vec::Vec<::serde_json::Value>> for OneOfSeveral {
10098
fn from(value: ::std::vec::Vec<::serde_json::Value>) -> Self {
10199
Self::Array(value)
102100
}
103101
}
104-
impl ::std::convert::From<i64> for OneOfSeveral {
105-
fn from(value: i64) -> Self {
106-
Self::Integer(value)
102+
impl ::std::convert::From<::serde_json::Map<::std::string::String, ::serde_json::Value>>
103+
for OneOfSeveral
104+
{
105+
fn from(value: ::serde_json::Map<::std::string::String, ::serde_json::Value>) -> Self {
106+
Self::Object(value)
107107
}
108108
}
109109
#[doc = "`ReallyJustNull`"]

0 commit comments

Comments
 (0)