wip(opnsense-config): Refactoring to improve usability and features
This commit is contained in:
22
harmony-rs/opnsense-config-xml/Cargo.toml
Normal file
22
harmony-rs/opnsense-config-xml/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "opnsense-config-xml"
|
||||
edition = "2021"
|
||||
version.workspace = true
|
||||
readme.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.123", features = [ "derive" ] }
|
||||
log = { workspace = true }
|
||||
env_logger = { workspace = true }
|
||||
#yaserde = { git = "https://git.nationtech.io/NationTech/yaserde" }
|
||||
#yaserde_derive = { git = "https://git.nationtech.io/NationTech/yaserde" }
|
||||
yaserde = { path = "../../../../github/yaserde/yaserde" }
|
||||
yaserde_derive = { path = "../../../../github/yaserde/yaserde_derive" }
|
||||
xml-rs = "0.8"
|
||||
thiserror = "1.0"
|
||||
async-trait = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.4.1"
|
||||
116
harmony-rs/opnsense-config-xml/src/data/dhcpd.rs
Normal file
116
harmony-rs/opnsense-config-xml/src/data/dhcpd.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
use yaserde_derive::{YaDeserialize, YaSerialize};
|
||||
|
||||
use crate::xml_utils::MaybeString;
|
||||
|
||||
use super::opnsense::{NumberOption, Range, StaticMap};
|
||||
|
||||
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
|
||||
#[yaserde(rename = "dhcpd")]
|
||||
pub struct Dhcpd {
|
||||
#[yaserde(rename = "lan")]
|
||||
pub lan: DhcpInterface,
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
|
||||
pub struct DhcpInterface {
|
||||
pub enable: i32,
|
||||
pub gateway: String,
|
||||
pub domain: String,
|
||||
#[yaserde(rename = "ddnsdomainalgorithm")]
|
||||
pub ddns_domain_algorithm: String,
|
||||
#[yaserde(rename = "numberoptions")]
|
||||
pub number_options: Vec<NumberOption>,
|
||||
#[yaserde(rename = "range")]
|
||||
pub range: Range,
|
||||
pub winsserver: MaybeString,
|
||||
pub dnsserver: MaybeString,
|
||||
pub ntpserver: MaybeString,
|
||||
#[yaserde(rename = "staticmap")]
|
||||
pub staticmaps: Vec<StaticMap>,
|
||||
pub pool: MaybeString,
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
|
||||
pub struct DhcpRange {
|
||||
#[yaserde(rename = "from")]
|
||||
pub from: String,
|
||||
#[yaserde(rename = "to")]
|
||||
pub to: String,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
use crate::xml_utils::to_xml_str;
|
||||
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn dhcpd_should_deserialize_serialize_identical() {
|
||||
let dhcpd: Dhcpd =
|
||||
yaserde::de::from_str(SERIALIZED_DHCPD).expect("Deserialize Dhcpd failed");
|
||||
|
||||
assert_eq!(
|
||||
to_xml_str(&dhcpd).expect("Serialize Dhcpd failed"),
|
||||
SERIALIZED_DHCPD
|
||||
);
|
||||
}
|
||||
|
||||
const SERIALIZED_DHCPD: &str = "<?xml version=\"1.0\"?>
|
||||
<dhcpd>
|
||||
<lan>
|
||||
<enable>1</enable>
|
||||
<gateway>192.168.20.1</gateway>
|
||||
<domain>somedomain.yourlocal.mcd</domain>
|
||||
<ddnsdomainalgorithm>hmac-md5</ddnsdomainalgorithm>
|
||||
<numberoptions>
|
||||
<item/>
|
||||
</numberoptions>
|
||||
<range>
|
||||
<from>192.168.20.50</from>
|
||||
<to>192.168.20.200</to>
|
||||
</range>
|
||||
<winsserver/>
|
||||
<dnsserver>192.168.20.1</dnsserver>
|
||||
<ntpserver/>
|
||||
<staticmap>
|
||||
<mac>55:55:55:55:55:1c</mac>
|
||||
<ipaddr>192.168.20.160</ipaddr>
|
||||
<hostname>somehost983</hostname>
|
||||
<descr>someservire8</descr>
|
||||
<winsserver/>
|
||||
<dnsserver/>
|
||||
<ntpserver/>
|
||||
</staticmap>
|
||||
<staticmap>
|
||||
<mac>55:55:55:55:55:1c</mac>
|
||||
<ipaddr>192.168.20.155</ipaddr>
|
||||
<hostname>somehost893</hostname>
|
||||
<winsserver/>
|
||||
<dnsserver/>
|
||||
<ntpserver/>
|
||||
</staticmap>
|
||||
<staticmap>
|
||||
<mac>55:55:55:55:55:1c</mac>
|
||||
<ipaddr>192.168.20.165</ipaddr>
|
||||
<hostname>somehost893</hostname>
|
||||
<descr/>
|
||||
<winsserver/>
|
||||
<dnsserver/>
|
||||
<ntpserver/>
|
||||
</staticmap>
|
||||
<staticmap>
|
||||
<mac>55:55:55:55:55:1c</mac>
|
||||
<ipaddr>192.168.20.50</ipaddr>
|
||||
<hostname>hostswitch2</hostname>
|
||||
<descr>switch-2 (bottom)</descr>
|
||||
<winsserver/>
|
||||
<dnsserver/>
|
||||
<ntpserver/>
|
||||
</staticmap>
|
||||
<pool/>
|
||||
</lan>
|
||||
</dhcpd>\n";
|
||||
}
|
||||
229
harmony-rs/opnsense-config-xml/src/data/interfaces.rs
Normal file
229
harmony-rs/opnsense-config-xml/src/data/interfaces.rs
Normal file
@@ -0,0 +1,229 @@
|
||||
use xml::reader::XmlEvent;
|
||||
use yaserde::{YaDeserialize as YaDeserializeTrait, YaSerialize as YaSerializeTrait};
|
||||
use yaserde_derive::{YaDeserialize, YaSerialize};
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::xml_utils::MaybeString;
|
||||
|
||||
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
|
||||
pub struct Interfaces {
|
||||
pub interfaces: HashMap<String, Interface>,
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Debug, YaDeserialize, YaSerialize)]
|
||||
pub struct Interface {
|
||||
#[yaserde(rename = "if")]
|
||||
pub physical_interface_name: String,
|
||||
pub descr: String,
|
||||
pub enable: MaybeString,
|
||||
#[yaserde(rename = "spoofmac")]
|
||||
pub spoof_mac: Option<MaybeString>,
|
||||
pub internal_dynamic: Option<MaybeString>,
|
||||
pub ipaddr: Option<MaybeString>,
|
||||
#[yaserde(rename = "blockpriv")]
|
||||
pub block_priv: Option<MaybeString>,
|
||||
#[yaserde(rename = "blockbogons")]
|
||||
pub block_bogons: Option<MaybeString>,
|
||||
pub lock: Option<MaybeString>,
|
||||
#[yaserde(rename = "type")]
|
||||
pub r#type: Option<MaybeString>,
|
||||
#[yaserde(rename = "virtual")]
|
||||
pub r#virtual: Option<MaybeString>,
|
||||
pub subnet: Option<MaybeString>,
|
||||
pub networks: Option<MaybeString>,
|
||||
pub subnetv6: Option<MaybeString>,
|
||||
pub ipaddrv6: Option<MaybeString>,
|
||||
#[yaserde(rename = "track6-interface")]
|
||||
pub track6_interface: Option<MaybeString>,
|
||||
#[yaserde(rename = "track6-prefix-id")]
|
||||
pub track6_prefix_id: Option<MaybeString>,
|
||||
}
|
||||
|
||||
pub trait MyDeserialize : YaDeserializeTrait {}
|
||||
pub trait MySerialize : YaSerializeTrait {}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn should_deserialize_interfaces() {
|
||||
let interfaces = yaserde::de::from_str::<Interfaces>(FULL_INTERFACES_XML).unwrap();
|
||||
assert_eq!(interfaces.interfaces.len(), 6)
|
||||
|
||||
}
|
||||
|
||||
const FULL_INTERFACES_XML: &str = "<interfaces>
|
||||
<wan>
|
||||
<if>pppoe0</if>
|
||||
<descr>WAN</descr>
|
||||
<enable>1</enable>
|
||||
<lock>1</lock>
|
||||
<spoofmac/>
|
||||
<blockpriv>1</blockpriv>
|
||||
<blockbogons>1</blockbogons>
|
||||
<ipaddr>pppoe</ipaddr>
|
||||
</wan>
|
||||
<lan>
|
||||
<if>em1</if>
|
||||
<descr>LAN</descr>
|
||||
<enable>1</enable>
|
||||
<spoofmac/>
|
||||
<ipaddr>192.168.20.1</ipaddr>
|
||||
<subnet>24</subnet>
|
||||
<ipaddrv6>track6</ipaddrv6>
|
||||
<track6-interface/>
|
||||
<track6-prefix-id>0</track6-prefix-id>
|
||||
</lan>
|
||||
<lo0>
|
||||
<internal_dynamic>1</internal_dynamic>
|
||||
<descr>Loopback</descr>
|
||||
<enable>1</enable>
|
||||
<if>lo0</if>
|
||||
<ipaddr>127.0.0.1</ipaddr>
|
||||
<ipaddrv6>::1</ipaddrv6>
|
||||
<subnet>8</subnet>
|
||||
<subnetv6>128</subnetv6>
|
||||
<type>none</type>
|
||||
<virtual>1</virtual>
|
||||
</lo0>
|
||||
<opt1>
|
||||
<if>em5</if>
|
||||
<descr>backup_sync</descr>
|
||||
<enable>1</enable>
|
||||
<lock>1</lock>
|
||||
<spoofmac/>
|
||||
<ipaddr>10.10.5.1</ipaddr>
|
||||
<subnet>24</subnet>
|
||||
</opt1>
|
||||
<wireguard>
|
||||
<internal_dynamic>1</internal_dynamic>
|
||||
<descr>WireGuard (Group)</descr>
|
||||
<if>wireguard</if>
|
||||
<virtual>1</virtual>
|
||||
<enable>1</enable>
|
||||
<type>group</type>
|
||||
<networks/>
|
||||
</wireguard>
|
||||
<openvpn>
|
||||
<internal_dynamic>1</internal_dynamic>
|
||||
<enable>1</enable>
|
||||
<if>openvpn</if>
|
||||
<descr>OpenVPN</descr>
|
||||
<type>group</type>
|
||||
<virtual>1</virtual>
|
||||
<networks/>
|
||||
</openvpn>
|
||||
</interfaces>";
|
||||
}
|
||||
|
||||
|
||||
// impl YaSerializeTrait for Interfaces {
|
||||
// fn serialize<W: std::io::Write>(
|
||||
// &self,
|
||||
// writer: &mut yaserde::ser::Serializer<W>,
|
||||
// ) -> Result<(), String> {
|
||||
// writer.write("Interfaces serializer TODO").map_err(|e| e.to_string())
|
||||
// }
|
||||
//
|
||||
// fn serialize_attributes(
|
||||
// &self,
|
||||
// attributes: Vec<xml::attribute::OwnedAttribute>,
|
||||
// namespace: xml::namespace::Namespace,
|
||||
// ) -> Result<
|
||||
// (
|
||||
// Vec<xml::attribute::OwnedAttribute>,
|
||||
// xml::namespace::Namespace,
|
||||
// ),
|
||||
// String,
|
||||
// > {
|
||||
// todo!()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// impl YaDeserializeTrait for Interfaces {
|
||||
// fn deserialize<R: std::io::Read>(
|
||||
// reader: &mut yaserde::de::Deserializer<R>,
|
||||
// ) -> Result<Self, String> {
|
||||
// let mut interfaces = Interfaces::default();
|
||||
// let mut current_interface_name = String::new();
|
||||
// let mut current_interface = Interface::default();
|
||||
//
|
||||
// let mut event = reader.next_event()?;
|
||||
// loop {
|
||||
// match event {
|
||||
// xml::reader::XmlEvent::EndElement { name } => {
|
||||
// println!(
|
||||
// "Handling EndElement {}, current_interface_name: {}",
|
||||
// name.local_name, current_interface_name
|
||||
// );
|
||||
// println!("Peeking after EndElement {:?}", reader.peek()?);
|
||||
// if name.local_name == "interfaces" {
|
||||
// break;
|
||||
// }
|
||||
// todo!("Should not hit here");
|
||||
// }
|
||||
// xml::reader::XmlEvent::StartElement { ref name, .. } => {
|
||||
// println!("Got start Element {:?}", name);
|
||||
// current_interface_name = name.local_name.clone();
|
||||
// println!("About to deserialize interface from name {:?}", name);
|
||||
// // TODO store names
|
||||
// 'inner_iface: loop {
|
||||
// let peek = reader.peek()?;
|
||||
// println!("Peeking before forcing next event {:?}", peek);
|
||||
//
|
||||
// if let XmlEvent::EndElement { name } = peek {
|
||||
// // TODO save the interface name and struct in the hashmap
|
||||
// println!("Forcing next_event");
|
||||
// reader.next_event()?;
|
||||
//
|
||||
// let peek = reader.peek()?;
|
||||
// println!("Peeking after next event deserializing {:?}", peek);
|
||||
// if let XmlEvent::EndElement { name } = peek {
|
||||
// println!("Got two EndElement in a row, breaking out of loop");
|
||||
// break 'inner_iface;
|
||||
// }
|
||||
// }
|
||||
// println!("Peeking before deserializing {:?}", reader.peek()?);
|
||||
// let interface = <Interface as yaserde::YaDeserialize>::deserialize(reader)?;
|
||||
// println!("Interface deserialized {:?}", interface);
|
||||
// println!("Peeking after deserializing {:?}", reader.peek()?);
|
||||
// }
|
||||
// println!(
|
||||
// "Done with inner interface, loop completed {:?}",
|
||||
// reader.peek()?
|
||||
// );
|
||||
// }
|
||||
// xml::reader::XmlEvent::EndDocument => break,
|
||||
// _ => {
|
||||
// return Err(
|
||||
// "This Interfaces Deserializer does not support all XmlEvent types".into(),
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// let peek = reader.peek()?;
|
||||
//
|
||||
// let read_next = true;
|
||||
// if let XmlEvent::EndElement { name } = peek {
|
||||
// if name.local_name == "interfaces" {
|
||||
// println!("Got endElement interfaces, not reading next event");
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if read_next {
|
||||
// event = reader.next_event()?;
|
||||
// }
|
||||
// println!("Outer loop got event {:?}", event);
|
||||
// println!("Outer loop got peek {:?}", reader.peek()?);
|
||||
// }
|
||||
// println!("Done with interfaces {:?}", interfaces);
|
||||
// println!("reader peeking shows {:?}", reader.peek());
|
||||
//
|
||||
// Ok(interfaces)
|
||||
// }
|
||||
// }
|
||||
|
||||
6
harmony-rs/opnsense-config-xml/src/data/mod.rs
Normal file
6
harmony-rs/opnsense-config-xml/src/data/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
mod opnsense;
|
||||
mod interfaces;
|
||||
mod dhcpd;
|
||||
pub use opnsense::*;
|
||||
pub use interfaces::*;
|
||||
pub use dhcpd::*;
|
||||
2015
harmony-rs/opnsense-config-xml/src/data/opnsense.rs
Normal file
2015
harmony-rs/opnsense-config-xml/src/data/opnsense.rs
Normal file
File diff suppressed because it is too large
Load Diff
3
harmony-rs/opnsense-config-xml/src/lib.rs
Normal file
3
harmony-rs/opnsense-config-xml/src/lib.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
mod xml_utils;
|
||||
mod data;
|
||||
pub use data::*;
|
||||
254
harmony-rs/opnsense-config-xml/src/xml_utils/generic_xml.rs
Normal file
254
harmony-rs/opnsense-config-xml/src/xml_utils/generic_xml.rs
Normal file
@@ -0,0 +1,254 @@
|
||||
use xml::reader::XmlEvent as ReadEvent;
|
||||
use yaserde::{ser, YaDeserialize as YaDeserializeTrait, YaSerialize as YaSerializeTrait};
|
||||
|
||||
#[derive(Debug, PartialEq, Default)]
|
||||
pub struct RawXml(String);
|
||||
|
||||
impl YaDeserializeTrait for RawXml {
|
||||
fn deserialize<R: std::io::Read>(
|
||||
reader: &mut yaserde::de::Deserializer<R>,
|
||||
) -> Result<Self, String> {
|
||||
let mut buffer = String::new();
|
||||
let mut depth = 0;
|
||||
|
||||
let own_name = match reader.peek()? {
|
||||
ReadEvent::StartElement { name, .. } => name.local_name.clone(),
|
||||
_ => return Err("RawXml Should start deserializing with StartElement".to_string()),
|
||||
};
|
||||
println!("RawXml deserialize from root element name : {own_name}");
|
||||
loop {
|
||||
let current_event = reader.peek()?.to_owned();
|
||||
match current_event.clone() {
|
||||
ReadEvent::StartElement {
|
||||
name, attributes, ..
|
||||
} => {
|
||||
println!("StartElement {name} depth {depth}");
|
||||
depth += 1;
|
||||
let mut attr_string = String::new();
|
||||
attributes.iter().for_each(|a| {
|
||||
attr_string.push_str(&format!(r#" {}="{}""#, &a.name, &a.value));
|
||||
});
|
||||
buffer.push_str(&format!("<{}{}>", name, attr_string));
|
||||
let _event = reader.next_event()?;
|
||||
}
|
||||
ReadEvent::EndElement { name } => {
|
||||
println!("EndElement {name} depth {depth}");
|
||||
depth -= 1;
|
||||
buffer.push_str(&format!("</{}>", name));
|
||||
println!(
|
||||
"Checking if name.local_name {} matches own_name {} at depth {depth}",
|
||||
&name.local_name, &own_name
|
||||
);
|
||||
if name.local_name == own_name && depth == 0 {
|
||||
println!(
|
||||
"Found next EndElement is closing my struct, breaking out of loop"
|
||||
);
|
||||
break;
|
||||
} else {
|
||||
let _event = reader.next_event()?;
|
||||
}
|
||||
}
|
||||
ReadEvent::Characters(content) => {
|
||||
println!("Characters {content} depth {depth}");
|
||||
buffer.push_str(&content);
|
||||
let _event = reader.next_event()?;
|
||||
}
|
||||
ReadEvent::StartDocument {
|
||||
version,
|
||||
encoding,
|
||||
standalone,
|
||||
} => todo!(
|
||||
"StartDocument {:?} {:?} {:?}",
|
||||
version,
|
||||
encoding,
|
||||
standalone
|
||||
),
|
||||
ReadEvent::EndDocument => todo!(),
|
||||
ReadEvent::ProcessingInstruction { name, data } => {
|
||||
todo!("ProcessingInstruction {:?}, {:?}", name, data)
|
||||
}
|
||||
ReadEvent::CData(cdata) => todo!("CData, {:?}", cdata),
|
||||
ReadEvent::Comment(comment) => todo!("Comment, {:?}", comment),
|
||||
ReadEvent::Whitespace(whitespace) => todo!("Whitespace, {:?}", whitespace),
|
||||
}
|
||||
let next = reader.peek()?;
|
||||
println!(
|
||||
"Processing done on \ncurrent_event : {:?} \nnext : {:?}",
|
||||
¤t_event, &next
|
||||
);
|
||||
}
|
||||
|
||||
println!("buffered events {buffer}");
|
||||
|
||||
Ok(RawXml(buffer))
|
||||
}
|
||||
}
|
||||
|
||||
impl YaSerializeTrait for RawXml {
|
||||
fn serialize<W: std::io::Write>(&self, writer: &mut ser::Serializer<W>) -> Result<(), String> {
|
||||
let content = self.0.clone();
|
||||
let content = xml::EventReader::from_str(content.as_str());
|
||||
let mut reader = yaserde::de::Deserializer::new(content);
|
||||
loop {
|
||||
let e = reader.next_event()?;
|
||||
if let ReadEvent::EndDocument = e {
|
||||
break;
|
||||
}
|
||||
writer
|
||||
.write(e.as_writer_event().unwrap())
|
||||
.expect("Writer should write write event");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_attributes(
|
||||
&self,
|
||||
attributes: Vec<xml::attribute::OwnedAttribute>,
|
||||
namespace: xml::namespace::Namespace,
|
||||
) -> Result<
|
||||
(
|
||||
Vec<xml::attribute::OwnedAttribute>,
|
||||
xml::namespace::Namespace,
|
||||
),
|
||||
String,
|
||||
> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::xml_utils::to_xml_str;
|
||||
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
use yaserde_derive::YaDeserialize;
|
||||
use yaserde_derive::YaSerialize;
|
||||
|
||||
#[derive(Debug, PartialEq, Default, YaDeserialize)]
|
||||
pub struct Parent {
|
||||
// pub rawxml_child: RawXml,
|
||||
pub string_child: String,
|
||||
pub child_child: Child,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Default, YaDeserialize)]
|
||||
pub struct Child {
|
||||
pub child_val: String,
|
||||
pub child_val2: String,
|
||||
pub child_option: Option<String>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rawxml_should_buffer_empty_element() {
|
||||
let rawxml: RawXml = yaserde::de::from_str("<something/>").unwrap();
|
||||
assert_eq!(rawxml.0, String::from("<something></something>"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rawxml_should_buffer_elements_with_different_case_as_they_are() {
|
||||
let xml = "<xml><Some_thing></Some_thing><something></something></xml>";
|
||||
let rawxml: RawXml = yaserde::de::from_str(xml).unwrap();
|
||||
assert_eq!(rawxml.0, String::from(xml));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rawxml_should_buffer_elements_with_attributes() {
|
||||
let xml = r#"<xml version="ababa"><Some_thing></Some_thing><something></something></xml>"#;
|
||||
let rawxml: RawXml = yaserde::de::from_str(xml).unwrap();
|
||||
assert_eq!(rawxml.0, String::from(xml));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rawxml_should_handle_complex_documents() {
|
||||
let xml = r#"<xml><OpenVPN version="1.0.0"><Overwrites></Overwrites><Instances></Instances><StaticKeys></StaticKeys></OpenVPN><Gateways version="0.0.1"></Gateways><HAProxy version="4.0.0"><general><enabled>1</enabled><gracefulStop>0</gracefulStop><hardStopAfter>60s</hardStopAfter><closeSpreadTime></closeSpreadTime><seamlessReload>0</seamlessReload><storeOcsp>0</storeOcsp><showIntro>1</showIntro><peers><enabled>0</enabled><name1></name1><listen1></listen1><port1>1024</port1><name2></name2><listen2></listen2><port2>1024</port2></peers><tuning><root>0</root><maxConnections></maxConnections><nbthread>1</nbthread><sslServerVerify>ignore</sslServerVerify><maxDHSize>2048</maxDHSize><bufferSize>16384</bufferSize></tuning></general></HAProxy></xml>"#;
|
||||
let rawxml: RawXml = yaserde::de::from_str(xml).unwrap();
|
||||
assert_eq!(rawxml.0, String::from(xml));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rawxml_should_serialize_simple_documents() {
|
||||
let xml = r#"<?xml version="1.0" encoding="utf-8"?><xml />"#;
|
||||
let rawxml: RawXml = yaserde::de::from_str(xml).unwrap();
|
||||
assert_eq!(yaserde::ser::to_string(&rawxml).unwrap(), xml);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rawxml_should_serialize_complex_documents() {
|
||||
let xml = r#"<?xml version="1.0"?>
|
||||
<xml>
|
||||
<OpenVPN version="1.0.0">
|
||||
<Overwrites/>
|
||||
<Instances/>
|
||||
<StaticKeys/>
|
||||
</OpenVPN>
|
||||
<Gateways version="0.0.1"/>
|
||||
<HAProxy version="4.0.0">
|
||||
<general>
|
||||
<enabled>1</enabled>
|
||||
<gracefulStop>0</gracefulStop>
|
||||
<hardStopAfter>60s</hardStopAfter>
|
||||
<closeSpreadTime/>
|
||||
<seamlessReload>0</seamlessReload>
|
||||
<storeOcsp>0</storeOcsp>
|
||||
<showIntro>1</showIntro>
|
||||
<peers>
|
||||
<enabled>0</enabled>
|
||||
<name1/>
|
||||
<listen1/>
|
||||
<port1>1024</port1>
|
||||
<name2/>
|
||||
<listen2/>
|
||||
<port2>1024</port2>
|
||||
</peers>
|
||||
<tuning>
|
||||
<root>0</root>
|
||||
<maxConnections/>
|
||||
<nbthread>1</nbthread>
|
||||
<sslServerVerify>ignore</sslServerVerify>
|
||||
<maxDHSize>2048</maxDHSize>
|
||||
<bufferSize>16384</bufferSize>
|
||||
</tuning>
|
||||
</general>
|
||||
</HAProxy>
|
||||
</xml>
|
||||
"#;
|
||||
let rawxml: RawXml = yaserde::de::from_str(xml).unwrap();
|
||||
assert_eq!(to_xml_str(&rawxml).unwrap(), xml);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rawxml_should_allow_siblings_before() {
|
||||
#[derive(YaDeserialize, YaSerialize)]
|
||||
struct Config {
|
||||
paul: Vec<String>,
|
||||
raw: RawXml,
|
||||
}
|
||||
let xml = r#"<?xml version="1.0" encoding="utf-8"?><Config><paul>bobob</paul><paul>patate</paul><raw>allo something</raw></Config>"#;
|
||||
let config: Config = yaserde::de::from_str(xml).unwrap();
|
||||
assert_eq!(yaserde::ser::to_string(&config).unwrap(), xml);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rawxml_should_allow_siblings_after() {
|
||||
#[derive(YaDeserialize, YaSerialize)]
|
||||
struct Config {
|
||||
raw: RawXml,
|
||||
paul: Vec<String>,
|
||||
}
|
||||
let xml = r#"<?xml version="1.0" encoding="utf-8"?><Config><raw>allo something</raw><paul>bobob</paul><paul>patate</paul></Config>"#;
|
||||
let config: Config = yaserde::de::from_str(xml).unwrap();
|
||||
assert_eq!(config.paul.get(0).unwrap(), "bobob");
|
||||
assert_eq!(config.paul.get(1).unwrap(), "patate");
|
||||
assert_eq!(config.paul.len(), 2);
|
||||
assert_eq!(config.raw.0, "<raw>allo something</raw>");
|
||||
assert_eq!(yaserde::ser::to_string(&config).unwrap(), xml);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rawxml_should_allow_being_end_of_document() {
|
||||
let xml = r#"<?xml version="1.0" encoding="utf-8"?><Config><raw>allo something</raw><paul>bobob</paul><paul>patate</paul></Config>"#;
|
||||
let config: RawXml = yaserde::de::from_str(xml).unwrap();
|
||||
assert_eq!(yaserde::ser::to_string(&config).unwrap(), xml);
|
||||
}
|
||||
}
|
||||
173
harmony-rs/opnsense-config-xml/src/xml_utils/maybe_string.rs
Normal file
173
harmony-rs/opnsense-config-xml/src/xml_utils/maybe_string.rs
Normal file
@@ -0,0 +1,173 @@
|
||||
use xml::reader::XmlEvent as ReadEvent;
|
||||
use xml::writer::XmlEvent as WriteEvent;
|
||||
use yaserde::{ser, YaDeserialize as YaDeserializeTrait, YaSerialize as YaSerializeTrait};
|
||||
|
||||
#[derive(Debug, PartialEq, Default)]
|
||||
pub struct MaybeString {
|
||||
field_name: String,
|
||||
content: Option<String>,
|
||||
}
|
||||
|
||||
impl YaDeserializeTrait for MaybeString {
|
||||
fn deserialize<R: std::io::Read>(
|
||||
reader: &mut yaserde::de::Deserializer<R>,
|
||||
) -> Result<Self, String> {
|
||||
let field_name = match reader.peek()? {
|
||||
ReadEvent::StartElement {
|
||||
name, attributes, ..
|
||||
} => {
|
||||
if attributes.len() > 0 {
|
||||
return Err(String::from(
|
||||
"Attributes not currently supported by MaybeString",
|
||||
));
|
||||
}
|
||||
|
||||
name.local_name.clone()
|
||||
}
|
||||
_ => return Err(String::from("Unsupporte ReadEvent type")),
|
||||
};
|
||||
reader.next_event()?;
|
||||
|
||||
let content = match reader.peek()? {
|
||||
ReadEvent::Characters(content) => Some(content.clone()),
|
||||
ReadEvent::EndElement { name } => {
|
||||
if name.local_name != field_name {
|
||||
return Err(format!(
|
||||
"Invalid EndElement, expected {field_name} but got {}",
|
||||
name.local_name
|
||||
));
|
||||
}
|
||||
None
|
||||
}
|
||||
_ => return Err(String::from("Unsupporte ReadEvent type")),
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
field_name,
|
||||
content,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl YaSerializeTrait for MaybeString {
|
||||
fn serialize<W: std::io::Write>(&self, writer: &mut ser::Serializer<W>) -> Result<(), String> {
|
||||
let start_element_event = WriteEvent::start_element(self.field_name.as_str());
|
||||
writer.write(start_element_event).expect("Writer failed");
|
||||
match &self.content {
|
||||
Some(content) => {
|
||||
writer
|
||||
.write(WriteEvent::characters(content))
|
||||
.expect("Writer failed");
|
||||
}
|
||||
None => {}
|
||||
};
|
||||
|
||||
writer
|
||||
.write(WriteEvent::end_element())
|
||||
.expect("Writer failed");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_attributes(
|
||||
&self,
|
||||
_attributes: Vec<xml::attribute::OwnedAttribute>,
|
||||
_namespace: xml::namespace::Namespace,
|
||||
) -> Result<
|
||||
(
|
||||
Vec<xml::attribute::OwnedAttribute>,
|
||||
xml::namespace::Namespace,
|
||||
),
|
||||
String,
|
||||
> {
|
||||
unimplemented!("MaybeString does not currently support attributes")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
use yaserde_derive::YaDeserialize;
|
||||
use yaserde_derive::YaSerialize;
|
||||
|
||||
#[derive(Debug, PartialEq, Default, YaDeserialize, YaSerialize)]
|
||||
struct TestStruct {
|
||||
maybe: MaybeString,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maybe_string_should_deserialize_empty_element() {
|
||||
let initial_xml = "<struct><maybe/></struct>";
|
||||
let test_struct: TestStruct =
|
||||
yaserde::de::from_str(initial_xml).expect("Shoudl deserialize teststruct");
|
||||
println!("Got test_struct {:?}", test_struct);
|
||||
assert_eq!(
|
||||
test_struct,
|
||||
TestStruct {
|
||||
maybe: MaybeString {
|
||||
field_name: String::from("maybe"),
|
||||
content: None
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maybe_string_should_deserialize_content() {
|
||||
let initial_xml = "<struct><maybe>some content</maybe></struct>";
|
||||
let test_struct: TestStruct =
|
||||
yaserde::de::from_str(initial_xml).expect("Shoudl deserialize teststruct");
|
||||
println!("Got test_struct {:?}", test_struct);
|
||||
assert_eq!(
|
||||
test_struct,
|
||||
TestStruct {
|
||||
maybe: MaybeString {
|
||||
field_name: String::from("maybe"),
|
||||
content: Some(String::from("some content"))
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maybe_string_should_deserialize_empty_long_format() {
|
||||
let initial_xml = "<struct><maybe></maybe></struct>";
|
||||
let test_struct: TestStruct =
|
||||
yaserde::de::from_str(initial_xml).expect("Shoudl deserialize teststruct");
|
||||
println!("Got test_struct {:?}", test_struct);
|
||||
assert_eq!(
|
||||
test_struct,
|
||||
TestStruct {
|
||||
maybe: MaybeString {
|
||||
field_name: String::from("maybe"),
|
||||
content: None
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maybe_string_should_serialize_to_empty_element() {
|
||||
let initial_xml =
|
||||
r#"<?xml version="1.0" encoding="utf-8"?><TestStruct><maybe /></TestStruct>"#;
|
||||
let test_struct: TestStruct =
|
||||
yaserde::de::from_str(initial_xml).expect("Shoudl deserialize teststruct");
|
||||
println!("Got test_struct {:?}", test_struct);
|
||||
assert_eq!(
|
||||
yaserde::ser::to_string(&test_struct).expect("should serialize teststruct"),
|
||||
initial_xml
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maybe_string_should_serialize_content() {
|
||||
let initial_xml = r#"<?xml version="1.0" encoding="utf-8"?><TestStruct><maybe>some content</maybe></TestStruct>"#;
|
||||
let test_struct: TestStruct =
|
||||
yaserde::de::from_str(initial_xml).expect("Shoudl deserialize teststruct");
|
||||
println!("Got test_struct {:?}", test_struct);
|
||||
assert_eq!(
|
||||
yaserde::ser::to_string(&test_struct).expect("should serialize teststruct"),
|
||||
initial_xml
|
||||
);
|
||||
}
|
||||
}
|
||||
6
harmony-rs/opnsense-config-xml/src/xml_utils/mod.rs
Normal file
6
harmony-rs/opnsense-config-xml/src/xml_utils/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
mod generic_xml;
|
||||
mod maybe_string;
|
||||
mod yaserde;
|
||||
pub use generic_xml::*;
|
||||
pub use maybe_string::*;
|
||||
pub use yaserde::*;
|
||||
20
harmony-rs/opnsense-config-xml/src/xml_utils/yaserde.rs
Normal file
20
harmony-rs/opnsense-config-xml/src/xml_utils/yaserde.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
use yaserde::YaSerialize;
|
||||
|
||||
pub fn to_xml_str<T: YaSerialize>(model: &T) -> Result<String, String> {
|
||||
let yaserde_cfg = yaserde::ser::Config {
|
||||
perform_indent: true,
|
||||
write_document_declaration: false,
|
||||
pad_self_closing: false,
|
||||
..Default::default()
|
||||
};
|
||||
let serialized = yaserde::ser::to_string_with_config::<T>(model, &yaserde_cfg)?;
|
||||
|
||||
// Opnsense does not specify encoding in the document declaration
|
||||
//
|
||||
// yaserde / xml-rs does not allow disabling the encoding attribute in the
|
||||
// document declaration
|
||||
//
|
||||
// So here we just manually prefix the xml document with the exact document declaration
|
||||
// that opnsense uses
|
||||
Ok(format!("<?xml version=\"1.0\"?>\n{serialized}\n"))
|
||||
}
|
||||
Reference in New Issue
Block a user