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
// Copyright 2017-2021 Lukas Pustina <lukas@pustina.de>
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.

use anyhow::Result;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use thiserror::Error;

use crate::app::console::{Console, ConsoleOpts};
use crate::app::resolver::{NameBuilder, NameBuilderOpts};
use crate::app::{self, AppConfig, ExitStatus};

pub mod check;
pub mod discover;
pub mod get_server_lists;
pub mod lookup;

/** Return type for App modules that go through multiple steps
 *
 * An App module might go through multiple distinct steps to eventually fulfill its task. Along this
 * way, errors might occur. Errors should be reported using `Result`. But what if a step finishes
 * without errors but still couldn't obtain the necessary information for the next step to proceed?
 * For this use case, `PartialResult` works similar to an Either type for the `Result::Err` side of the
 * execution.
 *
 * Think of it as a means for early returns.
 */
pub type PartialResult<T> = std::result::Result<T, PartialError>;

pub trait PartialResultExt {
    fn into_result(self) -> anyhow::Result<ExitStatus>;
}

impl PartialResultExt for PartialResult<ExitStatus> {
    fn into_result(self) -> anyhow::Result<ExitStatus> {
        match self {
            Ok(exit_status) => Ok(exit_status),
            Err(PartialError::Failed(exit_status)) => Ok(exit_status),
            Err(err @ PartialError::Err { .. }) => Err(err.into()),
        }
    }
}

#[derive(Debug, Error)]
pub enum PartialError {
    #[error("module step failed")]
    Failed(app::ExitStatus),
    #[error(transparent)]
    Err(#[from] anyhow::Error),
}

impl From<crate::error::Error> for PartialError {
    fn from(err: crate::error::Error) -> Self {
        PartialError::Err(anyhow::Error::new(err))
    }
}

impl From<crate::resolver::error::Error> for PartialError {
    fn from(err: crate::resolver::error::Error) -> Self {
        PartialError::Err(anyhow::Error::new(err))
    }
}

impl From<crate::services::error::Error> for PartialError {
    fn from(err: crate::services::error::Error) -> Self {
        PartialError::Err(anyhow::Error::new(err))
    }
}

/// Information about the current execution of `mhost`
#[derive(Debug, Serialize, Deserialize)]
pub struct RunInfo {
    pub start_time: DateTime<Utc>,
    pub command_line: String,
    pub version: &'static str,
}

impl RunInfo {
    pub fn now() -> Self {
        // skip args[0] as it contains the bin name
        let args: Vec<_> = std::env::args().skip(1).collect();
        let command_line = args.join(" ");
        RunInfo {
            start_time: Utc::now(),
            command_line,
            version: env!("CARGO_PKG_VERSION"),
        }
    }
}

/// Pass environment like configs and console access from step to step
pub struct Environment<'a, T> {
    pub run_info: RunInfo,
    pub app_config: &'a AppConfig,
    pub mod_config: &'a T,
    pub console: Console,
    pub name_builder: NameBuilder,
}

impl<'a, T> Environment<'a, T> {
    pub fn new(
        app_config: &'a AppConfig,
        mod_config: &'a T,
        console: Console,
        name_builder: NameBuilder,
    ) -> Environment<'a, T> {
        Environment {
            run_info: RunInfo::now(),
            app_config,
            mod_config,
            console,
            name_builder,
        }
    }
}

/// Base implementation for App modules
pub trait AppModule<T: ModConfig> {
    fn init_env<'a>(app_config: &'a AppConfig, config: &'a T) -> Result<Environment<'a, T>> {
        let console_opts = ConsoleOpts::from(app_config).with_partial_results(config.partial_results());
        let console = Console::new(console_opts);

        let name_builder_ops = if let Some(ref search_domain) = app_config.search_domain {
            NameBuilderOpts::new(app_config.ndots, search_domain.as_ref())
        } else {
            NameBuilderOpts::from_hostname(app_config.ndots)
        }?;
        let name_builder = NameBuilder::new(name_builder_ops);

        let env = Environment::new(app_config, config, console, name_builder);

        Ok(env)
    }
}

/// Base implementation for module configuration
pub trait ModConfig {
    fn partial_results(&self) -> bool {
        false
    }
}