subcomponent 0.1.0

A components orchestrator
/*
 * Copyright (c) 2017 Jean Guyomarc'h
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

extern crate std;
use std::error::Error as FmtError;
use config;
use compiler::parser;
use std::ops::Deref;
use subprocess;
use unpacker;
use self::Error::*;

/*
 *
 * To register a new fetch method, add the name of the fetch method module
 * as a new field of this macro.
 *
 */
macro_rules! foreach_fetch {
    ($mac:ident) => {
        $mac!(git);
        $mac!(artifact);
    }
}

macro_rules! declare_mod {
    ($name:ident) => { pub mod $name; }
}
foreach_fetch!(declare_mod);

#[derive(Debug)]
pub enum Error {
   ProcessError(subprocess::Error),
   EverythingFailed,
   InvalidReference,
   Dirty,
   UnpackError(unpacker::Error),
   ComponentPathNotCreated,
}


impl std::error::Error for Error {
   fn description(&self) -> &str {
      match *self {
         ProcessError(ref err) => err.description(),
         EverythingFailed => "All fetchers failed",
         InvalidReference => "Reference could not be resolved",
         Dirty => "Component is dirty",
         UnpackError(ref err) => err.description(),
         ComponentPathNotCreated => "The component path has not been created",
      }
   }
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "{}", self.description())
    }
}

impl From<subprocess::Error> for Error {
   fn from(error: subprocess::Error) -> Self {
      ProcessError(error)
   }
}

impl From<unpacker::Error> for Error {
   fn from(error: unpacker::Error) -> Self {
      UnpackError(error)
   }
}

impl From<std::io::Error> for Error {
   fn from(error: std::io::Error) -> Self {
      let err = subprocess::Error::IOError(error);
      ProcessError(err)
   }
}

pub trait Method {
    fn fetch_new(&self, component: &config::Component) -> Result<(), Error>;
    fn fetch_update(&self, component: &config::Component, force: bool) -> Result<(), Error>;
    fn is_fetchable(&self, component: &config::Component) -> bool;
    fn name_get(&self) -> &str;
}

pub fn parse_url(component: &str, method: &str, cfg: &config::Config) -> Result<Vec<String>, parser::Error> {
    /*
     * Parse the url property. We can have:
     * - a single string
     * - or a list of strings
     * This property MUST be defined.
     */
    let key = format!("/subcomponents/{}/fetch/{}/url", component, method);
    if let Some(prop) = cfg.get(&key) {
        match prop.deref().value_get() {
            parser::PropertyValue::StringValue(val) => {
                return Ok(vec![val.clone()]);
            },
            parser::PropertyValue::StringListValue(val) => {
                return Ok(val.clone());
            },
            _ => {
                return Err(parser::Error::InvalidPropertyType);
            }
        }
    }
    Err(parser::Error::MissingRequiredProperty)
}

pub fn parse(parser: &mut parser::Parser) -> Result<(), parser::Error> { // FIXME  IS MUT NECESSARY??
    let cfg = parser.config_get();
    let components = parser.components_get();

    /*
     * We will verify fetch methods component by component
     */
    for component in components.iter() {
        let mut methods_count = 0;
        let id = {
            let borrowed = component.borrow();
            borrowed.id_get().clone()
        };

        /*
         * Call the verification methods for each fetch method.
         */
        macro_rules! verify_method {
            ($name:ident) => {
                /*
                 * Before checking the config, make sure that the
                 * fetch method has been registered in the config.
                 */
                let key = format!("/subcomponents/{}/fetch/{}/",
                                  id, stringify!($name));
                if cfg.get(&key).is_some() {
                    let method = try!($name::parse(&id, cfg));
                    /* Okay, at this point, we have a valid fetch method */
                    methods_count += 1;

                    let mut mut_component = component.borrow_mut();
                    mut_component.fetch_method_add(method);
                }
            }
        }
        foreach_fetch!(verify_method);

        /* At least one fetch method is required */
        if methods_count <= 0 {
            return Err(parser::Error::NoFetchMethod)
        }
    }

    /*
     * If we have not returned before, we are good to go!
     */
    Ok(())
}