-
-
Notifications
You must be signed in to change notification settings - Fork 18
Expand file tree
/
Copy pathconfig_file_writer.rs
More file actions
144 lines (126 loc) · 4.53 KB
/
Copy pathconfig_file_writer.rs
File metadata and controls
144 lines (126 loc) · 4.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
//! Writers for Hadoop XML config files and Java `.properties` files.
use std::{fmt::Write as _, io::Write};
use java_properties::{PropertiesError, PropertiesWriter};
use snafu::{ResultExt, Snafu};
use xml::escape::escape_str_attribute;
#[derive(Debug, Snafu)]
pub enum PropertiesWriterError {
#[snafu(display("failed to create properties file"))]
Properties { source: PropertiesError },
#[snafu(display("failed to convert properties file byte array to UTF-8"))]
FromUtf8 { source: std::string::FromUtf8Error },
}
/// Creates a common Java properties file string in the format:
/// `property_1=value_1\nproperty_2=value_2\n`.
pub fn to_java_properties_string<'a, T>(properties: T) -> Result<String, PropertiesWriterError>
where
T: Iterator<Item = (&'a String, &'a Option<String>)>,
{
let mut output = Vec::new();
write_java_properties(&mut output, properties)?;
String::from_utf8(output).context(FromUtf8Snafu)
}
/// Writes Java properties to the given writer. A `None` value is written as an
/// empty value (`key=`).
fn write_java_properties<'a, W, T>(writer: W, properties: T) -> Result<(), PropertiesWriterError>
where
W: Write,
T: Iterator<Item = (&'a String, &'a Option<String>)>,
{
let mut writer = PropertiesWriter::new(writer);
for (k, v) in properties {
let property_value = v.as_deref().unwrap_or_default();
writer.write(k, property_value).context(PropertiesSnafu)?;
}
writer.flush().context(PropertiesSnafu)?;
Ok(())
}
/// Converts properties into a Hadoop configuration XML, including the wrapping
/// `<configuration>...</configuration>` elements. Properties with a `None` value
/// are skipped. Keys and values are XML-escaped.
pub fn to_hadoop_xml<'a, T>(properties: T) -> String
where
T: Iterator<Item = (&'a String, &'a Option<String>)>,
{
let mut snippet = String::new();
for (k, v) in properties {
let escaped_value = match v {
Some(value) => escape_str_attribute(value),
None => continue,
};
let escaped_key = escape_str_attribute(k);
write!(
snippet,
" <property>\n <name>{escaped_key}</name>\n <value>{escaped_value}</value>\n </property>\n"
)
.expect("writing to a String is infallible");
}
format!("<?xml version=\"1.0\"?>\n<configuration>\n{snippet}</configuration>")
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use super::*;
fn xml(pairs: &[(&str, Option<&str>)]) -> String {
let map: BTreeMap<String, Option<String>> = pairs
.iter()
.map(|(k, v)| (k.to_string(), v.map(str::to_string)))
.collect();
to_hadoop_xml(map.iter())
}
fn props(pairs: &[(&str, Option<&str>)]) -> String {
let map: BTreeMap<String, Option<String>> = pairs
.iter()
.map(|(k, v)| (k.to_string(), v.map(str::to_string)))
.collect();
to_java_properties_string(map.iter()).unwrap()
}
#[test]
fn hadoop_xml_wraps_empty_configuration() {
assert_eq!(
xml(&[]),
"<?xml version=\"1.0\"?>\n<configuration>\n</configuration>"
);
}
#[test]
fn hadoop_xml_renders_single_property() {
assert_eq!(
xml(&[("fs.defaultFS", Some("hdfs://hdfs/"))]),
"<?xml version=\"1.0\"?>\n<configuration>\n \
<property>\n <name>fs.defaultFS</name>\n \
<value>hdfs://hdfs/</value>\n </property>\n</configuration>"
);
}
#[test]
fn hadoop_xml_skips_none_values() {
assert_eq!(
xml(&[("kept", Some("1")), ("dropped", None)]),
"<?xml version=\"1.0\"?>\n<configuration>\n \
<property>\n <name>kept</name>\n \
<value>1</value>\n </property>\n</configuration>"
);
}
#[test]
fn hadoop_xml_escapes_special_characters() {
let rendered = xml(&[("k", Some("<a>&b"))]);
assert!(
rendered.contains("<value><a>&b</value>"),
"{rendered}"
);
}
#[test]
fn java_properties_renders_key_value() {
assert_eq!(props(&[("a", Some("1")), ("b", Some("2"))]), "a=1\nb=2\n");
}
#[test]
fn java_properties_renders_none_as_empty() {
assert_eq!(props(&[("none", None)]), "none=\n");
}
#[test]
fn java_properties_escapes_colon_in_value() {
assert_eq!(
props(&[("url", Some("file://this/location/file.abc"))]),
"url=file\\://this/location/file.abc\n"
);
}
}