nautilus_common/messages/system/
component.rs1use std::{
17 any::Any,
18 fmt::{Debug, Display},
19};
20
21use indexmap::IndexMap;
22use nautilus_core::{UUID4, UnixNanos};
23use nautilus_model::identifiers::TraderId;
24use serde::{Deserialize, Serialize};
25use ustr::Ustr;
26
27use crate::enums::ComponentState;
28
29#[repr(C)]
31#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
32#[serde(tag = "type")]
33#[cfg_attr(
34 feature = "python",
35 pyo3::pyclass(module = "nautilus_trader.model", from_py_object)
36)]
37pub struct ComponentStateChanged {
38 pub trader_id: TraderId,
40 pub component_id: Ustr,
42 pub component_type: Ustr,
44 pub state: ComponentState,
46 pub config: IndexMap<String, String>,
48 pub event_id: UUID4,
50 pub ts_event: UnixNanos,
52 pub ts_init: UnixNanos,
54}
55
56impl ComponentStateChanged {
57 #[expect(clippy::too_many_arguments)]
59 #[must_use]
60 pub fn new(
61 trader_id: TraderId,
62 component_id: Ustr,
63 component_type: Ustr,
64 state: ComponentState,
65 config: IndexMap<String, String>,
66 event_id: UUID4,
67 ts_event: UnixNanos,
68 ts_init: UnixNanos,
69 ) -> Self {
70 Self {
71 trader_id,
72 component_id,
73 component_type,
74 state,
75 config,
76 event_id,
77 ts_event,
78 ts_init,
79 }
80 }
81
82 pub fn as_any(&self) -> &dyn Any {
83 self
84 }
85}
86
87impl Display for ComponentStateChanged {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 write!(
90 f,
91 "{}(trader_id={}, component_id={}, component_type={}, state={}, event_id={})",
92 stringify!(ComponentStateChanged),
93 self.trader_id,
94 self.component_id,
95 self.component_type,
96 self.state,
97 self.event_id,
98 )
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use rstest::rstest;
105
106 use super::*;
107
108 fn component_state_changed() -> ComponentStateChanged {
109 let mut config = IndexMap::new();
110 config.insert("timeout_secs".to_string(), "30".to_string());
111
112 ComponentStateChanged::new(
113 TraderId::from("TESTER-001"),
114 Ustr::from("DataEngine"),
115 Ustr::from("DataEngine"),
116 ComponentState::Running,
117 config,
118 UUID4::from("00000000-0000-4000-8000-000000000001"),
119 UnixNanos::from(3),
120 UnixNanos::from(5),
121 )
122 }
123
124 #[rstest]
125 fn test_new_assigns_every_field() {
126 let event = component_state_changed();
127
128 assert_eq!(event.trader_id, TraderId::from("TESTER-001"));
129 assert_eq!(event.component_id, Ustr::from("DataEngine"));
130 assert_eq!(event.component_type, Ustr::from("DataEngine"));
131 assert_eq!(event.state, ComponentState::Running);
132 assert_eq!(event.config.get("timeout_secs").unwrap(), "30");
133 assert_eq!(
134 event.event_id,
135 UUID4::from("00000000-0000-4000-8000-000000000001")
136 );
137 assert_eq!(event.ts_event, UnixNanos::from(3));
138 assert_eq!(event.ts_init, UnixNanos::from(5));
139 }
140
141 #[rstest]
142 fn test_display_reports_identity_state_and_event_id() {
143 assert_eq!(
144 component_state_changed().to_string(),
145 "ComponentStateChanged(trader_id=TESTER-001, component_id=DataEngine, \
146 component_type=DataEngine, state=RUNNING, \
147 event_id=00000000-0000-4000-8000-000000000001)"
148 );
149 }
150
151 #[rstest]
152 fn test_as_any_downcasts_to_self() {
153 let event = component_state_changed();
154
155 assert_eq!(
156 event.as_any().downcast_ref::<ComponentStateChanged>(),
157 Some(&event)
158 );
159 }
160
161 #[rstest]
162 fn test_serde_round_trips() {
163 let event = component_state_changed();
164
165 let json = serde_json::to_string(&event).unwrap();
166
167 assert_eq!(
168 serde_json::from_str::<ComponentStateChanged>(&json).unwrap(),
169 event
170 );
171 }
172}