netconf/message/rpc/operation/
edit_config.rs1use std::{fmt::Debug, io::Write};
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::{params::Required, Datastore, EmptyReply, Operation, Url, WriteXml};
13
14#[derive(Debug, Clone)]
15pub struct EditConfig<D> {
16 target: Datastore,
17 source: Source<D>,
18 default_operation: DefaultOperation,
19 error_option: ErrorOption,
20 test_option: TestOption,
21}
22
23impl<D> Operation for EditConfig<D>
24where
25 D: WriteXml + Debug + Send + Sync,
26{
27 const NAME: &'static str = "edit-config";
28 const REQUIRED_CAPABILITIES: Requirements = Requirements::None;
29 type Builder<'a> = Builder<'a, D>;
30 type Reply = EmptyReply;
31}
32
33impl<D> WriteXml for EditConfig<D>
34where
35 D: WriteXml + Debug + Send + Sync,
36{
37 fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<(), WriteError> {
38 writer
39 .create_element(Self::NAME)
40 .write_inner_content(|writer| {
41 _ = writer
42 .create_element("target")
43 .write_inner_content(|writer| self.target.write_xml(writer))?;
44 if self.default_operation.is_non_default() {
45 _ = writer
46 .create_element("default-operation")
47 .write_text_content(BytesText::new(self.default_operation.as_str()))?;
48 };
49 if self.error_option.is_non_default() {
50 _ = writer
51 .create_element("error-option")
52 .write_text_content(BytesText::new(self.error_option.as_str()))?;
53 };
54 if self.test_option.is_non_default() {
55 _ = writer
56 .create_element("test-option")
57 .write_text_content(BytesText::new(self.test_option.as_str()))?;
58 };
59 self.source.write_xml(writer)?;
60 Ok(())
61 })
62 .map(|_| ())
63 }
64}
65
66#[derive(Debug, Clone)]
67#[must_use]
68pub struct Builder<'a, D> {
69 ctx: &'a Context,
70 target: Required<Datastore>,
71 source: Required<Source<D>>,
72 default_operation: DefaultOperation,
73 error_option: ErrorOption,
74 test_option: TestOption,
75}
76
77impl<D> Builder<'_, D>
78where
79 D: WriteXml + Debug + Send + Sync,
80{
81 pub fn target(mut self, target: Datastore) -> Result<Self, Error> {
82 target.try_as_target(self.ctx).map(|target| {
83 self.target.set(target);
84 self
85 })
86 }
87
88 pub fn config(mut self, config: D) -> Self {
89 self.source.set(Source::Config(config));
90 self
91 }
92
93 pub fn url<S: AsRef<str>>(mut self, url: S) -> Result<Self, Error> {
94 Url::try_new(url, self.ctx).map(|url| {
95 self.source.set(Source::Url(url));
96 self
97 })
98 }
99
100 pub const fn default_operation(mut self, default_operation: DefaultOperation) -> Self {
101 self.default_operation = default_operation;
102 self
103 }
104
105 pub fn error_option(mut self, error_option: ErrorOption) -> Result<Self, Error> {
106 error_option.try_use::<D>(self.ctx).map(|error_option| {
107 self.error_option = error_option;
108 self
109 })
110 }
111
112 pub fn test_option(mut self, test_option: TestOption) -> Result<Self, Error> {
113 test_option.try_use::<D>(self.ctx).map(|test_option| {
114 self.test_option = test_option;
115 self
116 })
117 }
118}
119
120impl<'a, D> super::Builder<'a, EditConfig<D>> for Builder<'a, D>
121where
122 D: WriteXml + Debug + Send + Sync,
123{
124 fn new(ctx: &'a Context) -> Self {
125 Self {
126 ctx,
127 target: Required::init(),
128 source: Required::init(),
129 default_operation: DefaultOperation::default(),
130 error_option: ErrorOption::default(),
131 test_option: TestOption::default(),
132 }
133 }
134
135 fn finish(self) -> Result<EditConfig<D>, Error> {
136 Ok(EditConfig {
137 target: self.target.require::<EditConfig<D>>("target")?,
138 source: self.source.require::<EditConfig<D>>("config")?,
139 default_operation: self.default_operation,
140 error_option: self.error_option,
141 test_option: self.test_option,
142 })
143 }
144}
145
146#[derive(Debug, Clone)]
147pub enum Source<D> {
148 Config(D),
149 Url(Url),
150}
151
152impl<D> WriteXml for Source<D>
153where
154 D: WriteXml,
155{
156 fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<(), WriteError> {
157 match self {
158 Self::Config(config) => writer
159 .create_element("config")
160 .write_inner_content(|writer| config.write_xml(writer))
161 .map(|_| ()),
162 Self::Url(url) => url.write_xml(writer),
163 }
164 }
165}
166
167#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
168pub enum DefaultOperation {
169 #[default]
170 Merge,
171 Replace,
172 None,
173}
174
175impl DefaultOperation {
176 fn is_non_default(self) -> bool {
177 self != Self::default()
178 }
179
180 const fn as_str(self) -> &'static str {
181 match self {
182 Self::Merge => "merge",
183 Self::Replace => "replace",
184 Self::None => "none",
185 }
186 }
187}
188
189#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
190pub enum TestOption {
191 #[default]
192 TestThenSet,
193 Set,
194 TestOnly,
195}
196
197impl TestOption {
198 fn is_non_default(self) -> bool {
199 self != Self::default()
200 }
201
202 const fn as_str(self) -> &'static str {
203 match self {
204 Self::TestThenSet => "test-then-set",
205 Self::Set => "set",
206 Self::TestOnly => "test-only",
207 }
208 }
209
210 fn try_use<D>(self, ctx: &Context) -> Result<Self, Error>
211 where
212 D: WriteXml + Debug + Send + Sync,
213 {
214 let required_capabilities = match self {
215 Self::TestThenSet | Self::Set => {
216 Requirements::Any(&[Capability::ValidateV1_0, Capability::ValidateV1_1])
217 }
218 Self::TestOnly => Requirements::One(Capability::ValidateV1_1),
219 };
220 if required_capabilities.check(ctx.server_capabilities()) {
221 Ok(self)
222 } else {
223 Err(Error::UnsupportedOperParameterValue {
224 operation_name: EditConfig::<D>::NAME,
225 param_name: "test-option",
226 param_value: self.as_str(),
227 required_capabilities,
228 })
229 }
230 }
231}
232
233#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
234pub enum ErrorOption {
235 #[default]
236 StopOnError,
237 ContinueOnError,
238 RollbackOnError,
239}
240
241impl ErrorOption {
242 fn is_non_default(self) -> bool {
243 self != Self::default()
244 }
245
246 const fn as_str(self) -> &'static str {
247 match self {
248 Self::StopOnError => "stop-on-error",
249 Self::ContinueOnError => "continue-on-error",
250 Self::RollbackOnError => "rollback-on-error",
251 }
252 }
253
254 fn try_use<D>(self, ctx: &Context) -> Result<Self, Error>
255 where
256 D: WriteXml + Debug + Send + Sync,
257 {
258 let required_capabilities = match self {
259 Self::StopOnError | Self::ContinueOnError => Requirements::None,
260 Self::RollbackOnError => Requirements::One(Capability::RollbackOnError),
261 };
262 if required_capabilities.check(ctx.server_capabilities()) {
263 Ok(self)
264 } else {
265 Err(Error::UnsupportedOperParameterValue {
266 operation_name: EditConfig::<D>::NAME,
267 param_name: "error-option",
268 param_value: self.as_str(),
269 required_capabilities,
270 })
271 }
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278 use crate::message::{
279 rpc::{operation::Opaque, MessageId, Request},
280 ClientMsg,
281 };
282
283 #[test]
284 fn request_to_xml() {
285 let req = Request {
286 message_id: MessageId(101),
287 operation: EditConfig {
288 target: Datastore::Running,
289 source: Source::<Opaque>::Config("<foo>bar</foo>".into()),
290 default_operation: DefaultOperation::default(),
291 error_option: ErrorOption::default(),
292 test_option: TestOption::default(),
293 },
294 };
295 let expect = r#"<rpc message-id="101"><edit-config><target><running/></target><config><foo>bar</foo></config></edit-config></rpc>]]>]]>"#;
296 assert_eq!(req.to_xml().unwrap(), expect);
297 }
298}