netconf/message/rpc/operation/
commit.rs1use 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}