bnr_xfs/xfs/
method_call.rs

1//! XFS `method call` types.
2
3use std::fmt;
4
5use super::operation_id::OperationId;
6use super::params::{XfsParam, XfsParams};
7use super::xfs_struct::XfsStruct;
8use crate::{Error, Result};
9
10/// Represents an XFS method call containing a list of [params](XfsParams).
11#[repr(C)]
12#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
13#[serde(rename = "methodCall")]
14pub struct XfsMethodCall {
15    #[serde(rename = "methodName")]
16    name: String,
17    params: XfsParams,
18}
19
20impl XfsMethodCall {
21    /// Creates a new [XfsMethodCall].
22    pub const fn new() -> Self {
23        Self {
24            name: String::new(),
25            params: XfsParams::new(),
26        }
27    }
28
29    /// Creates a new [XfsMethodCall] with the provided parameters.
30    pub fn create<P: Into<Vec<XfsParam>>>(name: XfsMethodName, params: P) -> Self {
31        Self {
32            name: <&str>::from(name).into(),
33            params: XfsParams::create(params.into()),
34        }
35    }
36
37    /// Gets the [name](XfsMethodName).
38    ///
39    /// Returns:
40    ///
41    /// - `Ok(XfsMethodName)` if [XfsMethodCall] has a valid [XfsMethodName] set.
42    /// - `Err(_)` otherwise
43    pub fn name(&self) -> Result<XfsMethodName> {
44        self.name.as_str().try_into()
45    }
46
47    /// Gets the [name](XfsMethodName) as a string.
48    pub fn name_str(&self) -> &str {
49        self.name.as_str()
50    }
51
52    /// Sets the [name](XfsMethodName).
53    pub fn set_name(&mut self, name: XfsMethodName) {
54        self.name = <&str>::from(name).into();
55    }
56
57    /// Builder function that sets the [name](XfsMethodName).
58    pub fn with_name(mut self, name: XfsMethodName) -> Self {
59        self.set_name(name);
60        self
61    }
62
63    /// Gets the [params](XfsParams).
64    pub const fn params(&self) -> &XfsParams {
65        &self.params
66    }
67
68    /// Sets the [params](XfsParams).
69    pub fn set_params(&mut self, params: XfsParams) {
70        self.params = params;
71    }
72
73    /// Builder function that sets the [params](XfsParams).
74    pub fn with_params(mut self, params: XfsParams) -> Self {
75        self.set_params(params);
76        self
77    }
78
79    /// Gets whether the [XfsMethodCall] is asynchronous.
80    ///
81    /// Multiple asynchronous calls are `illegal`, and will fail after the initial call.
82    pub fn is_async(&self) -> bool {
83        if let Ok(name) = self.name() {
84            matches!(
85                name,
86                XfsMethodName::CashIn
87                    | XfsMethodName::CashInStart
88                    | XfsMethodName::CashInRollback
89                    | XfsMethodName::CashInEnd
90                    | XfsMethodName::Empty
91                    | XfsMethodName::Eject
92                    | XfsMethodName::Reset
93                    | XfsMethodName::Park
94                    | XfsMethodName::Denominate
95                    | XfsMethodName::Dispense
96                    | XfsMethodName::Present
97                    | XfsMethodName::Retract
98            )
99        } else {
100            false
101        }
102    }
103
104    /// Gets the async callback ID from the [XfsMethodCall].
105    ///
106    /// Returns: `Ok(i32)` on success, `Err(Error)` on failure
107    pub fn call_id(&self) -> Result<i32> {
108        self.params()
109            .params()
110            .iter()
111            .find(|&p| p.inner().value().i4().is_some())
112            .ok_or(Error::Xfs("missing callback ID".into()))?
113            .inner()
114            .value()
115            .i4()
116            .cloned()
117            .ok_or(Error::Xfs("missing callback ID".into()))
118    }
119
120    /// Gets the async operation ID from the [XfsMethodCall].
121    ///
122    /// Returns: `Ok(OperationId)` on success, `Err(Error)` on failure
123    pub fn operation_id(&self) -> Result<OperationId> {
124        Ok(OperationId::create(
125            self.params()
126                .params()
127                .iter()
128                .filter(|&p| p.inner().value().i4().is_some())
129                .nth(1)
130                .ok_or(Error::Xfs("missing operation ID".into()))?
131                .inner()
132                .value()
133                .i4()
134                .cloned()
135                .ok_or(Error::Xfs("missing operation ID".into()))? as u32,
136        ))
137    }
138
139    /// Gets the async operation result from the [XfsMethodCall].
140    ///
141    /// Returns: `Ok(OperationId)` on success, `Err(Error)` on failure
142    pub fn result(&self) -> Result<i32> {
143        self.params()
144            .params()
145            .iter()
146            .filter(|&p| p.inner().value().i4().is_some())
147            .nth(2)
148            .ok_or(Error::Xfs("missing result".into()))?
149            .inner()
150            .value()
151            .i4()
152            .cloned()
153            .ok_or(Error::Xfs("missing result".into()))
154    }
155
156    /// Gets the async operation extended result from the [XfsMethodCall].
157    ///
158    /// Returns: `Ok(OperationId)` on success, `Err(Error)` on failure
159    pub fn ext_result(&self) -> Result<i32> {
160        self.params()
161            .params()
162            .iter()
163            .filter(|&p| p.inner().value().i4().is_some())
164            .nth(3)
165            .ok_or(Error::Xfs("missing extended result".into()))?
166            .inner()
167            .value()
168            .i4()
169            .cloned()
170            .ok_or(Error::Xfs("missing extended result".into()))
171    }
172
173    /// Gets the async operation [XfsStruct] parameter from the [XfsMethodCall].
174    ///
175    /// Returns: `Ok(OperationId)` on success, `Err(Error)` on failure
176    pub fn xfs_struct(&self) -> Result<XfsStruct> {
177        self.params()
178            .params()
179            .iter()
180            .find(|&p| p.inner().value().xfs_struct().is_some())
181            .ok_or(Error::Xfs("missing XFS struct parameter".into()))?
182            .inner()
183            .value()
184            .xfs_struct()
185            .cloned()
186            .ok_or(Error::Xfs("missing XFS struct parameter".into()))
187    }
188}
189
190impl From<&XfsMethodName> for XfsMethodCall {
191    fn from(val: &XfsMethodName) -> Self {
192        Self {
193            name: <&str>::from(val).into(),
194            params: XfsParams::new(),
195        }
196    }
197}
198
199impl From<XfsMethodName> for XfsMethodCall {
200    fn from(val: XfsMethodName) -> Self {
201        (&val).into()
202    }
203}
204
205impl fmt::Display for XfsMethodCall {
206    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
207        write!(f, "{{")?;
208        write!(f, r#""name": {}", "#, self.name)?;
209        write!(f, r#""params": ["#)?;
210        for (i, param) in self.params.params().iter().enumerate() {
211            if i != 0 {
212                write!(f, ", ")?;
213            }
214            write!(f, "{param}")?;
215        }
216        write!(f, "]}}")
217    }
218}
219
220/// Represents the [XfsMethodCall] name used in a procedure call to a BNR device.
221#[repr(C)]
222#[derive(Clone, Copy, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)]
223pub enum XfsMethodName {
224    #[serde(rename = "bnr.getdatetime")]
225    GetDateTime,
226    #[serde(rename = "bnr.setdatetime")]
227    SetDateTime,
228    #[serde(rename = "bnr.cashinstart")]
229    CashInStart,
230    #[serde(rename = "bnr.cashin")]
231    CashIn,
232    #[serde(rename = "bnr.cashinrollback")]
233    CashInRollback,
234    #[serde(rename = "bnr.cashinend")]
235    CashInEnd,
236    #[serde(rename = "module.getidentification")]
237    #[default]
238    GetIdentification,
239    #[serde(rename = "bnr.getstatus")]
240    GetStatus,
241    #[serde(rename = "bnr.stopsession")]
242    StopSession,
243    #[serde(rename = "bnr.reset")]
244    Reset,
245    #[serde(rename = "bnr.reboot")]
246    Reboot,
247    #[serde(rename = "bnr.cancel")]
248    Cancel,
249    #[serde(rename = "bnr.park")]
250    Park,
251    #[serde(rename = "bnr.empty")]
252    Empty,
253    #[serde(rename = "bnr.eject")]
254    Eject,
255    #[serde(rename = "bnr.querycashunit")]
256    QueryCashUnit,
257    #[serde(rename = "bnr.configurecashunit")]
258    ConfigureCashUnit,
259    #[serde(rename = "bnr.updatecashunit")]
260    UpdateCashUnit,
261    #[serde(rename = "bnr.denominate")]
262    Denominate,
263    #[serde(rename = "bnr.dispense")]
264    Dispense,
265    #[serde(rename = "bnr.present")]
266    Present,
267    #[serde(rename = "bnr.cancelwaitingcashtaken")]
268    CancelWaitingCashTaken,
269    #[serde(rename = "bnr.retract")]
270    Retract,
271    #[serde(rename = "bnr.getcapabilities")]
272    GetCapabilities,
273    #[serde(rename = "bnr.setcapabilities")]
274    SetCapabilities,
275    #[serde(rename = "bnr.querydenominations")]
276    QueryDenominations,
277    #[serde(rename = "bnr.updatedenominations")]
278    UpdateDenominations,
279    #[serde(rename = "bnr.querybillsetids")]
280    QueryBillsetIds,
281    #[serde(rename = "bnr.getbillacceptancehistory")]
282    GetBillAcceptanceHistory,
283    #[serde(rename = "bnr.getbilldispensehistory")]
284    GetBillDispenseHistory,
285    #[serde(rename = "bnr.getfailurehistory")]
286    GetFailureHistory,
287    #[serde(rename = "bnr.getrestarthistory")]
288    GetRestartHistory,
289    #[serde(rename = "bnr.getusehistory")]
290    GetUseHistory,
291    // **NOTE**: `Occured` is not a typo here, it is a mispelling in the protocol message that we have to replicate
292    #[serde(rename = "BnrListener.operationCompleteOccured")]
293    OperationCompleteOccurred,
294    // **NOTE**: `Occured` is not a typo here, it is a mispelling in the protocol message that we have to replicate
295    #[serde(rename = "BnrListener.intermediateOccured")]
296    IntermediateOccurred,
297    // **NOTE**: `Occured` is not a typo here, it is a mispelling in the protocol message that we have to replicate
298    #[serde(rename = "BnrListener.statusOccured")]
299    StatusOccurred,
300}
301
302impl XfsMethodName {
303    /// Creates a new [XfsMethodName].
304    pub const fn new() -> Self {
305        Self::GetIdentification
306    }
307}
308
309impl From<&XfsMethodName> for &'static str {
310    fn from(val: &XfsMethodName) -> Self {
311        match val {
312            XfsMethodName::GetDateTime => "bnr.getdatetime",
313            XfsMethodName::SetDateTime => "bnr.setdatetime",
314            XfsMethodName::CashInStart => "bnr.cashinstart",
315            XfsMethodName::CashIn => "bnr.cashin",
316            XfsMethodName::CashInRollback => "bnr.cashinrollback",
317            XfsMethodName::CashInEnd => "bnr.cashinend",
318            XfsMethodName::GetIdentification => "module.getidentification",
319            XfsMethodName::GetStatus => "bnr.getstatus",
320            XfsMethodName::StopSession => "bnr.stopsession",
321            XfsMethodName::Reset => "bnr.reset",
322            XfsMethodName::Reboot => "bnr.reboot",
323            XfsMethodName::Cancel => "bnr.cancel",
324            XfsMethodName::Park => "bnr.park",
325            XfsMethodName::Empty => "bnr.empty",
326            XfsMethodName::Eject => "bnr.eject",
327            XfsMethodName::QueryCashUnit => "bnr.querycashunit",
328            XfsMethodName::ConfigureCashUnit => "bnr.configurecashunit",
329            XfsMethodName::UpdateCashUnit => "bnr.updatecashunit",
330            XfsMethodName::Denominate => "bnr.denominate",
331            XfsMethodName::Dispense => "bnr.dispense",
332            XfsMethodName::Present => "bnr.present",
333            XfsMethodName::CancelWaitingCashTaken => "bnr.cancelwaitingcashtaken",
334            XfsMethodName::Retract => "bnr.retract",
335            XfsMethodName::GetCapabilities => "bnr.getcapabilities",
336            XfsMethodName::SetCapabilities => "bnr.setcapabilities",
337            XfsMethodName::QueryDenominations => "bnr.querydenominations",
338            XfsMethodName::UpdateDenominations => "bnr.updatedenominations",
339            XfsMethodName::QueryBillsetIds => "bnr.querybillsetids",
340            XfsMethodName::GetBillAcceptanceHistory => "bnr.getbillacceptancehistory",
341            XfsMethodName::GetBillDispenseHistory => "bnr.getbilldispensehistory",
342            XfsMethodName::GetFailureHistory => "bnr.getfailurehistory",
343            XfsMethodName::GetRestartHistory => "bnr.getrestarthistory",
344            XfsMethodName::GetUseHistory => "bnr.getusehistory",
345            XfsMethodName::OperationCompleteOccurred => "BnrListener.operationCompleteOccured",
346            XfsMethodName::IntermediateOccurred => "BnrListener.intermediateOccured",
347            XfsMethodName::StatusOccurred => "BnrListener.statusOccured",
348        }
349    }
350}
351
352impl From<XfsMethodName> for &'static str {
353    fn from(val: XfsMethodName) -> Self {
354        (&val).into()
355    }
356}
357
358impl TryFrom<&str> for XfsMethodName {
359    type Error = Error;
360
361    fn try_from(val: &str) -> Result<Self> {
362        match val.to_lowercase().as_str() {
363            "bnr.getdatetime" => Ok(Self::GetDateTime),
364            "bnr.setdatetime" => Ok(Self::SetDateTime),
365            "bnr.cashinstart" => Ok(Self::CashInStart),
366            "bnr.cashin" => Ok(Self::CashIn),
367            "bnr.cashinrollback" => Ok(Self::CashInRollback),
368            "bnr.cashinend" => Ok(Self::CashInEnd),
369            "module.getidentification" => Ok(Self::GetIdentification),
370            "bnr.getstatus" => Ok(Self::GetStatus),
371            "bnr.stopsession" => Ok(Self::StopSession),
372            "bnr.reset" => Ok(Self::Reset),
373            "bnr.reboot" => Ok(Self::Reboot),
374            "bnr.cancel" => Ok(Self::Cancel),
375            "bnr.park" => Ok(Self::Park),
376            "bnr.empty" => Ok(Self::Empty),
377            "bnr.eject" => Ok(Self::Eject),
378            "bnr.querycashunit" => Ok(Self::QueryCashUnit),
379            "bnr.configurecashunit" => Ok(Self::ConfigureCashUnit),
380            "bnr.updatecashunit" => Ok(Self::UpdateCashUnit),
381            "bnr.denominate" => Ok(Self::Denominate),
382            "bnr.dispense" => Ok(Self::Dispense),
383            "bnr.present" => Ok(Self::Present),
384            "bnr.cancelwaitingcashtaken" => Ok(Self::CancelWaitingCashTaken),
385            "bnr.retract" => Ok(Self::Retract),
386            "bnr.getcapabilities" => Ok(Self::GetCapabilities),
387            "bnr.setcapabilities" => Ok(Self::SetCapabilities),
388            "bnr.querydenominations" => Ok(Self::QueryDenominations),
389            "bnr.updatedenominations" => Ok(Self::UpdateDenominations),
390            "bnr.querybillsetids" => Ok(Self::QueryBillsetIds),
391            "bnr.getbillacceptancehistory" => Ok(Self::GetBillAcceptanceHistory),
392            "bnr.getbilldispensehistory" => Ok(Self::GetBillDispenseHistory),
393            "bnr.getfailurehistory" => Ok(Self::GetFailureHistory),
394            "bnr.getrestarthistory" => Ok(Self::GetRestartHistory),
395            "bnr.getusehistory" => Ok(Self::GetUseHistory),
396            "bnrlistener.operationcompleteoccured" => Ok(Self::OperationCompleteOccurred),
397            "bnrlistener.intermediateoccured" => Ok(Self::IntermediateOccurred),
398            "bnrlistener.statusoccured" => Ok(Self::StatusOccurred),
399            _ => Err(Error::Parsing(format!("unknown method name: {val}"))),
400        }
401    }
402}
403
404impl fmt::Display for XfsMethodName {
405    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
406        write!(f, r#""{}""#, <&str>::from(self))
407    }
408}
409
410#[cfg(test)]
411mod tests {
412    use super::*;
413    use crate::{xfs, Result};
414
415    #[test]
416    fn test_method_call_serde() -> Result<()> {
417        let exp_xml = r#"<?xml version="1.0" encoding="UTF-8"?><methodCall><methodName></methodName><params/></methodCall>"#;
418        let xml_str = xfs::to_string(&XfsMethodCall::new())?;
419
420        assert_eq!(xml_str.as_str(), exp_xml);
421
422        let exp_xml = r#"<?xml version="1.0" encoding="UTF-8"?><methodCall><methodName>module.getidentification</methodName><params/></methodCall>"#;
423        let xml_str = xfs::to_string(&XfsMethodCall::from(XfsMethodName::GetIdentification))?;
424
425        assert_eq!(xml_str.as_str(), exp_xml);
426
427        Ok(())
428    }
429}