tugger_snapcraft/
yaml.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5use {
6    serde::{Deserialize, Serialize},
7    std::{borrow::Cow, collections::HashMap},
8};
9
10/// Represents the value of the `type` field.
11#[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/// Represents the value of an architecture in an `architectures` field.
28#[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/// Represents the value of a `confinement` field.
49#[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/// Represents the value of a `grade` field.
66#[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/// Represents the value of an `adapter` field.
82#[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/// Represents the value of a `daemon` field.
98#[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/// Represents the value of a `restart-condition` field.
116#[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/// Represents the value of a `source-type` field.
136#[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/// Represents the values in a `build-attributes` field.
163#[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/// Represents the value of an `architecture` field.
182#[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/// Represents the `apps.<app-name>` entries in a `snapcraft.yaml`.
191#[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/// Represents the `parts.<part-name>` entries in a `snapcraft.yaml`.
237#[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/// Represents a `snapcraft.yaml` file content.
293#[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    /// Add a named application to this instance.
363    pub fn add_app(&mut self, name: Cow<'a, str>, app: SnapApp<'a>) {
364        self.apps.insert(name, app);
365    }
366
367    /// Add a named part to this instance.
368    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}