anodizer_core/config/
upx.rs1use schemars::JsonSchema;
2use serde::{Deserialize, Deserializer, Serialize};
3
4use super::{StringOrBool, deserialize_string_or_bool_opt};
5
6#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
11#[serde(default, deny_unknown_fields)]
12pub struct UpxConfig {
13 pub id: Option<String>,
15 pub ids: Option<Vec<String>>,
17 #[serde(deserialize_with = "deserialize_string_or_bool_opt", default)]
20 pub enabled: Option<StringOrBool>,
21 pub binary: String,
23 pub args: Vec<String>,
25 pub required: bool,
27 pub targets: Option<Vec<String>>,
29 pub compress: Option<String>,
31 pub lzma: Option<bool>,
33 pub brute: Option<bool>,
35}
36
37impl Default for UpxConfig {
38 fn default() -> Self {
39 UpxConfig {
40 id: None,
41 ids: None,
42 enabled: None,
43 binary: "upx".to_string(),
44 args: Vec::new(),
45 required: false,
46 targets: None,
47 compress: None,
48 lzma: None,
49 brute: None,
50 }
51 }
52}
53
54pub(super) fn deserialize_upx<'de, D>(deserializer: D) -> Result<Vec<UpxConfig>, D::Error>
60where
61 D: Deserializer<'de>,
62{
63 use serde::de::{self, Visitor};
64
65 struct UpxVisitor;
66
67 impl<'de> Visitor<'de> for UpxVisitor {
68 type Value = Vec<UpxConfig>;
69
70 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 f.write_str("a UPX config object or an array of UPX config objects")
72 }
73
74 fn visit_seq<A: de::SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
75 let mut configs = Vec::new();
76 while let Some(item) = seq.next_element::<UpxConfig>()? {
77 configs.push(item);
78 }
79 Ok(configs)
80 }
81
82 fn visit_map<M: de::MapAccess<'de>>(self, map: M) -> Result<Self::Value, M::Error> {
83 let config = UpxConfig::deserialize(de::value::MapAccessDeserializer::new(map))?;
84 Ok(vec![config])
85 }
86
87 fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
88 Ok(Vec::new())
89 }
90
91 fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
92 Ok(Vec::new())
93 }
94 }
95
96 deserializer.deserialize_any(UpxVisitor)
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[derive(Deserialize)]
107 struct UpxWrapper {
108 #[serde(default, deserialize_with = "deserialize_upx")]
109 upx: Vec<UpxConfig>,
110 }
111
112 #[test]
113 fn default_fills_upx_binary_and_empty_lists() {
114 let d = UpxConfig::default();
115 assert_eq!(d.binary, "upx");
116 assert!(d.args.is_empty());
117 assert!(!d.required);
118 assert!(d.id.is_none());
119 assert!(d.ids.is_none());
120 assert!(d.enabled.is_none());
121 assert!(d.targets.is_none());
122 assert!(d.compress.is_none());
123 assert!(d.lzma.is_none());
124 assert!(d.brute.is_none());
125 }
126
127 #[test]
128 fn single_object_becomes_one_element_vec() {
129 let w: UpxWrapper = serde_yaml_ng::from_str("upx:\n compress: best\n").unwrap();
130 assert_eq!(w.upx.len(), 1);
131 assert_eq!(w.upx[0].compress.as_deref(), Some("best"));
132 assert_eq!(w.upx[0].binary, "upx");
134 }
135
136 #[test]
137 fn array_collects_every_entry() {
138 let w: UpxWrapper =
139 serde_yaml_ng::from_str("upx:\n - id: a\n - id: b\n - id: c\n").unwrap();
140 assert_eq!(w.upx.len(), 3);
141 assert_eq!(w.upx[0].id.as_deref(), Some("a"));
142 assert_eq!(w.upx[2].id.as_deref(), Some("c"));
143 }
144
145 #[test]
146 fn null_and_missing_yield_empty_vec() {
147 let null: UpxWrapper = serde_yaml_ng::from_str("upx: null").unwrap();
148 assert!(null.upx.is_empty());
149 let missing: UpxWrapper = serde_yaml_ng::from_str("{}").unwrap();
150 assert!(missing.upx.is_empty());
151 }
152
153 #[test]
154 fn unknown_field_is_rejected() {
155 let r: Result<UpxWrapper, _> = serde_yaml_ng::from_str("upx:\n compres: best\n");
158 assert!(r.is_err(), "unknown field `compres` must be rejected");
159 }
160
161 #[test]
162 fn enabled_accepts_bool_and_template_string() {
163 let as_bool: UpxWrapper = serde_yaml_ng::from_str("upx:\n enabled: true\n").unwrap();
164 assert_eq!(as_bool.upx[0].enabled, Some(StringOrBool::Bool(true)));
165
166 let as_tmpl: UpxWrapper =
167 serde_yaml_ng::from_str("upx:\n enabled: \"{{ if IsSnapshot }}false{{ endif }}\"\n")
168 .unwrap();
169 assert_eq!(
170 as_tmpl.upx[0].enabled,
171 Some(StringOrBool::String(
172 "{{ if IsSnapshot }}false{{ endif }}".into()
173 ))
174 );
175 }
176
177 #[test]
178 fn full_flag_surface_deserializes() {
179 let w: UpxWrapper = serde_yaml_ng::from_str(
180 "upx:\n - id: pack\n binary: /opt/upx\n args: [\"-9\", \"--brute\"]\n required: true\n targets: [\"x86_64-unknown-linux-gnu\"]\n compress: \"9\"\n lzma: true\n brute: false\n ids: [\"cli\"]\n",
181 )
182 .unwrap();
183 let c = &w.upx[0];
184 assert_eq!(c.binary, "/opt/upx");
185 assert_eq!(c.args, vec!["-9", "--brute"]);
186 assert!(c.required);
187 assert_eq!(c.targets.as_deref().unwrap(), ["x86_64-unknown-linux-gnu"]);
188 assert_eq!(c.compress.as_deref(), Some("9"));
189 assert_eq!(c.lzma, Some(true));
190 assert_eq!(c.brute, Some(false));
191 assert_eq!(c.ids.as_deref().unwrap(), ["cli"]);
192 }
193}