dbc_rs/message/
message_builder.rs

1#[cfg(any(feature = "alloc", feature = "kernel"))]
2use crate::compat::{Box, String, Vec, str_to_string};
3use crate::{
4    error::messages,
5    {Error, Message, ParseOptions, Result, Signal},
6};
7
8#[cfg(any(feature = "alloc", feature = "kernel"))]
9#[derive(Debug, Clone, Default)]
10pub struct MessageBuilder {
11    id: Option<u32>,
12    name: Option<String>,
13    dlc: Option<u8>,
14    sender: Option<String>,
15    signals: Vec<Signal<'static>>,
16}
17
18#[cfg(any(feature = "alloc", feature = "kernel"))]
19impl MessageBuilder {
20    pub fn new() -> Self {
21        Self::default()
22    }
23
24    #[must_use]
25    pub fn id(mut self, id: u32) -> Self {
26        self.id = Some(id);
27        self
28    }
29
30    #[must_use]
31    pub fn name(mut self, name: impl AsRef<str>) -> Self {
32        self.name = Some(str_to_string(name));
33        self
34    }
35
36    #[must_use]
37    pub fn dlc(mut self, dlc: u8) -> Self {
38        self.dlc = Some(dlc);
39        self
40    }
41
42    #[must_use]
43    pub fn sender(mut self, sender: impl AsRef<str>) -> Self {
44        self.sender = Some(str_to_string(sender));
45        self
46    }
47
48    #[must_use]
49    pub fn add_signal(mut self, signal: Signal<'static>) -> Self {
50        self.signals.push(signal);
51        self
52    }
53
54    #[must_use]
55    pub fn add_signals(mut self, signals: impl IntoIterator<Item = Signal<'static>>) -> Self {
56        self.signals.extend(signals);
57        self
58    }
59
60    #[must_use]
61    pub fn signals(mut self, signals: Vec<Signal<'static>>) -> Self {
62        self.signals = signals;
63        self
64    }
65
66    #[must_use]
67    pub fn clear_signals(mut self) -> Self {
68        self.signals.clear();
69        self
70    }
71
72    fn extract_fields(self) -> Result<(u32, String, u8, String, Vec<Signal<'static>>)> {
73        let id = self.id.ok_or_else(|| {
74            Error::Message(crate::error::str_to_error_string(
75                messages::MESSAGE_ID_REQUIRED,
76            ))
77        })?;
78        let name = self.name.ok_or_else(|| {
79            Error::Message(crate::error::str_to_error_string(
80                messages::MESSAGE_NAME_EMPTY,
81            ))
82        })?;
83        let dlc = self.dlc.ok_or_else(|| {
84            Error::Message(crate::error::str_to_error_string(
85                messages::MESSAGE_DLC_REQUIRED,
86            ))
87        })?;
88        let sender = self.sender.ok_or_else(|| {
89            Error::Message(crate::error::str_to_error_string(
90                messages::MESSAGE_SENDER_EMPTY,
91            ))
92        })?;
93        Ok((id, name, dlc, sender, self.signals))
94    }
95
96    #[must_use = "validation result should be checked"]
97    pub fn validate(self) -> Result<Self> {
98        let (id, name, dlc, sender, signals) = self.extract_fields()?;
99        // Convert Vec to Option array for validation (all Some)
100        let signals_options: Vec<Option<Signal<'static>>> =
101            signals.iter().cloned().map(Some).collect();
102        let signals_options_slice: &[Option<Signal<'static>>] = &signals_options;
103        Message::validate_internal(
104            id,
105            &name,
106            dlc,
107            &sender,
108            signals_options_slice,
109            signals_options_slice.len(),
110            ParseOptions::new(), // Builder always uses strict mode
111        )
112        .map_err(Error::from)?;
113        Ok(Self {
114            id: Some(id),
115            name: Some(name),
116            dlc: Some(dlc),
117            sender: Some(sender),
118            signals,
119        })
120    }
121
122    pub fn build(self) -> Result<Message<'static>> {
123        let (id, name, dlc, sender, signals) = self.extract_fields()?;
124        // Convert Vec to Option array for validation (all Some)
125        let signals_options: Vec<Option<Signal<'static>>> =
126            signals.iter().cloned().map(Some).collect();
127        let signals_options_slice: &[Option<Signal<'static>>] = &signals_options;
128        // Validate before construction
129        Message::validate_internal(
130            id,
131            &name,
132            dlc,
133            &sender,
134            signals_options_slice,
135            signals_options_slice.len(),
136            ParseOptions::new(), // Builder always uses strict mode
137        )
138        .map_err(|e| match e {
139            crate::error::ParseError::Version(msg) => {
140                Error::Dbc(crate::error::str_to_error_string(msg))
141            }
142            _ => Error::ParseError(e),
143        })?;
144        // Convert owned strings to static references by leaking Box<str>
145        let name_boxed: Box<str> = name.into_boxed_str();
146        let name_static: &'static str = Box::leak(name_boxed);
147        let sender_boxed: Box<str> = sender.into_boxed_str();
148        let sender_static: &'static str = Box::leak(sender_boxed);
149        // Convert Vec to slice and leak to get static lifetime
150        let signals_boxed: Box<[Signal<'static>]> = signals.into_boxed_slice();
151        let signals_static: &'static [Signal<'static>] = Box::leak(signals_boxed);
152        Ok(Message::new(
153            id,
154            name_static,
155            dlc,
156            sender_static,
157            signals_static,
158        ))
159    }
160}