libcnb_data/
build_plan.rs

1use serde::Serialize;
2use serde::ser::Error;
3use std::collections::VecDeque;
4use toml::value::Table;
5
6#[derive(Serialize, Debug, Default)]
7#[must_use]
8pub struct BuildPlan {
9    #[serde(skip_serializing_if = "Vec::is_empty")]
10    pub provides: Vec<Provide>,
11    #[serde(skip_serializing_if = "Vec::is_empty")]
12    pub requires: Vec<Require>,
13    #[serde(skip_serializing_if = "Vec::is_empty")]
14    pub or: Vec<Or>,
15}
16
17impl BuildPlan {
18    pub fn new() -> Self {
19        Self::default()
20    }
21}
22
23#[derive(Default)]
24#[must_use]
25pub struct BuildPlanBuilder {
26    acc: VecDeque<(Vec<Provide>, Vec<Require>)>,
27    current_provides: Vec<Provide>,
28    current_requires: Vec<Require>,
29}
30
31impl BuildPlanBuilder {
32    pub fn new() -> Self {
33        Self::default()
34    }
35
36    pub fn provides(mut self, name: impl AsRef<str>) -> Self {
37        self.current_provides.push(Provide::new(name.as_ref()));
38        self
39    }
40
41    pub fn requires(mut self, require: impl Into<Require>) -> Self {
42        self.current_requires.push(require.into());
43        self
44    }
45
46    pub fn or(mut self) -> Self {
47        self.acc
48            .push_back((self.current_provides, self.current_requires));
49        self.current_provides = Vec::new();
50        self.current_requires = Vec::new();
51
52        self
53    }
54
55    pub fn build(self) -> BuildPlan {
56        let mut xyz = self.or();
57
58        if let Some(head) = xyz.acc.pop_front() {
59            let mut build_plan = BuildPlan::new();
60            build_plan.provides = head.0;
61            build_plan.requires = head.1;
62
63            for alternative in xyz.acc {
64                build_plan.or.push(Or {
65                    provides: alternative.0,
66                    requires: alternative.1,
67                });
68            }
69
70            build_plan
71        } else {
72            BuildPlan::new()
73        }
74    }
75}
76
77#[derive(Serialize, Debug)]
78pub struct Or {
79    #[serde(skip_serializing_if = "Vec::is_empty")]
80    provides: Vec<Provide>,
81    #[serde(skip_serializing_if = "Vec::is_empty")]
82    requires: Vec<Require>,
83}
84
85#[derive(Serialize, Debug)]
86pub struct Provide {
87    name: String,
88}
89
90impl Provide {
91    pub fn new(name: impl Into<String>) -> Self {
92        Self { name: name.into() }
93    }
94}
95
96#[derive(Serialize, Debug)]
97pub struct Require {
98    pub name: String,
99    pub metadata: Table,
100}
101
102impl Require {
103    pub fn new(name: impl Into<String>) -> Self {
104        Self {
105            name: name.into(),
106            metadata: Table::new(),
107        }
108    }
109
110    /// Convert a Serializable struct and store it as a toml Table for metadata
111    ///
112    /// # Errors
113    /// This will return error for any normal TOML serialization error as well if it's not
114    /// possible to serialize as a TOML Table.
115    pub fn metadata<T: Serialize>(&mut self, metadata: T) -> Result<(), toml::ser::Error> {
116        if let toml::Value::Table(table) = toml::Value::try_from(metadata)? {
117            self.metadata = table;
118
119            Ok(())
120        } else {
121            Err(toml::ser::Error::custom(String::from(
122                "Couldn't be serialized as a TOML Table.",
123            )))
124        }
125    }
126}
127
128impl<S: Into<String>> From<S> for Require {
129    fn from(s: S) -> Self {
130        Self::new(s)
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    #[test]
139    fn it_writes_simple_build_plan() {
140        let mut build_plan = BuildPlan::new();
141        build_plan.provides.push(Provide::new("rust"));
142        build_plan.requires.push(Require::new("rust"));
143
144        assert!(toml::to_string(&build_plan).is_ok());
145    }
146
147    #[test]
148    fn it_serializes_metadata() {
149        #[derive(Serialize)]
150        struct Metadata {
151            foo: String,
152        }
153
154        let mut require = Require::new("foo");
155        let metadata = Metadata {
156            foo: String::from("bar"),
157        };
158        let result = require.metadata(metadata);
159        assert!(result.is_ok());
160        assert_eq!(
161            require.metadata.get("foo"),
162            Some(&toml::Value::String(String::from("bar")))
163        );
164    }
165}