Basic support for NonZero types with not field

This commit is contained in:
Piotr Siuszko 2025-07-05 08:22:40 +02:00
parent 492ad8be31
commit 0b375625e0
2 changed files with 74 additions and 5 deletions

View File

@ -11,7 +11,9 @@ use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::schemas::{
reflect_info::{SchemaNumber, TypeInformation, TypeReferenceId, TypeReferencePath},
reflect_info::{
is_non_zero_number_type, SchemaNumber, TypeInformation, TypeReferenceId, TypeReferencePath,
},
SchemaTypesMetadata,
};
@ -257,6 +259,11 @@ pub struct JsonSchemaBevyType {
/// Type description
#[serde(skip_serializing_if = "Option::is_none", default)]
pub description: Option<Cow<'static, str>>,
/// This keyword's value MUST be a valid JSON Schema.
/// An instance is valid against this keyword if it fails to validate successfully against the schema defined by this keyword.
#[serde(skip_serializing_if = "Option::is_none", default)]
#[reflect(ignore)]
pub not: Option<Box<JsonSchemaBevyType>>,
/// Default value for the schema.
#[serde(skip_serializing_if = "Option::is_none", default, rename = "default")]
#[reflect(ignore)]
@ -404,6 +411,7 @@ impl From<TypeId> for SchemaType {
|| value.eq(&TypeId::of::<i64>())
|| value.eq(&TypeId::of::<i128>())
|| value.eq(&TypeId::of::<isize>())
|| is_non_zero_number_type(value)
{
Self::Integer
} else if value.eq(&TypeId::of::<str>())

View File

@ -20,6 +20,10 @@ use core::{
};
use serde::de::{self, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::num::{
NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU128,
NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize,
};
use crate::schemas::json_schema::{
JsonSchemaBevyType, JsonSchemaVariant, SchemaKind, SchemaType, SchemaTypeVariant,
@ -836,7 +840,10 @@ impl From<TypeId> for MinMaxValues {
fn from(value: TypeId) -> Self {
let mut min: Option<BoundValue> = None;
let mut max: Option<BoundValue> = None;
if value.eq(&TypeId::of::<u8>()) {
if value.eq(&TypeId::of::<NonZeroU8>()) {
min = Some(BoundValue::Inclusive(1.into()));
max = Some(BoundValue::Inclusive(u8::MAX.into()));
} else if value.eq(&TypeId::of::<u8>()) {
min = Some(BoundValue::Inclusive(0.into()));
max = Some(BoundValue::Inclusive(u8::MAX.into()));
} else if value.eq(&TypeId::of::<u16>()) {
@ -850,19 +857,35 @@ impl From<TypeId> for MinMaxValues {
|| value.eq(&TypeId::of::<u128>())
{
min = Some(BoundValue::Inclusive(0.into()));
} else if value.eq(&TypeId::of::<i8>()) {
} else if value.eq(&TypeId::of::<i8>()) || value.eq(&TypeId::of::<NonZeroI8>()) {
min = Some(BoundValue::Inclusive(i8::MIN.into()));
max = Some(BoundValue::Inclusive(i8::MAX.into()));
} else if value.eq(&TypeId::of::<i16>()) {
} else if value.eq(&TypeId::of::<i16>()) || value.eq(&TypeId::of::<NonZeroI16>()) {
min = Some(BoundValue::Inclusive(i16::MIN.into()));
max = Some(BoundValue::Inclusive(i16::MAX.into()));
} else if value.eq(&TypeId::of::<i32>()) {
} else if value.eq(&TypeId::of::<i32>()) || value.eq(&TypeId::of::<NonZeroI32>()) {
min = Some(BoundValue::Inclusive(i32::MIN.into()));
max = Some(BoundValue::Inclusive(i32::MAX.into()));
}
MinMaxValues { min, max }
}
}
pub(super) fn is_non_zero_number_type(t: TypeId) -> bool {
t.eq(&TypeId::of::<NonZeroI8>())
|| t.eq(&TypeId::of::<NonZeroI16>())
|| t.eq(&TypeId::of::<NonZeroI32>())
|| t.eq(&TypeId::of::<NonZeroI64>())
|| t.eq(&TypeId::of::<NonZeroI128>())
|| t.eq(&TypeId::of::<NonZeroIsize>())
|| t.eq(&TypeId::of::<NonZeroU8>())
|| t.eq(&TypeId::of::<NonZeroU16>())
|| t.eq(&TypeId::of::<NonZeroU32>())
|| t.eq(&TypeId::of::<NonZeroU64>())
|| t.eq(&TypeId::of::<NonZeroU128>())
|| t.eq(&TypeId::of::<NonZeroUsize>())
}
/// Enum representing the internal schema type information for different Rust types.
/// This enum categorizes how different types should be represented in JSON schema.
#[derive(Clone, Debug, Default)]
@ -1119,9 +1142,18 @@ impl SchemaTypeInfo {
.map(TypeReferencePath::definition),
self.into(),
);
let not = if is_non_zero_number_type(self.ty_info.type_id()) {
Some(Box::new(JsonSchemaBevyType {
const_value: Some(0.into()),
..default()
}))
} else {
None
};
let mut schema = JsonSchemaBevyType {
description,
not,
minimum: range.min.get_inclusive(),
maximum: range.max.get_inclusive(),
exclusive_minimum: range.min.get_exclusive(),
@ -2154,6 +2186,35 @@ pub(super) mod tests {
&[serde_json::json!("json")],
);
}
#[test]
fn reflect_non_zero_type() {
#[derive(Reflect, Deserialize, Serialize, Component)]
/// A tuple struct with one field.
pub struct TupleStruct(pub NonZeroI8);
impl Default for TupleStruct {
fn default() -> Self {
TupleStruct(NonZeroI8::new(15i8).expect("Should not fail"))
}
}
let type_info =
TypeInformation::from(&TupleStruct::get_type_registration()).to_schema_type_info();
let schema: JsonSchemaBevyType = type_info.to_definition().into();
validate::<TupleStruct>(
schema,
&[TupleStruct(NonZeroI8::new(115i8).expect("Should not fail"))],
&[
serde_json::json!(15),
serde_json::json!(50),
serde_json::json!(-49),
serde_json::json!(0),
],
&[serde_json::Value::Null],
);
}
#[test]
fn reflect_tuple_struct_with_one_field() {
use bevy_ecs::prelude::ReflectComponent;