1use {
6 serde::{Deserialize, Serialize},
7 std::{borrow::Cow, collections::HashMap},
8};
9
10#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
12#[serde(rename_all = "lowercase")]
13pub enum Type {
14 Gadget,
15 Kernel,
16 Base,
17}
18
19impl TryFrom<&str> for Type {
20 type Error = serde_yaml::Error;
21
22 fn try_from(s: &str) -> Result<Self, Self::Error> {
23 serde_yaml::from_str(s)
24 }
25}
26
27#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
29#[serde(rename_all = "lowercase")]
30pub enum Architecture {
31 All,
32 S390x,
33 Ppc64el,
34 Arm64,
35 Armhf,
36 Amd64,
37 I386,
38}
39
40impl TryFrom<&str> for Architecture {
41 type Error = serde_yaml::Error;
42
43 fn try_from(s: &str) -> Result<Self, Self::Error> {
44 serde_yaml::from_str(s)
45 }
46}
47
48#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
50#[serde(rename_all = "lowercase")]
51pub enum Confinement {
52 Strict,
53 Devmode,
54 Classic,
55}
56
57impl TryFrom<&str> for Confinement {
58 type Error = serde_yaml::Error;
59
60 fn try_from(s: &str) -> Result<Self, Self::Error> {
61 serde_yaml::from_str(s)
62 }
63}
64
65#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
67#[serde(rename_all = "lowercase")]
68pub enum Grade {
69 Devel,
70 Stable,
71}
72
73impl TryFrom<&str> for Grade {
74 type Error = serde_yaml::Error;
75
76 fn try_from(s: &str) -> Result<Self, Self::Error> {
77 serde_yaml::from_str(s)
78 }
79}
80
81#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
83#[serde(rename_all = "lowercase")]
84pub enum Adapter {
85 None,
86 Full,
87}
88
89impl TryFrom<&str> for Adapter {
90 type Error = serde_yaml::Error;
91
92 fn try_from(s: &str) -> Result<Self, Self::Error> {
93 serde_yaml::from_str(s)
94 }
95}
96
97#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
99#[serde(rename_all = "lowercase")]
100pub enum Daemon {
101 Simple,
102 Oneshot,
103 Forking,
104 Notify,
105}
106
107impl TryFrom<&str> for Daemon {
108 type Error = serde_yaml::Error;
109
110 fn try_from(s: &str) -> Result<Self, Self::Error> {
111 serde_yaml::from_str(s)
112 }
113}
114
115#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
117#[serde(rename_all = "kebab-case")]
118pub enum RestartCondition {
119 OnFailure,
120 OnSuccess,
121 OnAbnormal,
122 OnAbort,
123 Always,
124 Never,
125}
126
127impl TryFrom<&str> for RestartCondition {
128 type Error = serde_yaml::Error;
129
130 fn try_from(s: &str) -> Result<Self, Self::Error> {
131 serde_yaml::from_str(s)
132 }
133}
134
135#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
137#[serde(rename_all = "lowercase")]
138pub enum SourceType {
139 Bzr,
140 Deb,
141 Git,
142 Hg,
143 Local,
144 Mercurial,
145 Rpm,
146 Subversion,
147 Svn,
148 Tar,
149 Zip,
150 #[serde(rename = "7z")]
151 SevenZip,
152}
153
154impl TryFrom<&str> for SourceType {
155 type Error = serde_yaml::Error;
156
157 fn try_from(s: &str) -> Result<Self, Self::Error> {
158 serde_yaml::from_str(s)
159 }
160}
161
162#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
164#[serde(rename_all = "kebab-case")]
165pub enum BuildAttribute {
166 Debug,
167 KeepExecstack,
168 NoPatchelf,
169 EnablePatchelf,
170 NoInstall,
171}
172
173impl TryFrom<&str> for BuildAttribute {
174 type Error = serde_yaml::Error;
175
176 fn try_from(s: &str) -> Result<Self, Self::Error> {
177 serde_yaml::from_str(s)
178 }
179}
180
181#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
183#[serde(rename_all = "kebab-case")]
184pub struct Architectures {
185 pub build_on: Vec<Architecture>,
186 #[serde(default, skip_serializing_if = "Vec::is_empty")]
187 pub run_on: Vec<Architecture>,
188}
189
190#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
192#[serde(rename_all = "kebab-case")]
193pub struct SnapApp<'a> {
194 #[serde(skip_serializing_if = "Option::is_none")]
195 pub adapter: Option<Adapter>,
196 #[serde(skip_serializing_if = "Option::is_none")]
197 pub autostart: Option<Cow<'a, str>>,
198 #[serde(skip_serializing_if = "Option::is_none")]
199 pub command: Option<Cow<'a, str>>,
200 #[serde(default, skip_serializing_if = "Vec::is_empty")]
201 pub command_chain: Vec<Cow<'a, str>>,
202 #[serde(skip_serializing_if = "Option::is_none")]
203 pub common_id: Option<Cow<'a, str>>,
204 #[serde(skip_serializing_if = "Option::is_none")]
205 pub daemon: Option<Daemon>,
206 #[serde(skip_serializing_if = "Option::is_none")]
207 pub desktop: Option<Cow<'a, str>>,
208 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
209 pub environment: HashMap<Cow<'a, str>, Cow<'a, str>>,
210 #[serde(default, skip_serializing_if = "Vec::is_empty")]
211 pub extensions: Vec<Cow<'a, str>>,
212 #[serde(default, skip_serializing_if = "Vec::is_empty")]
213 pub plugs: Vec<Cow<'a, str>>,
214 #[serde(default, skip_serializing_if = "Vec::is_empty")]
215 pub slots: Vec<Cow<'a, str>>,
216 #[serde(skip_serializing_if = "Option::is_none")]
217 pub stop_command: Option<Cow<'a, str>>,
218 #[serde(skip_serializing_if = "Option::is_none")]
219 pub post_stop_command: Option<Cow<'a, str>>,
220 #[serde(skip_serializing_if = "Option::is_none")]
221 pub stop_timeout: Option<Cow<'a, str>>,
222 #[serde(skip_serializing_if = "Option::is_none")]
223 pub timer: Option<Cow<'a, str>>,
224 #[serde(skip_serializing_if = "Option::is_none")]
225 pub restart_condition: Option<RestartCondition>,
226 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
227 pub socket: HashMap<Cow<'a, str>, Cow<'a, str>>,
228 #[serde(skip_serializing_if = "Option::is_none")]
229 pub socket_mode: Option<i64>,
230 #[serde(skip_serializing_if = "Option::is_none")]
231 pub listen_stream: Option<Cow<'a, str>>,
232 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
233 pub passthrough: HashMap<Cow<'a, str>, Cow<'a, str>>,
234}
235
236#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
238#[serde(rename_all = "kebab-case")]
239pub struct SnapPart<'a> {
240 #[serde(skip_serializing_if = "Option::is_none")]
241 pub plugin: Option<Cow<'a, str>>,
242 #[serde(skip_serializing_if = "Option::is_none")]
243 pub source: Option<Cow<'a, str>>,
244 #[serde(skip_serializing_if = "Option::is_none")]
245 pub source_type: Option<SourceType>,
246 #[serde(skip_serializing_if = "Option::is_none")]
247 pub source_checksum: Option<Cow<'a, str>>,
248 #[serde(skip_serializing_if = "Option::is_none")]
249 pub source_depth: Option<i64>,
250 #[serde(skip_serializing_if = "Option::is_none")]
251 pub source_branch: Option<Cow<'a, str>>,
252 #[serde(skip_serializing_if = "Option::is_none")]
253 pub source_commit: Option<Cow<'a, str>>,
254 #[serde(skip_serializing_if = "Option::is_none")]
255 pub source_tag: Option<Cow<'a, str>>,
256 #[serde(skip_serializing_if = "Option::is_none")]
257 pub source_subdir: Option<Cow<'a, str>>,
258 #[serde(default, skip_serializing_if = "Vec::is_empty")]
259 pub after: Vec<Cow<'a, str>>,
260 #[serde(default, skip_serializing_if = "Vec::is_empty")]
261 pub build_environment: Vec<HashMap<Cow<'a, str>, Cow<'a, str>>>,
262 #[serde(default, skip_serializing_if = "Vec::is_empty")]
263 pub build_snaps: Vec<Cow<'a, str>>,
264 #[serde(default, skip_serializing_if = "Vec::is_empty")]
265 pub build_packages: Vec<Cow<'a, str>>,
266 #[serde(default, skip_serializing_if = "Vec::is_empty")]
267 pub stage_packages: Vec<Cow<'a, str>>,
268 #[serde(default, skip_serializing_if = "Vec::is_empty")]
269 pub stage_snaps: Vec<Cow<'a, str>>,
270 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
271 pub organize: HashMap<Cow<'a, str>, Cow<'a, str>>,
272 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
273 pub filesets: HashMap<Cow<'a, str>, Vec<Cow<'a, str>>>,
274 #[serde(default, skip_serializing_if = "Vec::is_empty")]
275 pub stage: Vec<Cow<'a, str>>,
276 #[serde(skip_serializing_if = "Option::is_none")]
277 pub parse_info: Option<Cow<'a, str>>,
278 #[serde(default, skip_serializing_if = "Vec::is_empty")]
279 pub prime: Vec<Cow<'a, str>>,
280 #[serde(skip_serializing_if = "Option::is_none")]
281 pub override_build: Option<Cow<'a, str>>,
282 #[serde(skip_serializing_if = "Option::is_none")]
283 pub override_prime: Option<Cow<'a, str>>,
284 #[serde(skip_serializing_if = "Option::is_none")]
285 pub override_pull: Option<Cow<'a, str>>,
286 #[serde(skip_serializing_if = "Option::is_none")]
287 pub override_stage: Option<Cow<'a, str>>,
288 #[serde(default, skip_serializing_if = "Vec::is_empty")]
289 pub build_attributes: Vec<BuildAttribute>,
290}
291
292#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
294#[serde(rename_all = "kebab-case")]
295pub struct Snapcraft<'a> {
296 pub name: Cow<'a, str>,
297 #[serde(skip_serializing_if = "Option::is_none")]
298 pub title: Option<Cow<'a, str>>,
299 #[serde(skip_serializing_if = "Option::is_none")]
300 pub base: Option<Cow<'a, str>>,
301 pub version: Cow<'a, str>,
302 pub summary: Cow<'a, str>,
303 pub description: Cow<'a, str>,
304 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
305 pub snap_type: Option<Type>,
306 #[serde(skip_serializing_if = "Option::is_none")]
307 pub confinement: Option<Confinement>,
308 #[serde(skip_serializing_if = "Option::is_none")]
309 pub icon: Option<Cow<'a, str>>,
310 #[serde(skip_serializing_if = "Option::is_none")]
311 pub license: Option<Cow<'a, str>>,
312 #[serde(skip_serializing_if = "Option::is_none")]
313 pub grade: Option<Grade>,
314 #[serde(skip_serializing_if = "Option::is_none")]
315 pub adopt_info: Option<Cow<'a, str>>,
316 #[serde(skip_serializing_if = "Option::is_none")]
317 pub architectures: Option<Architectures>,
318 #[serde(default, skip_serializing_if = "Vec::is_empty")]
319 pub assumes: Vec<Cow<'a, str>>,
320 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
321 pub passthrough: HashMap<Cow<'a, str>, Cow<'a, str>>,
322 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
323 pub apps: HashMap<Cow<'a, str>, SnapApp<'a>>,
324 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
325 pub parts: HashMap<Cow<'a, str>, SnapPart<'a>>,
326 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
327 pub plugs: HashMap<Cow<'a, str>, HashMap<Cow<'a, str>, Cow<'a, str>>>,
328 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
329 pub slots: HashMap<Cow<'a, str>, HashMap<Cow<'a, str>, Cow<'a, str>>>,
330}
331
332impl<'a> Snapcraft<'a> {
333 pub fn new(
334 name: Cow<'a, str>,
335 version: Cow<'a, str>,
336 summary: Cow<'a, str>,
337 description: Cow<'a, str>,
338 ) -> Self {
339 Self {
340 name,
341 version,
342 summary,
343 description,
344 title: None,
345 base: None,
346 snap_type: None,
347 confinement: None,
348 icon: None,
349 license: None,
350 grade: None,
351 adopt_info: None,
352 architectures: None,
353 assumes: vec![],
354 passthrough: HashMap::new(),
355 apps: HashMap::new(),
356 parts: HashMap::new(),
357 plugs: HashMap::new(),
358 slots: HashMap::new(),
359 }
360 }
361
362 pub fn add_app(&mut self, name: Cow<'a, str>, app: SnapApp<'a>) {
364 self.apps.insert(name, app);
365 }
366
367 pub fn add_part(&mut self, name: Cow<'a, str>, part: SnapPart<'a>) {
369 self.parts.insert(name, part);
370 }
371}
372
373#[cfg(test)]
374mod tests {
375 use super::*;
376
377 #[test]
378 fn test_type_from_str() -> Result<(), serde_yaml::Error> {
379 let t = Type::try_from("gadget")?;
380 assert_eq!(t, Type::Gadget);
381
382 Ok(())
383 }
384
385 #[test]
386 fn test_architecture_from_str() -> Result<(), serde_yaml::Error> {
387 assert_eq!(Architecture::try_from("all")?, Architecture::All);
388 assert_eq!(Architecture::try_from("s390x")?, Architecture::S390x);
389 assert_eq!(Architecture::try_from("ppc64el")?, Architecture::Ppc64el);
390
391 Ok(())
392 }
393
394 #[test]
395 fn test_source_type_from_str() -> Result<(), serde_yaml::Error> {
396 assert_eq!(SourceType::try_from("7z")?, SourceType::SevenZip);
397
398 Ok(())
399 }
400}