Skip to main content

nautilus_common/messages/system/
component.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use 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/// Represents an event which includes information on the state of a component.
30#[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    /// The trader ID associated with the event.
39    pub trader_id: TraderId,
40    /// The component ID associated with the event.
41    pub component_id: Ustr,
42    /// The component type.
43    pub component_type: Ustr,
44    /// The component state.
45    pub state: ComponentState,
46    /// The component configuration.
47    pub config: IndexMap<String, String>,
48    /// The event ID.
49    pub event_id: UUID4,
50    /// UNIX timestamp (nanoseconds) when the event occurred.
51    pub ts_event: UnixNanos,
52    /// UNIX timestamp (nanoseconds) when the instance was initialized.
53    pub ts_init: UnixNanos,
54}
55
56impl ComponentStateChanged {
57    /// Creates a new [`ComponentStateChanged`] instance.
58    #[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}