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
use crate::newtypes::libcnb_newtype;
use serde::Serialize;
use std::collections::HashMap;

/// Output of a CNB exec.d program.
///
/// See [Cloud Native Buildpack specification](https://github.com/buildpacks/spec/blob/main/buildpack.md#execd)
#[derive(Serialize, Clone)]
pub struct ExecDProgramOutput(HashMap<ExecDProgramOutputKey, String>);

impl ExecDProgramOutput {
    #[must_use]
    pub fn new(map: HashMap<ExecDProgramOutputKey, String>) -> Self {
        Self(map)
    }
}

impl<K: Into<ExecDProgramOutputKey>, V: Into<String>, A: IntoIterator<Item = (K, V)>> From<A>
    for ExecDProgramOutput
{
    fn from(a: A) -> Self {
        Self(
            a.into_iter()
                .map(|(key, value)| (key.into(), value.into()))
                .collect(),
        )
    }
}

libcnb_newtype!(
    exec_d,
    /// Construct a [`ExecDProgramOutputKey`] value at compile time.
    ///
    /// Passing a string that is not a valid `ExecDProgramOutputKey` value will yield a compilation
    /// error.
    ///
    /// # Examples:
    /// ```
    /// use libcnb_data::exec_d::ExecDProgramOutputKey;
    /// use libcnb_data::exec_d_program_output_key;
    ///
    /// let key: ExecDProgramOutputKey = exec_d_program_output_key!("PATH");
    /// ```
    exec_d_program_output_key,
    /// A key of exec.d program output
    ///
    /// It MUST only contain numbers, letters, and the characters `_` and `-`.
    ///
    /// Use the [`exec_d_program_output_key`](crate::exec_d_program_output_key) macro to construct
    /// a `ExecDProgramOutputKey` from a literal string. To parse a dynamic string into a
    /// `ExecDProgramOutputKey`, use [`str::parse`](str::parse).
    ///
    /// # Examples
    /// ```
    /// use libcnb_data::exec_d::ExecDProgramOutputKey;
    /// use libcnb_data::exec_d_program_output_key;
    ///
    /// let from_literal = exec_d_program_output_key!("ENV_VAR");
    ///
    /// let input = "ENV_VAR";
    /// let from_dynamic: ExecDProgramOutputKey = input.parse().unwrap();
    /// assert_eq!(from_dynamic, from_literal);
    ///
    /// let input = "!nv4lid";
    /// let invalid: Result<ExecDProgramOutputKey, _> = input.parse();
    /// assert!(invalid.is_err());
    /// ```
    ExecDProgramOutputKey,
    ExecDProgramOutputKeyError,
    r"^[A-Za-z0-9_-]+$"
);

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn exec_d_program_output_key_validation_valid() {
        assert!("FOO".parse::<ExecDProgramOutputKey>().is_ok());
        assert!("foo".parse::<ExecDProgramOutputKey>().is_ok());
        assert!("FOO_BAR".parse::<ExecDProgramOutputKey>().is_ok());
        assert!("foo_bar".parse::<ExecDProgramOutputKey>().is_ok());
        assert!("123".parse::<ExecDProgramOutputKey>().is_ok());
        assert!("FOO-bar".parse::<ExecDProgramOutputKey>().is_ok());
        assert!("foo-BAR".parse::<ExecDProgramOutputKey>().is_ok());
    }

    #[test]
    fn exec_d_program_output_key_validation_invalid() {
        assert_eq!(
            "FOO BAR".parse::<ExecDProgramOutputKey>(),
            Err(ExecDProgramOutputKeyError::InvalidValue(String::from(
                "FOO BAR"
            )))
        );

        assert_eq!(
            "FÜCHSCHEN".parse::<ExecDProgramOutputKey>(),
            Err(ExecDProgramOutputKeyError::InvalidValue(String::from(
                "FÜCHSCHEN"
            )))
        );

        assert_eq!(
            "🦊".parse::<ExecDProgramOutputKey>(),
            Err(ExecDProgramOutputKeyError::InvalidValue(String::from("🦊")))
        );

        assert_eq!(
            "".parse::<ExecDProgramOutputKey>(),
            Err(ExecDProgramOutputKeyError::InvalidValue(String::new()))
        );
    }
}