cf_services/
lib.rs

1#![deny(missing_docs)]
2#![deny(missing_debug_implementations)]
3#![cfg_attr(test, deny(warnings))]
4#![cfg_attr(docsrs, feature(doc_cfg))]
5
6//! # cf-services
7//!
8//! The `cf-services` crate provides an easy way to retrieve information about services bounded to
9//! an application in Cloud Foundry.
10//!
11//! It retrieves and parses the `cf_services::VCAP_SERVICES` environment variable into a struct
12//! for easier consumption.
13//!
14//! ## Retrieving Services
15//!
16//! To retrieve all the services, simply use `cf_services::get_services_from_env`.
17//!
18//! ## Service Credential
19//!
20//! To retrieve a service's credential information, either use
21//! `cf_services::get_service_cred_from_env` or the convenience function
22//! `cf_services::get_service_credentials`.
23
24use std::{env, fmt};
25use std::collections::HashMap;
26use std::fmt::Formatter;
27
28use serde::Deserialize;
29
30/// The environment variable key that contains all the bounded services to the application.
31pub const VCAP_SERVICES: &str = "VCAP_SERVICES";
32
33/// The high level service information for a service bounded to an application. Multiple services of
34/// the same type can be bounded to an application (e.g. multiple Config Servers).
35#[derive(Deserialize, Debug)]
36pub struct Service {
37    /// The name of the service.
38    #[serde(default)]
39    pub name: String,
40    /// The instance name of the service.
41    #[serde(default)]
42    pub instance_name: String,
43    /// The name the service is bounded as.
44    #[serde(default)]
45    pub binding_name: String,
46    /// The credentials of the service.
47    pub credentials: Credentials,
48    /// The label associated with the service.
49    #[serde(default)]
50    pub label: String,
51}
52
53/// The credentials information for authenticating with the service.
54#[derive(Deserialize, Debug, Clone)]
55pub struct Credentials {
56    /// The URI of the service.
57    #[serde(default)]
58    pub uri: String,
59    /// The JDBC URI of the service.
60    #[serde(rename(deserialize = "jdbcUrl"))]
61    #[serde(default)]
62    pub jdbc_url: String,
63    /// The API URI of the service.
64    #[serde(rename(deserialize = "http_api_uri"))]
65    #[serde(default)]
66    pub api_uri: String,
67    /// Th license key for the service.
68    #[serde(rename(deserialize = "licenseKey"))]
69    #[serde(default)]
70    pub license_key: String,
71    /// The Client Secret for generating a token via OAuth.
72    #[serde(default)]
73    pub client_secret: String,
74    #[serde(default)]
75    /// The Client ID for generating a token via OAuth.
76    pub client_id: String,
77    #[serde(default)]
78    /// The Access Token URI for generating a token via OAuth.
79    pub access_token_uri: String,
80    /// The hostname of the service.
81    #[serde(default)]
82    pub hostname: String,
83    /// The username to authenticate with the service.
84    #[serde(default)]
85    pub username: String,
86    /// The password of the username.
87    #[serde(default)]
88    pub password: String,
89    /// The Port of the service.
90    #[serde(default)]
91    pub port: i16,
92    /// The name of the credentials.
93    #[serde(default)]
94    pub name: String,
95}
96
97/// Retrieves the credential information of the specified service.
98pub fn get_service_cred_from_env(service_name: String) -> Result<Vec<Credentials>, CFError> {
99    get_services_from_env()
100        .and_then(|services| get_service_credentials(services, service_name))
101}
102
103/// Retrieves all service information.
104pub fn get_services_from_env() -> Result<HashMap<String, Vec<Service>>, CFError> {
105    env::var(VCAP_SERVICES)
106        .map_err(|_| CFError::EnvNotSet)
107        .and_then(|val| serde_json::from_str(&val).map_err(|_| CFError::MalformedJSON))
108}
109
110/// Retrieves the credential information from the provided services that match the specified service
111/// name.
112pub fn get_service_credentials(services: HashMap<String, Vec<Service>>, service_name: String) -> Result<Vec<Credentials>, CFError> {
113    match services.get(&service_name) {
114        Some(services) => Ok(services.iter().map(|service| service.credentials.clone()).collect()),
115        None => Err(CFError::ServiceNotPresent(service_name))
116    }
117}
118
119/// Enumeration of the different errors that can occur.
120#[derive(PartialEq, Debug)]
121pub enum CFError {
122    /// Error when the environment variable is not set.
123    EnvNotSet,
124    /// Error then the environment variable JSON is malformed.
125    MalformedJSON,
126    /// Error when a service is not present.
127    ServiceNotPresent(String),
128}
129
130impl fmt::Display for CFError {
131    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
132        match *self {
133            CFError::EnvNotSet => write!(f, "environment variable {:?} is not set", VCAP_SERVICES),
134            CFError::MalformedJSON => write!(f, "environment variable {:?} is malformed", VCAP_SERVICES),
135            CFError::ServiceNotPresent(ref s) => write!(f, "service {:?} is not bounded to the application", s)
136        }
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use std::collections::HashMap;
143    use std::env;
144
145    use crate::{CFError, Credentials, get_service_cred_from_env, get_service_credentials, get_services_from_env, Service, VCAP_SERVICES};
146
147    #[test]
148    fn test_get_service_cred_from_env() {
149        let json = r#"{
150      "serviceA": [
151        {
152          "name":"service_a",
153          "credentials": {
154            "uri": "example_uri",
155            "port": 8080
156          }
157        }
158      ]
159    }"#;
160        env::set_var(VCAP_SERVICES, json);
161        let creds = get_service_cred_from_env("serviceA".to_string()).unwrap();
162        assert_eq!(1, creds.len());
163        let cred = creds.get(0).unwrap();
164        assert_eq!("example_uri", cred.uri);
165        assert_eq!(8080, cred.port);
166        env::remove_var(VCAP_SERVICES);
167    }
168
169    #[test]
170    fn test_get_services_from_env() {
171        let json = r#"{
172      "serviceA": [
173        {
174          "name":"service_a",
175          "credentials": {
176            "uri": "example_uri",
177            "port": 8080
178          }
179        }
180      ]
181    }"#;
182        env::set_var(VCAP_SERVICES, json);
183        let services = get_services_from_env().unwrap();
184        let service_a = services.get("serviceA").unwrap();
185        assert_eq!(1, service_a.len());
186        let service_details = service_a.get(0).unwrap();
187        assert_eq!("service_a", service_details.name);
188        assert_eq!("example_uri", service_details.credentials.uri);
189        assert_eq!(8080, service_details.credentials.port);
190        env::remove_var(VCAP_SERVICES);
191    }
192
193    #[test]
194    fn test_get_services_from_env_not_set() {
195        env::remove_var(VCAP_SERVICES);
196        let err = get_services_from_env().err().unwrap();
197        assert_eq!(CFError::EnvNotSet, err);
198    }
199
200    #[test]
201    fn test_get_services_from_env_malformed_json() {
202        let json = r#"{
203      "serviceA": [
204        {
205          "name":"service_a",
206          "credentials": {
207            "uri": "example_uri"
208          }
209        }
210      ]"#;
211        env::set_var(VCAP_SERVICES, json);
212        let err = get_services_from_env().err().unwrap();
213        assert_eq!(CFError::MalformedJSON, err);
214        env::remove_var(VCAP_SERVICES);
215    }
216
217    #[test]
218    fn test_get_service_credentials() {
219        let mut services = HashMap::new();
220        let mut service_a: Vec<Service> = Vec::new();
221        let service = Service {
222            name: "service_a".to_string(),
223            instance_name: "".to_string(),
224            binding_name: "".to_string(),
225            credentials: Credentials {
226                uri: "example_uri".to_string(),
227                jdbc_url: "".to_string(),
228                api_uri: "".to_string(),
229                license_key: "".to_string(),
230                client_secret: "".to_string(),
231                client_id: "".to_string(),
232                access_token_uri: "".to_string(),
233                hostname: "".to_string(),
234                username: "".to_string(),
235                password: "".to_string(),
236                port: 0,
237                name: "".to_string(),
238            },
239            label: "".to_string(),
240        };
241        service_a.push(service);
242        services.insert("serviceA".to_string(), service_a);
243        let creds = get_service_credentials(services, "serviceA".to_string()).unwrap();
244        assert_eq!(1, creds.len());
245        let cred = creds.get(0).unwrap();
246        assert_eq!("example_uri", cred.uri);
247    }
248
249    #[test]
250    fn test_get_service_credentials_not_found() {
251        let mut services = HashMap::new();
252        let service_a: Vec<Service> = Vec::new();
253        services.insert("serviceA".to_string(), service_a);
254        let err = get_service_credentials(services, "serviceB".to_string()).err().unwrap();
255        assert_eq!(CFError::ServiceNotPresent("serviceB".to_string()), err)
256    }
257}