use std::collections::BTreeMap;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use crate::{
convert::STD_NUM_NONZERO_PREFIX,
type_entry::{
DefaultKind, EnumTagType, StructProperty, StructPropertyRename, StructPropertyState,
TypeEntry, TypeEntryDetails, TypeEntryEnum, TypeEntryNewtype, TypeEntryStruct, Variant,
VariantDetails, WrappedValue,
},
util::{sanitize, Case},
DefaultImpl, Error, Result, TypeId, TypeSpace,
};
impl From<&DefaultImpl> for TokenStream {
fn from(default: &DefaultImpl) -> Self {
match default {
DefaultImpl::Boolean => quote! {
pub(super) fn default_bool<const V: bool>() -> bool {
V
}
},
DefaultImpl::I64 => quote! {
pub(super) fn default_i64<T, const V: i64>() -> T
where
T: ::std::convert::TryFrom<i64>,
<T as ::std::convert::TryFrom<i64>>::Error: ::std::fmt::Debug,
{
T::try_from(V).unwrap()
}
},
DefaultImpl::U64 => quote! {
pub(super) fn default_u64<T, const V: u64>() -> T
where
T: ::std::convert::TryFrom<u64>,
<T as ::std::convert::TryFrom<u64>>::Error: ::std::fmt::Debug,
{
T::try_from(V).unwrap()
}
},
DefaultImpl::NZU64 => quote! {
pub(super) fn default_nzu64<T, const V: u64>() -> T
where
T: ::std::convert::TryFrom<::std::num::NonZeroU64>,
<T as ::std::convert::TryFrom<::std::num::NonZeroU64>>::Error:
::std::fmt::Debug,
{
T::try_from(::std::num::NonZeroU64::try_from(V).unwrap())
.unwrap()
}
},
}
}
}
impl TypeEntry {
pub(crate) fn check_defaults(&self, type_space: &mut TypeSpace) -> Result<()> {
match &self.details {
TypeEntryDetails::Enum(TypeEntryEnum {
default: Some(WrappedValue(default)),
..
})
| TypeEntryDetails::Struct(TypeEntryStruct {
default: Some(WrappedValue(default)),
..
})
| TypeEntryDetails::Newtype(TypeEntryNewtype {
default: Some(WrappedValue(default)),
..
}) => {
if let DefaultKind::Generic(default_fn) =
self.validate_value(type_space, default)?
{
type_space.defaults.insert(default_fn);
}
}
_ => (),
}
match &self.details {
TypeEntryDetails::Struct(TypeEntryStruct { properties, .. }) => {
properties
.iter()
.try_for_each(|prop| Self::check_property_defaults(prop, type_space))?;
}
TypeEntryDetails::Enum(TypeEntryEnum { variants, .. }) => {
variants.iter().try_for_each(|variant| {
if let VariantDetails::Struct(properties) = &variant.details {
properties
.iter()
.try_for_each(|prop| Self::check_property_defaults(prop, type_space))
} else {
Ok(())
}
})?;
}
_ => (),
};
Ok(())
}
fn check_property_defaults(
property: &StructProperty,
type_space: &mut TypeSpace,
) -> Result<()> {
if let StructProperty {
state: StructPropertyState::Default(WrappedValue(prop_default)),
type_id,
..
} = property
{
let type_entry = type_space.id_to_entry.get(type_id).unwrap();
if let DefaultKind::Generic(default_fn) =
type_entry.validate_value(type_space, prop_default)?
{
type_space.defaults.insert(default_fn);
}
}
Ok(())
}
pub(crate) fn validate_value(
&self,
type_space: &TypeSpace,
default: &serde_json::Value,
) -> Result<DefaultKind> {
match &self.details {
TypeEntryDetails::Enum(TypeEntryEnum {
tag_type, variants, ..
}) => match tag_type {
EnumTagType::External => {
validate_default_for_external_enum(type_space, variants, default)
.ok_or_else(Error::invalid_value)
}
EnumTagType::Internal { tag } => {
validate_default_for_internal_enum(type_space, variants, default, tag)
.ok_or_else(Error::invalid_value)
}
EnumTagType::Adjacent { tag, content } => {
validate_default_for_adjacent_enum(type_space, variants, default, tag, content)
.ok_or_else(Error::invalid_value)
}
EnumTagType::Untagged => {
validate_default_for_untagged_enum(type_space, variants, default)
.ok_or_else(Error::invalid_value)
}
},
TypeEntryDetails::Struct(TypeEntryStruct { properties, .. }) => {
validate_default_struct_props(properties, type_space, default)
.ok_or_else(Error::invalid_value)
}
TypeEntryDetails::Newtype(TypeEntryNewtype { type_id, .. }) => {
validate_type_id(type_id, type_space, default)
}
TypeEntryDetails::Option(type_id) => {
if let serde_json::Value::Null = default {
Ok(DefaultKind::Intrinsic)
} else {
let _ = validate_type_id(type_id, type_space, default)?;
Ok(DefaultKind::Specific)
}
}
TypeEntryDetails::Box(type_id) => validate_type_id(type_id, type_space, default),
TypeEntryDetails::Vec(type_id) => {
if let serde_json::Value::Array(v) = default {
if v.is_empty() {
Ok(DefaultKind::Intrinsic)
} else {
let type_entry = type_space.id_to_entry.get(type_id).unwrap();
for value in v {
let _ = type_entry.validate_value(type_space, value)?;
}
Ok(DefaultKind::Specific)
}
} else {
Err(Error::invalid_value())
}
}
TypeEntryDetails::Map(key_id, value_id) => {
if let serde_json::Value::Object(m) = default {
if m.is_empty() {
Ok(DefaultKind::Intrinsic)
} else {
let key_ty = type_space.id_to_entry.get(key_id).unwrap();
let value_ty = type_space.id_to_entry.get(value_id).unwrap();
for (key, value) in m {
let _ = key_ty.validate_value(
type_space,
&serde_json::Value::String(key.clone()),
)?;
let _ = value_ty.validate_value(type_space, value)?;
}
Ok(DefaultKind::Specific)
}
} else {
Err(Error::invalid_value())
}
}
TypeEntryDetails::Set(type_id) => {
if let serde_json::Value::Array(v) = default {
if v.is_empty() {
Ok(DefaultKind::Intrinsic)
} else {
let type_entry = type_space.id_to_entry.get(type_id).unwrap();
for (i, value) in v.iter().enumerate() {
for other in &v[(i + 1)..] {
if value == other {
return Err(Error::invalid_value());
}
}
let _ = type_entry.validate_value(type_space, value)?;
}
Ok(DefaultKind::Specific)
}
} else {
Err(Error::invalid_value())
}
}
TypeEntryDetails::Tuple(ids) => {
validate_default_tuple(ids, type_space, default).ok_or_else(Error::invalid_value)
}
TypeEntryDetails::Array(type_id, length) => {
let Some(arr) = default.as_array() else {
return Err(Error::invalid_value());
};
if arr.len() != *length {
return Err(Error::invalid_value());
}
let type_entry = type_space.id_to_entry.get(type_id).unwrap();
for value in arr {
let _ = type_entry.validate_value(type_space, value)?;
}
Ok(DefaultKind::Specific)
}
TypeEntryDetails::Unit => {
if let serde_json::Value::Null = default {
Ok(DefaultKind::Intrinsic)
} else {
Err(Error::invalid_value())
}
}
TypeEntryDetails::Native(_) => {
Ok(DefaultKind::Specific)
}
TypeEntryDetails::JsonValue => Ok(DefaultKind::Specific),
TypeEntryDetails::Boolean => match default {
serde_json::Value::Bool(false) => Ok(DefaultKind::Intrinsic),
serde_json::Value::Bool(true) => Ok(DefaultKind::Generic(DefaultImpl::Boolean)),
_ => Err(Error::invalid_value()),
},
TypeEntryDetails::Integer(itype) => match (default.as_u64(), default.as_i64()) {
(None, None) => Err(Error::invalid_value()),
(Some(0), _) => Ok(DefaultKind::Intrinsic),
(_, Some(0)) => unreachable!(),
(Some(_), _) => {
if itype.starts_with(STD_NUM_NONZERO_PREFIX) {
Ok(DefaultKind::Generic(DefaultImpl::NZU64))
} else {
Ok(DefaultKind::Generic(DefaultImpl::U64))
}
}
(_, Some(_)) => Ok(DefaultKind::Generic(DefaultImpl::I64)),
},
TypeEntryDetails::Float(_) => {
if let Some(value) = default.as_f64() {
if value == 0.0 {
Ok(DefaultKind::Intrinsic)
} else {
Ok(DefaultKind::Generic(DefaultImpl::I64))
}
} else {
Err(Error::invalid_value())
}
}
TypeEntryDetails::String => {
if let Some("") = default.as_str() {
Ok(DefaultKind::Intrinsic)
} else {
Ok(DefaultKind::Specific)
}
}
TypeEntryDetails::Reference(_) => unreachable!(),
}
}
pub(crate) fn default_fn(
&self,
default: &serde_json::Value,
type_space: &TypeSpace,
type_name: &str,
prop_name: &str,
) -> (String, Option<TokenStream>) {
let maybe_builtin = match &self.details {
TypeEntryDetails::Unit => unreachable!(),
TypeEntryDetails::Boolean => Some("defaults::default_bool::<true>".to_string()),
TypeEntryDetails::Integer(name) => {
if let Some(value) = default.as_u64() {
if name.starts_with(STD_NUM_NONZERO_PREFIX) {
Some(format!("defaults::default_nzu64::<{}, {}>", name, value))
} else {
Some(format!("defaults::default_u64::<{}, {}>", name, value))
}
} else if let Some(value) = default.as_i64() {
Some(format!("defaults::default_i64::<{}, {}>", name, value))
} else {
panic!()
}
}
_ => None,
};
if let Some(fn_name) = maybe_builtin {
(fn_name, None)
} else {
let n = self.type_ident(type_space, &Some("super".to_string()));
let value = self
.output_value(type_space, default, "e! { super:: })
.unwrap_or_else(|| {
panic!(
"{}\nvalue: {}\ntype: {:#?}",
"The default value could not be rendered for this type",
serde_json::to_string_pretty(default).unwrap(),
self,
)
});
let fn_name = sanitize(&format!("{}_{}", type_name, prop_name), Case::Snake);
let fn_ident = format_ident!("{}", fn_name);
let def = quote! {
pub(super) fn #fn_ident() -> #n {
#value
}
};
(format!("defaults::{}", fn_name), Some(def))
}
}
}
pub(crate) fn validate_default_for_external_enum(
type_space: &TypeSpace,
variants: &[Variant],
default: &serde_json::Value,
) -> Option<DefaultKind> {
if let Some(simple_name) = default.as_str() {
let variant = variants
.iter()
.find(|variant| simple_name == variant.raw_name)?;
matches!(&variant.details, VariantDetails::Simple).then(|| ())?;
Some(DefaultKind::Specific)
} else {
let map = default.as_object()?;
if map.len() != 1 {
return None;
}
let (name, value) = map.iter().next()?;
let variant = variants.iter().find(|variant| name == &variant.raw_name)?;
match &variant.details {
VariantDetails::Simple => None,
VariantDetails::Item(type_id) => validate_type_id(type_id, type_space, value).ok(),
VariantDetails::Tuple(tup) => validate_default_tuple(tup, type_space, value),
VariantDetails::Struct(props) => {
validate_default_struct_props(props, type_space, value)
}
}
}
}
pub(crate) fn validate_default_for_internal_enum(
type_space: &TypeSpace,
variants: &[Variant],
default: &serde_json::Value,
tag: &str,
) -> Option<DefaultKind> {
let map = default.as_object()?;
let name = map.get(tag).and_then(serde_json::Value::as_str)?;
let variant = variants.iter().find(|variant| name == variant.raw_name)?;
match &variant.details {
VariantDetails::Simple => Some(DefaultKind::Specific),
VariantDetails::Struct(props) => {
let inner_default = serde_json::Value::Object(
map.clone()
.into_iter()
.filter(|(name, _)| name != tag)
.collect(),
);
validate_default_struct_props(props, type_space, &inner_default)
}
VariantDetails::Item(_) | VariantDetails::Tuple(_) => unreachable!(),
}
}
pub(crate) fn validate_default_for_adjacent_enum(
type_space: &TypeSpace,
variants: &[Variant],
default: &serde_json::Value,
tag: &str,
content: &str,
) -> Option<DefaultKind> {
let map = default.as_object()?;
let (tag_value, content_value) = match (
map.len(),
map.get(tag).and_then(serde_json::Value::as_str),
map.get(content),
) {
(1, Some(tag_value), None) => (tag_value, None),
(2, Some(tag_value), content_value @ Some(_)) => (tag_value, content_value),
_ => return None,
};
let variant = variants
.iter()
.find(|variant| tag_value == variant.raw_name)?;
match (&variant.details, content_value) {
(VariantDetails::Simple, None) => Some(DefaultKind::Specific),
(VariantDetails::Tuple(tup), Some(content_value)) => {
validate_default_tuple(tup, type_space, content_value)
}
(VariantDetails::Struct(props), Some(content_value)) => {
validate_default_struct_props(props, type_space, content_value)
}
_ => None,
}
}
pub(crate) fn validate_default_for_untagged_enum(
type_space: &TypeSpace,
variants: &[Variant],
default: &serde_json::Value,
) -> Option<DefaultKind> {
variants.iter().find_map(|variant| {
match &variant.details {
VariantDetails::Simple => {
default.as_null()?;
Some(DefaultKind::Specific)
}
VariantDetails::Item(type_id) => validate_type_id(type_id, type_space, default).ok(),
VariantDetails::Tuple(tup) => validate_default_tuple(tup, type_space, default),
VariantDetails::Struct(props) => {
validate_default_struct_props(props, type_space, default)
}
}
})
}
fn validate_type_id(
type_id: &TypeId,
type_space: &TypeSpace,
default: &serde_json::Value,
) -> Result<DefaultKind> {
let type_entry = type_space.id_to_entry.get(type_id).unwrap();
type_entry.validate_value(type_space, default)
}
fn validate_default_tuple(
types: &[TypeId],
type_space: &TypeSpace,
default: &serde_json::Value,
) -> Option<DefaultKind> {
let arr = default.as_array()?;
if arr.len() != types.len() {
return None;
}
types
.iter()
.zip(arr.iter())
.all(|(type_id, value)| validate_type_id(type_id, type_space, value).is_ok())
.then_some(DefaultKind::Specific)
}
fn validate_default_struct_props(
properties: &[StructProperty],
type_space: &TypeSpace,
default: &serde_json::Value,
) -> Option<DefaultKind> {
let map = default.as_object()?;
let (named_properties, unnamed_properties): (Vec<_>, Vec<_>) = properties
.iter()
.flat_map(|property| all_props(property, type_space))
.partition(|(name, _, _)| name.is_some());
let named_properties = named_properties
.into_iter()
.map(|(name, type_id, required)| (name.unwrap(), (type_id, required)))
.collect::<BTreeMap<_, _>>();
let unnamed_properties = unnamed_properties
.into_iter()
.map(|(_, type_id, _)| type_id)
.collect::<Vec<_>>();
map.iter().try_for_each(|(name, default_value)| {
if let Some((type_id, _)) = named_properties.get(name) {
validate_type_id(type_id, type_space, default_value)
.ok()
.map(|_| ())
} else {
unnamed_properties
.iter()
.any(|type_id| validate_type_id(type_id, type_space, default_value).is_ok())
.then_some(())
}
})?;
named_properties
.iter()
.filter(|(_, (_, required))| *required)
.try_for_each(|(name, _)| map.get(*name).map(|_| ()))?;
Some(DefaultKind::Specific)
}
fn all_props<'a>(
property: &'a StructProperty,
type_space: &'a TypeSpace,
) -> Vec<(Option<&'a String>, &'a TypeId, bool)> {
let maybe_name = match &property.rename {
StructPropertyRename::None => Some(&property.name),
StructPropertyRename::Rename(rename) => Some(rename),
StructPropertyRename::Flatten => None,
};
if let Some(name) = maybe_name {
let required = match &property.state {
StructPropertyState::Required => true,
StructPropertyState::Optional | StructPropertyState::Default(_) => false,
};
vec![(Some(name), &property.type_id, required)]
} else {
let type_entry = type_space.id_to_entry.get(&property.type_id).unwrap();
let (properties, all_required) = match &type_entry.details {
TypeEntryDetails::Struct(TypeEntryStruct { properties, .. }) => {
let optional = matches!(&property.state, StructPropertyState::Optional);
(properties, !optional)
}
TypeEntryDetails::Option(type_id) => {
let type_entry = type_space.id_to_entry.get(type_id).unwrap();
if let TypeEntryDetails::Struct(TypeEntryStruct { properties, .. }) =
&type_entry.details
{
(properties, false)
} else {
unreachable!()
}
}
TypeEntryDetails::Map(_, value_id) => return vec![(None, value_id, false)],
_ => unreachable!(),
};
properties
.iter()
.flat_map(|property| all_props(property, type_space))
.map(|(name, type_id, required)| (name, type_id, required && all_required))
.collect()
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use schemars::JsonSchema;
use serde_json::json;
use uuid::Uuid;
use crate::{
test_util::get_type,
type_entry::{DefaultKind, TypeEntry},
DefaultImpl,
};
#[test]
fn test_default_option() {
let (type_space, type_id) = get_type::<Option<u32>>();
let type_entry = type_space.id_to_entry.get(&type_id).unwrap();
assert!(type_entry
.validate_value(&type_space, &json!("forty-two"))
.is_err());
assert!(matches!(
type_entry.validate_value(&type_space, &json!(null)),
Ok(DefaultKind::Intrinsic)
));
assert!(matches!(
type_entry.validate_value(&type_space, &json!(42)),
Ok(DefaultKind::Specific)
));
}
#[test]
fn test_default_box() {
let (type_space, type_id) = get_type::<Option<u32>>();
let type_entry = TypeEntry {
details: crate::type_entry::TypeEntryDetails::Box(type_id),
extra_derives: Default::default(),
extra_attrs: Default::default(),
};
assert!(type_entry
.validate_value(&type_space, &json!("forty-two"))
.is_err());
assert!(matches!(
type_entry.validate_value(&type_space, &json!(null)),
Ok(DefaultKind::Intrinsic)
));
assert!(matches!(
type_entry.validate_value(&type_space, &json!(42)),
Ok(DefaultKind::Specific)
));
}
#[test]
fn test_default_array() {
let (type_space, type_id) = get_type::<Vec<u32>>();
let type_entry = type_space.id_to_entry.get(&type_id).unwrap();
assert!(type_entry
.validate_value(&type_space, &json!([null]))
.is_err());
assert!(matches!(
type_entry.validate_value(&type_space, &json!([])),
Ok(DefaultKind::Intrinsic),
));
assert!(matches!(
type_entry.validate_value(&type_space, &json!([1, 2, 5])),
Ok(DefaultKind::Specific),
));
}
#[test]
fn test_default_map() {
let (type_space, type_id) = get_type::<HashMap<String, u32>>();
let type_entry = type_space.id_to_entry.get(&type_id).unwrap();
assert!(type_entry.validate_value(&type_space, &json!([])).is_err());
assert!(matches!(
type_entry.validate_value(&type_space, &json!({})),
Ok(DefaultKind::Intrinsic),
));
assert!(matches!(
type_entry.validate_value(&type_space, &json!({"a": 1, "b": 2})),
Ok(DefaultKind::Specific),
));
}
#[test]
fn test_default_tuple() {
let (type_space, type_id) = get_type::<(u32, u32, String)>();
let type_entry = type_space.id_to_entry.get(&type_id).unwrap();
assert!(type_entry
.validate_value(&type_space, &json!([1, 2, "three", 4]))
.is_err());
assert!(matches!(
type_entry.validate_value(&type_space, &json!([1, 2, "three"])),
Ok(DefaultKind::Specific),
));
}
#[test]
fn test_default_builtin() {
let (type_space, type_id) = get_type::<Uuid>();
let type_entry = type_space.id_to_entry.get(&type_id).unwrap();
assert!(matches!(
type_entry.validate_value(&type_space, &json!("not-a-uuid")),
Ok(DefaultKind::Specific)
));
}
#[test]
fn test_default_bool() {
let (type_space, type_id) = get_type::<bool>();
let type_entry = type_space.id_to_entry.get(&type_id).unwrap();
assert!(matches!(
type_entry.validate_value(&type_space, &json!(false)),
Ok(DefaultKind::Intrinsic),
));
assert!(matches!(
type_entry.validate_value(&type_space, &json!(true)),
Ok(DefaultKind::Generic(DefaultImpl::Boolean)),
));
}
#[test]
fn test_default_numbers_and_string() {
let (type_space, type_id) = get_type::<u32>();
let type_entry = type_space.id_to_entry.get(&type_id).unwrap();
assert!(type_entry
.validate_value(&type_space, &json!(true))
.is_err());
assert!(matches!(
type_entry.validate_value(&type_space, &json!(0)),
Ok(DefaultKind::Intrinsic),
));
assert!(matches!(
type_entry.validate_value(&type_space, &json!(42)),
Ok(DefaultKind::Generic(DefaultImpl::U64)),
));
let (type_space, type_id) = get_type::<String>();
let type_entry = type_space.id_to_entry.get(&type_id).unwrap();
assert!(matches!(
type_entry.validate_value(&type_space, &json!("")),
Ok(DefaultKind::Intrinsic),
));
assert!(matches!(
type_entry.validate_value(&type_space, &json!("howdy")),
Ok(DefaultKind::Specific),
));
}
#[test]
fn test_struct_simple() {
#[derive(JsonSchema)]
#[allow(dead_code)]
struct Test {
a: String,
b: u32,
c: Option<String>,
d: Option<f64>,
}
let (type_space, type_id) = get_type::<Test>();
let type_entry = type_space.id_to_entry.get(&type_id).unwrap();
assert!(matches!(
type_entry.validate_value(
&type_space,
&json!(
{
"a": "aaaa",
"b": 7,
"c": "cccc"
}
)
),
Ok(DefaultKind::Specific),
));
assert!(type_entry
.validate_value(
&type_space,
&json!(
{
"a": "aaaa",
"c": "cccc",
"d": 7
}
)
)
.is_err());
assert!(type_entry
.validate_value(
&type_space,
&json!(
{
"a": "aaaa",
"b": 7,
"d": {}
}
)
)
.is_err());
}
#[test]
fn test_enum_external() {
#[derive(JsonSchema)]
#[allow(dead_code)]
enum Test {
A,
B(String, String),
C { cc: String, dd: String },
}
let (type_space, type_id) = get_type::<Test>();
let type_entry = type_space.id_to_entry.get(&type_id).unwrap();
assert!(matches!(
type_entry.validate_value(&type_space, &json!("A")),
Ok(DefaultKind::Specific),
));
assert!(matches!(
type_entry.validate_value(
&type_space,
&json!({
"B": ["xx", "yy"]
})
),
Ok(DefaultKind::Specific),
));
assert!(matches!(
type_entry.validate_value(
&type_space,
&json!({
"C": { "cc": "xx", "dd": "yy" }
})
),
Ok(DefaultKind::Specific),
));
assert!(type_entry
.validate_value(&type_space, &json!({ "A": null }))
.is_err());
assert!(type_entry.validate_value(&type_space, &json!("B")).is_err());
}
#[test]
fn test_enum_internal() {
#[derive(JsonSchema)]
#[allow(dead_code)]
#[serde(tag = "tag")]
enum Test {
A,
C { cc: String, dd: String },
}
let (type_space, type_id) = get_type::<Test>();
let type_entry = type_space.id_to_entry.get(&type_id).unwrap();
assert!(matches!(
type_entry.validate_value(
&type_space,
&json!({
"tag": "A"
})
),
Ok(DefaultKind::Specific),
));
assert!(matches!(
type_entry.validate_value(
&type_space,
&json!({
"tag": "C",
"cc": "xx",
"dd": "yy"
})
),
Ok(DefaultKind::Specific),
));
assert!(type_entry
.validate_value(
&type_space,
&json!({
"not-tag": "A"
})
)
.is_err());
assert!(type_entry
.validate_value(
&type_space,
&json!({
"tag": "B",
"cc": "where's D?"
})
)
.is_err());
}
#[test]
fn test_enum_adjacent() {
#[derive(JsonSchema)]
#[allow(dead_code)]
#[serde(tag = "tag", content = "content")]
enum Test {
A,
B(String, String),
C { cc: String, dd: String },
}
let (type_space, type_id) = get_type::<Test>();
let type_entry = type_space.id_to_entry.get(&type_id).unwrap();
assert!(matches!(
type_entry.validate_value(
&type_space,
&json!({
"tag": "A"
})
),
Ok(DefaultKind::Specific),
));
assert!(matches!(
type_entry.validate_value(
&type_space,
&json!({
"tag": "B",
"content": ["xx", "yy"]
})
),
Ok(DefaultKind::Specific),
));
assert!(matches!(
type_entry.validate_value(
&type_space,
&json!({
"tag": "C",
"content": { "cc": "xx", "dd": "yy" }
})
),
Ok(DefaultKind::Specific),
));
assert!(type_entry.validate_value(&type_space, &json!("A")).is_err());
assert!(type_entry
.validate_value(
&type_space,
&json!({
"tag": "A",
"content": null,
})
)
.is_err());
}
#[test]
fn test_enum_untagged() {
#[derive(JsonSchema)]
#[allow(dead_code)]
#[serde(untagged)]
enum Test {
A,
B(String, String),
C { cc: String, dd: String },
}
let (type_space, type_id) = get_type::<Test>();
let type_entry = type_space.id_to_entry.get(&type_id).unwrap();
assert!(matches!(
type_entry.validate_value(&type_space, &json!(null)),
Ok(DefaultKind::Specific),
));
assert!(matches!(
type_entry.validate_value(&type_space, &json!(["xx", "yy"])),
Ok(DefaultKind::Specific),
));
assert!(matches!(
type_entry.validate_value(&type_space, &json!( { "cc": "xx", "dd": "yy" })),
Ok(DefaultKind::Specific),
));
assert!(type_entry.validate_value(&type_space, &json!({})).is_err());
}
}