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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
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!(
            "FOO.BAR".parse::<ExecDProgramOutputKey>(),
            Err(ExecDProgramOutputKeyError::InvalidValue(String::from(
                "FOO.BAR"
            )))
        );

        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()))
        );
    }
}