1use crate::core::config::PhyTraceConfig;
7use crate::error::{BuilderError, PhyTraceResult};
8use crate::models::domains::location::LocalCoordinates;
9use crate::models::domains::*;
10use crate::models::enums::{EventType, SourceType};
11use crate::models::event::UdmEvent;
12
13pub struct EventBuilder {
33 event: UdmEvent,
34 config: PhyTraceConfig,
35}
36
37impl Clone for EventBuilder {
38 fn clone(&self) -> Self {
39 Self {
40 event: self.event.clone(),
41 config: self.config.clone(),
42 }
43 }
44}
45
46impl EventBuilder {
47 pub fn new(config: &PhyTraceConfig) -> Self {
49 let mut event = UdmEvent::new(config.source.source_type);
50
51 event.source_id = config.source.source_id.clone();
53
54 event.identity = Some(IdentityDomain {
56 source_id: Some(config.source.source_id.clone()),
57 fleet_id: config.source.fleet_id.clone(),
58 site_id: config.source.site_id.clone(),
59 organization_id: config.source.organization_id.clone(),
60 tags: if config.source.tags.is_empty() {
61 None
62 } else {
63 Some(config.source.tags.clone())
64 },
65 ..Default::default()
66 });
67
68 Self {
69 event,
70 config: config.clone(),
71 }
72 }
73
74 pub fn event_type(mut self, event_type: EventType) -> Self {
76 self.event.event_type = event_type;
77 self
78 }
79
80 pub fn source_type(mut self, source_type: SourceType) -> Self {
82 self.event.source_type = source_type;
83 self
84 }
85
86 pub fn source_id(mut self, source_id: impl Into<String>) -> Self {
93 let sid = source_id.into();
94 self.event.source_id = sid.clone();
96 let identity = self.event.identity.get_or_insert_with(Default::default);
98 identity.source_id = Some(sid);
99 self
100 }
101
102 pub fn model_info(
104 mut self,
105 _manufacturer: impl Into<String>,
106 model: impl Into<String>,
107 firmware: impl Into<String>,
108 ) -> Self {
109 let identity = self.event.identity.get_or_insert_with(Default::default);
110 identity.model = Some(model.into());
111 identity.firmware_version = Some(firmware.into());
112 self
113 }
114
115 pub fn tag(mut self, tag: impl Into<String>) -> Self {
117 let identity = self.event.identity.get_or_insert_with(Default::default);
118 let tags = identity.tags.get_or_insert_with(Vec::new);
119 tags.push(tag.into());
120 self
121 }
122
123 pub fn position_local(mut self, x: f64, y: f64, z: f64) -> Self {
129 let location = self.event.location.get_or_insert_with(Default::default);
130 location.local = Some(LocalCoordinates {
131 x_m: Some(x),
132 y_m: Some(y),
133 z_m: Some(z),
134 ..Default::default()
135 });
136 self
137 }
138
139 pub fn position_2d(mut self, x: f64, y: f64) -> Self {
141 let location = self.event.location.get_or_insert_with(Default::default);
142 location.local = Some(LocalCoordinates {
143 x_m: Some(x),
144 y_m: Some(y),
145 z_m: Some(0.0),
146 ..Default::default()
147 });
148 self
149 }
150
151 pub fn position_3d(self, x: f64, y: f64, z: f64) -> Self {
153 self.position_local(x, y, z)
154 }
155
156 pub fn heading_deg(mut self, heading: f64) -> Self {
158 let location = self.event.location.get_or_insert_with(Default::default);
159 location.heading_deg = Some(heading);
160 self
161 }
162
163 pub fn heading(self, heading_rad: f64) -> Self {
165 let heading_deg = heading_rad.to_degrees();
166 self.heading_deg(heading_deg)
167 }
168
169 pub fn gps(mut self, latitude: f64, longitude: f64) -> Self {
171 let location = self.event.location.get_or_insert_with(Default::default);
172 location.latitude = Some(latitude);
173 location.longitude = Some(longitude);
174 self
175 }
176
177 pub fn floor(mut self, floor: i32) -> Self {
179 let location = self.event.location.get_or_insert_with(Default::default);
180 location.floor = Some(floor);
181 self
182 }
183
184 pub fn map(mut self, map_id: impl Into<String>, frame_id: impl Into<String>) -> Self {
186 let location = self.event.location.get_or_insert_with(Default::default);
187 location.map_id = Some(map_id.into());
188 location.frame_id = Some(frame_id.into());
189 self
190 }
191
192 pub fn linear_velocity(mut self, vx: f64, vy: f64, vz: f64) -> Self {
198 let motion = self.event.motion.get_or_insert_with(Default::default);
199 motion.linear_velocity = Some(motion::LinearVelocity {
200 x_mps: Some(vx),
201 y_mps: Some(vy),
202 z_mps: Some(vz),
203 });
204 self
205 }
206
207 pub fn angular_velocity(mut self, roll_rps: f64, pitch_rps: f64, yaw_rps: f64) -> Self {
209 let motion = self.event.motion.get_or_insert_with(Default::default);
210 motion.angular_velocity = Some(motion::AngularVelocity {
211 roll_dps: Some(roll_rps.to_degrees()),
212 pitch_dps: Some(pitch_rps.to_degrees()),
213 yaw_dps: Some(yaw_rps.to_degrees()),
214 });
215 self
216 }
217
218 pub fn speed(mut self, speed_mps: f64) -> Self {
220 let motion = self.event.motion.get_or_insert_with(Default::default);
221 motion.speed_mps = Some(speed_mps);
222 self
223 }
224
225 pub fn battery_soc(mut self, soc_percent: f64) -> Self {
231 let power = self.event.power.get_or_insert_with(Default::default);
232 let battery = power.battery.get_or_insert_with(Default::default);
233 battery.state_of_charge_pct = Some(soc_percent);
234 self
235 }
236
237 pub fn battery_voltage(mut self, voltage_v: f64) -> Self {
239 let power = self.event.power.get_or_insert_with(Default::default);
240 let battery = power.battery.get_or_insert_with(Default::default);
241 battery.voltage_v = Some(voltage_v);
242 self
243 }
244
245 pub fn battery_health(mut self, health_percent: f64) -> Self {
247 let power = self.event.power.get_or_insert_with(Default::default);
248 let battery = power.battery.get_or_insert_with(Default::default);
249 battery.state_of_health_pct = Some(health_percent);
250 self
251 }
252
253 pub fn charging(mut self, is_charging: bool) -> Self {
255 use crate::models::enums::ChargingState;
256 let power = self.event.power.get_or_insert_with(Default::default);
257 let charging = power.charging.get_or_insert_with(Default::default);
258 charging.state = Some(if is_charging {
259 ChargingState::Charging
260 } else {
261 ChargingState::NotCharging
262 });
263 self
264 }
265
266 pub fn operational_mode(mut self, mode: crate::models::enums::OperationalMode) -> Self {
272 let operational = self.event.operational.get_or_insert_with(Default::default);
273 operational.mode = Some(mode);
274 self
275 }
276
277 pub fn operational_state(mut self, state: crate::models::enums::OperationalState) -> Self {
279 let operational = self.event.operational.get_or_insert_with(Default::default);
280 operational.state = Some(state);
281 self
282 }
283
284 pub fn task(mut self, task_id: impl Into<String>, task_type: impl Into<String>) -> Self {
286 let operational = self.event.operational.get_or_insert_with(Default::default);
287 operational.task = Some(operational::Task {
288 task_id: Some(task_id.into()),
289 task_type: Some(task_type.into()),
290 ..Default::default()
291 });
292 self
293 }
294
295 pub fn safety_state(mut self, state: crate::models::enums::SafetyState) -> Self {
301 let safety = self.event.safety.get_or_insert_with(Default::default);
302 safety.safety_state = Some(state);
303 self
304 }
305
306 pub fn estop_active(mut self, active: bool) -> Self {
308 let safety = self.event.safety.get_or_insert_with(Default::default);
309 let estop = safety.e_stop.get_or_insert_with(Default::default);
310 estop.is_active = Some(active);
311 self
312 }
313
314 pub fn localization_quality(
320 mut self,
321 quality: crate::models::enums::LocalizationQuality,
322 ) -> Self {
323 let nav = self.event.navigation.get_or_insert_with(Default::default);
324 let loc = nav.localization.get_or_insert_with(Default::default);
325 loc.quality = Some(quality);
326 self
327 }
328
329 pub fn path_state(mut self, state: crate::models::enums::PathState) -> Self {
331 let nav = self.event.navigation.get_or_insert_with(Default::default);
332 let path = nav.path.get_or_insert_with(Default::default);
333 path.state = Some(state);
334 self
335 }
336
337 pub fn identity(mut self, identity: IdentityDomain) -> Self {
343 self.event.identity = Some(identity);
344 self
345 }
346
347 pub fn location(mut self, location: LocationDomain) -> Self {
349 self.event.location = Some(location);
350 self
351 }
352
353 pub fn motion(mut self, motion: MotionDomain) -> Self {
355 self.event.motion = Some(motion);
356 self
357 }
358
359 pub fn power(mut self, power: PowerDomain) -> Self {
361 self.event.power = Some(power);
362 self
363 }
364
365 pub fn operational(mut self, operational: OperationalDomain) -> Self {
367 self.event.operational = Some(operational);
368 self
369 }
370
371 pub fn navigation(mut self, navigation: NavigationDomain) -> Self {
373 self.event.navigation = Some(navigation);
374 self
375 }
376
377 pub fn perception(mut self, perception: PerceptionDomain) -> Self {
379 self.event.perception = Some(perception);
380 self
381 }
382
383 pub fn safety(mut self, safety: SafetyDomain) -> Self {
385 self.event.safety = Some(safety);
386 self
387 }
388
389 pub fn actuators(mut self, actuators: ActuatorsDomain) -> Self {
391 self.event.actuators = Some(actuators);
392 self
393 }
394
395 pub fn communication(mut self, communication: CommunicationDomain) -> Self {
397 self.event.communication = Some(communication);
398 self
399 }
400
401 pub fn compute(mut self, compute: ComputeDomain) -> Self {
403 self.event.compute = Some(compute);
404 self
405 }
406
407 pub fn ai(mut self, ai: AiDomain) -> Self {
409 self.event.ai = Some(ai);
410 self
411 }
412
413 pub fn maintenance(mut self, maintenance: MaintenanceDomain) -> Self {
415 self.event.maintenance = Some(maintenance);
416 self
417 }
418
419 pub fn context(mut self, context: ContextDomain) -> Self {
421 self.event.context = Some(context);
422 self
423 }
424
425 pub fn payload(mut self, payload: PayloadDomain) -> Self {
427 self.event.payload = Some(payload);
428 self
429 }
430
431 pub fn manipulation(mut self, manipulation: ManipulationDomain) -> Self {
433 self.event.manipulation = Some(manipulation);
434 self
435 }
436
437 pub fn hri(mut self, hri: HriDomain) -> Self {
439 self.event.hri = Some(hri);
440 self
441 }
442
443 pub fn coordination(mut self, coordination: CoordinationDomain) -> Self {
445 self.event.coordination = Some(coordination);
446 self
447 }
448
449 pub fn simulation(mut self, simulation: SimulationDomain) -> Self {
451 self.event.simulation = Some(simulation);
452 self
453 }
454
455 pub fn thermal(mut self, thermal: ThermalDomain) -> Self {
457 self.event.thermal = Some(thermal);
458 self
459 }
460
461 pub fn audio(mut self, audio: AudioDomain) -> Self {
463 self.event.audio = Some(audio);
464 self
465 }
466
467 pub fn environment_interaction(
469 mut self,
470 environment_interaction: EnvironmentInteractionDomain,
471 ) -> Self {
472 self.event.environment_interaction = Some(environment_interaction);
473 self
474 }
475
476 pub fn compliance(mut self, compliance: ComplianceDomain) -> Self {
478 self.event.compliance = Some(compliance);
479 self
480 }
481
482 pub fn extensions(mut self, extensions: serde_json::Value) -> Self {
484 self.event.extensions = Some(extensions);
485 self
486 }
487
488 pub fn build(self) -> PhyTraceResult<UdmEvent> {
494 if !self.event.has_domain_data() {
496 return Err(BuilderError::MissingField("at least one domain".to_string()).into());
497 }
498
499 Ok(self.event)
500 }
501
502 pub fn build_unchecked(self) -> UdmEvent {
504 self.event
505 }
506
507 pub fn peek(&self) -> &UdmEvent {
509 &self.event
510 }
511}
512
513#[cfg(test)]
514mod tests {
515 use super::*;
516 use crate::models::domains::{
517 ActuatorsDomain, AiDomain, AudioDomain, CommunicationDomain, ComplianceDomain,
518 ComputeDomain, ContextDomain, CoordinationDomain, EnvironmentInteractionDomain, HriDomain,
519 MaintenanceDomain, ManipulationDomain, MotionDomain, NavigationDomain, PayloadDomain,
520 PerceptionDomain, SafetyDomain, SimulationDomain, ThermalDomain,
521 };
522 use crate::models::enums::{
523 ChargingState, LocalizationQuality, OperationalMode, OperationalState, PathState,
524 SafetyState,
525 };
526
527 fn test_config() -> PhyTraceConfig {
528 PhyTraceConfig::new("robot-001").with_source_type(SourceType::Amr)
529 }
530
531 #[test]
532 fn test_builder_basic() {
533 let event = EventBuilder::new(&test_config())
534 .position_2d(10.0, 20.0)
535 .build()
536 .unwrap();
537
538 assert_eq!(
539 event.identity.as_ref().unwrap().source_id,
540 Some("robot-001".to_string())
541 );
542 assert!(event.location.is_some());
543 }
544
545 #[test]
546 fn test_builder_all_helpers() {
547 let event = EventBuilder::new(&test_config())
548 .event_type(EventType::TelemetryPeriodic)
549 .position_2d(10.0, 20.0)
550 .heading_deg(90.0)
551 .speed(2.5)
552 .battery_soc(85.0)
553 .operational_mode(OperationalMode::Autonomous)
554 .safety_state(SafetyState::Normal)
555 .build()
556 .unwrap();
557
558 assert!(event.location.is_some());
559 assert!(event.motion.is_some());
560 assert!(event.power.is_some());
561 assert!(event.operational.is_some());
562 assert!(event.safety.is_some());
563 }
564
565 #[test]
566 fn test_builder_with_full_domain() {
567 let location = LocationDomain {
568 heading_deg: Some(180.0),
569 ..Default::default()
570 };
571
572 let event = EventBuilder::new(&test_config())
573 .location(location)
574 .build()
575 .unwrap();
576
577 let loc = event.location.unwrap();
578 assert_eq!(loc.heading_deg, Some(180.0));
579 }
580
581 #[test]
582 fn test_builder_empty_fails() {
583 let config = PhyTraceConfig::new("robot-001");
584 let mut builder = EventBuilder::new(&config);
585 builder.event.identity = None;
587
588 let result = builder.build();
589 assert!(result.is_err());
590 }
591
592 #[test]
597 fn test_builder_clone() {
598 let builder = EventBuilder::new(&test_config()).position_2d(10.0, 20.0);
599 let cloned = builder.clone();
600
601 let event1 = builder.build().unwrap();
602 let event2 = cloned.build().unwrap();
603
604 assert_eq!(
605 event1
606 .location
607 .as_ref()
608 .unwrap()
609 .local
610 .as_ref()
611 .unwrap()
612 .x_m,
613 event2
614 .location
615 .as_ref()
616 .unwrap()
617 .local
618 .as_ref()
619 .unwrap()
620 .x_m
621 );
622 }
623
624 #[test]
625 fn test_builder_source_type_override() {
626 let event = EventBuilder::new(&test_config())
627 .source_type(SourceType::Drone)
628 .position_2d(0.0, 0.0)
629 .build()
630 .unwrap();
631
632 assert_eq!(event.source_type, SourceType::Drone);
633 }
634
635 #[test]
636 fn test_builder_model_info() {
637 let event = EventBuilder::new(&test_config())
638 .model_info("Manufacturer", "Model X", "1.2.3")
639 .position_2d(0.0, 0.0)
640 .build()
641 .unwrap();
642
643 let identity = event.identity.unwrap();
644 assert_eq!(identity.model, Some("Model X".to_string()));
645 assert_eq!(identity.firmware_version, Some("1.2.3".to_string()));
646 }
647
648 #[test]
649 fn test_builder_tag() {
650 let event = EventBuilder::new(&test_config())
651 .tag("warehouse")
652 .tag("zone-a")
653 .position_2d(0.0, 0.0)
654 .build()
655 .unwrap();
656
657 let identity = event.identity.unwrap();
658 let tags = identity.tags.unwrap();
659 assert!(tags.contains(&"warehouse".to_string()));
660 assert!(tags.contains(&"zone-a".to_string()));
661 }
662
663 #[test]
664 fn test_builder_position_local() {
665 let event = EventBuilder::new(&test_config())
666 .position_local(1.0, 2.0, 3.0)
667 .build()
668 .unwrap();
669
670 let local = event.location.unwrap().local.unwrap();
671 assert_eq!(local.x_m, Some(1.0));
672 assert_eq!(local.y_m, Some(2.0));
673 assert_eq!(local.z_m, Some(3.0));
674 }
675
676 #[test]
677 fn test_builder_position_3d() {
678 let event = EventBuilder::new(&test_config())
679 .position_3d(5.0, 6.0, 7.0)
680 .build()
681 .unwrap();
682
683 let local = event.location.unwrap().local.unwrap();
684 assert_eq!(local.x_m, Some(5.0));
685 assert_eq!(local.y_m, Some(6.0));
686 assert_eq!(local.z_m, Some(7.0));
687 }
688
689 #[test]
690 fn test_builder_heading() {
691 let event = EventBuilder::new(&test_config())
693 .heading(std::f64::consts::PI) .position_2d(0.0, 0.0)
695 .build()
696 .unwrap();
697
698 let heading = event.location.unwrap().heading_deg.unwrap();
699 assert!((heading - 180.0).abs() < 0.001);
700 }
701
702 #[test]
703 fn test_builder_gps() {
704 let event = EventBuilder::new(&test_config())
705 .gps(41.8781, -87.6298)
706 .build()
707 .unwrap();
708
709 let location = event.location.unwrap();
710 assert_eq!(location.latitude, Some(41.8781));
711 assert_eq!(location.longitude, Some(-87.6298));
712 }
713
714 #[test]
715 fn test_builder_floor() {
716 let event = EventBuilder::new(&test_config())
717 .floor(3)
718 .position_2d(0.0, 0.0)
719 .build()
720 .unwrap();
721
722 assert_eq!(event.location.unwrap().floor, Some(3));
723 }
724
725 #[test]
726 fn test_builder_map() {
727 let event = EventBuilder::new(&test_config())
728 .map("map_001", "base_link")
729 .position_2d(0.0, 0.0)
730 .build()
731 .unwrap();
732
733 let location = event.location.unwrap();
734 assert_eq!(location.map_id, Some("map_001".to_string()));
735 assert_eq!(location.frame_id, Some("base_link".to_string()));
736 }
737
738 #[test]
739 fn test_builder_linear_velocity() {
740 let event = EventBuilder::new(&test_config())
741 .linear_velocity(1.0, 0.5, 0.0)
742 .build()
743 .unwrap();
744
745 let lv = event.motion.unwrap().linear_velocity.unwrap();
746 assert_eq!(lv.x_mps, Some(1.0));
747 assert_eq!(lv.y_mps, Some(0.5));
748 assert_eq!(lv.z_mps, Some(0.0));
749 }
750
751 #[test]
752 fn test_builder_angular_velocity() {
753 let event = EventBuilder::new(&test_config())
754 .angular_velocity(0.0, 0.0, 1.0) .build()
756 .unwrap();
757
758 let av = event.motion.unwrap().angular_velocity.unwrap();
759 assert!((av.yaw_dps.unwrap() - 57.2958).abs() < 0.1); }
761
762 #[test]
763 fn test_builder_battery_voltage() {
764 let event = EventBuilder::new(&test_config())
765 .battery_voltage(24.5)
766 .build()
767 .unwrap();
768
769 let battery = event.power.unwrap().battery.unwrap();
770 assert_eq!(battery.voltage_v, Some(24.5));
771 }
772
773 #[test]
774 fn test_builder_battery_health() {
775 let event = EventBuilder::new(&test_config())
776 .battery_health(95.0)
777 .build()
778 .unwrap();
779
780 let battery = event.power.unwrap().battery.unwrap();
781 assert_eq!(battery.state_of_health_pct, Some(95.0));
782 }
783
784 #[test]
785 fn test_builder_charging() {
786 let event_charging = EventBuilder::new(&test_config())
787 .charging(true)
788 .build()
789 .unwrap();
790
791 let charging = event_charging.power.unwrap().charging.unwrap();
792 assert_eq!(charging.state, Some(ChargingState::Charging));
793
794 let event_not_charging = EventBuilder::new(&test_config())
795 .charging(false)
796 .build()
797 .unwrap();
798
799 let charging = event_not_charging.power.unwrap().charging.unwrap();
800 assert_eq!(charging.state, Some(ChargingState::NotCharging));
801 }
802
803 #[test]
804 fn test_builder_operational_state() {
805 let event = EventBuilder::new(&test_config())
806 .operational_state(OperationalState::ExecutingTask)
807 .build()
808 .unwrap();
809
810 assert_eq!(
811 event.operational.unwrap().state,
812 Some(OperationalState::ExecutingTask)
813 );
814 }
815
816 #[test]
817 fn test_builder_task() {
818 let event = EventBuilder::new(&test_config())
819 .task("task-001", "delivery")
820 .build()
821 .unwrap();
822
823 let task = event.operational.unwrap().task.unwrap();
824 assert_eq!(task.task_id, Some("task-001".to_string()));
825 assert_eq!(task.task_type, Some("delivery".to_string()));
826 }
827
828 #[test]
829 fn test_builder_estop_active() {
830 let event = EventBuilder::new(&test_config())
831 .estop_active(true)
832 .build()
833 .unwrap();
834
835 let estop = event.safety.unwrap().e_stop.unwrap();
836 assert_eq!(estop.is_active, Some(true));
837 }
838
839 #[test]
840 fn test_builder_localization_quality() {
841 let event = EventBuilder::new(&test_config())
842 .localization_quality(LocalizationQuality::Good)
843 .build()
844 .unwrap();
845
846 let loc = event.navigation.unwrap().localization.unwrap();
847 assert_eq!(loc.quality, Some(LocalizationQuality::Good));
848 }
849
850 #[test]
851 fn test_builder_path_state() {
852 let event = EventBuilder::new(&test_config())
853 .path_state(PathState::Executing)
854 .build()
855 .unwrap();
856
857 let path = event.navigation.unwrap().path.unwrap();
858 assert_eq!(path.state, Some(PathState::Executing));
859 }
860
861 #[test]
862 fn test_builder_all_domain_setters() {
863 let event = EventBuilder::new(&test_config())
864 .identity(IdentityDomain::default())
865 .location(LocationDomain::default())
866 .motion(MotionDomain::default())
867 .power(PowerDomain::default())
868 .operational(OperationalDomain::default())
869 .navigation(NavigationDomain::default())
870 .perception(PerceptionDomain::default())
871 .safety(SafetyDomain::default())
872 .actuators(ActuatorsDomain::default())
873 .communication(CommunicationDomain::default())
874 .compute(ComputeDomain::default())
875 .ai(AiDomain::default())
876 .maintenance(MaintenanceDomain::default())
877 .context(ContextDomain::default())
878 .payload(PayloadDomain::default())
879 .manipulation(ManipulationDomain::default())
880 .hri(HriDomain::default())
881 .coordination(CoordinationDomain::default())
882 .simulation(SimulationDomain::default())
883 .thermal(ThermalDomain::default())
884 .audio(AudioDomain::default())
885 .environment_interaction(EnvironmentInteractionDomain::default())
886 .compliance(ComplianceDomain::default())
887 .build()
888 .unwrap();
889
890 assert!(event.identity.is_some());
891 assert!(event.location.is_some());
892 assert!(event.motion.is_some());
893 assert!(event.power.is_some());
894 assert!(event.operational.is_some());
895 assert!(event.navigation.is_some());
896 assert!(event.perception.is_some());
897 assert!(event.safety.is_some());
898 assert!(event.actuators.is_some());
899 assert!(event.communication.is_some());
900 assert!(event.compute.is_some());
901 assert!(event.ai.is_some());
902 assert!(event.maintenance.is_some());
903 assert!(event.context.is_some());
904 assert!(event.payload.is_some());
905 assert!(event.manipulation.is_some());
906 assert!(event.hri.is_some());
907 assert!(event.coordination.is_some());
908 assert!(event.simulation.is_some());
909 assert!(event.thermal.is_some());
910 assert!(event.audio.is_some());
911 assert!(event.environment_interaction.is_some());
912 assert!(event.compliance.is_some());
913 }
914
915 #[test]
916 fn test_builder_extensions() {
917 let extensions = serde_json::json!({
918 "custom_field": "custom_value",
919 "nested": { "key": 123 }
920 });
921
922 let event = EventBuilder::new(&test_config())
923 .extensions(extensions.clone())
924 .position_2d(0.0, 0.0)
925 .build()
926 .unwrap();
927
928 assert_eq!(event.extensions, Some(extensions));
929 }
930
931 #[test]
932 fn test_builder_build_unchecked() {
933 let config = PhyTraceConfig::new("robot-001");
934 let mut builder = EventBuilder::new(&config);
935 builder.event.identity = None;
936
937 let event = builder.build_unchecked();
939 assert!(event.identity.is_none());
940 }
941
942 #[test]
943 fn test_builder_peek() {
944 let builder = EventBuilder::new(&test_config()).position_2d(10.0, 20.0);
945
946 let event = builder.peek();
947 assert!(event.location.is_some());
948 }
949
950 #[test]
951 fn test_builder_config_with_tags() {
952 let config = PhyTraceConfig::new("robot-001").with_source_type(SourceType::Amr);
954
955 let event = EventBuilder::new(&config)
956 .tag("test-tag")
957 .position_2d(0.0, 0.0)
958 .build()
959 .unwrap();
960
961 let identity = event.identity.unwrap();
962 assert!(identity.tags.is_some());
963 assert!(identity.tags.unwrap().contains(&"test-tag".to_string()));
964 }
965}