ichen_openprotocol/
messages.rs

1use super::filters::Filters;
2use super::utils::*;
3use super::{
4    ActionID, Controller, Error, JobCard, JobMode, KeyValuePair, Language, OpMode, Result,
5    StateValues, TextID, TextName, ID, R32,
6};
7use chrono::{DateTime, FixedOffset};
8use indexmap::IndexMap;
9use serde::{Deserialize, Serialize};
10use std::borrow::Cow;
11use std::convert::TryInto;
12use std::sync::atomic::{AtomicU64, Ordering};
13use Message::*;
14
15// Auto-incrementing global counter for message sequence numbers.
16static SEQ: AtomicU64 = AtomicU64::new(1);
17
18/// Common options of an Open Protocol message.
19///
20#[derive(Debug, Hash, Clone, Serialize, Deserialize)]
21#[serde(rename_all = "camelCase")]
22pub struct MessageOptions<'a> {
23    /// Unique ID (if any) of the message for tracking and storage retrieval purposes.
24    ///
25    /// The iChen Server may tag certain messages with a unique tracking key that can be used to
26    /// retrieve the message from persistent storage later.
27    #[serde(skip_serializing_if = "Option::is_none")]
28    #[serde(borrow)]
29    id: Option<TextID<'a>>,
30    //
31    /// Ever-increasing message sequence number.
32    ///
33    /// This number is usually auto-incremented with each message created, starting from 1.
34    sequence: u64,
35    //
36    /// Priority of the message, smaller number is higher priority.  Default = 0.
37    #[serde(skip_serializing_if = "is_zero")]
38    #[serde(default)]
39    priority: i32,
40}
41
42impl<'a> MessageOptions<'a> {
43    /// Get the message ID, if any.
44    ///
45    /// # Examples
46    ///
47    /// ~~~
48    /// # use ichen_openprotocol::*;
49    /// # fn main() -> std::result::Result<(), String> {
50    /// let mut opt = MessageOptions::new();
51    /// opt.set_id("hello")?;
52    /// assert_eq!(Some("hello"), opt.id());
53    /// opt.clear_id();
54    /// assert_eq!(None, opt.id());
55    /// # Ok(())
56    /// # }
57    /// ~~~
58    pub fn id(&self) -> Option<&str> {
59        self.id.as_ref().map(|x| x.get())
60    }
61
62    // Get the message sequence number.
63    ///
64    /// # Examples
65    ///
66    /// ~~~
67    /// # use ichen_openprotocol::*;
68    /// let opt1 = MessageOptions::new();
69    /// assert_eq!(1, opt1.sequence());
70    ///
71    /// let opt2 = MessageOptions::new();
72    /// assert_eq!(2, opt2.sequence());       // `sequence` auto-increments.
73    /// ~~~
74    pub fn sequence(&self) -> u64 {
75        self.sequence
76    }
77
78    /// Get the message priority.
79    ///
80    /// # Examples
81    ///
82    /// ~~~
83    /// # use ichen_openprotocol::*;
84    /// let opt1 = MessageOptions::new_with_priority(100);
85    /// assert_eq!(100, opt1.priority());
86    ///
87    /// let opt2 = MessageOptions::new_with_priority(-42);
88    /// assert_eq!(-42, opt2.priority());
89    /// ~~~
90    pub fn priority(&self) -> i32 {
91        self.priority
92    }
93
94    /// Set the message ID.
95    ///
96    /// # Errors
97    ///
98    /// Returns `Err(String)` if the ID string is empty or all-whitespace.
99    ///
100    /// ## Error Examples
101    ///
102    /// ~~~
103    /// # use ichen_openprotocol::*;
104    /// let mut opt = MessageOptions::new();
105    /// assert_eq!(Err("invalid value: a non-empty, non-whitespace, all-ASCII string required".into()), opt.set_id(""));
106    /// ~~~
107    ///
108    /// # Examples
109    ///
110    /// ~~~
111    /// # use ichen_openprotocol::*;
112    /// # fn main() -> std::result::Result<(), String> {
113    /// let mut opt = MessageOptions::new();
114    /// opt.set_id("hello")?;
115    /// assert_eq!(Some("hello"), opt.id());
116    /// opt.clear_id();
117    /// assert_eq!(None, opt.id());
118    /// # Ok(())
119    /// # }
120    /// ~~~
121    pub fn set_id(&mut self, id: &'a str) -> std::result::Result<(), String> {
122        self.id = Some(id.try_into()?);
123        Ok(())
124    }
125
126    /// Set the message ID to `None`.
127    ///
128    /// # Examples
129    ///
130    /// ~~~
131    /// # use ichen_openprotocol::*;
132    /// # fn main() -> std::result::Result<(), String> {
133    /// let mut opt = MessageOptions::new();
134    /// opt.set_id("hello")?;
135    /// assert_eq!(Some("hello"), opt.id());
136    /// opt.clear_id();
137    /// assert_eq!(None, opt.id());
138    /// # Ok(())
139    /// # }
140    /// ~~~
141    pub fn clear_id(&mut self) {
142        self.id = None;
143    }
144
145    /// Create a `MessageOptions` with default values (for example, the `sequence` field
146    /// auto-increments).
147    ///
148    /// # Examples
149    ///
150    /// ~~~
151    /// # use ichen_openprotocol::*;
152    /// let opt1 = MessageOptions::new();
153    /// assert_eq!(1, opt1.sequence());
154    /// assert_eq!(0, opt1.priority());
155    ///
156    /// let opt2 = MessageOptions::new();
157    /// assert_eq!(2, opt2.sequence());       // `sequence` auto-increments.
158    /// assert_eq!(0, opt2.priority());
159    /// ~~~
160    pub fn new() -> Self {
161        Default::default()
162    }
163
164    /// Create a `MessageOptions` with a particular `priority` but otherwise
165    /// default values (for example, the `sequence` field auto-increments).
166    ///
167    /// # Examples
168    ///
169    /// ~~~
170    /// # use ichen_openprotocol::*;
171    /// let opt1 = MessageOptions::new_with_priority(100);
172    /// assert_eq!(1, opt1.sequence());
173    /// assert_eq!(100, opt1.priority());
174    ///
175    /// let opt2 = MessageOptions::new_with_priority(-42);
176    /// assert_eq!(2, opt2.sequence());       // `sequence` auto-increments.
177    /// assert_eq!(-42, opt2.priority());
178    /// ~~~
179    pub fn new_with_priority(priority: i32) -> Self {
180        Self { priority, ..Self::new() }
181    }
182}
183
184impl Default for MessageOptions<'_> {
185    /// Default value for `MessageOptions`.
186    ///
187    /// The `sequence` field is auto-incrementing.
188    ///
189    /// # Examples
190    ///
191    /// ~~~
192    /// # use ichen_openprotocol::*;
193    /// let opt1: MessageOptions = Default::default();
194    /// assert_eq!(1, opt1.sequence());
195    /// assert_eq!(0, opt1.priority());
196    ///
197    /// let opt2: MessageOptions = Default::default();
198    /// assert_eq!(2, opt2.sequence());       // `sequence` auto-increments.
199    /// assert_eq!(0, opt2.priority());
200    /// ~~~
201    fn default() -> Self {
202        Self { id: None, sequence: SEQ.fetch_add(1, Ordering::SeqCst), priority: 0 }
203    }
204}
205
206/// All Open Protocol message types.
207///
208/// See [this document] for details.
209///
210/// [this document]: https://github.com/chenhsong/OpenProtocol/blob/master/cs/doc/messages_reference.md
211///
212#[derive(Debug, Clone, Serialize, Deserialize)]
213#[serde(tag = "$type")]
214pub enum Message<'a> {
215    /// The `ALIVE` message, sent periodically as the keep-alive mechanism.
216    #[serde(rename_all = "camelCase")]
217    Alive {
218        /// Message configuration options.
219        #[serde(flatten)]
220        options: MessageOptions<'a>,
221    },
222    //
223    /// The `CNTRLER_ACTION` message, sent by the server whenever the current *action* of a controller changes.
224    #[serde(rename_all = "camelCase")]
225    ControllerAction {
226        /// Unique ID of the controller.
227        controller_id: ID,
228        /// Unique action code.
229        ///
230        /// See [this document] for details.
231        ///
232        /// [this document]: https://github.com/chenhsong/OpenProtocol/blob/master/doc/actions.md
233        action_id: ActionID,
234        //
235        /// Time-stamp of the event.
236        timestamp: DateTime<FixedOffset>,
237        //
238        /// Message configuration options.
239        #[serde(flatten)]
240        options: MessageOptions<'a>,
241    },
242    //
243    /// The `REQ_CNTRLER_LIST` message, sent to the server to request a list of controllers (i.e. machines)
244    /// within the user's organization.
245    ///
246    /// # Response
247    ///
248    /// The Server should reply with a [`ControllersList`] message.
249    ///
250    /// [`ControllersList`]: enum.Message.html#variant.ControllersList
251    ///
252    #[serde(rename_all = "camelCase")]
253    RequestControllersList {
254        /// Unique ID of the controller to request.
255        ///
256        /// If omitted, all controllers of the user's organization will be returned.
257        #[serde(skip_serializing_if = "Option::is_none")]
258        controller_id: Option<ID>,
259        //
260        /// Message configuration options.
261        #[serde(flatten)]
262        options: MessageOptions<'a>,
263    },
264    //
265    /// The `RESP_CNTRLER_LIST` message, sent by the server in response to a
266    /// [`RequestControllersList`] message.
267    ///
268    /// [`RequestControllersList`]: enum.Message.html#variant.RequestControllersList
269    ///
270    #[serde(rename_all = "camelCase")]
271    ControllersList {
272        /// List of controllers requested by a previous `RequestControllersList` message.
273        ///
274        /// Each controller data structure contains the last-known values of the controller's state.
275        //
276        // Custom deserialization of string into integer key.
277        // No need for custom serialization because ID to string is fine.
278        #[serde(deserialize_with = "deserialize_indexmap")]
279        data: IndexMap<ID, Controller<'a>>,
280        //
281        /// Message configuration options.
282        #[serde(flatten)]
283        options: MessageOptions<'a>,
284    },
285    //
286    /// The `UPD_CNTRLER` message, sent by the server whenever the status of a connected controller changes.
287    ///
288    /// Only the changed fields will be set, with other fields/properties being set to
289    /// `None` as they are not relevant.
290    #[serde(rename_all = "camelCase")]
291    ControllerStatus {
292        /// Unique ID of the controller.
293        controller_id: ID,
294        //
295        /// Human-friendly name for display (or `None` if not relevant).
296        #[serde(skip_serializing_if = "Option::is_none")]
297        display_name: Option<Box<TextName<'a>>>,
298        //
299        /// If true, the controller has disconnected from the iChenĀ® Server.
300        #[serde(skip_serializing_if = "Option::is_none")]
301        is_disconnected: Option<bool>,
302        //
303        /// Current operation mode of the controller (or `None` if not relevant).
304        #[serde(skip_serializing_if = "Option::is_none")]
305        op_mode: Option<OpMode>,
306        //
307        /// Current job mode of the controller (or `None` if not relevant).
308        #[serde(skip_serializing_if = "Option::is_none")]
309        job_mode: Option<JobMode>,
310        //
311        /// State of an alarm (if any) on the controller (or `None` if not relevant).
312        ///
313        /// See [this document] for valid alarm codes.
314        ///
315        /// [this document]: https://github.com/chenhsong/OpenProtocol/blob/master/doc/alarms.md
316        #[serde(skip_serializing_if = "Option::is_none")]
317        alarm: Option<Box<KeyValuePair<TextID<'a>, bool>>>,
318        //
319        /// Change of a setting (if any) on the controller for audit trail purpose
320        /// (or `None` if not relevant).
321        #[serde(skip_serializing_if = "Option::is_none")]
322        audit: Option<Box<KeyValuePair<TextID<'a>, R32>>>,
323        //
324        /// Change of a variable (if any) on the controller (or `None` if not relevant).
325        #[serde(skip_serializing_if = "Option::is_none")]
326        variable: Option<Box<KeyValuePair<TextID<'a>, R32>>>,
327        //
328        /// Unique ID of the current logged-on user, `Some(None)` if a user has logged out
329        /// (or `None` if not relevant).
330        #[serde(serialize_with = "serialize_some_none_to_invalid")]
331        #[serde(deserialize_with = "deserialize_invalid_to_some_none")]
332        #[serde(skip_serializing_if = "Option::is_none")]
333        #[serde(default)]
334        operator_id: Option<Option<ID>>,
335        //
336        /// Name of the current logged-on user, `Some(None)` if the current user has no name
337        /// (or `None` if not relevant).
338        #[serde(deserialize_with = "deserialize_null_to_some_none")]
339        #[serde(skip_serializing_if = "Option::is_none")]
340        #[serde(default)]
341        operator_name: Option<Option<Box<TextName<'a>>>>,
342        //
343        /// Unique ID of the current job card loaded, `Some(None)` if no job card is currently loaded
344        /// (or `None` if not relevant).
345        #[serde(deserialize_with = "deserialize_null_to_some_none")]
346        #[serde(skip_serializing_if = "Option::is_none")]
347        #[serde(default)]
348        #[serde(borrow)]
349        job_card_id: Option<Option<Box<TextName<'a>>>>,
350        //
351        /// Unique ID of the current mold data set loaded, `Some(None)` if no mold data set is currently loaded
352        /// (or `None` if not relevant).
353        #[serde(deserialize_with = "deserialize_null_to_some_none")]
354        #[serde(skip_serializing_if = "Option::is_none")]
355        #[serde(default)]
356        #[serde(borrow)]
357        mold_id: Option<Option<Box<TextName<'a>>>>,
358        //
359        /// Snapshot of the current known states of the controller.
360        state: StateValues<'a>,
361        //
362        /// A [`Controller`] data structure containing the last-known state of the controller.
363        ///
364        /// This field is only sent once by the server as soon as a new controller has connected
365        /// to the network.
366        /// All subsequent messages have this field set to `None`.
367        ///
368        /// If this field is not `None`, then all other info fields should be `None` or have values
369        /// equal to the matching fields in `controller`.
370        ///
371        /// [`Controller`]: struct.Controller.html
372        #[serde(skip_serializing_if = "Option::is_none")]
373        controller: Option<Box<Controller<'a>>>,
374        //
375        /// Message configuration options.
376        #[serde(flatten)]
377        options: MessageOptions<'a>,
378    },
379    //
380    /// The `CYCLE_DATA` message, sent by the server at the end of each machine cycle.
381    #[serde(rename_all = "camelCase")]
382    CycleData {
383        /// Unique ID of the controller.
384        controller_id: ID,
385        //
386        /// A data dictionary containing a set of cycle data.
387        ///
388        /// See [this document] for examples.
389        ///
390        /// [this document]: https://github.com/chenhsong/OpenProtocol/blob/master/doc/cycledata.md
391        data: IndexMap<TextID<'a>, R32>,
392        //
393        /// Time-stamp of the event.
394        timestamp: DateTime<FixedOffset>,
395        //
396        /// Snapshot of the current known states of the controller.
397        #[serde(flatten)]
398        state: StateValues<'a>,
399        //
400        /// Message configuration options.
401        #[serde(flatten)]
402        options: MessageOptions<'a>,
403    },
404    //
405    /// The `REQ_JOBCARDS_LIST` message, sent by the server when a connected controller
406    /// requests a list of job cards.
407    ///
408    /// # Action Required
409    ///
410    /// The user should send a [`JobCardsList`] message to the Server as a reply.
411    ///
412    /// [`JobCardsList`]: enum.Message.html#variant.JobCardsList
413    #[serde(rename_all = "camelCase")]
414    RequestJobCardsList {
415        /// Unique ID of the controller.
416        controller_id: ID,
417        //
418        /// Message configuration options.
419        #[serde(flatten)]
420        options: MessageOptions<'a>,
421    },
422    //
423    /// The `RESP_JOBSLIST` message, sent to the server in response to a [`RequestJobCardsList`] message.
424    ///
425    /// [`RequestJobCardsList`]: enum.Message.html#variant.RequestJobCardsList
426    #[serde(rename_all = "camelCase")]
427    JobCardsList {
428        /// Unique ID of the controller.
429        controller_id: ID,
430        //
431        /// A data dictionary containing a set of `JobCard` data structures.
432        data: IndexMap<TextName<'a>, JobCard<'a>>,
433        //
434        /// Message configuration options.
435        #[serde(flatten)]
436        options: MessageOptions<'a>,
437    },
438    //
439    /// The `JOIN` message, sent to log onto the server.
440    ///
441    /// # Response
442    ///
443    /// The Server should reply with a [`JoinResponse`] message.
444    ///
445    /// [`JoinResponse`]: enum.Message.html#variant.JoinResponse
446    #[serde(rename_all = "camelCase")]
447    Join {
448        /// Organization ID (if any).
449        #[serde(skip_serializing_if = "Option::is_none")]
450        org_id: Option<TextID<'a>>,
451        //
452        /// The maximum protocol version supported, in the format `x.x.x.x`.
453        ///
454        /// The current protocol version implemented is in the constant `PROTOCOL_VERSION`.
455        version: TextID<'a>,
456        //
457        /// Password to log onto the server.
458        password: &'a str,
459        //
460        /// Language encoding.
461        language: Language,
462        //
463        /// A collection of [`Filter`] values containing what type(s) of messages to receive.
464        ///
465        /// [`Filter`]: struct.Filters.html
466        filter: Filters,
467        //
468        /// Message configuration options.
469        #[serde(flatten)]
470        options: MessageOptions<'a>,
471    },
472    //
473    /// The `RESP_JOIN` message, sent by the Server in response to a [`Join`] message.
474    ///
475    /// [`Join`]: enum.Message.html#variant.Join
476    #[serde(rename_all = "camelCase")]
477    JoinResponse {
478        /// Result code, >= 100 indicates success.
479        result: u32,
480        #[serde(skip_serializing_if = "Option::is_none")]
481        //
482        /// The allowed access level for this client.
483        level: Option<u32>,
484        //
485        /// A message (mostly likely an error message in case of failure), if any.
486        #[serde(skip_serializing_if = "Option::is_none")]
487        #[serde(borrow)]
488        message: Option<Box<Cow<'a, str>>>,
489        //
490        /// Message configuration options.
491        #[serde(flatten)]
492        options: MessageOptions<'a>,
493    },
494    //
495    /// The `REQ_MOLD` message, sent to the server to request the set of mold settings data of a controller.
496    ///
497    /// # Response
498    ///
499    /// The Server should reply with a [`MoldData`] message.
500    ///
501    /// [`MoldData`]: enum.Message.html#variant.MoldData
502    #[serde(rename_all = "camelCase")]
503    RequestMoldData {
504        /// Unique ID of the controller.
505        controller_id: ID,
506        //
507        /// Message configuration options.
508        #[serde(flatten)]
509        options: MessageOptions<'a>,
510    },
511    //
512    /// The `RESP_MOLD` message, sent by the server in response to a [`RequestMoldData`] message
513    /// or a [`ReadMoldData`] message with `field` set to `None` (meaning read all).
514    ///
515    /// [`RequestMoldData`]: enum.Message.html#variant.RequestMoldData
516    /// [`ReadMoldData`]: enum.Message.html#variant.ReadMoldData
517    #[serde(rename_all = "camelCase")]
518    MoldData {
519        /// Unique ID of the controller.
520        controller_id: ID,
521        //
522        /// A data dictionary containing a set of mold settings.
523        data: IndexMap<TextID<'a>, R32>,
524        //
525        /// Time-stamp of the event.
526        timestamp: DateTime<FixedOffset>,
527        //
528        /// Snapshot of the current known states of the controller.
529        #[serde(flatten)]
530        state: StateValues<'a>,
531        //
532        /// Message configuration options.
533        #[serde(flatten)]
534        options: MessageOptions<'a>,
535    },
536    //
537    /// The `READ_MOLD_DATA` message, sent to the server to read the current value of a
538    /// particular mold setting.
539    ///
540    /// The server keeps a cache of the states of all mold settings for each controller.
541    /// The value returned is based on the server cache.
542    /// No command is sent to controller to poll the latest value.
543    ///
544    /// # Response
545    ///
546    /// The Server should reply with a [`MoldData`] message if `field` is `None`,
547    /// or a [`MoldDataValue`] message.
548    ///
549    /// [`MoldData`]: enum.Message.html#variant.MoldData
550    /// [`MoldDataValue`]: enum.Message.html#variant.MoldDataValue
551    #[serde(rename_all = "camelCase")]
552    ReadMoldData {
553        /// Unique ID of the controller.
554        controller_id: ID,
555        //
556        /// Name of the mold setting to read, `None` for all.
557        field: Option<TextID<'a>>,
558        //
559        /// Message configuration options.
560        #[serde(flatten)]
561        options: MessageOptions<'a>,
562    },
563    //
564    /// The `RESP_MOLD_DATA_VALUE` message, sent by the server in response to a
565    /// [`ReadMoldData`] message.
566    ///
567    /// [`ReadMoldData`]: enum.Message.html#variant.ReadMoldData
568    #[serde(rename_all = "camelCase")]
569    MoldDataValue {
570        /// Unique ID of the controller.
571        controller_id: ID,
572        //
573        /// Name of the mold setting to read.
574        field: TextID<'a>,
575        //
576        /// Current cached value of the mold setting.
577        value: R32,
578        //
579        /// Message configuration options.
580        #[serde(flatten)]
581        options: MessageOptions<'a>,
582    },
583    //
584    /// The `REQ_PWD_LEVEL` message, sent by server when a connected controller attempts to
585    /// authenticate and authorize a user password.
586    ///
587    /// # Action Required
588    ///
589    /// The user should send an [`OperatorInfo`] message to the Server as a reply.
590    ///
591    /// [`OperatorInfo`]: enum.Message.html#variant.OperatorInfo
592    #[serde(rename_all = "camelCase")]
593    LoginOperator {
594        /// Unique ID of the controller.
595        controller_id: ID,
596        //
597        /// User password.
598        password: &'a str,
599        //
600        /// Message configuration options.
601        #[serde(flatten)]
602        options: MessageOptions<'a>,
603    },
604    //
605    /// The `RESP_PWD_LEVEL` message, sent to the server in response to a
606    /// [`LoginOperator`] message.
607    ///
608    /// [`LoginOperator`]: enum.Message.html#variant.LoginOperator
609    #[serde(rename_all = "camelCase")]
610    OperatorInfo {
611        /// Unique ID of the controller.
612        controller_id: ID,
613        //
614        /// Unique ID of the authenticated user.
615        #[serde(skip_serializing_if = "Option::is_none")]
616        operator_id: Option<ID>,
617        //
618        /// Name of the user.
619        name: TextName<'a>,
620        //
621        /// User password.
622        password: TextName<'a>,
623        //
624        /// Allowed access level for the user.
625        ///
626        /// Valid values are from 0 to [`MAX_OPERATOR_LEVEL`] (usually 10).
627        ///
628        /// [`MAX_OPERATOR_LEVEL`]: enum.Message.html#associatedconstant.MAX_OPERATOR_LEVEL
629        level: u8,
630        //
631        /// Message configuration options.
632        #[serde(flatten)]
633        options: MessageOptions<'a>,
634    },
635}
636
637impl<'a> Message<'a> {
638    /// Current protocol version: 4.0.
639    pub const PROTOCOL_VERSION: &'static str = "4.0";
640
641    /// Default language to use: `EN` (English).
642    pub const DEFAULT_LANGUAGE: Language = Language::EN;
643
644    /// Maximum operator level: 10.
645    pub const MAX_OPERATOR_LEVEL: u8 = 10;
646
647    /// Parse a JSON string into a `Message`.
648    ///
649    /// # Errors
650    ///
651    /// Return `Err(`[`OpenProtocolError`]`)` if there is an error during parsing.
652    ///
653    /// [`OpenProtocolError`]: enum.OpenProtocolError.html
654    ///
655    pub fn parse_from_json_str(json: &'a str) -> Result<'a, Self> {
656        let m = serde_json::from_str::<Message>(json).map_err(Error::JsonError)?;
657        m.validate()?;
658        Ok(m)
659    }
660
661    /// Validate all the fields in the `Message`, then serialize it into a JSON string.
662    ///
663    /// # Errors
664    ///
665    /// Return `Err(`[`OpenProtocolError`]`)` if there is an error.
666    ///
667    /// [`OpenProtocolError`]: enum.OpenProtocolError.html
668    ///
669    /// # Examples
670    ///
671    /// ~~~
672    /// # use ichen_openprotocol::*;
673    /// # fn main() -> std::result::Result<(), String> {
674    /// let msg = Message::try_new_join_with_org("MyPassword", Filters::Status + Filters::Cycle, "MyCompany")?;
675    /// assert_eq!(
676    ///     r#"{"$type":"Join","orgId":"MyCompany","version":"4.0","password":"MyPassword","language":"EN","filter":"Status, Cycle","sequence":1}"#,
677    ///     msg.to_json_str()?
678    /// );
679    /// # Ok(())
680    /// # }
681    /// ~~~
682    pub fn to_json_str(&self) -> Result<'_, String> {
683        self.validate()?;
684        serde_json::to_string(self).map_err(Error::JsonError)
685    }
686
687    /// Create an `ALIVE` message.
688    ///
689    /// # Examples
690    ///
691    /// ~~~
692    /// # use ichen_openprotocol::*;
693    /// let msg = Message::new_alive();
694    /// if let Message::Alive { options } = msg {
695    ///     assert_eq!(1, options.sequence());
696    ///     assert_eq!(0, options.priority());
697    ///     assert_eq!(None, options.id());
698    /// } else {
699    ///     panic!();
700    /// }
701    /// ~~~
702    pub fn new_alive() -> Self {
703        Alive { options: Default::default() }
704    }
705
706    /// Create a `JOIN` message with default language and protocol version.
707    ///
708    /// The default language is [`DEFAULT_LANGUAGE`] (usually `EN`).
709    ///
710    /// The default protocol version is given in [`PROTOCOL_VERSION`].
711    ///
712    /// [`DEFAULT_LANGUAGE`]: enum.Message.html#associatedconstant.DEFAULT_LANGUAGE
713    /// [`PROTOCOL_VERSION`]: enum.Message.html#associatedconstant.PROTOCOL_VERSION
714    ///
715    /// # Examples
716    ///
717    /// ~~~
718    /// # use ichen_openprotocol::*;
719    /// let msg = Message::new_join("MyPassword", Filters::Status + Filters::Cycle);
720    /// if let Message::Join { org_id, version, password, language, filter, options } = msg {
721    ///     assert_eq!(None, org_id);
722    ///     assert_eq!(Message::PROTOCOL_VERSION, &version);
723    ///     assert_eq!("MyPassword", password);
724    ///     assert_eq!(Message::DEFAULT_LANGUAGE, language);
725    ///     assert_eq!(Filters::Status + Filters::Cycle, filter);
726    ///     assert_eq!(1, options.sequence());
727    ///     assert_eq!(0, options.priority());
728    ///     assert_eq!(None, options.id());
729    /// } else {
730    ///     panic!();
731    /// }
732    /// ~~~
733    pub fn new_join(password: &'a str, filter: Filters) -> Self {
734        Join {
735            org_id: None,
736            version: Self::PROTOCOL_VERSION.try_into().unwrap(),
737            password,
738            language: Self::DEFAULT_LANGUAGE,
739            filter,
740            options: Default::default(),
741        }
742    }
743
744    /// Create a `JOIN` message with non-default organization.
745    ///
746    /// # Errors
747    ///
748    /// Returns `Err(String)` if the organization ID is empty or all-whitespace or contains
749    /// any non-ASCII characters.
750    ///
751    /// ## Error Examples
752    ///
753    /// ~~~
754    /// # use ichen_openprotocol::*;
755    /// match Message::try_new_join_with_org("MyPassword", Filters::Status + Filters::Cycle, "") {
756    ///     Err(e) => assert_eq!("invalid value: a non-empty, non-whitespace, all-ASCII string required", e),
757    ///     _ => ()
758    /// }
759    /// ~~~
760    ///
761    /// # Examples
762    ///
763    /// ~~~
764    /// # use ichen_openprotocol::*;
765    /// # fn main() -> std::result::Result<(), String> {
766    /// let msg = Message::try_new_join_with_org("MyPassword", Filters::Status + Filters::Cycle, "MyCompany")?;
767    ///
768    /// if let Message::Join { org_id, version, password, language, filter, options } = msg {
769    ///     assert_eq!(Some("MyCompany"), org_id.as_ref().map(|x| x.get()));
770    ///     assert_eq!(Message::PROTOCOL_VERSION, version.get());
771    ///     assert_eq!("MyPassword", password);
772    ///     assert_eq!(Message::DEFAULT_LANGUAGE, language);
773    ///     assert_eq!(Filters::Status + Filters::Cycle, filter);
774    ///     assert_eq!(1, options.sequence());
775    ///     assert_eq!(0, options.priority());
776    ///     assert_eq!(None, options.id());
777    /// } else {
778    ///     panic!();
779    /// }
780    /// # Ok(())
781    /// # }
782    /// ~~~
783    pub fn try_new_join_with_org(
784        password: &'a str,
785        filter: Filters,
786        org: &'a str,
787    ) -> std::result::Result<Self, String> {
788        let mut msg = Self::new_join(password, filter);
789
790        if let Join { ref mut org_id, .. } = msg {
791            *org_id = Some(org.try_into()?);
792        }
793
794        Ok(msg)
795    }
796
797    /// Get the optional message ID from the `options` field.
798    pub fn id(&self) -> Option<&str> {
799        match self {
800            Alive { options }
801            | ControllerAction { options, .. }
802            | RequestControllersList { options, .. }
803            | ControllersList { options, .. }
804            | ControllerStatus { options, .. }
805            | CycleData { options, .. }
806            | RequestJobCardsList { options, .. }
807            | JobCardsList { options, .. }
808            | Join { options, .. }
809            | JoinResponse { options, .. }
810            | RequestMoldData { options, .. }
811            | MoldData { options, .. }
812            | ReadMoldData { options, .. }
813            | MoldDataValue { options, .. }
814            | LoginOperator { options, .. }
815            | OperatorInfo { options, .. } => options.id(),
816        }
817    }
818
819    /// Get the message sequence number from the `options` field.
820    pub fn sequence(&self) -> u64 {
821        match self {
822            Alive { options }
823            | ControllerAction { options, .. }
824            | RequestControllersList { options, .. }
825            | ControllersList { options, .. }
826            | ControllerStatus { options, .. }
827            | CycleData { options, .. }
828            | RequestJobCardsList { options, .. }
829            | JobCardsList { options, .. }
830            | Join { options, .. }
831            | JoinResponse { options, .. }
832            | RequestMoldData { options, .. }
833            | MoldData { options, .. }
834            | ReadMoldData { options, .. }
835            | MoldDataValue { options, .. }
836            | LoginOperator { options, .. }
837            | OperatorInfo { options, .. } => options.sequence(),
838        }
839    }
840
841    /// Get the message priority from the `options` field.
842    pub fn priority(&self) -> i32 {
843        match self {
844            Alive { options, .. }
845            | ControllerAction { options, .. }
846            | RequestControllersList { options, .. }
847            | ControllersList { options, .. }
848            | ControllerStatus { options, .. }
849            | CycleData { options, .. }
850            | RequestJobCardsList { options, .. }
851            | JobCardsList { options, .. }
852            | Join { options, .. }
853            | JoinResponse { options, .. }
854            | RequestMoldData { options, .. }
855            | MoldData { options, .. }
856            | ReadMoldData { options, .. }
857            | MoldDataValue { options, .. }
858            | LoginOperator { options, .. }
859            | OperatorInfo { options, .. } => options.priority(),
860        }
861    }
862
863    /// Validate the `Message` data structure.
864    ///
865    /// # Errors
866    ///
867    /// Returns `Err(`[`OpenProtocolError`]`)` if some fields in the `Message` are not valid.
868    ///
869    /// [`OpenProtocolError`]: enum.OpenProtocolError.html
870    ///
871    /// # Examples
872    ///
873    /// ~~~
874    /// # use ichen_openprotocol::*;
875    /// let msg = Message::ControllerStatus {
876    ///     controller_id: ID::from_u32(12345),
877    ///     display_name: None,
878    ///     is_disconnected: None,
879    ///     op_mode: None,
880    ///     job_mode: None,
881    ///     job_card_id: Some(None),
882    ///     mold_id: Some(Some(Box::new(TextName::new_from_str("Test-123").unwrap()))),     // Value is "Test-123"
883    ///     operator_id: None,
884    ///     operator_name: None,
885    ///     variable: None,
886    ///     audit: None,
887    ///     alarm: None,
888    ///     controller: None,
889    ///     state: StateValues::try_new_with_all(
890    ///         OpMode::Automatic,
891    ///         JobMode::ID02,
892    ///         None,
893    ///         None,
894    ///         Some("Test-FooBar"),    // Notice that this state value should be "Test-123"
895    ///     ).unwrap(),
896    ///     options: Default::default(),
897    /// };
898    ///
899    /// // Validation should error because `state.mold_id` is not the same as the `mold_id` field.
900    /// assert_eq!(
901    ///     Err(Error::InconsistentState("mold_id")),
902    ///     msg.validate()
903    /// );
904    /// ~~~
905    pub fn validate(&self) -> Result<'a, ()> {
906        match self {
907            Alive { .. }
908            | ControllerAction { .. }
909            | RequestControllersList { .. }
910            | RequestJobCardsList { .. }
911            | JoinResponse { .. }
912            | RequestMoldData { .. }
913            | ControllersList { .. }
914            | CycleData { .. }
915            | ReadMoldData { .. }
916            | MoldDataValue { .. }
917            | LoginOperator { .. }
918            | JobCardsList { .. }
919            | MoldData { .. } => (),
920
921            ControllerStatus {
922                display_name,
923                is_disconnected,
924                op_mode,
925                job_mode,
926                alarm,
927                audit,
928                variable,
929                operator_id,
930                operator_name,
931                job_card_id,
932                mold_id,
933                state,
934                controller,
935                ..
936            } => {
937                if let Some(c) = controller {
938                    // If controller is present, some fields must be None
939                    if !is_disconnected.is_none()
940                        || !alarm.is_none()
941                        || !audit.is_none()
942                        || !variable.is_none()
943                    {
944                        return Err(Error::ConstraintViolated(
945                            "All other fields must be set to None if controller is present.".into(),
946                        ));
947                    }
948
949                    // Check controller fields with specified fields
950                    if display_name.is_some()
951                        && display_name.as_ref().unwrap().get() != &c.display_name
952                    {
953                        return Err(Error::InconsistentField("display_name"));
954                    }
955                    if operator_name.is_some()
956                        && operator_name.as_ref().unwrap().as_ref().map(|x| x.get())
957                            != c.operator.as_ref().map(|u| u.name()).flatten()
958                    {
959                        return Err(Error::InconsistentField("operator_name"));
960                    }
961
962                    // Check controller fields with the state
963                    if state.op_mode() != c.op_mode {
964                        return Err(Error::InconsistentState("op_mode"));
965                    }
966                    if state.job_mode() != c.job_mode {
967                        return Err(Error::InconsistentState("job_mode"));
968                    }
969                    if state.operator_id() != c.operator.as_ref().map(|user| user.id()) {
970                        return Err(Error::InconsistentState("operator_id"));
971                    }
972                    if state.job_card_id() != c.job_card_id.as_ref().map(|x| x.as_ref().as_ref()) {
973                        return Err(Error::InconsistentState("job_card_id"));
974                    }
975                    if state.mold_id() != c.mold_id.as_ref().map(|x| x.as_ref().as_ref()) {
976                        return Err(Error::InconsistentField("mold_id"));
977                    }
978                }
979
980                // Check controller fields with the state
981                if op_mode.is_some() && op_mode.unwrap() != state.op_mode() {
982                    return Err(Error::InconsistentState("op_mode"));
983                }
984                if job_mode.is_some() && job_mode.unwrap() != state.job_mode() {
985                    return Err(Error::InconsistentState("job_mode"));
986                }
987                if operator_id.is_some() && operator_id.unwrap() != state.operator_id() {
988                    return Err(Error::InconsistentState("operator_id"));
989                }
990                if job_card_id.is_some()
991                    && state.job_card_id()
992                        != job_card_id.as_ref().unwrap().as_ref().map(|jc| jc.get())
993                {
994                    return Err(Error::InconsistentState("job_card_id"));
995                }
996                if mold_id.is_some()
997                    && state.mold_id() != mold_id.as_ref().unwrap().as_ref().map(|m| m.get())
998                {
999                    return Err(Error::InconsistentState("mold_id"));
1000                }
1001            }
1002
1003            Join { language, .. } => {
1004                // Check for invalid language
1005                if *language == Language::Unknown {
1006                    return Err(Error::InvalidField {
1007                        field: "language",
1008                        value: "Unknown".into(),
1009                        description: "language cannot be Unknown".into(),
1010                    });
1011                }
1012            }
1013
1014            OperatorInfo { level, .. } => {
1015                if *level > Self::MAX_OPERATOR_LEVEL {
1016                    return Err(Error::ConstraintViolated(
1017                        format!(
1018                            "Level {} is too high - must be between 0 and {}.",
1019                            level,
1020                            Self::MAX_OPERATOR_LEVEL
1021                        )
1022                        .into(),
1023                    ));
1024                }
1025            }
1026        }
1027
1028        Ok(())
1029    }
1030}
1031
1032// Tests
1033
1034#[cfg(test)]
1035mod test {
1036    use super::*;
1037    use std::result::Result;
1038
1039    impl<'a> MessageOptions<'a> {
1040        /// A private constructor function that creates a `MessageOptions` structure
1041        /// with `sequence` always set to 1 (for testing purposes).
1042        fn default_new() -> Self {
1043            Self { sequence: 1, ..Self::new() }
1044        }
1045    }
1046
1047    #[test]
1048    fn test_message_alive_to_json() -> Result<(), String> {
1049        let mut options = MessageOptions::new_with_priority(20);
1050        options.sequence = 999;
1051        options.set_id("hello")?;
1052
1053        let msg = Alive { options };
1054
1055        let serialized = serde_json::to_string(&msg).map_err(|x| x.to_string())?;
1056
1057        assert_eq!(r#"{"$type":"Alive","id":"hello","sequence":999,"priority":20}"#, serialized);
1058
1059        Ok(())
1060    }
1061
1062    #[test]
1063    fn test_message_mold_data_to_json() -> Result<(), String> {
1064        let mut map: IndexMap<TextID, R32> = IndexMap::new();
1065
1066        map.insert("Hello".try_into().unwrap(), R32::new(123.0));
1067        map.insert("World".try_into().unwrap(), R32::new(-987.6543));
1068        map.insert("foo".try_into().unwrap(), R32::new(0.0));
1069
1070        let mut options = MessageOptions::new_with_priority(-20);
1071        options.sequence = 999;
1072
1073        let msg = MoldData {
1074            controller_id: ID::from_u32(123),
1075            data: map,
1076
1077            timestamp: DateTime::parse_from_rfc3339("2019-02-26T02:03:04+08:00")
1078                .map_err(|x| x.to_string())?,
1079
1080            state: StateValues::try_new_with_all(
1081                OpMode::SemiAutomatic,
1082                JobMode::Offline,
1083                Some(ID::from_u32(42)),
1084                Some("Hello World!"),
1085                None,
1086            )?,
1087
1088            options,
1089        };
1090
1091        let serialized = serde_json::to_string(&msg).map_err(|x| x.to_string())?;
1092
1093        assert_eq!(
1094            r#"{"$type":"MoldData","controllerId":123,"data":{"Hello":123.0,"World":-987.6543,"foo":0.0},"timestamp":"2019-02-26T02:03:04+08:00","opMode":"SemiAutomatic","jobMode":"Offline","operatorId":42,"jobCardId":"Hello World!","sequence":999,"priority":-20}"#,
1095            serialized
1096        );
1097
1098        let m2 = Message::parse_from_json_str(&serialized).map_err(|x| x.to_string())?;
1099
1100        assert_eq!(format!("{:?}", msg), format!("{:?}", m2));
1101
1102        Ok(())
1103    }
1104
1105    #[test]
1106    fn test_message_controllers_list_from_json() -> Result<(), String> {
1107        let json = r#"{"$type":"ControllersList","data":{"12345":{"controllerId":12345,"displayName":"Hello","controllerType":"Ai12","version":"1.0.0","model":"JM128-Ai","IP":"192.168.5.1:123","opMode":"Manual","jobMode":"ID11","lastCycleData":{"Z_QDGODCNT":8567,"Z_QDCYCTIM":979,"Z_QDINJTIM":5450,"Z_QDPLSTIM":7156,"Z_QDINJENDPOS":8449,"Z_QDPLSENDPOS":2212,"Z_QDFLAG":8988,"Z_QDPRDCNT":65500,"Z_QDCOLTIM":4435,"Z_QDMLDOPNTIM":652,"Z_QDMLDCLSTIM":2908,"Z_QDVPPOS":4732,"Z_QDMLDOPNENDPOS":6677,"Z_QDMAXINJSPD":7133,"Z_QDMAXPLSRPM":641,"Z_QDNOZTEMP":6693,"Z_QDTEMPZ01":9964,"Z_QDTEMPZ02":7579,"Z_QDTEMPZ03":4035,"Z_QDTEMPZ04":5510,"Z_QDTEMPZ05":8460,"Z_QDTEMPZ06":9882,"Z_QDBCKPRS":2753,"Z_QDHLDTIM":9936},"lastConnectionTime":"2016-03-06T23:11:27.1442177+08:00"},"22334":{"controllerId":22334,"displayName":"World","controllerType":"Ai01","version":"1.0.0","model":"JM128-Ai","IP":"192.168.5.2:234","opMode":"SemiAutomatic","jobMode":"ID12","lastCycleData":{"Z_QDGODCNT":6031,"Z_QDCYCTIM":7526,"Z_QDINJTIM":4896,"Z_QDPLSTIM":5196,"Z_QDINJENDPOS":1250,"Z_QDPLSENDPOS":8753,"Z_QDFLAG":3314,"Z_QDPRDCNT":65500,"Z_QDCOLTIM":3435,"Z_QDMLDOPNTIM":7854,"Z_QDMLDCLSTIM":4582,"Z_QDVPPOS":7504,"Z_QDMLDOPNENDPOS":7341,"Z_QDMAXINJSPD":7322,"Z_QDMAXPLSRPM":6024,"Z_QDNOZTEMP":3406,"Z_QDTEMPZ01":3067,"Z_QDTEMPZ02":9421,"Z_QDTEMPZ03":2080,"Z_QDTEMPZ04":8845,"Z_QDTEMPZ05":4478,"Z_QDTEMPZ06":3126,"Z_QDBCKPRS":2807,"Z_QDHLDTIM":3928},"lastConnectionTime":"2016-03-06T23:11:27.149218+08:00"}},"sequence":68568}"#;
1108
1109        let msg = Message::parse_from_json_str(&json).map_err(|x| x.to_string())?;
1110
1111        if let ControllersList { data, .. } = &msg {
1112            assert_eq!(2, data.len());
1113            let c = data.get(&ID::from_u32(12345)).unwrap();
1114            assert_eq!("Hello", &c.display_name);
1115            Ok(())
1116        } else {
1117            Err(format!("Expected ControllersList, got {:#?}", msg))
1118        }
1119    }
1120
1121    #[test]
1122    fn test_message_cycle_data_from_json() -> Result<(), String> {
1123        let json = r#"{"$type":"CycleData","timestamp":"2016-02-26T01:12:23+08:00","opMode":"Automatic","jobMode":"ID02","controllerId":123,"data":{"Z_QDGODCNT":123,"Z_QDCYCTIM":12.33,"Z_QDINJTIM":3,"Z_QDPLSTIM":4.4,"Z_QDINJENDPOS":30.1,"Z_QDPLSENDPOS":20.3,"Z_QDFLAG":1,"Z_QDPRDCNT":500,"Z_QDCOLTIM":12.12,"Z_QDMLDOPNTIM":2.1,"Z_QDMLDCLSTIM":1.3,"Z_QDVPPOS":12.11,"Z_QDMLDOPNENDPOS":130.1,"Z_QDMAXINJSPD":213.12,"Z_QDMAXPLSRPM":551,"Z_QDNOZTEMP":256,"Z_QDTEMPZ01":251,"Z_QDTEMPZ02":252,"Z_QDTEMPZ03":253,"Z_QDTEMPZ04":254,"Z_QDTEMPZ05":255,"Z_QDTEMPZ06":256,"Z_QDBCKPRS":54,"Z_QDHLDTIM":2.3,"Z_QDCPT01":231,"Z_QDCPT02":232,"Z_QDCPT03":233,"Z_QDCPT04":234,"Z_QDCPT05":235,"Z_QDCPT06":236,"Z_QDCPT07":237,"Z_QDCPT08":238,"Z_QDCPT09":239,"Z_QDCPT10":240,"Z_QDCPT11":241,"Z_QDCPT12":242,"Z_QDCPT13":243,"Z_QDCPT14":244,"Z_QDCPT15":245,"Z_QDCPT16":246,"Z_QDCPT17":247,"Z_QDCPT18":248,"Z_QDCPT19":249,"Z_QDCPT20":250,"Z_QDCPT21":251,"Z_QDCPT22":252,"Z_QDCPT23":253,"Z_QDCPT24":254,"Z_QDCPT25":255,"Z_QDCPT26":256,"Z_QDCPT27":257,"Z_QDCPT28":258,"Z_QDCPT29":259,"Z_QDCPT30":260,"Z_QDCPT31":261,"Z_QDCPT32":262,"Z_QDCPT33":263,"Z_QDCPT34":264,"Z_QDCPT35":265,"Z_QDCPT36":266,"Z_QDCPT37":267,"Z_QDCPT38":268,"Z_QDCPT39":269,"Z_QDCPT40":270},"sequence":1}"#;
1124
1125        let msg = Message::parse_from_json_str(&json).map_err(|x| x.to_string())?;
1126
1127        if let CycleData { controller_id, data, .. } = &msg {
1128            assert_eq!(0, msg.priority());
1129            assert_eq!(123, *controller_id);
1130            assert_eq!(64, data.len());
1131            assert!(*data.get(&TextID::new("Z_QDCPT13").unwrap()).unwrap() == R32::new(243.0));
1132            Ok(())
1133        } else {
1134            Err(format!("Expected CycleData, got {:#?}", msg))
1135        }
1136    }
1137
1138    #[test]
1139    fn test_message_controller_status_without_controller_from_json() -> Result<(), String> {
1140        let json = r#"{"$type":"ControllerStatus","controllerId":123,"displayName":"Testing","opMode":"Automatic","alarm":{"key":"hello","value":true},"jobMode":"ID05","jobCardId":"XYZ","moldId":"Mold-123","state":{"opMode":"Automatic","jobMode":"ID05","jobCardId":"XYZ","moldId":"Mold-123"},"sequence":1,"priority":50}"#;
1141
1142        let msg = Message::parse_from_json_str(&json).map_err(|x| x.to_string())?;
1143
1144        if let ControllerStatus { controller_id, display_name, controller, alarm, .. } = &msg {
1145            assert_eq!(50, msg.priority());
1146            assert_eq!(1, msg.sequence());
1147            assert_eq!(123, *controller_id);
1148            assert_eq!(Some(Box::new("Testing".try_into().unwrap())), *display_name);
1149            assert!(controller.is_none());
1150            assert_eq!(
1151                Some(Box::new(KeyValuePair::new("hello".try_into().unwrap(), true))),
1152                *alarm
1153            );
1154            Ok(())
1155        } else {
1156            Err(format!("Expected ControllerStatus, got {:#?}", msg))
1157        }
1158    }
1159
1160    #[test]
1161    fn test_message_controller_status_with_controller_from_json() -> Result<(), String> {
1162        let json = r#"{"$type":"ControllerStatus","controllerId":123,"state":{"opMode":"Automatic","jobMode":"ID05","jobCardId":"XYZ","moldId":"Mold-123"},"controller":{"controllerId":123,"displayName":"Testing","controllerType":"Ai02","version":"2.2","model":"JM138Ai","IP":"192.168.1.1:12345","geoLatitude":23.0,"geoLongitude":-121.0,"opMode":"Automatic","jobMode":"ID05","jobCardId":"XYZ","lastCycleData":{"INJ":5,"CLAMP":400},"moldId":"Mold-123"},"sequence":1}"#;
1163
1164        let msg = Message::parse_from_json_str(&json).map_err(|x| x.to_string())?;
1165
1166        if let ControllerStatus { controller_id, display_name, state, controller, .. } = &msg {
1167            assert_eq!(0, msg.priority());
1168            assert_eq!(1, msg.sequence());
1169            assert_eq!(123, *controller_id);
1170            assert_eq!(None, *display_name);
1171            assert_eq!(OpMode::Automatic, state.op_mode());
1172            assert_eq!(JobMode::ID05, state.job_mode());
1173            assert_eq!(Some("XYZ"), state.job_card_id());
1174            let c = controller.as_ref().unwrap();
1175            assert_eq!("JM138Ai", &c.model);
1176            let d = &c.last_cycle_data;
1177            assert!(c.operator.is_none());
1178            assert_eq!(2, d.len());
1179            assert!(*d.get(&TextID::new("INJ").unwrap()).unwrap() == R32::new(5.0));
1180            Ok(())
1181        } else {
1182            Err(format!("Expected ControllerStatus, got {:#?}", msg))
1183        }
1184    }
1185
1186    #[test]
1187    fn test_message_controller_status_to_json() -> Result<(), String> {
1188        let status: Message = ControllerStatus {
1189            controller_id: ID::from_u32(12345),
1190            display_name: None,
1191            is_disconnected: None,
1192            op_mode: None,
1193            job_mode: None,
1194            job_card_id: None,
1195            mold_id: Some(None),
1196            operator_id: Some(Some(ID::from_u32(123))),
1197            operator_name: Some(None),
1198            variable: None,
1199            audit: None,
1200            alarm: Some(Box::new(KeyValuePair::new("hello".try_into().unwrap(), true))),
1201            controller: None,
1202            state: StateValues::try_new_with_all(
1203                OpMode::Automatic,
1204                JobMode::ID02,
1205                Some(ID::from_u32(123)),
1206                None,
1207                None,
1208            )?,
1209            options: MessageOptions::default_new(),
1210        };
1211
1212        let msg = status.to_json_str()?;
1213        assert_eq!(
1214            r#"{"$type":"ControllerStatus","controllerId":12345,"alarm":{"key":"hello","value":true},"operatorId":123,"operatorName":null,"moldId":null,"state":{"opMode":"Automatic","jobMode":"ID02","operatorId":123},"sequence":1}"#,
1215            msg
1216        );
1217        Ok(())
1218    }
1219
1220    #[test]
1221    fn test_message_controller_status_to_json2() -> Result<(), String> {
1222        let status = ControllerStatus {
1223            controller_id: ID::from_u32(12345),
1224            display_name: None,
1225            is_disconnected: Some(true),
1226            op_mode: None,
1227            job_mode: None,
1228            job_card_id: Some(None),
1229            mold_id: Some(Some(Box::new("Test".try_into().unwrap()))),
1230            operator_id: Some(None),
1231            operator_name: Some(None),
1232            variable: None,
1233            audit: None,
1234            alarm: None,
1235            controller: None,
1236            state: StateValues::try_new_with_all(
1237                OpMode::Automatic,
1238                JobMode::ID02,
1239                None,
1240                None,
1241                Some("Test"),
1242            )?,
1243            options: MessageOptions::default_new(),
1244        };
1245
1246        let msg = status.to_json_str()?;
1247        assert_eq!(
1248            r#"{"$type":"ControllerStatus","controllerId":12345,"isDisconnected":true,"operatorId":0,"operatorName":null,"jobCardId":null,"moldId":"Test","state":{"opMode":"Automatic","jobMode":"ID02","moldId":"Test"},"sequence":1}"#,
1249            msg
1250        );
1251        Ok(())
1252    }
1253}