netconf/message/rpc/operation/
commit.rs

1use std::{io::Write, time::Duration};
2
3use quick_xml::{events::BytesText, Writer};
4
5use crate::{
6    capabilities::{Capability, Requirements},
7    message::WriteError,
8    session::Context,
9    Error,
10};
11
12use super::{EmptyReply, Operation, Timeout, Token, WriteXml};
13
14#[derive(Debug, Clone)]
15pub struct Commit {
16    confirmed: bool,
17    confirm_timeout: Timeout,
18    persist: Option<Token>,
19    persist_id: Option<Token>,
20}
21
22impl Operation for Commit {
23    const NAME: &'static str = "commit";
24    const REQUIRED_CAPABILITIES: Requirements = Requirements::One(Capability::Candidate);
25
26    type Builder<'a> = Builder<'a>;
27    type Reply = EmptyReply;
28}
29
30impl WriteXml for Commit {
31    fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<(), WriteError> {
32        let elem = writer.create_element("commit");
33        if self.confirmed {
34            elem.write_inner_content(|writer| {
35                _ = writer.create_element("confirmed").write_empty()?;
36                if self.confirm_timeout != Timeout::default() {
37                    _ = writer
38                        .create_element("confirm-timeout")
39                        .write_text_content(self.confirm_timeout.seconds())?;
40                };
41                if let Some(ref token) = self.persist {
42                    _ = writer
43                        .create_element("persist")
44                        .write_text_content(BytesText::new(&token.to_string()))?;
45                }
46                Ok(())
47            })
48            .map(|_| ())
49        } else if let Some(ref token) = self.persist_id {
50            elem.write_inner_content(|writer| {
51                _ = writer
52                    .create_element("persist-id")
53                    .write_text_content(BytesText::new(&token.to_string()))?;
54                Ok(())
55            })
56            .map(|_| ())
57        } else {
58            _ = elem.write_empty()?;
59            Ok(())
60        }
61    }
62}
63
64#[derive(Debug, Clone)]
65#[must_use]
66pub struct Builder<'a> {
67    ctx: &'a Context,
68    confirmed: bool,
69    confirm_timeout: Timeout,
70    persist: Option<Token>,
71    persist_id: Option<Token>,
72}
73
74impl Builder<'_> {
75    pub fn confirmed(mut self, confirmed: bool) -> Result<Self, Error> {
76        self.try_use_confirmed("confirmed").map(|()| {
77            self.confirmed = confirmed;
78            self
79        })
80    }
81
82    pub fn confirm_timeout(mut self, timeout: Duration) -> Result<Self, Error> {
83        self.try_use_confirmed("confirm-timeout").map(|()| {
84            self.confirm_timeout = Timeout(timeout);
85            self
86        })
87    }
88
89    pub fn persist(mut self, token: Option<Token>) -> Result<Self, Error> {
90        self.try_use_persist("persist").map(|()| {
91            self.persist = token;
92            self
93        })
94    }
95
96    pub fn persist_id(mut self, token: Option<Token>) -> Result<Self, Error> {
97        self.try_use_persist("persist-id").map(|()| {
98            self.persist_id = token;
99            self
100        })
101    }
102
103    fn try_use_confirmed(&self, param_name: &'static str) -> Result<(), Error> {
104        let required_capabilities = Requirements::Any(&[
105            Capability::ConfirmedCommitV1_0,
106            Capability::ConfirmedCommitV1_1,
107        ]);
108        self.try_use(required_capabilities, param_name)
109    }
110
111    fn try_use_persist(&self, param_name: &'static str) -> Result<(), Error> {
112        let required_capabilities = Requirements::One(Capability::ConfirmedCommitV1_1);
113        self.try_use(required_capabilities, param_name)
114    }
115
116    fn try_use(
117        &self,
118        required_capabilities: Requirements,
119        param_name: &'static str,
120    ) -> Result<(), Error> {
121        required_capabilities
122            .check(self.ctx.server_capabilities())
123            .then_some(())
124            .ok_or(Error::UnsupportedOperationParameter {
125                operation_name: Commit::NAME,
126                param_name,
127                required_capabilities,
128            })
129    }
130}
131
132impl<'a> super::Builder<'a, Commit> for Builder<'a> {
133    fn new(ctx: &'a Context) -> Self {
134        Self {
135            ctx,
136            confirmed: false,
137            confirm_timeout: Timeout::default(),
138            persist: None,
139            persist_id: None,
140        }
141    }
142
143    fn finish(self) -> Result<Commit, Error> {
144        if self.confirmed && self.persist_id.is_some() {
145            return Err(Error::IncompatibleOperationParameters {
146                operation_name: Commit::NAME,
147                parameters: vec!["confirmed = true", "persist-id"],
148            });
149        }
150        if !self.confirmed && self.persist.is_some() {
151            return Err(Error::IncompatibleOperationParameters {
152                operation_name: Commit::NAME,
153                parameters: vec!["confirmed = false", "persist"],
154            });
155        }
156        Ok(Commit {
157            confirmed: self.confirmed,
158            confirm_timeout: self.confirm_timeout,
159            persist: self.persist,
160            persist_id: self.persist_id,
161        })
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168    use crate::message::{
169        rpc::{MessageId, Request},
170        ClientMsg,
171    };
172
173    #[test]
174    fn unconfirmed_request_to_xml() {
175        let req = Request {
176            message_id: MessageId(101),
177            operation: Commit {
178                confirmed: false,
179                confirm_timeout: Timeout::default(),
180                persist: None,
181                persist_id: None,
182            },
183        };
184        let expect = r#"<rpc message-id="101"><commit/></rpc>]]>]]>"#;
185        assert_eq!(req.to_xml().unwrap(), expect);
186    }
187
188    #[test]
189    fn confirmed_request_to_xml() {
190        let token = Token::generate();
191        let req = Request {
192            message_id: MessageId(101),
193            operation: Commit {
194                confirmed: true,
195                confirm_timeout: Timeout::default(),
196                persist: Some(token.clone()),
197                persist_id: None,
198            },
199        };
200        let expect = format!(
201            r#"<rpc message-id="101"><commit><confirmed/><persist>{token}</persist></commit></rpc>]]>]]>"#
202        );
203        assert_eq!(req.to_xml().unwrap(), expect);
204    }
205
206    #[test]
207    fn confirmed_with_timeout_request_to_xml() {
208        let req = Request {
209            message_id: MessageId(101),
210            operation: Commit {
211                confirmed: true,
212                confirm_timeout: Timeout(Duration::from_secs(60)),
213                persist: None,
214                persist_id: None,
215            },
216        };
217        let expect = r#"<rpc message-id="101"><commit><confirmed/><confirm-timeout>60</confirm-timeout></commit></rpc>]]>]]>"#;
218        assert_eq!(req.to_xml().unwrap(), expect);
219    }
220}