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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

//! Code for extracting service config from the user's environment.

use std::fmt;

/// A struct used with the [`LoadServiceConfig`] trait to extract service config from the user's environment.
// [profile active-profile]
// services = dev
//
// [services dev]
// service-id =
//   config-key = config-value
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ServiceConfigKey<'a> {
    service_id: &'a str,
    profile: &'a str,
    env: &'a str,
}

impl<'a> ServiceConfigKey<'a> {
    /// Create a new [`ServiceConfigKey`] builder struct.
    pub fn builder() -> builder::Builder<'a> {
        Default::default()
    }
    /// Get the service ID.
    pub fn service_id(&self) -> &'a str {
        self.service_id
    }
    /// Get the profile key.
    pub fn profile(&self) -> &'a str {
        self.profile
    }
    /// Get the environment key.
    pub fn env(&self) -> &'a str {
        self.env
    }
}

pub mod builder {
    //! Builder for [`ServiceConfigKey`].

    use super::ServiceConfigKey;
    use std::fmt;

    /// Builder for [`ServiceConfigKey`].
    #[derive(Default, Debug)]
    pub struct Builder<'a> {
        service_id: Option<&'a str>,
        profile: Option<&'a str>,
        env: Option<&'a str>,
    }

    impl<'a> Builder<'a> {
        /// Set the service ID.
        pub fn service_id(mut self, service_id: &'a str) -> Self {
            self.service_id = Some(service_id);
            self
        }

        /// Set the profile key.
        pub fn profile(mut self, profile: &'a str) -> Self {
            self.profile = Some(profile);
            self
        }

        /// Set the environment key.
        pub fn env(mut self, env: &'a str) -> Self {
            self.env = Some(env);
            self
        }

        /// Build the [`ServiceConfigKey`].
        ///
        /// Returns an error if any of the required fields are missing.
        pub fn build(self) -> Result<ServiceConfigKey<'a>, Error> {
            Ok(ServiceConfigKey {
                service_id: self.service_id.ok_or_else(Error::missing_service_id)?,
                profile: self.profile.ok_or_else(Error::missing_profile)?,
                env: self.env.ok_or_else(Error::missing_env)?,
            })
        }
    }

    #[allow(clippy::enum_variant_names)]
    #[derive(Debug)]
    enum ErrorKind {
        MissingServiceId,
        MissingProfile,
        MissingEnv,
    }

    impl fmt::Display for ErrorKind {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            match self {
                ErrorKind::MissingServiceId => write!(f, "missing required service-id"),
                ErrorKind::MissingProfile => write!(f, "missing required active profile name"),
                ErrorKind::MissingEnv => write!(f, "missing required environment variable name"),
            }
        }
    }

    /// Error type for [`ServiceConfigKey::builder`]
    #[derive(Debug)]
    pub struct Error {
        kind: ErrorKind,
    }

    impl fmt::Display for Error {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            write!(f, "couldn't build a ServiceEnvConfigKey: {}", self.kind)
        }
    }

    impl std::error::Error for Error {}

    impl Error {
        /// Create a new "missing service ID" error
        pub fn missing_service_id() -> Self {
            Self {
                kind: ErrorKind::MissingServiceId,
            }
        }
        /// Create a new "missing profile key" error
        pub fn missing_profile() -> Self {
            Self {
                kind: ErrorKind::MissingProfile,
            }
        }
        /// Create a new "missing env key" error
        pub fn missing_env() -> Self {
            Self {
                kind: ErrorKind::MissingEnv,
            }
        }
    }
}

/// Implementers of this trait can provide service config defined in a user's environment.
pub trait LoadServiceConfig: fmt::Debug + Send + Sync {
    /// Given a [`ServiceConfigKey`], return the value associated with it.
    fn load_config(&self, key: ServiceConfigKey<'_>) -> Option<String>;
}