atter/
config.rs

1// Copyright (c) 2018 atter developers
2//
3// Licensed under the Apache License, Version 2.0
4// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
5// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. All files in the project carrying such notice may not be copied,
7// modified, or distributed except according to those terms.
8
9//! `atter` config
10use error::{Error, Result};
11use std::convert::TryFrom;
12use std::fmt;
13use std::io::{Read, Write};
14use std::path::PathBuf;
15use std::time::Duration;
16use toml;
17
18/// The runtime environment.
19#[derive(Clone, Debug, PartialEq)]
20pub enum RuntimeEnvironment {
21    /// Production
22    Prod,
23    /// Stage
24    Stage,
25    /// Test
26    Test,
27    /// Development
28    Dev,
29}
30
31impl fmt::Display for RuntimeEnvironment {
32    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
33        write!(
34            f,
35            "{}",
36            match *self {
37                RuntimeEnvironment::Prod => "-prod",
38                RuntimeEnvironment::Stage => "-stage",
39                RuntimeEnvironment::Test => "-test",
40                RuntimeEnvironment::Dev => "-dev",
41            }
42        )
43    }
44}
45
46impl<'a> TryFrom<&'a str> for RuntimeEnvironment {
47    type Error = Error;
48
49    fn try_from(env: &str) -> Result<Self> {
50        match env {
51            "prod" => Ok(RuntimeEnvironment::Prod),
52            "stage" => Ok(RuntimeEnvironment::Stage),
53            "test" => Ok(RuntimeEnvironment::Test),
54            "dev" => Ok(RuntimeEnvironment::Dev),
55            _ => Err("invalid runtime enviroment".into()),
56        }
57    }
58}
59
60/// The Echo index
61#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
62pub enum EchoIndex {
63    /// mobile-service-layer-checkout-*
64    MslCheckout,
65    /// proxy-api-*
66    ProxyApi,
67    /// proxy-ecsb-*
68    ProxyEcsb,
69    /// proxy-banner-*
70    ProxyBanner,
71    /// proxy-dc2-*
72    ProxyDC2,
73}
74
75impl Default for EchoIndex {
76    fn default() -> Self {
77        EchoIndex::MslCheckout
78    }
79}
80
81impl fmt::Display for EchoIndex {
82    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
83        write!(
84            f,
85            "{}",
86            match *self {
87                EchoIndex::MslCheckout => "mobile-service-layer-checkout",
88                EchoIndex::ProxyApi => "proxy-api",
89                EchoIndex::ProxyEcsb => "proxy-ecsb",
90                EchoIndex::ProxyBanner => "proxy-banner",
91                EchoIndex::ProxyDC2 => "proxy-dc2",
92            }
93        )
94    }
95}
96
97/// The base `atter` config.
98#[derive(Clone, Debug, Default, Deserialize, Getters, PartialEq, Serialize, Setters)]
99pub struct Config {
100    /// Common Config
101    #[get = "pub"]
102    #[set = "pub"]
103    common: Common,
104    /// Index Config
105    #[get = "pub"]
106    #[set = "pub"]
107    index: Vec<Index>,
108    /// Production Config
109    #[get = "pub"]
110    #[set = "pub"]
111    prod: Environment,
112    /// Stage Config
113    #[get = "pub"]
114    #[set = "pub"]
115    stage: Environment,
116    /// Test Config
117    #[get = "pub"]
118    #[set = "pub"]
119    test: Environment,
120    /// Development Config
121    #[get = "pub"]
122    #[set = "pub"]
123    dev: Environment,
124}
125
126/// The common `atter` config.
127#[derive(Clone, Debug, Default, Deserialize, Getters, PartialEq, Serialize, Setters)]
128pub struct Common {
129    /// The path where the sqlite database lives.
130    #[get = "pub"]
131    #[set = "pub"]
132    base_db_path: PathBuf,
133    /// The Echo Elasticsearch URL prefix (usually 'http://echo')
134    #[set = "pub"]
135    search_prefix: Option<String>,
136    /// The Echo Elasticsearch URL affix (usually '.kroger.com/elastic/')
137    #[set = "pub"]
138    search_affix: Option<String>,
139    /// The Echo Elasticsearch URL suffix (usually '/_search')
140    #[set = "pub"]
141    search_suffix: Option<String>,
142    /// The sqlite database prefix (usually 'ordmon')
143    #[set = "pub"]
144    db_prefix: Option<String>,
145    /// The sqlite database suffix (usually '.db')
146    #[set = "pub"]
147    db_suffix: Option<String>,
148}
149
150impl Common {
151    pub fn search_prefix(&self) -> &str {
152        if let Some(ref prefix) = self.search_prefix {
153            prefix
154        } else {
155            "http://echo"
156        }
157    }
158
159    pub fn search_affix(&self) -> &str {
160        if let Some(ref affix) = self.search_affix {
161            affix
162        } else {
163            ".kroger.com/elastic"
164        }
165    }
166
167    pub fn search_suffix(&self) -> &str {
168        if let Some(ref suffix) = self.search_suffix {
169            suffix
170        } else {
171            "/_search"
172        }
173    }
174
175    pub fn scroll_affix(&self) -> &str {
176        "/scroll"
177    }
178
179    pub fn db_prefix(&self) -> &str {
180        if let Some(ref prefix) = self.search_prefix {
181            prefix
182        } else {
183            "ordmon"
184        }
185    }
186
187    pub fn db_suffix(&self) -> &str {
188        if let Some(ref suffix) = self.search_suffix {
189            suffix
190        } else {
191            ".db"
192        }
193    }
194}
195
196/// An Echo index config
197#[derive(Clone, Debug, Default, Deserialize, Getters, PartialEq, Serialize, Setters)]
198pub struct Index {
199    /// The Echo index prefix.
200    #[get = "pub"]
201    #[set = "pub"]
202    idx_prefix: EchoIndex,
203    /// The array of field configs.
204    #[get = "pub"]
205    #[set = "pub"]
206    fields: Vec<Field>,
207}
208
209/// A field definition
210#[derive(Clone, Debug, Default, Deserialize, Getters, PartialEq, Serialize, Setters)]
211pub struct Field {
212    /// The field name.
213    #[get = "pub"]
214    #[set = "pub"]
215    name: String,
216    /// The sqlite data type.
217    #[get = "pub"]
218    #[set = "pub"]
219    data_type: SqliteDataType,
220    /// Is this field a primary key?
221    #[get = "pub"]
222    #[set = "pub"]
223    primary_key: Option<bool>,
224    /// Is this field non-null?
225    #[get = "pub"]
226    #[set = "pub"]
227    not_null: Option<bool>,
228    /// Is this field part of the unique index?
229    #[get = "pub"]
230    #[set = "pub"]
231    unique: Option<bool>,
232}
233
234/// A sqlite data type
235#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
236pub enum SqliteDataType {
237    /// An INTEGER data type.
238    Integer,
239    /// A TEXT data type.
240    Text,
241}
242
243impl Default for SqliteDataType {
244    fn default() -> Self {
245        SqliteDataType::Integer
246    }
247}
248
249impl fmt::Display for SqliteDataType {
250    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
251        write!(
252            f,
253            "{}",
254            match *self {
255                SqliteDataType::Integer => "INTEGER",
256                SqliteDataType::Text => "TEXT",
257            }
258        )
259    }
260}
261
262/// Environment specific `atter` config
263#[derive(Clone, Debug, Default, Deserialize, Getters, PartialEq, Serialize, Setters)]
264pub struct Environment {
265    /// The optional Echo Elasticsearch environment affix (usually '-env').
266    #[get = "pub"]
267    #[set = "pub"]
268    echo_env_affix: Option<String>,
269    /// Toggle for verbose curl output.
270    #[get = "pub"]
271    #[set = "pub"]
272    verbose: bool,
273    /// Duration between searches.
274    #[get = "pub"]
275    #[set = "pub"]
276    duration: Duration,
277}
278
279/// Read TOML from the given `reader` and deserialize into a `Config` struct.
280pub fn read_toml<R>(reader: &mut R) -> Result<Config>
281where
282    R: Read,
283{
284    let mut toml_str = String::new();
285    let bytes_read = reader.read_to_string(&mut toml_str)?;
286
287    if bytes_read > 0 {
288        Ok(toml::from_str(&toml_str)?)
289    } else {
290        Err("Unable to read any bytes from the reader".into())
291    }
292}
293
294/// Write TOML serialized from the `Config` struct to the given `writer`.
295pub fn write_toml<W>(config: &Config, writer: &mut W) -> Result<()>
296where
297    W: Write,
298{
299    let toml = toml::to_string(&config)?;
300    writer.write_all(toml.as_bytes())?;
301    Ok(())
302}
303
304pub fn echo_url(
305    config: &Config,
306    runtime_env: &RuntimeEnvironment,
307    echo_idx: &EchoIndex,
308    scroll: bool,
309) -> String {
310    let common = config.common();
311    let env = match *runtime_env {
312        RuntimeEnvironment::Prod => config.prod(),
313        RuntimeEnvironment::Stage => config.stage(),
314        RuntimeEnvironment::Test => config.test(),
315        RuntimeEnvironment::Dev => config.dev(),
316    };
317    let echo_affix = if let Some(ref affix) = env.echo_env_affix() {
318        affix.to_string()
319    } else {
320        runtime_env.to_string()
321    };
322
323    let index_str = echo_idx.to_string();
324
325    if scroll {
326        format!(
327            "{}{}{}/{}{}{}?scroll=1m",
328            common.search_prefix(),
329            echo_affix,
330            common.search_affix(),
331            index_str,
332            runtime_env,
333            common.search_suffix()
334        )
335    } else {
336        format!(
337            "{}{}{}/{}{}{}",
338            common.search_prefix(),
339            echo_affix,
340            common.search_affix(),
341            index_str,
342            runtime_env,
343            common.search_suffix()
344        )
345    }
346}
347
348pub fn scroll_url(config: &Config, runtime_env: &RuntimeEnvironment) -> String {
349    let common = config.common();
350    let env = match *runtime_env {
351        RuntimeEnvironment::Prod => config.prod(),
352        RuntimeEnvironment::Stage => config.stage(),
353        RuntimeEnvironment::Test => config.test(),
354        RuntimeEnvironment::Dev => config.dev(),
355    };
356    let echo_affix = if let Some(ref affix) = env.echo_env_affix() {
357        affix.to_string()
358    } else {
359        runtime_env.to_string()
360    };
361    format!(
362        "{}{}{}{}{}",
363        common.search_prefix(),
364        echo_affix,
365        common.search_affix(),
366        common.search_suffix(),
367        common.scroll_affix()
368    )
369}
370
371pub fn db_path(config: &Config, runtime_env: &RuntimeEnvironment) -> PathBuf {
372    let common = config.common();
373    let mut database_path = common.base_db_path().clone();
374    let filename = format!(
375        "{}{}{}",
376        common.db_prefix(),
377        runtime_env,
378        common.db_suffix()
379    );
380    database_path.push(filename);
381    database_path
382}
383
384#[cfg(test)]
385mod test {
386    use super::{
387        Common, Config, EchoIndex, Environment, Field, Index, RuntimeEnvironment, SqliteDataType,
388    };
389    use error::Result;
390    use std::convert::TryFrom;
391    use std::path::PathBuf;
392    use std::time::Duration;
393    use toml;
394
395    const TEST_TOML: &str = r#"[common]
396base_db_path = "/Users/kon8116/projects/kon8116/atters"
397
398[[index]]
399idx_prefix = "MslCheckout"
400
401[[index.fields]]
402name = "id"
403data_type = "Integer"
404primary_key = true
405
406[[index.fields]]
407name = "correlation_id"
408data_type = "Text"
409not_null = true
410
411[[index]]
412idx_prefix = "ProxyApi"
413
414[[index.fields]]
415name = "id"
416data_type = "Integer"
417primary_key = true
418
419[[index.fields]]
420name = "correlation_id"
421data_type = "Text"
422not_null = true
423
424[prod]
425echo_env_affix = "-digital"
426verbose = false
427
428[prod.duration]
429secs = 900
430nanos = 0
431
432[stage]
433verbose = false
434
435[stage.duration]
436secs = 900
437nanos = 0
438
439[test]
440verbose = true
441
442[test.duration]
443secs = 900
444nanos = 0
445
446[dev]
447verbose = true
448
449[dev.duration]
450secs = 900
451nanos = 0
452"#;
453
454    fn setup_config() -> Config {
455        let mut config: Config = Default::default();
456        let mut common: Common = Default::default();
457        let mut msl_checkout_index: Index = Default::default();
458        let mut proxy_api_index: Index = Default::default();
459        let mut prod: Environment = Default::default();
460        let mut stage: Environment = Default::default();
461        let mut test: Environment = Default::default();
462        let mut dev: Environment = Default::default();
463
464        common.set_base_db_path(PathBuf::from("/Users/kon8116/projects/kon8116/atters"));
465
466        let mut id: Field = Default::default();
467        id.set_name("id".to_string());
468        id.set_primary_key(Some(true));
469
470        let mut correlation_id: Field = Default::default();
471        correlation_id.set_name("correlation_id".to_string());
472        correlation_id.set_data_type(SqliteDataType::Text);
473        correlation_id.set_not_null(Some(true));
474
475        msl_checkout_index.set_fields(vec![id.clone(), correlation_id.clone()]);
476
477        proxy_api_index.set_idx_prefix(EchoIndex::ProxyApi);
478        proxy_api_index.set_fields(vec![id, correlation_id]);
479
480        prod.set_echo_env_affix(Some("-digital".to_string()));
481        prod.set_verbose(false);
482        prod.set_duration(Duration::from_millis(900000));
483
484        stage.set_verbose(false);
485        stage.set_duration(Duration::from_millis(900000));
486
487        test.set_verbose(true);
488        test.set_duration(Duration::from_millis(900000));
489
490        dev.set_verbose(true);
491        dev.set_duration(Duration::from_millis(900000));
492
493        config.set_common(common);
494        config.set_index(vec![msl_checkout_index, proxy_api_index]);
495        config.set_prod(prod);
496        config.set_stage(stage);
497        config.set_test(test);
498        config.set_dev(dev);
499
500        config
501    }
502
503    #[test]
504    fn serialize() {
505        let config = setup_config();
506        let toml = toml::to_string(&config).expect("Unable to serialize to TOML");
507        assert_eq!(TEST_TOML, toml);
508    }
509
510    #[test]
511    fn deserialize() {
512        let config: Config = toml::from_str(TEST_TOML).expect("Unable to deserialize TOML");
513        let common = config.common();
514        let prod = config.prod();
515        let stage = config.stage();
516        let test = config.test();
517        let dev = config.dev();
518
519        assert_eq!(
520            common.base_db_path(),
521            &PathBuf::from("/Users/kon8116/projects/kon8116/atters")
522        );
523        assert_eq!(common.search_prefix(), "http://echo".to_string());
524        assert_eq!(common.search_affix(), ".kroger.com/elastic".to_string());
525        assert_eq!(common.search_suffix(), "/_search".to_string());
526        assert_eq!(common.db_prefix(), "ordmon".to_string());
527        assert_eq!(common.db_suffix(), ".db".to_string());
528        // assert_eq!(
529        //     common.msl_checkout_idx_prefix(),
530        //     "mobile-service-layer-checkout"
531        // );
532        // assert_eq!(common.proxy_api_idx_prefix(), "proxy-api");
533
534        assert_eq!(*prod.echo_env_affix(), Some("-digital".to_string()));
535        assert!(!prod.verbose());
536        assert_eq!(*prod.duration(), Duration::from_millis(900000));
537        assert!(!stage.verbose());
538        assert_eq!(*stage.duration(), Duration::from_millis(900000));
539        assert!(test.verbose());
540        assert_eq!(*test.duration(), Duration::from_millis(900000));
541        assert!(dev.verbose());
542        assert_eq!(*dev.duration(), Duration::from_millis(900000));
543    }
544
545    #[test]
546    fn convert() {
547        let mut runtime_env: RuntimeEnvironment =
548            TryFrom::try_from("prod").expect("invalid runtime env");
549        assert_eq!(runtime_env, RuntimeEnvironment::Prod);
550        runtime_env = TryFrom::try_from("stage").expect("invalid runtime env");
551        assert_eq!(runtime_env, RuntimeEnvironment::Stage);
552        runtime_env = TryFrom::try_from("test").expect("invalid runtime env");
553        assert_eq!(runtime_env, RuntimeEnvironment::Test);
554        runtime_env = TryFrom::try_from("dev").expect("invalid runtime env");
555        assert_eq!(runtime_env, RuntimeEnvironment::Dev);
556        let err_runtime_env: Result<RuntimeEnvironment> = TryFrom::try_from("blah");
557        match err_runtime_env {
558            Ok(_) => assert!(false, "This shouldn't be a valid env"),
559            Err(_) => assert!(true),
560        }
561    }
562
563    #[test]
564    fn echo_url() {
565        let config: Config = toml::from_str(TEST_TOML).expect("Unable to deserialize TOML");
566        assert_eq!(
567            super::echo_url(
568                &config,
569                &RuntimeEnvironment::Prod,
570                &EchoIndex::MslCheckout,
571                false
572            ),
573            "http://echo-digital.kroger.com/elastic/mobile-service-layer-checkout-prod/_search"
574        );
575        assert_eq!(
576            super::echo_url(&config, &RuntimeEnvironment::Prod, &EchoIndex::MslCheckout, true),
577            "http://echo-digital.kroger.com/elastic/mobile-service-layer-checkout-prod/_search?scroll=1m"
578        );
579        assert_eq!(
580            super::echo_url(
581                &config,
582                &RuntimeEnvironment::Stage,
583                &EchoIndex::MslCheckout,
584                false
585            ),
586            "http://echo-stage.kroger.com/elastic/mobile-service-layer-checkout-stage/_search"
587        );
588        assert_eq!(
589            super::echo_url(
590                &config,
591                &RuntimeEnvironment::Test,
592                &EchoIndex::MslCheckout,
593                false
594            ),
595            "http://echo-test.kroger.com/elastic/mobile-service-layer-checkout-test/_search"
596        );
597        assert_eq!(
598            super::echo_url(
599                &config,
600                &RuntimeEnvironment::Dev,
601                &EchoIndex::MslCheckout,
602                false
603            ),
604            "http://echo-dev.kroger.com/elastic/mobile-service-layer-checkout-dev/_search"
605        );
606        assert_eq!(
607            super::echo_url(
608                &config,
609                &RuntimeEnvironment::Prod,
610                &EchoIndex::ProxyApi,
611                false
612            ),
613            "http://echo-digital.kroger.com/elastic/proxy-api-prod/_search"
614        );
615        assert_eq!(
616            super::echo_url(
617                &config,
618                &RuntimeEnvironment::Stage,
619                &EchoIndex::ProxyApi,
620                false
621            ),
622            "http://echo-stage.kroger.com/elastic/proxy-api-stage/_search"
623        );
624        assert_eq!(
625            super::echo_url(
626                &config,
627                &RuntimeEnvironment::Test,
628                &EchoIndex::ProxyApi,
629                false
630            ),
631            "http://echo-test.kroger.com/elastic/proxy-api-test/_search"
632        );
633        assert_eq!(
634            super::echo_url(
635                &config,
636                &RuntimeEnvironment::Dev,
637                &EchoIndex::ProxyApi,
638                false
639            ),
640            "http://echo-dev.kroger.com/elastic/proxy-api-dev/_search"
641        );
642    }
643
644    #[test]
645    fn scroll_url() {
646        let config: Config = toml::from_str(TEST_TOML).expect("Unable to deserialize TOML");
647        assert_eq!(
648            super::scroll_url(&config, &RuntimeEnvironment::Prod),
649            "http://echo-digital.kroger.com/elastic/_search/scroll"
650        );
651        assert_eq!(
652            super::scroll_url(&config, &RuntimeEnvironment::Stage),
653            "http://echo-stage.kroger.com/elastic/_search/scroll"
654        );
655        assert_eq!(
656            super::scroll_url(&config, &RuntimeEnvironment::Test),
657            "http://echo-test.kroger.com/elastic/_search/scroll"
658        );
659        assert_eq!(
660            super::scroll_url(&config, &RuntimeEnvironment::Dev),
661            "http://echo-dev.kroger.com/elastic/_search/scroll"
662        );
663    }
664
665    #[test]
666    fn db_path() {
667        let config: Config = toml::from_str(TEST_TOML).expect("Unable to deserialize TOML");
668        assert_eq!(
669            super::db_path(&config, &RuntimeEnvironment::Prod),
670            PathBuf::from("/Users/kon8116/projects/kon8116/atters/ordmon-prod.db")
671        );
672        assert_eq!(
673            super::db_path(&config, &RuntimeEnvironment::Stage),
674            PathBuf::from("/Users/kon8116/projects/kon8116/atters/ordmon-stage.db")
675        );
676        assert_eq!(
677            super::db_path(&config, &RuntimeEnvironment::Test),
678            PathBuf::from("/Users/kon8116/projects/kon8116/atters/ordmon-test.db")
679        );
680        assert_eq!(
681            super::db_path(&config, &RuntimeEnvironment::Dev),
682            PathBuf::from("/Users/kon8116/projects/kon8116/atters/ordmon-dev.db")
683        );
684    }
685}