Use oneOf for Option types, use jsonschema for testing schemas
This commit is contained in:
parent
efec7d192f
commit
714d56e1ed
@ -43,6 +43,9 @@ http-body-util = "0.1"
|
|||||||
async-channel = "2"
|
async-channel = "2"
|
||||||
bevy_log = { version = "0.17.0-dev", path = "../bevy_log" }
|
bevy_log = { version = "0.17.0-dev", path = "../bevy_log" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
jsonschema = "0.30.0"
|
||||||
|
|
||||||
# dependencies that will not compile on wasm
|
# dependencies that will not compile on wasm
|
||||||
[target.'cfg(not(target_family = "wasm"))'.dependencies]
|
[target.'cfg(not(target_family = "wasm"))'.dependencies]
|
||||||
async-io = { version = "2", optional = true }
|
async-io = { version = "2", optional = true }
|
||||||
|
|||||||
@ -1389,13 +1389,10 @@ pub fn process_remote_list_watching_request(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles a `bevy/registry/schema` request (list all registry types in form of schema) coming from a client.
|
fn export_registry_types_typed(
|
||||||
pub fn export_registry_types(In(params): In<Option<Value>>, world: &World) -> BrpResult {
|
filter: BrpJsonSchemaQueryFilter,
|
||||||
let filter: BrpJsonSchemaQueryFilter = match params {
|
world: &World,
|
||||||
None => Default::default(),
|
) -> Result<JsonSchemaBevyType, BrpError> {
|
||||||
Some(params) => parse(params)?,
|
|
||||||
};
|
|
||||||
|
|
||||||
let extra_info = world.resource::<crate::schemas::SchemaTypesMetadata>();
|
let extra_info = world.resource::<crate::schemas::SchemaTypesMetadata>();
|
||||||
let types = world.resource::<AppTypeRegistry>();
|
let types = world.resource::<AppTypeRegistry>();
|
||||||
let types = types.read();
|
let types = types.read();
|
||||||
@ -1438,8 +1435,17 @@ pub fn export_registry_types(In(params): In<Option<Value>>, world: &World) -> Br
|
|||||||
})
|
})
|
||||||
.flatten()
|
.flatten()
|
||||||
.collect();
|
.collect();
|
||||||
|
Ok(schema)
|
||||||
|
}
|
||||||
|
|
||||||
serde_json::to_value(schema).map_err(BrpError::internal)
|
/// Handles a `bevy/registry/schema` request (list all registry types in form of schema) coming from a client.
|
||||||
|
pub fn export_registry_types(In(params): In<Option<Value>>, world: &World) -> BrpResult {
|
||||||
|
let filter: BrpJsonSchemaQueryFilter = match params {
|
||||||
|
None => Default::default(),
|
||||||
|
Some(params) => parse(params)?,
|
||||||
|
};
|
||||||
|
let result = export_registry_types_typed(filter, world)?;
|
||||||
|
serde_json::to_value(result).map_err(BrpError::internal)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Immutably retrieves an entity from the [`World`], returning an error if the
|
/// Immutably retrieves an entity from the [`World`], returning an error if the
|
||||||
@ -1720,7 +1726,7 @@ mod tests {
|
|||||||
pub struct ThirdStruct {
|
pub struct ThirdStruct {
|
||||||
pub array_strings: Vec<String>,
|
pub array_strings: Vec<String>,
|
||||||
pub array_structs: [OtherStruct; 5],
|
pub array_structs: [OtherStruct; 5],
|
||||||
pub map_strings: HashMap<String, i32>,
|
// pub map_strings: HashMap<String, i32>,
|
||||||
}
|
}
|
||||||
#[derive(Reflect, Default, Deserialize, Serialize, Component)]
|
#[derive(Reflect, Default, Deserialize, Serialize, Component)]
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
@ -1754,7 +1760,7 @@ mod tests {
|
|||||||
assert_eq!(response.definitions.len(), 1);
|
assert_eq!(response.definitions.len(), 1);
|
||||||
{
|
{
|
||||||
let first = response.definitions.iter().next().expect("Should have one");
|
let first = response.definitions.iter().next().expect("Should have one");
|
||||||
assert_eq!(first.1.id, "urn:bevy:glam-Vec3");
|
assert_eq!(first.1.id, "urn:glam-Vec3");
|
||||||
}
|
}
|
||||||
let response = export_registry_types_ext(
|
let response = export_registry_types_ext(
|
||||||
BrpJsonSchemaQueryFilter {
|
BrpJsonSchemaQueryFilter {
|
||||||
@ -1781,15 +1787,9 @@ mod tests {
|
|||||||
input: BrpJsonSchemaQueryFilter,
|
input: BrpJsonSchemaQueryFilter,
|
||||||
world: &World,
|
world: &World,
|
||||||
) -> JsonSchemaBevyType {
|
) -> JsonSchemaBevyType {
|
||||||
let response_json = export_registry_types(
|
let response =
|
||||||
In(Some(
|
export_registry_types_typed(input, world).expect("Failed to export registry types");
|
||||||
serde_json::to_value(&input).expect("Failed to serialize input"),
|
|
||||||
)),
|
|
||||||
world,
|
|
||||||
)
|
|
||||||
.expect("Failed to export registry types");
|
|
||||||
|
|
||||||
serde_json::from_value::<JsonSchemaBevyType>(response_json)
|
response
|
||||||
.expect("Failed to deserialize response")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -168,12 +168,14 @@ pub struct JsonSchemaBevyType {
|
|||||||
///
|
///
|
||||||
/// It contains type info of value of the Map.
|
/// It contains type info of value of the Map.
|
||||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||||
pub value_type: Option<JsonSchemaVariant>,
|
#[reflect(ignore)]
|
||||||
|
pub value_type: Option<Box<JsonSchemaBevyType>>,
|
||||||
/// Bevy specific field, provided when [`SchemaKind`] `kind` field is equal to [`SchemaKind::Map`].
|
/// Bevy specific field, provided when [`SchemaKind`] `kind` field is equal to [`SchemaKind::Map`].
|
||||||
///
|
///
|
||||||
/// It contains type info of key of the Map.
|
/// It contains type info of key of the Map.
|
||||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||||
pub key_type: Option<JsonSchemaVariant>,
|
#[reflect(ignore)]
|
||||||
|
pub key_type: Option<Box<JsonSchemaBevyType>>,
|
||||||
/// The type keyword is fundamental to JSON Schema. It specifies the data type for a schema.
|
/// The type keyword is fundamental to JSON Schema. It specifies the data type for a schema.
|
||||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
@ -184,6 +186,13 @@ pub struct JsonSchemaBevyType {
|
|||||||
/// values of instance names that do not appear in the annotation results of either "properties" or "patternProperties".
|
/// values of instance names that do not appear in the annotation results of either "properties" or "patternProperties".
|
||||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||||
pub additional_properties: Option<JsonSchemaVariant>,
|
pub additional_properties: Option<JsonSchemaVariant>,
|
||||||
|
/// The behavior of this keyword depends on the presence and annotation results of "properties"
|
||||||
|
/// and "patternProperties" within the same schema object.
|
||||||
|
/// Validation with "additionalProperties" applies only to the child
|
||||||
|
/// values of instance names that do not appear in the annotation results of either "properties" or "patternProperties".
|
||||||
|
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
|
||||||
|
#[reflect(ignore)]
|
||||||
|
pub pattern_properties: HashMap<Cow<'static, str>, Box<JsonSchemaBevyType>>,
|
||||||
/// Validation succeeds if, for each name that appears in both the instance and as a name
|
/// Validation succeeds if, for each name that appears in both the instance and as a name
|
||||||
/// within this keyword's value, the child instance for that name successfully validates
|
/// within this keyword's value, the child instance for that name successfully validates
|
||||||
/// against the corresponding schema.
|
/// against the corresponding schema.
|
||||||
@ -194,7 +203,8 @@ pub struct JsonSchemaBevyType {
|
|||||||
pub required: Vec<Cow<'static, str>>,
|
pub required: Vec<Cow<'static, str>>,
|
||||||
/// An instance validates successfully against this keyword if it validates successfully against exactly one schema defined by this keyword's value.
|
/// An instance validates successfully against this keyword if it validates successfully against exactly one schema defined by this keyword's value.
|
||||||
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
||||||
pub one_of: Vec<JsonSchemaVariant>,
|
#[reflect(ignore)]
|
||||||
|
pub one_of: Vec<Box<JsonSchemaBevyType>>,
|
||||||
/// Validation succeeds if each element of the instance validates against the schema at the same position, if any. This keyword does not constrain the length of the array. If the array is longer than this keyword's value, this keyword validates only the prefix of matching length.
|
/// Validation succeeds if each element of the instance validates against the schema at the same position, if any. This keyword does not constrain the length of the array. If the array is longer than this keyword's value, this keyword validates only the prefix of matching length.
|
||||||
///
|
///
|
||||||
/// This keyword produces an annotation value which is the largest index to which this keyword
|
/// This keyword produces an annotation value which is the largest index to which this keyword
|
||||||
@ -712,7 +722,7 @@ mod tests {
|
|||||||
let schema = export_type::<Foo>();
|
let schema = export_type::<Foo>();
|
||||||
let schema_as_value = serde_json::to_value(&schema).expect("Failed to serialize schema");
|
let schema_as_value = serde_json::to_value(&schema).expect("Failed to serialize schema");
|
||||||
let mut value = json!({
|
let mut value = json!({
|
||||||
"$id": "urn:bevy:bevy_remote-schemas-json_schema-tests-Foo",
|
"$id": "urn:bevy_remote-schemas-json_schema-tests-Foo",
|
||||||
"shortPath": "Foo",
|
"shortPath": "Foo",
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"typePath": "bevy_remote::schemas::json_schema::tests::Foo",
|
"typePath": "bevy_remote::schemas::json_schema::tests::Foo",
|
||||||
|
|||||||
@ -202,6 +202,18 @@ pub enum TypeInformation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TypeInformation {
|
impl TypeInformation {
|
||||||
|
/// Try to get a regex pattern for the type.
|
||||||
|
pub fn try_get_regex_for_type(&self) -> Option<Cow<'static, str>> {
|
||||||
|
let primitive_type = self.try_get_primitive_type()?;
|
||||||
|
let pattern: Option<Cow<'static, str>> = match primitive_type {
|
||||||
|
SchemaType::String => Some(".*".into()),
|
||||||
|
SchemaType::Number => Some("\\d+(?:\\.'\\d+)?".into()),
|
||||||
|
SchemaType::Integer => Some("^(0|-*[1-9]+[0-9]*)$".into()),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
pattern
|
||||||
|
}
|
||||||
|
|
||||||
/// Checks for custom schema.
|
/// Checks for custom schema.
|
||||||
pub fn try_get_custom_schema(&self) -> Option<&super::ReflectJsonSchema> {
|
pub fn try_get_custom_schema(&self) -> Option<&super::ReflectJsonSchema> {
|
||||||
if let Self::TypeRegistration(reg) = self {
|
if let Self::TypeRegistration(reg) = self {
|
||||||
@ -210,6 +222,13 @@ impl TypeInformation {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Builds a `TypeReferencePath` from the type identifier.
|
||||||
|
pub fn try_get_path_id(&self) -> Option<TypeReferencePath> {
|
||||||
|
self.try_get_type_reference_id()
|
||||||
|
.map(|id| TypeReferencePath::new_ref(ReferenceLocation::Urn, id))
|
||||||
|
}
|
||||||
|
|
||||||
/// Builds a `TypeReferenceId` from the type path.
|
/// Builds a `TypeReferenceId` from the type path.
|
||||||
pub fn try_get_type_reference_id(&self) -> Option<TypeReferenceId> {
|
pub fn try_get_type_reference_id(&self) -> Option<TypeReferenceId> {
|
||||||
if let Some(schema) = self.try_get_custom_schema() {
|
if let Some(schema) = self.try_get_custom_schema() {
|
||||||
@ -242,11 +261,11 @@ impl TypeInformation {
|
|||||||
}
|
}
|
||||||
/// Converts the type information into a schema type information.
|
/// Converts the type information into a schema type information.
|
||||||
pub fn to_schema_type_info(self) -> SchemaTypeInfo {
|
pub fn to_schema_type_info(self) -> SchemaTypeInfo {
|
||||||
let stored_fields = (&self).into();
|
let internal_schema_type = (&self).into();
|
||||||
SchemaTypeInfo {
|
SchemaTypeInfo {
|
||||||
ty_info: self,
|
ty_info: self,
|
||||||
field_data: None,
|
field_data: None,
|
||||||
stored_fields,
|
internal_schema_type,
|
||||||
reflect_type_data: None,
|
reflect_type_data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -255,16 +274,16 @@ impl TypeInformation {
|
|||||||
self,
|
self,
|
||||||
metadata: &SchemaTypesMetadata,
|
metadata: &SchemaTypesMetadata,
|
||||||
) -> SchemaTypeInfo {
|
) -> SchemaTypeInfo {
|
||||||
let stored_fields = (&self).into();
|
|
||||||
let reflect_type_data = if let Self::TypeRegistration(reg) = &self {
|
let reflect_type_data = if let Self::TypeRegistration(reg) = &self {
|
||||||
Some(metadata.get_registered_reflect_types(reg))
|
Some(metadata.get_registered_reflect_types(reg))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
let internal_schema_type = (&self).into();
|
||||||
SchemaTypeInfo {
|
SchemaTypeInfo {
|
||||||
ty_info: self,
|
ty_info: self,
|
||||||
field_data: None,
|
field_data: None,
|
||||||
stored_fields,
|
internal_schema_type,
|
||||||
reflect_type_data,
|
reflect_type_data,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -585,6 +604,8 @@ pub enum ReferenceLocation {
|
|||||||
Components,
|
Components,
|
||||||
/// used by schemas
|
/// used by schemas
|
||||||
Url,
|
Url,
|
||||||
|
/// Used by JSON Schema
|
||||||
|
Urn,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ReferenceLocation {
|
impl Display for ReferenceLocation {
|
||||||
@ -593,6 +614,7 @@ impl Display for ReferenceLocation {
|
|||||||
ReferenceLocation::Definitions => write!(f, "#/$defs/"),
|
ReferenceLocation::Definitions => write!(f, "#/$defs/"),
|
||||||
ReferenceLocation::Components => write!(f, "#/components/"),
|
ReferenceLocation::Components => write!(f, "#/components/"),
|
||||||
ReferenceLocation::Url => write!(f, "https://"),
|
ReferenceLocation::Url => write!(f, "https://"),
|
||||||
|
ReferenceLocation::Urn => write!(f, "urn:"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -731,10 +753,27 @@ pub struct MinMaxValues {
|
|||||||
pub max: Option<BoundValue>,
|
pub max: Option<BoundValue>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<&JsonSchemaBevyType> for MinMaxValues {
|
||||||
|
fn from(value: &JsonSchemaBevyType) -> Self {
|
||||||
|
let min = match (&value.exclusive_minimum, &value.minimum) {
|
||||||
|
(Some(ex), None) => Some(BoundValue::Exclusive(ex.clone())),
|
||||||
|
(_, Some(inclusive)) => Some(BoundValue::Inclusive(inclusive.clone())),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
let max = match (&value.exclusive_maximum, &value.maximum) {
|
||||||
|
(Some(ex), None) => Some(BoundValue::Exclusive(ex.clone())),
|
||||||
|
(_, Some(inclusive)) => Some(BoundValue::Inclusive(inclusive.clone())),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
MinMaxValues { min, max }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl MinMaxValues {
|
impl MinMaxValues {
|
||||||
/// Checks if a given value falls within the defined range constraints.
|
/// Checks if a given value falls within the defined range constraints.
|
||||||
/// Returns true if the value is within bounds, false otherwise.
|
/// Returns true if the value is within bounds, false otherwise.
|
||||||
pub fn in_range(&self, value: SchemaNumber) -> bool {
|
pub fn in_range(&self, value: impl Into<SchemaNumber>) -> bool {
|
||||||
|
let value = value.into();
|
||||||
if let Some(min) = self.min {
|
if let Some(min) = self.min {
|
||||||
if let Some(min_value) = min.get_inclusive() {
|
if let Some(min_value) = min.get_inclusive() {
|
||||||
if value < min_value {
|
if value < min_value {
|
||||||
@ -873,6 +912,10 @@ pub enum InternalSchemaType {
|
|||||||
}
|
}
|
||||||
impl From<&TypeInformation> for InternalSchemaType {
|
impl From<&TypeInformation> for InternalSchemaType {
|
||||||
fn from(value: &TypeInformation) -> Self {
|
fn from(value: &TypeInformation) -> Self {
|
||||||
|
let field_information: Option<FieldsInformation> = value.into();
|
||||||
|
if let Some(fields_info) = field_information {
|
||||||
|
return InternalSchemaType::FieldsHolder(fields_info);
|
||||||
|
}
|
||||||
if let Some(type_info) = value.try_get_type_info() {
|
if let Some(type_info) = value.try_get_type_info() {
|
||||||
match type_info {
|
match type_info {
|
||||||
TypeInfo::Struct(struct_info) => {
|
TypeInfo::Struct(struct_info) => {
|
||||||
@ -940,15 +983,6 @@ impl From<&TypeInformation> for InternalSchemaType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<&SchemaTypeInfo> for InternalSchemaType {
|
|
||||||
fn from(value: &SchemaTypeInfo) -> Self {
|
|
||||||
if let Some(s) = &value.stored_fields {
|
|
||||||
InternalSchemaType::FieldsHolder(s.clone())
|
|
||||||
} else {
|
|
||||||
(&value.ty_info).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&InternalSchemaType> for Option<SchemaTypeVariant> {
|
impl From<&InternalSchemaType> for Option<SchemaTypeVariant> {
|
||||||
fn from(value: &InternalSchemaType) -> Self {
|
fn from(value: &InternalSchemaType) -> Self {
|
||||||
@ -977,17 +1011,9 @@ impl From<&InternalSchemaType> for Option<SchemaTypeVariant> {
|
|||||||
_ => Some(SchemaTypeVariant::Single(SchemaType::Array)),
|
_ => Some(SchemaTypeVariant::Single(SchemaType::Array)),
|
||||||
},
|
},
|
||||||
InternalSchemaType::Optional {
|
InternalSchemaType::Optional {
|
||||||
generic,
|
generic: _,
|
||||||
schema_type_info: _,
|
schema_type_info: _,
|
||||||
} => {
|
} => None,
|
||||||
let schema: InternalSchemaType =
|
|
||||||
(&TypeInformation::Type(Box::new(*generic.ty()))).into();
|
|
||||||
let s: Option<SchemaTypeVariant> = (&schema).into();
|
|
||||||
Some(
|
|
||||||
s.unwrap_or(SchemaTypeVariant::Single(SchemaType::Object))
|
|
||||||
.with(SchemaType::Null),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
InternalSchemaType::Map { .. } => Some(SchemaTypeVariant::Single(SchemaType::Object)),
|
InternalSchemaType::Map { .. } => Some(SchemaTypeVariant::Single(SchemaType::Object)),
|
||||||
InternalSchemaType::Regular(type_id) => {
|
InternalSchemaType::Regular(type_id) => {
|
||||||
Some(SchemaTypeVariant::Single((*type_id).into()))
|
Some(SchemaTypeVariant::Single((*type_id).into()))
|
||||||
@ -1010,7 +1036,7 @@ impl From<&FieldInformation> for SchemaTypeInfo {
|
|||||||
Self {
|
Self {
|
||||||
ty_info: value.type_info.clone(),
|
ty_info: value.type_info.clone(),
|
||||||
field_data: Some(value.field.clone()),
|
field_data: Some(value.field.clone()),
|
||||||
stored_fields: (&value.type_info).into(),
|
internal_schema_type: (&value.type_info).into(),
|
||||||
reflect_type_data: None,
|
reflect_type_data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1051,10 +1077,10 @@ pub struct SchemaTypeInfo {
|
|||||||
pub ty_info: TypeInformation,
|
pub ty_info: TypeInformation,
|
||||||
/// Field information for the type.
|
/// Field information for the type.
|
||||||
pub field_data: Option<SchemaFieldData>,
|
pub field_data: Option<SchemaFieldData>,
|
||||||
/// Fields stored in the type.
|
|
||||||
pub stored_fields: Option<FieldsInformation>,
|
|
||||||
/// Bevy specific field, names of the types that type reflects. Mapping of the names to the data types is provided by [`SchemaTypesMetadata`].
|
/// Bevy specific field, names of the types that type reflects. Mapping of the names to the data types is provided by [`SchemaTypesMetadata`].
|
||||||
pub reflect_type_data: Option<Vec<Cow<'static, str>>>,
|
pub reflect_type_data: Option<Vec<Cow<'static, str>>>,
|
||||||
|
/// Internal schema type information.
|
||||||
|
pub internal_schema_type: InternalSchemaType,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SchemaTypeInfo {
|
impl SchemaTypeInfo {
|
||||||
@ -1093,13 +1119,7 @@ impl SchemaTypeInfo {
|
|||||||
pub fn to_ref_schema(&self) -> JsonSchemaBevyType {
|
pub fn to_ref_schema(&self) -> JsonSchemaBevyType {
|
||||||
let range = self.get_range();
|
let range = self.get_range();
|
||||||
let description = self.get_docs();
|
let description = self.get_docs();
|
||||||
let internal_type: InternalSchemaType = (self).into();
|
let (ref_type, schema_type) = (self.ty_info.try_get_path_id(), self.into());
|
||||||
let (ref_type, schema_type) = (
|
|
||||||
self.ty_info
|
|
||||||
.try_get_type_reference_id()
|
|
||||||
.map(TypeReferencePath::definition),
|
|
||||||
self.into(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut schema = JsonSchemaBevyType {
|
let mut schema = JsonSchemaBevyType {
|
||||||
description,
|
description,
|
||||||
@ -1112,29 +1132,56 @@ impl SchemaTypeInfo {
|
|||||||
schema_type,
|
schema_type,
|
||||||
..default()
|
..default()
|
||||||
};
|
};
|
||||||
match internal_type {
|
match self.internal_schema_type.clone() {
|
||||||
InternalSchemaType::Array {
|
InternalSchemaType::Array {
|
||||||
element_ty,
|
element_ty,
|
||||||
min_size,
|
min_size,
|
||||||
max_size,
|
max_size,
|
||||||
} => {
|
} => {
|
||||||
schema.ref_type = None;
|
schema.ref_type = None;
|
||||||
let items_schema = SchemaTypeInfo {
|
let items_schema = element_ty.to_schema_type_info();
|
||||||
ty_info: element_ty.clone(),
|
|
||||||
field_data: None,
|
|
||||||
stored_fields: None,
|
|
||||||
reflect_type_data: None,
|
|
||||||
};
|
|
||||||
schema.items = Some(items_schema.to_ref_schema().into());
|
schema.items = Some(items_schema.to_ref_schema().into());
|
||||||
schema.min_items = min_size;
|
schema.min_items = min_size;
|
||||||
schema.max_items = max_size;
|
schema.max_items = max_size;
|
||||||
}
|
}
|
||||||
InternalSchemaType::EnumHolder(_)
|
InternalSchemaType::Map { key, value } => {
|
||||||
| InternalSchemaType::EnumVariant(_)
|
schema.kind = Some(SchemaKind::Map);
|
||||||
| InternalSchemaType::FieldsHolder(_)
|
let key_info = SchemaTypeInfo {
|
||||||
| InternalSchemaType::Map { key: _, value: _ } => {
|
ty_info: key.clone(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
schema.key_type = Some(key_info.to_ref_schema().into());
|
||||||
|
let value_info = SchemaTypeInfo {
|
||||||
|
ty_info: value.clone(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
schema.value_type = Some(value_info.to_ref_schema().into());
|
||||||
|
|
||||||
|
if let Some(p) = key.try_get_regex_for_type() {
|
||||||
|
schema.pattern_properties = [(p, value_info.to_ref_schema().into())].into();
|
||||||
|
schema.additional_properties = Some(JsonSchemaVariant::BoolValue(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InternalSchemaType::EnumHolder(_) | InternalSchemaType::EnumVariant(_) => {
|
||||||
schema.ref_type = None;
|
schema.ref_type = None;
|
||||||
}
|
}
|
||||||
|
InternalSchemaType::Optional {
|
||||||
|
generic,
|
||||||
|
ref schema_type_info,
|
||||||
|
} => {
|
||||||
|
let schema_optional = SchemaTypeInfo {
|
||||||
|
ty_info: TypeInformation::Type(Box::new(*generic.ty())),
|
||||||
|
..(**schema_type_info).clone()
|
||||||
|
};
|
||||||
|
schema.ref_type = None;
|
||||||
|
schema.one_of = vec![
|
||||||
|
Box::new(JsonSchemaBevyType {
|
||||||
|
schema_type: Some(SchemaTypeVariant::Single(SchemaType::Null)),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
Box::new(schema_optional.to_ref_schema()),
|
||||||
|
];
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1152,7 +1199,7 @@ impl SchemaTypeInfo {
|
|||||||
definitions,
|
definitions,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
let range = self.ty_info.get_range();
|
let range = self.get_range();
|
||||||
|
|
||||||
let (type_path, short_path, crate_name, module_path) =
|
let (type_path, short_path, crate_name, module_path) =
|
||||||
if let Some(type_path_table) = self.ty_info.try_get_type_path_table() {
|
if let Some(type_path_table) = self.ty_info.try_get_type_path_table() {
|
||||||
@ -1165,9 +1212,10 @@ impl SchemaTypeInfo {
|
|||||||
} else {
|
} else {
|
||||||
(Cow::default(), Cow::default(), None, None)
|
(Cow::default(), Cow::default(), None, None)
|
||||||
};
|
};
|
||||||
let schema_id = id
|
let schema_id = self
|
||||||
.as_ref()
|
.ty_info
|
||||||
.map(|id| Cow::Owned(format!("urn:bevy:{id}")))
|
.try_get_path_id()
|
||||||
|
.map(|id| Cow::Owned(id.to_string()))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let mut schema = JsonSchemaBevyType {
|
let mut schema = JsonSchemaBevyType {
|
||||||
id: schema_id,
|
id: schema_id,
|
||||||
@ -1185,30 +1233,26 @@ impl SchemaTypeInfo {
|
|||||||
reflect_type_data: self.reflect_type_data.clone().unwrap_or_default(),
|
reflect_type_data: self.reflect_type_data.clone().unwrap_or_default(),
|
||||||
..default()
|
..default()
|
||||||
};
|
};
|
||||||
let internal_type: InternalSchemaType = (self).into();
|
match self.internal_schema_type.clone() {
|
||||||
match internal_type {
|
|
||||||
InternalSchemaType::Map { key, value } => {
|
InternalSchemaType::Map { key, value } => {
|
||||||
let key: SchemaTypeInfo = SchemaTypeInfo {
|
let key = key.to_schema_type_info();
|
||||||
ty_info: key.clone(),
|
let value = value.to_schema_type_info();
|
||||||
field_data: None,
|
if let Some(_) = key.ty_info.try_get_primitive_type() {
|
||||||
stored_fields: None,
|
if let Some(p) = key.ty_info.try_get_regex_for_type() {
|
||||||
reflect_type_data: None,
|
schema.pattern_properties = [(p, value.to_ref_schema().into())].into();
|
||||||
};
|
schema.additional_properties = Some(JsonSchemaVariant::BoolValue(false));
|
||||||
let value: SchemaTypeInfo = SchemaTypeInfo {
|
}
|
||||||
ty_info: value.clone(),
|
} else {
|
||||||
field_data: None,
|
{
|
||||||
stored_fields: None,
|
let SchemaDefinition {
|
||||||
reflect_type_data: None,
|
id,
|
||||||
};
|
schema: _,
|
||||||
if !key.ty_info.is_primitive_type() {
|
definitions: field_definitions,
|
||||||
let SchemaDefinition {
|
} = key.to_definition();
|
||||||
id,
|
if let Some(id) = id {
|
||||||
schema: _,
|
definitions.insert(id, key.clone());
|
||||||
definitions: field_definitions,
|
definitions.extend(field_definitions);
|
||||||
} = key.to_definition();
|
}
|
||||||
if let Some(id) = id {
|
|
||||||
definitions.insert(id, key.clone());
|
|
||||||
definitions.extend(field_definitions);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !value.ty_info.is_primitive_type() {
|
if !value.ty_info.is_primitive_type() {
|
||||||
@ -1222,7 +1266,6 @@ impl SchemaTypeInfo {
|
|||||||
definitions.extend(field_definitions);
|
definitions.extend(field_definitions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
schema.additional_properties = Some(key.to_ref_schema().into());
|
|
||||||
schema.value_type = Some(value.to_ref_schema().into());
|
schema.value_type = Some(value.to_ref_schema().into());
|
||||||
schema.key_type = Some(key.to_ref_schema().into());
|
schema.key_type = Some(key.to_ref_schema().into());
|
||||||
}
|
}
|
||||||
@ -1258,10 +1301,12 @@ impl SchemaTypeInfo {
|
|||||||
let schema_field = SchemaTypeInfo {
|
let schema_field = SchemaTypeInfo {
|
||||||
ty_info,
|
ty_info,
|
||||||
field_data,
|
field_data,
|
||||||
stored_fields: Some(FieldsInformation {
|
internal_schema_type: InternalSchemaType::FieldsHolder(
|
||||||
fields,
|
FieldsInformation {
|
||||||
fields_type: FieldType::Named,
|
fields,
|
||||||
}),
|
fields_type: FieldType::Named,
|
||||||
|
},
|
||||||
|
),
|
||||||
reflect_type_data: None,
|
reflect_type_data: None,
|
||||||
};
|
};
|
||||||
let definition = schema_field.to_definition();
|
let definition = schema_field.to_definition();
|
||||||
@ -1276,11 +1321,13 @@ impl SchemaTypeInfo {
|
|||||||
let schema_field = SchemaTypeInfo {
|
let schema_field = SchemaTypeInfo {
|
||||||
ty_info,
|
ty_info,
|
||||||
field_data: None,
|
field_data: None,
|
||||||
stored_fields: Some(FieldsInformation {
|
internal_schema_type: InternalSchemaType::FieldsHolder(
|
||||||
fields: stored_fields,
|
FieldsInformation {
|
||||||
|
fields: stored_fields,
|
||||||
|
|
||||||
fields_type: FieldType::Unnamed,
|
fields_type: FieldType::Unnamed,
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
reflect_type_data: None,
|
reflect_type_data: None,
|
||||||
};
|
};
|
||||||
let definition = schema_field.to_definition();
|
let definition = schema_field.to_definition();
|
||||||
@ -1291,10 +1338,11 @@ impl SchemaTypeInfo {
|
|||||||
schema.required = vec![variant_info.name().into()];
|
schema.required = vec![variant_info.name().into()];
|
||||||
}
|
}
|
||||||
VariantInfo::Unit(unit_variant_info) => {
|
VariantInfo::Unit(unit_variant_info) => {
|
||||||
|
let internal_schema_type = (&ty_info).into();
|
||||||
let schema_field = SchemaTypeInfo {
|
let schema_field = SchemaTypeInfo {
|
||||||
ty_info,
|
ty_info,
|
||||||
field_data,
|
field_data,
|
||||||
stored_fields: None,
|
internal_schema_type,
|
||||||
reflect_type_data: None,
|
reflect_type_data: None,
|
||||||
};
|
};
|
||||||
return SchemaDefinition {
|
return SchemaDefinition {
|
||||||
@ -1347,6 +1395,7 @@ impl SchemaTypeInfo {
|
|||||||
}
|
}
|
||||||
FieldType::Unnamed if fields.fields.len() == 1 => {
|
FieldType::Unnamed if fields.fields.len() == 1 => {
|
||||||
let field_schema = SchemaTypeInfo::from(&fields.fields[0]);
|
let field_schema = SchemaTypeInfo::from(&fields.fields[0]);
|
||||||
|
|
||||||
let SchemaDefinition {
|
let SchemaDefinition {
|
||||||
id,
|
id,
|
||||||
schema: new_schema_type,
|
schema: new_schema_type,
|
||||||
@ -1354,11 +1403,41 @@ impl SchemaTypeInfo {
|
|||||||
} = field_schema.to_definition();
|
} = field_schema.to_definition();
|
||||||
definitions.extend(field_definitions);
|
definitions.extend(field_definitions);
|
||||||
if let Some(id) = id {
|
if let Some(id) = id {
|
||||||
definitions.insert(id, field_schema);
|
definitions.insert(id.clone(), field_schema);
|
||||||
|
schema.ref_type = Some(TypeReferencePath::definition(id));
|
||||||
|
} else {
|
||||||
|
let (type_path, short_path, crate_name, module_path) =
|
||||||
|
if let Some(type_path_table) = self.ty_info.try_get_type_path_table() {
|
||||||
|
(
|
||||||
|
type_path_table.path().into(),
|
||||||
|
type_path_table.short_path().into(),
|
||||||
|
type_path_table.crate_name().map(Into::into),
|
||||||
|
type_path_table.module_path().map(Into::into),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(Cow::default(), Cow::default(), None, None)
|
||||||
|
};
|
||||||
|
schema = JsonSchemaBevyType {
|
||||||
|
id: self
|
||||||
|
.ty_info
|
||||||
|
.try_get_type_reference_id()
|
||||||
|
.map(|id| {
|
||||||
|
Cow::Owned(
|
||||||
|
TypeReferencePath::new_ref(ReferenceLocation::Urn, id)
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.unwrap_or_default(),
|
||||||
|
short_path,
|
||||||
|
type_path,
|
||||||
|
module_path,
|
||||||
|
crate_name,
|
||||||
|
kind: Some(SchemaKind::TupleStruct),
|
||||||
|
schema_type: self.into(),
|
||||||
|
description: self.get_docs(),
|
||||||
|
..new_schema_type
|
||||||
|
};
|
||||||
}
|
}
|
||||||
schema = new_schema_type;
|
|
||||||
schema.schema_type = self.into();
|
|
||||||
schema.description = self.get_docs();
|
|
||||||
}
|
}
|
||||||
s => {
|
s => {
|
||||||
let schema_fields: Vec<SchemaTypeInfo> =
|
let schema_fields: Vec<SchemaTypeInfo> =
|
||||||
@ -1417,12 +1496,7 @@ impl SchemaTypeInfo {
|
|||||||
max_size,
|
max_size,
|
||||||
} => {
|
} => {
|
||||||
id = None;
|
id = None;
|
||||||
let items_schema = SchemaTypeInfo {
|
let items_schema = element_ty.to_schema_type_info();
|
||||||
ty_info: element_ty.clone(),
|
|
||||||
field_data: None,
|
|
||||||
stored_fields: None,
|
|
||||||
reflect_type_data: None,
|
|
||||||
};
|
|
||||||
schema.items = Some(items_schema.to_ref_schema().into());
|
schema.items = Some(items_schema.to_ref_schema().into());
|
||||||
schema.min_items = min_size;
|
schema.min_items = min_size;
|
||||||
schema.max_items = max_size;
|
schema.max_items = max_size;
|
||||||
@ -1447,7 +1521,17 @@ impl SchemaTypeInfo {
|
|||||||
ty_info: TypeInformation::Type(Box::new(*generic.ty())),
|
ty_info: TypeInformation::Type(Box::new(*generic.ty())),
|
||||||
..(**schema_type_info).clone()
|
..(**schema_type_info).clone()
|
||||||
};
|
};
|
||||||
return schema_optional.to_definition();
|
let definition = schema_optional.to_definition();
|
||||||
|
definitions.extend(definition.definitions);
|
||||||
|
schema.ref_type = None;
|
||||||
|
schema.schema_type = None;
|
||||||
|
schema.one_of = vec![
|
||||||
|
Box::new(JsonSchemaBevyType {
|
||||||
|
schema_type: Some(SchemaTypeVariant::Single(SchemaType::Null)),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
Box::new(definition.schema),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SchemaDefinition {
|
SchemaDefinition {
|
||||||
@ -1552,11 +1636,11 @@ where
|
|||||||
fn from(value: &'a T) -> Self {
|
fn from(value: &'a T) -> Self {
|
||||||
let ty_info: TypeInformation = value.into();
|
let ty_info: TypeInformation = value.into();
|
||||||
let field_data: SchemaFieldData = value.into();
|
let field_data: SchemaFieldData = value.into();
|
||||||
let stored_fields = (&ty_info).into();
|
let internal_schema_type = (&ty_info).into();
|
||||||
SchemaTypeInfo {
|
SchemaTypeInfo {
|
||||||
ty_info,
|
ty_info,
|
||||||
field_data: Some(field_data),
|
field_data: Some(field_data),
|
||||||
stored_fields,
|
internal_schema_type,
|
||||||
reflect_type_data: None,
|
reflect_type_data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1688,12 +1772,57 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
pub(super) mod tests {
|
||||||
|
use bevy_ecs::component::Component;
|
||||||
use bevy_platform::collections::HashMap;
|
use bevy_platform::collections::HashMap;
|
||||||
use bevy_reflect::GetTypeRegistration;
|
use bevy_reflect::GetTypeRegistration;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
/// Validate a JSON schema against a set of valid and invalid instances.
|
||||||
|
pub fn validate<T: GetTypeRegistration + Serialize + Default>(
|
||||||
|
schema: JsonSchemaBevyType,
|
||||||
|
valid_instances: &[T],
|
||||||
|
valid_values: &[serde_json::Value],
|
||||||
|
invalid_values: &[serde_json::Value],
|
||||||
|
) {
|
||||||
|
let schema_value = serde_json::to_value(&schema).unwrap();
|
||||||
|
let schema_validator = jsonschema::options()
|
||||||
|
.with_draft(jsonschema::Draft::Draft202012)
|
||||||
|
.build(&schema_value)
|
||||||
|
.expect("Failed to build schema validator");
|
||||||
|
let default_value = serde_json::to_value(T::default()).unwrap();
|
||||||
|
assert!(
|
||||||
|
schema_validator.validate(&default_value).is_ok(),
|
||||||
|
"Default value is invalid: {}, schema: {}",
|
||||||
|
default_value,
|
||||||
|
serde_json::to_string_pretty(&schema_value).unwrap_or_default()
|
||||||
|
);
|
||||||
|
let mut errors = Vec::new();
|
||||||
|
let valid_instances: Vec<serde_json::Value> = valid_instances
|
||||||
|
.iter()
|
||||||
|
.flat_map(|s| serde_json::to_value(s).ok())
|
||||||
|
.collect();
|
||||||
|
for value in valid_instances.iter() {
|
||||||
|
if let Err(error) = schema_validator.validate(value) {
|
||||||
|
errors.push(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for value in valid_values {
|
||||||
|
if let Err(error) = schema_validator.validate(&value) {
|
||||||
|
errors.push(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(
|
||||||
|
errors.is_empty(),
|
||||||
|
"Failed to validate valid instances, errors: {:?}",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
for value in invalid_values {
|
||||||
|
assert!(schema_validator.validate(&value).is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn integer_test() {
|
fn integer_test() {
|
||||||
let type_info = TypeInformation::from(&TypeId::of::<u16>()).to_schema_type_info();
|
let type_info = TypeInformation::from(&TypeId::of::<u16>()).to_schema_type_info();
|
||||||
@ -1753,10 +1882,10 @@ mod tests {
|
|||||||
let type_info = SchemaTypeInfo::from(field_info);
|
let type_info = SchemaTypeInfo::from(field_info);
|
||||||
let schema_type: Option<SchemaTypeVariant> = (&type_info).into();
|
let schema_type: Option<SchemaTypeVariant> = (&type_info).into();
|
||||||
let range = type_info.get_range();
|
let range = type_info.get_range();
|
||||||
assert!(!range.in_range((-1).into()));
|
assert!(!range.in_range(-1));
|
||||||
assert!(range.in_range(0.into()));
|
assert!(range.in_range(0));
|
||||||
assert!(range.in_range(12.into()));
|
assert!(range.in_range(12));
|
||||||
assert!(!range.in_range(13.into()));
|
assert!(!range.in_range(13));
|
||||||
assert_eq!(range.min, Some(BoundValue::Inclusive(0.into())));
|
assert_eq!(range.min, Some(BoundValue::Inclusive(0.into())));
|
||||||
assert_eq!(range.max, Some(BoundValue::Exclusive(13.into())));
|
assert_eq!(range.max, Some(BoundValue::Exclusive(13.into())));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -1765,6 +1894,8 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(type_info.get_docs(), Some("Test documentation".into()));
|
assert_eq!(type_info.get_docs(), Some("Test documentation".into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "bevy_math")]
|
||||||
#[test]
|
#[test]
|
||||||
fn other_ss_test() {
|
fn other_ss_test() {
|
||||||
#[derive(Reflect, Default, Deserialize, Serialize)]
|
#[derive(Reflect, Default, Deserialize, Serialize)]
|
||||||
@ -1772,6 +1903,19 @@ mod tests {
|
|||||||
/// Test doc
|
/// Test doc
|
||||||
a: u16,
|
a: u16,
|
||||||
}
|
}
|
||||||
|
validate::<Foo>(
|
||||||
|
TypeInformation::TypeRegistration(Foo::get_type_registration())
|
||||||
|
.to_schema_type_info()
|
||||||
|
.to_definition()
|
||||||
|
.into(),
|
||||||
|
&[Foo { a: 5 }, Foo { a: 1111 }],
|
||||||
|
&[serde_json::json!({"a": 5}), serde_json::json!({"a": 1})],
|
||||||
|
&[
|
||||||
|
serde_json::json!({"a": 1111111}),
|
||||||
|
serde_json::json!({"ab": -5555}),
|
||||||
|
serde_json::json!({"a": 5555,"b": 5555}),
|
||||||
|
],
|
||||||
|
);
|
||||||
let atr = bevy_ecs::reflect::AppTypeRegistry::default();
|
let atr = bevy_ecs::reflect::AppTypeRegistry::default();
|
||||||
{
|
{
|
||||||
let mut register = atr.write();
|
let mut register = atr.write();
|
||||||
@ -1785,7 +1929,24 @@ mod tests {
|
|||||||
.expect(""),
|
.expect(""),
|
||||||
)
|
)
|
||||||
.to_schema_type_info();
|
.to_schema_type_info();
|
||||||
let _: JsonSchemaBevyType = declaration.to_definition().into();
|
let schema: JsonSchemaBevyType = declaration.to_definition().into();
|
||||||
|
|
||||||
|
validate::<bevy_math::Vec3>(
|
||||||
|
schema,
|
||||||
|
&[
|
||||||
|
bevy_math::Vec3::new(5.0, 4.0, 4.0),
|
||||||
|
bevy_math::Vec3::new(25.0, 4.0, 4.0),
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
serde_json::json!([0, 4, 5]),
|
||||||
|
serde_json::json!([5.1, 5.2, 5.3]),
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
serde_json::json!([5.1, 5.2]),
|
||||||
|
serde_json::json!([5.1, 5.2, 4, 4]),
|
||||||
|
serde_json::json!({"x": 5.1, "y": 5.2, "z": 5.3}),
|
||||||
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -1812,6 +1973,15 @@ mod tests {
|
|||||||
Some(SchemaTypeVariant::Single(SchemaType::Integer))
|
Some(SchemaTypeVariant::Single(SchemaType::Integer))
|
||||||
);
|
);
|
||||||
assert_eq!(type_info.get_docs(), Some("Test documentation".into()));
|
assert_eq!(type_info.get_docs(), Some("Test documentation".into()));
|
||||||
|
validate::<TupleTest>(
|
||||||
|
TypeInformation::TypeRegistration(TupleTest::get_type_registration())
|
||||||
|
.to_schema_type_info()
|
||||||
|
.to_definition()
|
||||||
|
.into(),
|
||||||
|
&[TupleTest(10), TupleTest(11), TupleTest(0)],
|
||||||
|
&[serde_json::json!(5), serde_json::json!(1)],
|
||||||
|
&[serde_json::json!(55), serde_json::json!(-5555)],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -1828,17 +1998,51 @@ mod tests {
|
|||||||
Variant3(isize, usize),
|
Variant3(isize, usize),
|
||||||
Variant4(usize),
|
Variant4(usize),
|
||||||
}
|
}
|
||||||
|
let type_info =
|
||||||
|
TypeInformation::from(&EnumTest::get_type_registration()).to_schema_type_info();
|
||||||
|
let schema: JsonSchemaBevyType = type_info.to_definition().into();
|
||||||
|
validate::<EnumTest>(
|
||||||
|
schema,
|
||||||
|
&[
|
||||||
|
EnumTest::Variant1,
|
||||||
|
EnumTest::Variant2 {
|
||||||
|
field1: "test".into(),
|
||||||
|
field2: 42,
|
||||||
|
},
|
||||||
|
EnumTest::Variant3(1, 2),
|
||||||
|
EnumTest::Variant4(3),
|
||||||
|
],
|
||||||
|
&[],
|
||||||
|
&[],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn reflect_struct_with_array() {
|
fn reflect_struct_with_array() {
|
||||||
#[derive(Reflect, Default, Deserialize, Serialize)]
|
#[derive(Reflect, Default, Deserialize, Serialize)]
|
||||||
pub struct ArrayComponent {
|
pub struct ArrayComponent {
|
||||||
pub array: [i32; 3],
|
pub array: [u8; 3],
|
||||||
}
|
}
|
||||||
let type_info =
|
let type_info =
|
||||||
TypeInformation::from(&ArrayComponent::get_type_registration()).to_schema_type_info();
|
TypeInformation::from(&ArrayComponent::get_type_registration()).to_schema_type_info();
|
||||||
let _: JsonSchemaBevyType = type_info.to_definition().into();
|
let schema: JsonSchemaBevyType = type_info.to_definition().into();
|
||||||
|
validate::<ArrayComponent>(
|
||||||
|
schema,
|
||||||
|
&[
|
||||||
|
ArrayComponent::default(),
|
||||||
|
ArrayComponent { array: [1, 2, 3] },
|
||||||
|
ArrayComponent { array: [4, 5, 6] },
|
||||||
|
ArrayComponent { array: [7, 8, 9] },
|
||||||
|
],
|
||||||
|
&[],
|
||||||
|
&[
|
||||||
|
serde_json::json!({"array": [0,5]}),
|
||||||
|
serde_json::json!({"array": [0,5,-1]}),
|
||||||
|
serde_json::json!({"aa": [0,5,5]}),
|
||||||
|
serde_json::json!({"array": [0,5,5,5]}),
|
||||||
|
serde_json::json!({"array": [0.1,5.1,5]}),
|
||||||
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -1869,8 +2073,7 @@ mod tests {
|
|||||||
type_info.definitions.len(),
|
type_info.definitions.len(),
|
||||||
type_info_second.definitions.len()
|
type_info_second.definitions.len()
|
||||||
);
|
);
|
||||||
// let schema: JsonSchemaBevyType = type_info_second.into();
|
validate::<ArrayComponentWithMoreVariants>(type_info_second.into(), &[], &[], &[]);
|
||||||
// eprintln!("{}", serde_json::to_string_pretty(&schema).expect(""));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -1879,13 +2082,23 @@ mod tests {
|
|||||||
pub struct HashMapStruct {
|
pub struct HashMapStruct {
|
||||||
pub map: HashMap<i32, Option<i32>>,
|
pub map: HashMap<i32, Option<i32>>,
|
||||||
}
|
}
|
||||||
assert!(serde_json::from_str::<HashMapStruct>(
|
|
||||||
"{\"map\": {\"0\": 1, \"1\": 41, \"2\": null}}"
|
|
||||||
)
|
|
||||||
.is_ok());
|
|
||||||
let type_info =
|
let type_info =
|
||||||
TypeInformation::from(&HashMapStruct::get_type_registration()).to_schema_type_info();
|
TypeInformation::from(&HashMapStruct::get_type_registration()).to_schema_type_info();
|
||||||
let _: JsonSchemaBevyType = type_info.to_definition().into();
|
let schema: JsonSchemaBevyType = type_info.to_definition().into();
|
||||||
|
validate::<HashMapStruct>(
|
||||||
|
schema,
|
||||||
|
&[HashMapStruct {
|
||||||
|
map: [(5, Some(10)), (15, Some(20)), (-25, Some(30))].into(),
|
||||||
|
}],
|
||||||
|
&[
|
||||||
|
serde_json::json!({"map": {"-5": 10}}),
|
||||||
|
serde_json::json!({"map": {"5": None::<i32>}}),
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
serde_json::json!({"map": {"5.5": 10}}),
|
||||||
|
serde_json::json!({"map": {"s": 10}}),
|
||||||
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -1913,11 +2126,88 @@ mod tests {
|
|||||||
}
|
}
|
||||||
let type_info =
|
let type_info =
|
||||||
TypeInformation::from(&NestedStruct::get_type_registration()).to_schema_type_info();
|
TypeInformation::from(&NestedStruct::get_type_registration()).to_schema_type_info();
|
||||||
let _s: JsonSchemaBevyType = type_info.to_definition().into();
|
let s: JsonSchemaBevyType = type_info.to_definition().into();
|
||||||
// eprintln!("{}", serde_json::to_string_pretty(&s).expect("msg"));
|
assert_eq!(s.definitions.len(), 3);
|
||||||
// eprintln!(
|
validate::<NestedStruct>(
|
||||||
// "{}",
|
s,
|
||||||
// serde_json::to_string_pretty(&NestedStruct::default()).expect("msg")
|
&[NestedStruct {
|
||||||
// );
|
other: OtherStruct { field: "s".into() },
|
||||||
|
..Default::default()
|
||||||
|
}],
|
||||||
|
&[],
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reflect_tuple_struct_with_one_field_that_is_struct() {
|
||||||
|
use bevy_ecs::prelude::ReflectComponent;
|
||||||
|
use bevy_reflect::prelude::ReflectDefault;
|
||||||
|
|
||||||
|
#[derive(Reflect, Default, Deserialize, Serialize)]
|
||||||
|
pub struct ThirdStruct {
|
||||||
|
pub map_strings: HashMap<String, i32>,
|
||||||
|
}
|
||||||
|
#[derive(Reflect, Default, Deserialize, Serialize, Component)]
|
||||||
|
#[reflect(Component, Default)]
|
||||||
|
/// A tuple struct with one field.
|
||||||
|
pub struct TupleStruct(pub HashMap<String, i32>);
|
||||||
|
|
||||||
|
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(
|
||||||
|
[
|
||||||
|
("s".to_string(), 0),
|
||||||
|
("b".to_string(), 5),
|
||||||
|
("c".to_string(), 10),
|
||||||
|
]
|
||||||
|
.into(),
|
||||||
|
)],
|
||||||
|
&[serde_json::json!({"json": 5})],
|
||||||
|
&[serde_json::json!("json")],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn reflect_tuple_struct_with_one_field() {
|
||||||
|
use bevy_ecs::prelude::ReflectComponent;
|
||||||
|
use bevy_reflect::prelude::ReflectDefault;
|
||||||
|
#[derive(Reflect, Deserialize, Serialize, Component)]
|
||||||
|
#[reflect(Component, Default)]
|
||||||
|
/// A tuple struct with one field.
|
||||||
|
pub struct TupleStruct(#[reflect(@10..=50i8)] pub i8);
|
||||||
|
impl Default for TupleStruct {
|
||||||
|
fn default() -> Self {
|
||||||
|
TupleStruct(15)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let type_info =
|
||||||
|
TypeInformation::from(&TupleStruct::get_type_registration()).to_schema_type_info();
|
||||||
|
let s: JsonSchemaBevyType = type_info.to_definition().into();
|
||||||
|
let range: MinMaxValues = (&s).into();
|
||||||
|
assert!(!range.in_range(51));
|
||||||
|
assert!(range.in_range(15));
|
||||||
|
assert!(range.in_range(50));
|
||||||
|
assert!(!range.in_range(51));
|
||||||
|
|
||||||
|
validate::<TupleStruct>(
|
||||||
|
s,
|
||||||
|
&[TupleStruct(15)],
|
||||||
|
&[
|
||||||
|
serde_json::json!(15),
|
||||||
|
serde_json::json!(50),
|
||||||
|
serde_json::json!(49),
|
||||||
|
serde_json::json!(10),
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
serde_json::json!(9),
|
||||||
|
serde_json::json!(51),
|
||||||
|
serde_json::json!(-1),
|
||||||
|
serde_json::json!(5.3),
|
||||||
|
serde_json::Value::Null,
|
||||||
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user