-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
BRP registry JSON schema endpoint #16882
Conversation
Do you think it will be possible to do the inverse as well eventually? Take a serialized map and load it into the registry? Maybe @MrGVSV knows. |
"typeInfo": "Struct", | ||
"long_name": t.type_path(), | ||
"properties": properties, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Personally, I think these should follow the naming conventions in bevy_reflect.
So for example, typeInfo
would be kind
, long_name
would be type_path
, properties
would be fields
, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Responded here: #16882 (comment)
"typeInfo": "Struct", | ||
"long_name": t.type_path(), | ||
"properties": properties, | ||
"additionalProperties": false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this endpoint could use some documentation. At first glance, I'm not really sure what additionalProperties
means.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one is described here: https://json-schema.org/draft/2020-12/json-schema-core#section-10.3.2.3
But I am not sure about one thing- does it apply only when patternProperties
are present, or is it apply also when only properties
are.
"required": info | ||
.iter() | ||
.filter(|field| !field.type_path().starts_with("core::option::Option")) | ||
.map(NamedField::name) | ||
.collect::<Vec<_>>(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Could we store a required
property on the field itself rather than duplicating field names here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is part of the standard: https://json-schema.org/draft/2020-12/json-schema-validation#section-6.5.3
"additionalProperties": false, | ||
"required": info | ||
.iter() | ||
.filter(|field| !field.type_path().starts_with("core::option::Option")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do think Option
tends to imply optionality, but this might not always be true, especially when used by APIs to determine if a value can be "skipped" or not.
Ideally, Bevy would expose a common Required
attribute that could be applied to required fields (or vice-versa if we want the default to be that all fields are required) like #[reflect(@Required)]
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I would prefer to have right now is being sure that when we are not providing field etc. for value Rust is able to infer the default value, like when you specify the #[serde(default)]
for a field. But I am not sure yet how to do it here.
"long_name": t.type_path(), | ||
"oneOf": info |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: why are some property names camelCase and others snake_case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Responded here: #16882 (comment)
.map(|variant| match variant { | ||
VariantInfo::Unit(v) => v.name(), | ||
_ => unreachable!(), | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: If you wanted you could also use variant.as_unit_variant().expect("variant is unit variant"")
. Although, it could be argued that the use of unreachable!
is a bit clearer.
"long_name": v.name(), | ||
"short_name": v.name().split("::").last().unwrap_or(v.name()), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could be wrong, but I think name
is always just an identifier. Is this split actually necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it doesn't seems to make sense, could be a result of moving code from some of the previous versions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated, now it's the other way around, I am adding base type path to the variant and combine them:
"typePath": format!("{}::{}", type_path, v.name()),
"shortPath": v.name(),
@@ -1017,4 +1242,37 @@ mod tests { | |||
entity: Entity::from_raw(0), | |||
}); | |||
} | |||
|
|||
#[test] | |||
fn reflect_export_test() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'd be great if we had some more tests for different kinds of types (e.g. enums, tuples, etc.).
Would also be helpful if one of the tests also tested the JSON output (i.e. compared the entire output to a string). This also helps to ensure that everything not only behaves correctly, but looks right too (and it's nice for for future devs to see what the result is actually supposed to look like).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, I will try to add some more tests.
I mean, maybe? I don't have too big knowledge about reflect in bevy but AFAIK there is a option to register type by providing |
Co-authored-by: Gino Valente <[email protected]>
I actually like the idea of having this sort of endpoint be separate from Creating the registry the other way around is going to be a bit more complex, involves working it into
So yeah, because of the complexity involved, I think it's better to work out solutions in |
Co-authored-by: Gino Valente <[email protected]>
The reason I ask is because bsn is reflection-backed and (as far as I can tell) can't even be parsed when it uses a type that's not defined locally. Alice was wondering if this would let us get around that. From that point of view, doing stuff entirely on BRP won't be useful unless bsn is backed by BRP instead of reflection. And that isn't on the table. |
@MrGVSV For naming I think we should just follow https://json-schema.org/understanding-json-schema/keywords when possible. But we are providing here some values that are not definied in JSON Schema, this is mostly the ones with snake case, so I think for those we could try to follow Bevy naming scheme, but make them camel case so they are consistent with the rest of the fields. |
I see. We can definitely work towards it, but I wasn't planning on trying to get these sort of major changes into 0.16 since reflection just had a massive overhaul in 0.15, and this would likely be another major change. My current goal for 0.16 is just cleanup work, general improvements/fixes, and maybe some cool mostly-additive features (maybe some relatively small breaking changes). We could try to do it, but ideally most of the work would be completed and merged early into the cycle, and we're already a month in with no progress made yet 😅 |
Yeah no worries, just wondering about how much effort it would take. After some further thought, it's probably simpler just to write a second bsn parser that does use brp/schemas instead of reflection, as you suggested. |
…sResource when value is false
I did update branch quite a bit, most important changes:
|
I've updated structs documentation, added filtering options to the query, removed Example: #[derive(
bevy_reflect::Reflect, bevy_ecs::system::Resource, Default, Deserialize, Serialize,
)]
#[reflect(Resource, Default, Serialize, Deserialize)]
struct Foo {
a: f32,
} Type schema: {
"shortPath": "Foo",
"typePath": "bevy_remote::builtin_methods::tests::Foo",
"modulePath": "bevy_remote::builtin_methods::tests",
"crateName": "bevy_remote",
"reflectTypes": [
"Resource",
"Default",
"Serialize",
"Deserialize"
],
"kind": "Struct",
"type": "object",
"additionalProperties": false,
"properties": {
"a": {
"type": {
"$ref": "#/$defs/f32"
}
}
},
"required": [
"a"
]
} |
@MrGVSV IMHO now besides the more tests it is quite done, let me know how you feel about it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great!
# Objective Resolve bevyengine#16745 ## Solution Provide a way to map `AppTypeRegistry` types into a JSON Schema that can be used in other applications. I took code from https://github.com/kaosat-dev/Blenvy as a starting point, cleaned up and adapter more for `bevy_remote` needs. Based on feedback and needs it could be improved, I could add some filtering options, etc. ## Testing - I was comparing results with the ones from code in `blenvy` - There is added unit test, could be added more - I was testing it in my game with this code: ```rust fn types_to_file(world: &mut World) { use bevy_remote::builtin_methods::export_registry_types; let Ok(Ok(types_schema)) = world.run_system_cached_with(export_registry_types, None) else { return; }; let registry_save_path = std::path::Path::new("assets").join("registry.json"); let writer = std::fs::File::create(registry_save_path).expect("should have created schema file"); serde_json::to_writer_pretty(writer, &types_schema).expect("Failed to save types to file"); } ``` It can be run by adding it at startup ```rust app.add_systems(Startup, types_to_file); ``` --------- Co-authored-by: Gino Valente <[email protected]>
Objective
Resolve #16745
Solution
Provide a way to map
AppTypeRegistry
types into a JSON Schema that can be used in other applications. I took code from https://github.com/kaosat-dev/Blenvy as a starting point, cleaned up and adapter more forbevy_remote
needs. Based on feedback and needs it could be improved, I could add some filtering options, etc.Testing
blenvy
It can be run by adding it at startup