1#[cfg(feature = "protocol-agent")]
5mod agent;
6#[cfg(feature = "protocol-agent")]
7pub use agent::*;
8
9use crate::command::Command;
10use nightshade::ecs::audio::components::AudioSource;
11use nightshade::ecs::camera::components::Camera;
12use nightshade::ecs::decal::components::Decal;
13use nightshade::ecs::event::Event;
14use nightshade::ecs::gizmos::GizmoMode;
15use nightshade::ecs::light::components::Light;
16use nightshade::ecs::material::components::Material;
17use nightshade::ecs::particles::components::ParticleEmitter;
18use nightshade::ecs::scene::{SceneChunkConfig, SceneLayerConfig};
19use nightshade::ecs::text::components::TextProperties;
20use nightshade::ecs::vfx::components::{Beam, LightningBolt, Trail, VfxAnimator};
21use nightshade::ecs::water::components::Water;
22pub use nightshade::prelude::{
23 Atmosphere, CharacterControllerComponent, ColliderComponent, NavMeshAgent, RigidBodyComponent,
24};
25use serde::{Deserialize, Serialize};
26
27pub use nightshade::ecs::audio::components::AudioSource as AudioSourceComponent;
28pub use nightshade::ecs::camera::components::{
29 Camera as CameraComponent, OrthographicCamera, PerspectiveCamera, Projection,
30};
31pub use nightshade::ecs::decal::components::Decal as DecalComponent;
32pub use nightshade::ecs::gizmos::GizmoMode as GizmoModeKind;
33pub use nightshade::ecs::light::components::{AreaLightShape, Light as LightComponent, LightType};
34pub use nightshade::ecs::material::components::{AlphaMode, Material as MaterialComponent};
35pub use nightshade::ecs::particles::components::ParticleEmitter as ParticleEmitterComponent;
36pub use nightshade::ecs::physics::components::ColliderShape;
37pub use nightshade::ecs::physics::types::RigidBodyType;
38pub use nightshade::ecs::primitives::{CameraCullingMask, CullingMask, RenderLayer};
39pub use nightshade::ecs::text::components::TextProperties as TextPropertiesComponent;
40pub use nightshade::ecs::vfx::components::{
41 Beam as BeamComponent, LightningBolt as LightningBoltComponent, Trail as TrailComponent,
42 VfxAnimator as VfxAnimatorComponent,
43};
44pub use nightshade::ecs::water::components::Water as WaterComponent;
45pub use nightshade::prelude::{Atmosphere as AtmosphereKind, Vec2, Vec3, Vec4};
46
47pub const MESSAGE_KEY: &str = "message";
49pub const CANVAS_KEY: &str = "canvas";
51pub const BYTES_KEY: &str = "bytes";
53pub const GLTF_KEY: &str = "gltf";
55pub const RESOURCES_KEY: &str = "resources";
57
58#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
60pub enum TouchPhase {
61 Started,
62 Moved,
63 Ended,
64 Cancelled,
65}
66
67#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
69#[cfg_attr(
70 feature = "protocol-agent",
71 derive(enum2schema::Schema),
72 schema(string_enum)
73)]
74pub enum ShapeKind {
75 Cube,
76 Plane,
77 Cylinder,
78 Cone,
79 Sphere,
80 Torus,
81}
82
83#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
85#[cfg_attr(
86 feature = "protocol-agent",
87 derive(enum2schema::Schema),
88 schema(string_enum)
89)]
90pub enum LightKind {
91 Point,
92 Spot,
93 Directional,
94}
95
96#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
98#[cfg_attr(
99 feature = "protocol-agent",
100 derive(enum2schema::Schema),
101 schema(string_enum)
102)]
103pub enum GeneratorKind {
104 Building,
105 Room,
106 Tower,
107 Stairs,
108 Columns,
109 Perimeter,
110 CityBlock,
111 Courtyard,
112 Wfc,
113 WfcCity,
114}
115
116#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
118#[cfg_attr(
119 feature = "protocol-agent",
120 derive(enum2schema::Schema),
121 schema(string_enum)
122)]
123pub enum PolyhavenCategory {
124 Hdris,
125 Models,
126}
127
128pub use crate::reflect::{ComponentKind, ComponentPatch};
129
130#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
132#[cfg_attr(feature = "protocol-agent", derive(enum2schema::Schema))]
133pub enum AnimationCommand {
134 Play { index: u32 },
135 PlayAll,
136 Pause,
137 Resume,
138 Stop,
139 Seek { time: f32 },
140 SetSpeed { speed: f32 },
141 SetLooping { looping: bool },
142}
143
144#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
147#[cfg_attr(feature = "protocol-agent", derive(enum2schema::Schema))]
148pub struct GenerationSettings {
149 pub footprint_x: f32,
150 pub footprint_z: f32,
151 pub stories: u32,
152 pub story_height: f32,
153 pub wall_thickness: f32,
154 pub floor_thickness: f32,
155 pub window_spacing: f32,
156 pub seed: u32,
157 pub variation: f32,
158}
159
160#[derive(Clone, Copy, PartialEq, Serialize, Deserialize)]
163pub struct RenderSettingsState {
164 pub ambient_light: [f32; 4],
165 pub bloom_enabled: bool,
166 pub bloom_intensity: f32,
167 pub bloom_threshold: f32,
168 pub bloom_knee: f32,
169 pub bloom_filter_radius: f32,
170 pub ssao_enabled: bool,
171 pub ssao_radius: f32,
172 pub ssao_intensity: f32,
173 pub ssao_bias: f32,
174 pub ssao_sample_count: u32,
175 pub ssao_visualization: bool,
176 pub ssgi_enabled: bool,
177 pub ssgi_radius: f32,
178 pub ssgi_intensity: f32,
179 pub ssgi_max_steps: u32,
180 pub ssr_enabled: bool,
181 pub ssr_max_steps: u32,
182 pub ssr_thickness: f32,
183 pub ssr_max_distance: f32,
184 pub ssr_stride: f32,
185 pub ssr_fade_start: f32,
186 pub ssr_fade_end: f32,
187 pub ssr_intensity: f32,
188 pub fog_enabled: bool,
189 pub fog_color: [f32; 3],
190 pub fog_start: f32,
191 pub fog_end: f32,
192 pub dof_enabled: bool,
193 pub dof_focus_distance: f32,
194 pub dof_focus_range: f32,
195 pub dof_max_blur_radius: f32,
196 pub dof_bokeh_threshold: f32,
197 pub dof_bokeh_intensity: f32,
198 pub dof_quality: u32,
199 pub taa_enabled: bool,
200 pub render_scale: f32,
201 pub ibl_blend_factor: f32,
202 pub pbr_debug_index: u32,
203 pub unlit_mode: bool,
204 pub selection_outline_enabled: bool,
205 pub selection_outline_color: [f32; 4],
206 pub exposure: f32,
207 pub saturation: f32,
208 pub contrast: f32,
209 pub brightness: f32,
210 pub gamma: f32,
211 pub show_grid: bool,
212 pub show_sky: bool,
213 pub show_normals: bool,
214 pub navmesh_debug: bool,
215 pub atmosphere: Atmosphere,
216}
217
218#[derive(Clone, PartialEq, Serialize, Deserialize)]
220pub struct EditorFlags {
221 pub gizmo_mode: GizmoMode,
222 pub snap_enabled: bool,
223 pub snap_translation_step: f32,
224 pub snap_rotation_step_degrees: f32,
225 pub snap_scale_step: f32,
226 pub greybox_enabled: bool,
227 pub greybox_show_final: bool,
228 pub placement_active: bool,
229 pub placement_shape: ShapeKind,
230 pub placement_size: [f32; 3],
231 pub placement_orient: bool,
232 pub placement_status: String,
233 pub push_pull_active: bool,
234 pub scripting_active: bool,
235 pub playground_active: bool,
236 pub fly_mode: bool,
237 pub viewer_mode: bool,
238 pub frame_on_load: bool,
239 pub day_night_hour: f32,
240 pub day_night_auto: bool,
241 pub skeleton_view: bool,
242 pub rotation_speed: f32,
243 pub active_palette: usize,
244 pub edit_mode_target: Option<u32>,
245 pub generation: GenerationSettings,
246 pub global_scripts: Vec<nightshade::ecs::script::components::GlobalScript>,
247}
248
249#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
251#[cfg_attr(feature = "protocol-agent", derive(enum2schema::Schema))]
252pub enum GlobalScriptCommand {
253 Add,
254 AddTemplate { name: String, source: String },
255 Remove(u32),
256 MoveUp(u32),
257 MoveDown(u32),
258 SetEnabled { index: u32, enabled: bool },
259 SetSource { index: u32, source: String },
260 Rename { index: u32, name: String },
261}
262
263#[derive(Clone, PartialEq, Serialize, Deserialize)]
265pub struct TreeRow {
266 pub id: u32,
267 pub name: String,
268 pub depth: u32,
269 pub has_children: bool,
270 pub camera: bool,
271 pub light: bool,
272 pub mesh: bool,
273}
274
275#[derive(Clone, PartialEq, Serialize, Deserialize)]
277pub struct PaletteEntry {
278 pub label: String,
279 pub color: [f32; 3],
280}
281
282#[derive(Clone, PartialEq, Serialize, Deserialize)]
284pub struct PrefabLink {
285 pub name: String,
286 pub source_path: Option<String>,
287}
288
289#[derive(Clone, PartialEq, Serialize, Deserialize)]
292pub struct MorphMeshInfo {
293 pub id: u32,
294 pub name: String,
295 pub weights: Vec<f32>,
296}
297
298#[derive(Clone, PartialEq, Serialize, Deserialize)]
300pub struct AnimationInfo {
301 pub clips: Vec<String>,
302 pub current: Option<u32>,
303 pub playing: bool,
304 pub time: f32,
305 pub duration: f32,
306 pub speed: f32,
307 pub looping: bool,
308}
309
310#[derive(Clone, Serialize, Deserialize)]
312pub struct SelectedEntity {
313 pub id: u32,
314 pub revision: u32,
317 pub name: String,
318 pub translation: [f32; 3],
319 pub rotation: [f32; 3],
320 pub scale: [f32; 3],
321 pub mesh: Option<String>,
322 pub material_name: Option<String>,
323 pub tags: Vec<String>,
324 pub layer: Option<u32>,
325 pub chunk: Option<u32>,
326 pub prefab: Option<PrefabLink>,
327 pub animation: Option<AnimationInfo>,
328 pub morph_weights: Option<Vec<f32>>,
329 pub selected_count: u32,
330 pub visibility: Option<bool>,
331 pub casts_shadow: bool,
332 pub light: Option<Light>,
333 pub camera: Option<Camera>,
334 pub is_active_camera: bool,
335 pub rigid_body: Option<RigidBodyComponent>,
336 pub collider: Option<ColliderComponent>,
337 pub character_controller: Option<CharacterControllerComponent>,
338 pub navmesh_agent: Option<NavMeshAgent>,
339 pub particle_emitter: Option<Box<ParticleEmitter>>,
340 pub decal: Option<Decal>,
341 pub water: Option<Water>,
342 pub audio_source: Option<AudioSource>,
343 pub render_layer: Option<RenderLayer>,
344 pub culling_mask: Option<CullingMask>,
345 pub camera_culling_mask: Option<CameraCullingMask>,
346 pub ignore_parent_scale: bool,
347 pub text: Option<(String, TextProperties)>,
348 pub script: Option<String>,
349 pub beam: Option<Box<Beam>>,
350 pub lightning_bolt: Option<Box<LightningBolt>>,
351 pub trail: Option<Box<Trail>>,
352 pub vfx_animator: Option<Box<VfxAnimator>>,
353 pub present: Vec<ComponentKind>,
354 pub addable: Vec<ComponentKind>,
355}
356
357#[derive(Clone, Serialize, Deserialize)]
359pub struct MaterialEntry {
360 pub name: String,
361 pub material: Material,
362}
363
364#[derive(Clone, PartialEq, Serialize, Deserialize)]
366pub struct KhronosEntry {
367 pub label: String,
368 pub thumbnail: Option<String>,
369}
370
371#[derive(Clone, PartialEq, Serialize, Deserialize)]
373pub struct PolyhavenEntry {
374 pub slug: String,
375 pub name: String,
376 pub thumbnail: String,
377}
378
379#[derive(Clone, PartialEq, Serialize, Deserialize)]
381pub struct SceneStructure {
382 pub layers: Vec<SceneLayerConfig>,
383 pub chunks: Vec<SceneChunkConfig>,
384}
385
386#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
389#[cfg_attr(feature = "protocol-agent", derive(enum2schema::Schema))]
390pub enum EditorAction {
391 ToggleGroundGrid,
392 ToggleSky,
393 ToggleLitUnlit,
394 ToggleViewerMode,
395 SetViewerMode(bool),
396 FrameScene,
397 ToggleFrameOnLoad,
398 ClearScene,
399 SnapSelectionToFloor,
400 BakeNavmesh,
401 ToggleNavmeshDebug,
402 SetAtmosphere(Atmosphere),
403 SetGizmoMode(GizmoMode),
404 RandomizeBoth,
405 SelectEntity(u32),
406 ToggleSelectEntity(u32),
407 ClearSelection,
408 AddEntityChild(u32),
409 ViewCamera(u32),
410 ToggleSnap,
411 ToggleSkeletonView,
412 SetMaterialVariant(Option<String>),
413 ToggleCameraGizmos,
414 ToggleShowNormals,
415 ToggleDayNightCycle,
416 SetDayNightHour(f32),
417 LoadKhronosByIndex(usize),
418 LoadPolyhavenByIndex {
419 category: PolyhavenCategory,
420 index: usize,
421 },
422 SetPolyhavenResolution(u32),
423 RefreshBrowsers,
424 DeepLink(String),
425 SpawnLines,
426 SpawnMeshes,
427 Spawn3DText,
428 SpawnTextLattice,
429 ClothSponza,
430 NewProject,
431 LoadExampleProject,
432 SaveProject,
433 ExportGlb,
434 SaveSelectedAsPrefab,
435 RefreshSelectedPrefab,
436 BreakSelectedPrefabLink,
437 SpawnPrefab(String),
438 Undo,
439 Redo,
440 DeleteEntity(u32),
441 DeleteSelectedEntity,
442 DuplicateEntity(u32),
443 DuplicateSelectedEntity,
444 AddShape(ShapeKind),
445 AddInstancedShape(ShapeKind),
446 ConvertSimilarToInstanced,
447 AddEmpty,
448 AddCamera,
449 AddPlayerSpawn,
450 AddSavePoint,
451 AddPatrolPoint,
452 AddTriggerVolume,
453 StampDecalAtCursor,
454 AddLight(LightKind),
455 AddTagToSelected(String),
456 RemoveTagFromSelected(String),
457 ToggleGreyboxMode,
458 ApplyGreyboxToSelection,
459 SnapSelectionToGrid,
460 MarkSelectionAsBrush,
461 ToggleShowFinal,
462 ToggleScripting,
463 SetScripting(bool),
466 SetPlayground(bool),
470 LoadLesson(String),
472 GlobalScript(GlobalScriptCommand),
473 TogglePushPull,
474 SetPlacementActive(bool),
475 SetPlacementShape(ShapeKind),
476 SetPlacementSize([f32; 3]),
477 SetOrientToNormal(bool),
478 RunGenerator(GeneratorKind),
479 SetGenerationSettings(GenerationSettings),
480 SetPaletteIndex(usize),
481 SetSnapEnabled(bool),
482 SetSnapSteps {
483 translation: f32,
484 rotation_degrees: f32,
485 scale: f32,
486 },
487 SetRotationSpeed(f32),
488 SetEntityLayer {
489 id: u32,
490 layer: Option<u32>,
491 },
492 SetEntityChunk {
493 id: u32,
494 chunk: Option<u32>,
495 },
496 AddSceneLayer,
497 RemoveSceneLayer(u32),
498 UpdateSceneLayer(SceneLayerConfig),
499 AddSceneChunk,
500 RemoveSceneChunk(u32),
501 UpdateSceneChunk(SceneChunkConfig),
502}
503
504#[derive(Clone, Serialize, Deserialize)]
506pub struct CameraEntry {
507 pub id: u32,
508 pub name: String,
509}
510
511#[derive(Clone, Copy, Serialize, Deserialize)]
514pub struct ViewportTile {
515 pub camera: u32,
516 pub x: f32,
517 pub y: f32,
518 pub width: f32,
519 pub height: f32,
520}
521
522#[derive(Clone, Serialize, Deserialize)]
525pub enum ClientMessage {
526 Init {
528 width: f32,
529 height: f32,
530 },
531 Resize {
532 width: f32,
533 height: f32,
534 },
535 SetViewportTiles(Vec<ViewportTile>),
538 PointerMove {
539 x: f32,
540 y: f32,
541 },
542 PointerMotion {
545 dx: f32,
546 dy: f32,
547 },
548 PointerButton {
550 button: u8,
551 pressed: bool,
552 },
553 Wheel {
554 delta: f32,
555 },
556 Touch {
557 id: u64,
558 phase: TouchPhase,
559 x: f32,
560 y: f32,
561 },
562 Key {
565 code: String,
566 pressed: bool,
567 text: Option<String>,
568 },
569 Action(Box<EditorAction>),
570 SetTransform {
571 id: u32,
572 translation: [f32; 3],
573 rotation: [f32; 3],
574 scale: [f32; 3],
575 committed: bool,
576 },
577 SetEntityName {
578 id: u32,
579 name: String,
580 },
581 SetComponent {
582 id: u32,
583 payload: Box<ComponentPatch>,
584 committed: bool,
585 },
586 AddComponent {
587 id: u32,
588 kind: ComponentKind,
589 },
590 RemoveComponent {
591 id: u32,
592 kind: ComponentKind,
593 },
594 Animation {
595 id: u32,
596 command: AnimationCommand,
597 },
598 UpdateMaterial {
599 name: String,
600 material: Box<Material>,
601 committed: bool,
602 },
603 SetRenderSettings(Box<RenderSettingsState>),
604 DropAsset {
608 name: String,
609 },
610 LoadGltfBundle {
613 name: String,
614 },
615 UpgradeBrush {
617 name: String,
618 },
619 SubmitCommand {
624 command: String,
625 },
626 #[cfg(feature = "protocol-agent")]
629 Agent(Box<AgentRequest>),
630}
631
632#[derive(Clone, Serialize, Deserialize)]
634pub enum WorkerMessage {
635 Ready {
636 adapter: String,
637 },
638 Stats {
639 fps: f32,
640 entity_count: u32,
641 },
642 Camera {
644 right: [f32; 3],
645 up: [f32; 3],
646 forward: [f32; 3],
647 },
648 Scene {
649 rows: Vec<TreeRow>,
650 },
651 Selected {
652 detail: Option<Box<SelectedEntity>>,
653 },
654 Cameras {
657 entries: Vec<CameraEntry>,
658 },
659 Animation {
661 time: f32,
662 duration: f32,
663 playing: bool,
664 clip: Option<u32>,
665 },
666 MorphWeights {
669 id: u32,
670 weights: Vec<f32>,
671 },
672 MorphMeshes {
675 entries: Vec<MorphMeshInfo>,
676 },
677 MaterialVariants {
680 names: Vec<String>,
681 active: Option<String>,
682 },
683 RenderSettings(Box<RenderSettingsState>),
684 Flags(Box<EditorFlags>),
685 Materials {
686 entries: Vec<MaterialEntry>,
687 },
688 Tags {
689 entries: Vec<(String, u32)>,
690 },
691 Structure(SceneStructure),
692 Palette {
693 entries: Vec<PaletteEntry>,
694 },
695 KhronosList {
696 entries: Vec<KhronosEntry>,
697 },
698 PolyhavenList {
699 category: PolyhavenCategory,
700 entries: Vec<PolyhavenEntry>,
701 },
702 Loading {
703 active: bool,
704 label: String,
705 },
706 Project {
708 name: String,
709 modified: bool,
710 model: Option<String>,
711 mode: String,
712 },
713 SaveFile {
715 name: String,
716 },
717 PointerLock {
719 locked: bool,
720 },
721 FocusEntity {
723 id: u32,
724 },
725 UndoState {
726 can_undo: bool,
727 can_redo: bool,
728 },
729 Toast {
730 message: String,
731 error: bool,
732 },
733 CommandLog {
736 entries: Vec<CommandLogEntry>,
737 },
738 #[cfg(feature = "protocol-agent")]
741 Agent(Box<AgentResponse>),
742}
743
744#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
747pub enum CommandLogKind {
748 Command,
749 Event,
750 Error,
751}
752
753#[derive(Clone, Debug, Serialize, Deserialize)]
756pub struct CommandLogEntry {
757 pub cycle: u64,
758 pub kind: CommandLogKind,
759 pub label: String,
760 pub detail: String,
761 pub entities: Vec<u32>,
764}
765
766impl CommandLogEntry {
767 pub fn from_event(cycle: u64, event: &Event) -> Self {
770 Self {
771 cycle,
772 kind: CommandLogKind::Event,
773 label: event_label(event).to_string(),
774 detail: event_detail(event),
775 entities: event_entities(event),
776 }
777 }
778
779 pub fn from_command(cycle: u64, command: &Command) -> Self {
784 use enum2schema::serde_json::Value;
785 let value: Value = enum2schema::serde_json::to_value(command).unwrap_or(Value::Null);
786 let detail = value
787 .as_object()
788 .and_then(|object| object.values().next())
789 .map(|inner| inner.to_string())
790 .unwrap_or_else(|| value.to_string());
791 let mut entities = Vec::new();
792 collect_entity_ids(&value, &mut entities);
793 Self {
794 cycle,
795 kind: CommandLogKind::Command,
796 label: command.name().to_string(),
797 detail,
798 entities,
799 }
800 }
801
802 pub fn from_error(cycle: u64, message: String) -> Self {
806 Self {
807 cycle,
808 kind: CommandLogKind::Error,
809 label: "error".to_string(),
810 detail: message,
811 entities: Vec::new(),
812 }
813 }
814}
815
816fn event_label(event: &Event) -> &'static str {
817 match event {
818 Event::Collision { .. } => "Collision",
819 Event::Despawned { .. } => "Despawned",
820 Event::AnimationFinished { .. } => "AnimationFinished",
821 Event::AnimationEvent { .. } => "AnimationEvent",
822 Event::NavigationArrived { .. } => "NavigationArrived",
823 }
824}
825
826fn event_detail(event: &Event) -> String {
827 match event {
828 Event::Collision {
829 a,
830 b,
831 sensor,
832 started,
833 } => format!(
834 "#{} and #{}, {}{}",
835 a.id,
836 b.id,
837 if *started { "started" } else { "ended" },
838 if *sensor { ", sensor" } else { "" }
839 ),
840 Event::AnimationEvent { entity, name } => format!("#{} {}", entity.id, name),
841 Event::Despawned { entity }
842 | Event::AnimationFinished { entity }
843 | Event::NavigationArrived { entity } => format!("#{}", entity.id),
844 }
845}
846
847fn event_entities(event: &Event) -> Vec<u32> {
848 match event {
849 Event::Collision { a, b, .. } => vec![a.id, b.id],
850 Event::AnimationEvent { entity, .. } => vec![entity.id],
851 Event::Despawned { entity }
852 | Event::AnimationFinished { entity }
853 | Event::NavigationArrived { entity } => vec![entity.id],
854 }
855}
856
857fn collect_entity_ids(value: &enum2schema::serde_json::Value, ids: &mut Vec<u32>) {
860 use enum2schema::serde_json::Value;
861 match value {
862 Value::Object(map) => {
863 if let Some(id) = map.get("Existing").and_then(Value::as_u64) {
864 ids.push(id as u32);
865 }
866 if let Some(id) = map
867 .get("Entity")
868 .and_then(Value::as_object)
869 .and_then(|entity| entity.get("id"))
870 .and_then(Value::as_u64)
871 {
872 ids.push(id as u32);
873 }
874 for nested in map.values() {
875 collect_entity_ids(nested, ids);
876 }
877 }
878 Value::Array(array) => {
879 for nested in array {
880 collect_entity_ids(nested, ids);
881 }
882 }
883 _ => {}
884 }
885}
886
887#[cfg(all(test, feature = "protocol-agent"))]
888mod schema_tests {
889 use super::EditorAction;
890 use enum2schema::Schema;
891 use enum2schema::serde_json::Value;
892
893 fn assert_no_array_items(value: &Value) {
898 match value {
899 Value::Object(object) => {
900 if let Some(items) = object.get("items") {
901 assert!(
902 !items.is_array(),
903 "array-valued `items` is invalid in draft 2020-12, use `prefixItems`: {object:?}"
904 );
905 }
906 for nested in object.values() {
907 assert_no_array_items(nested);
908 }
909 }
910 Value::Array(array) => {
911 for nested in array {
912 assert_no_array_items(nested);
913 }
914 }
915 _ => {}
916 }
917 }
918
919 #[test]
920 fn editor_action_schema_is_draft_2020_12() {
921 assert_no_array_items(&EditorAction::schema());
922 }
923}