diff --git a/crates/bevy_remote/src/schemas/json_schema.rs b/crates/bevy_remote/src/schemas/json_schema.rs index c5fd21c874..77947e2929 100644 --- a/crates/bevy_remote/src/schemas/json_schema.rs +++ b/crates/bevy_remote/src/schemas/json_schema.rs @@ -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>, + /// 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>, /// Default value for the schema. #[serde(skip_serializing_if = "Option::is_none", default, rename = "default")] #[reflect(ignore)] @@ -404,6 +411,7 @@ impl From for SchemaType { || value.eq(&TypeId::of::()) || value.eq(&TypeId::of::()) || value.eq(&TypeId::of::()) + || is_non_zero_number_type(value) { Self::Integer } else if value.eq(&TypeId::of::()) diff --git a/crates/bevy_remote/src/schemas/reflect_info.rs b/crates/bevy_remote/src/schemas/reflect_info.rs index 687f6439dc..ce17b2eebc 100644 --- a/crates/bevy_remote/src/schemas/reflect_info.rs +++ b/crates/bevy_remote/src/schemas/reflect_info.rs @@ -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 for MinMaxValues { fn from(value: TypeId) -> Self { let mut min: Option = None; let mut max: Option = None; - if value.eq(&TypeId::of::()) { + if value.eq(&TypeId::of::()) { + min = Some(BoundValue::Inclusive(1.into())); + max = Some(BoundValue::Inclusive(u8::MAX.into())); + } else if value.eq(&TypeId::of::()) { min = Some(BoundValue::Inclusive(0.into())); max = Some(BoundValue::Inclusive(u8::MAX.into())); } else if value.eq(&TypeId::of::()) { @@ -850,19 +857,35 @@ impl From for MinMaxValues { || value.eq(&TypeId::of::()) { min = Some(BoundValue::Inclusive(0.into())); - } else if value.eq(&TypeId::of::()) { + } else if value.eq(&TypeId::of::()) || value.eq(&TypeId::of::()) { min = Some(BoundValue::Inclusive(i8::MIN.into())); max = Some(BoundValue::Inclusive(i8::MAX.into())); - } else if value.eq(&TypeId::of::()) { + } else if value.eq(&TypeId::of::()) || value.eq(&TypeId::of::()) { min = Some(BoundValue::Inclusive(i16::MIN.into())); max = Some(BoundValue::Inclusive(i16::MAX.into())); - } else if value.eq(&TypeId::of::()) { + } else if value.eq(&TypeId::of::()) || value.eq(&TypeId::of::()) { 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::()) + || t.eq(&TypeId::of::()) + || t.eq(&TypeId::of::()) + || t.eq(&TypeId::of::()) + || t.eq(&TypeId::of::()) + || t.eq(&TypeId::of::()) + || t.eq(&TypeId::of::()) + || t.eq(&TypeId::of::()) + || t.eq(&TypeId::of::()) + || t.eq(&TypeId::of::()) + || t.eq(&TypeId::of::()) + || t.eq(&TypeId::of::()) +} + /// 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::( + 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;