<commit_before>// Copyright 2013-2014 The gl-rs developers. For a full listing of the authors,
// refer to the AUTHORS file at the top-level directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! # gl_generator
//!
//! `gl_generator` is an OpenGL bindings generator. It defines a function
//!  named `generate_bindings` which can be used to generate all constants
//!  and functions of a given OpenGL version.
//!
//! ## Example
//!
//! In `build.rs`:
//!
//! ~~~no_run
//! extern crate gl_generator;
//! extern crate khronos_api;
//!
//! use std::os;
//! use std::old_io::File;
//!
//! fn main() {
//!     let dest = Path::new(os::getenv("OUT_DIR").unwrap());
//!
//!     let mut file = File::create(&dest.join("gl_bindings.rs")).unwrap();
//!
//!     gl_generator::generate_bindings(gl_generator::GlobalGenerator,
//!                                     gl_generator::registry::Ns::Gl,
//!                                     khronos_api::GL_XML, vec![], "4.5", "core"
//!                                     &mut file).unwrap();
//! }
//! ~~~
//!
//! In your project:
//!
//! ~~~ignore
//! include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
//! ~~~
//!
//! ## Arguments
//!
//! Each field can be specified at most once, or not at all. If the field is not
//! specified, then a default value will be used.
//!
//! - `api`: The API to generate. Can be either `"gl"`, `"gles1"`, `"gles2"`,
//!   `"wgl"`, `"glx"`, `"egl"`. Defaults to `"gl"`.
//! - `profile`: Can be either `"core"` or `"compatibility"`. Defaults to
//!   `"core"`. `"core"` will only include all functions supported by the
//!   requested version it self, while `"compatibility"` will include all the
//!   functions from previous versions as well.
//! - `version`: The requested API version. This is usually in the form
//!   `"major.minor"`. Defaults to `"1.0"`
//! - `generator`: The type of loader to generate. Can be either `"static"`,
//!   `"global"`, or `"struct"`. Defaults to `"static"`.
//! - `extensions`: Extra extensions to include in the bindings. These are
//!   specified as a list of strings. Defaults to `[]`.
//!
//! ## About EGL
//!
//! When you generate bindings for EGL, the following platform-specific types must be declared
//!  *at the same level where you include the bindings*:
//!
//! - `khronos_utime_nanoseconds_t`
//! - `khronos_uint64_t`
//! - `khronos_ssize_t`
//! - `EGLNativeDisplayType`
//! - `EGLNativePixmapType`
//! - `EGLNativeWindowType`
//! - `EGLint`
//! - `NativeDisplayType`
//! - `NativePixmapType`
//! - `NativeWindowType`
//!

#![crate_name = "gl_generator"]
#![crate_type = "dylib"]

#![feature(advanced_slice_patterns)]
#![feature(plugin)]
#![feature(plugin_registrar)]
#![feature(quote)]
#![plugin(log)]

#[macro_use]
extern crate log;

extern crate khronos_api;

use generators::Generator;
use registry::{Registry, Filter, Ns};

use std::old_io::IoResult;

pub use registry::Fallbacks;
pub use generators::global_gen::GlobalGenerator;
pub use generators::static_gen::StaticGenerator;
pub use generators::static_struct_gen::StaticStructGenerator;
pub use generators::struct_gen::StructGenerator;

pub mod generators;

#[allow(dead_code)]
pub mod registry;

/// Public function that generates Rust source code.
pub fn generate_bindings<G, W>(generator: G, ns: registry::Ns, fallbacks: Fallbacks, source: &[u8],
                               extensions: Vec<String>, version: &str, profile: &str,
                               dest: &mut W) -> IoResult<()> where G: Generator, W: Writer
{
    // Get generator field values, using default values if they have not been
    // specified
    let filter = Some(Filter {
        api: ns.to_string(),
        fallbacks: fallbacks,
        extensions: extensions,
        version: version.to_string(),
        profile: profile.to_string(),
    });

    // Generate the registry of all bindings
    let registry = {
        use std::old_io::BufReader;
        let reader = BufReader::new(source);
        Registry::from_xml(reader, ns, filter)
    };

    generator.write(&registry, ns, dest)
}
<commit_msg>Fix gl_generator doctests<commit_after>// Copyright 2013-2014 The gl-rs developers. For a full listing of the authors,
// refer to the AUTHORS file at the top-level directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! # gl_generator
//!
//! `gl_generator` is an OpenGL bindings generator. It defines a function
//!  named `generate_bindings` which can be used to generate all constants
//!  and functions of a given OpenGL version.
//!
//! ## Example
//!
//! In `build.rs`:
//!
//! ~~~no_run
//! extern crate gl_generator;
//! extern crate khronos_api;
//!
//! use std::os;
//! use std::old_io::File;
//!
//! fn main() {
//!     let dest = Path::new(os::getenv("OUT_DIR").unwrap());
//!
//!     let mut file = File::create(&dest.join("gl_bindings.rs")).unwrap();
//!
//!     gl_generator::generate_bindings(gl_generator::GlobalGenerator,
//!                                     gl_generator::registry::Ns::Gl,
//!                                     gl_generator::Fallbacks::All,
//!                                     khronos_api::GL_XML, vec![], "4.5", "core",
//!                                     &mut file).unwrap();
//! }
//! ~~~
//!
//! In your project:
//!
//! ~~~ignore
//! include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
//! ~~~
//!
//! ## Arguments
//!
//! Each field can be specified at most once, or not at all. If the field is not
//! specified, then a default value will be used.
//!
//! - `api`: The API to generate. Can be either `"gl"`, `"gles1"`, `"gles2"`,
//!   `"wgl"`, `"glx"`, `"egl"`. Defaults to `"gl"`.
//! - `profile`: Can be either `"core"` or `"compatibility"`. Defaults to
//!   `"core"`. `"core"` will only include all functions supported by the
//!   requested version it self, while `"compatibility"` will include all the
//!   functions from previous versions as well.
//! - `version`: The requested API version. This is usually in the form
//!   `"major.minor"`. Defaults to `"1.0"`
//! - `generator`: The type of loader to generate. Can be either `"static"`,
//!   `"global"`, or `"struct"`. Defaults to `"static"`.
//! - `extensions`: Extra extensions to include in the bindings. These are
//!   specified as a list of strings. Defaults to `[]`.
//!
//! ## About EGL
//!
//! When you generate bindings for EGL, the following platform-specific types must be declared
//!  *at the same level where you include the bindings*:
//!
//! - `khronos_utime_nanoseconds_t`
//! - `khronos_uint64_t`
//! - `khronos_ssize_t`
//! - `EGLNativeDisplayType`
//! - `EGLNativePixmapType`
//! - `EGLNativeWindowType`
//! - `EGLint`
//! - `NativeDisplayType`
//! - `NativePixmapType`
//! - `NativeWindowType`
//!

#![crate_name = "gl_generator"]
#![crate_type = "dylib"]

#![feature(advanced_slice_patterns)]
#![feature(plugin)]
#![feature(plugin_registrar)]
#![feature(quote)]
#![plugin(log)]

#[macro_use]
extern crate log;

extern crate khronos_api;

use generators::Generator;
use registry::{Registry, Filter, Ns};

use std::old_io::IoResult;

pub use registry::Fallbacks;
pub use generators::global_gen::GlobalGenerator;
pub use generators::static_gen::StaticGenerator;
pub use generators::static_struct_gen::StaticStructGenerator;
pub use generators::struct_gen::StructGenerator;

pub mod generators;

#[allow(dead_code)]
pub mod registry;

/// Public function that generates Rust source code.
pub fn generate_bindings<G, W>(generator: G, ns: registry::Ns, fallbacks: Fallbacks, source: &[u8],
                               extensions: Vec<String>, version: &str, profile: &str,
                               dest: &mut W) -> IoResult<()> where G: Generator, W: Writer
{
    // Get generator field values, using default values if they have not been
    // specified
    let filter = Some(Filter {
        api: ns.to_string(),
        fallbacks: fallbacks,
        extensions: extensions,
        version: version.to_string(),
        profile: profile.to_string(),
    });

    // Generate the registry of all bindings
    let registry = {
        use std::old_io::BufReader;
        let reader = BufReader::new(source);
        Registry::from_xml(reader, ns, filter)
    };

    generator.write(&registry, ns, dest)
}
<|endoftext|>


<commit_before>use std::io::Read;

use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::newline;
use nom::combinator::map;
use nom::multi::separated_list1;
use nom::sequence::preceded;
use nom::sequence::separated_pair;
use nom::sequence::tuple;
use nom::IResult;

use crate::common::read_input;

type Point3 = [i32; 3];

#[derive(Debug, Eq, PartialEq)]
struct Cuboid {
    lower: Point3,
    upper: Point3,
}

impl Cuboid {
    pub fn new(lower: Point3, upper: Point3) -> Self {
        // The input uses an inclusive range for representation, but an exclusive one simplifies a
        // lot of computations, so we convert here.
        Self::new_internal(lower, upper.map(|c| c + 1))
    }

    fn new_internal(lower: Point3, upper: Point3) -> Self {
        debug_assert!(lower.iter().zip(&upper).all(|(a, b)| a < b));
        Self { lower, upper }
    }

    pub fn is_small(&self) -> bool {
        self.lower
            .iter()
            .chain(&self.upper.map(|c| c - 1)) // begrudgingly convert back
            .all(|c| c.abs() <= 50)
    }

    pub fn volume(&self) -> i64 {
        self.lower
            .iter()
            .zip(&self.upper)
            .map(|(&l, &h)| (h - l) as i64)
            .product()
    }

    fn overlaps(&self, other: &Self) -> bool {
        self.lower
            .iter()
            .zip(&self.upper)
            .zip(other.lower.iter().zip(&other.upper))
            .all(|((&al, &ah), (&bl, &bh))| al < bh && bl < ah)
    }

    pub fn retain_nonoverlapping(&self, other: &Self, new_cubes: &mut Vec<Self>) -> bool {
        if !self.overlaps(other) {
            // Cube can be kept as-is.
            return true;
        }

        if other.lower[0] > self.lower[0] {
            // We have a set of X-coordinates below the overlap, add a cube for that
            new_cubes.push(Self::new_internal(
                self.lower,
                [other.lower[0], self.upper[1], self.upper[2]],
            ));
        }

        if other.upper[0] < self.upper[0] {
            // Similarly, we can remove an upper section.
            new_cubes.push(Self::new_internal(
                [other.upper[0], self.lower[1], self.lower[2]],
                self.upper,
            ));
        }

        // Now we can fix the X-coordinates of the overlap section
        let overlap_xl = self.lower[0].max(other.lower[0]);
        let overlap_xh = self.upper[0].min(other.upper[0]);

        // Same strategy for the Y axis
        if other.lower[1] > self.lower[1] {
            new_cubes.push(Self::new_internal(
                [overlap_xl, self.lower[1], self.lower[2]],
                [overlap_xh, other.lower[1], self.upper[2]],
            ))
        }
        if other.upper[1] < self.upper[1] {
            new_cubes.push(Self::new_internal(
                [overlap_xl, other.upper[1], self.lower[2]],
                [overlap_xh, self.upper[1], self.upper[2]],
            ))
        }

        let overlap_yl = self.lower[1].max(other.lower[1]);
        let overlap_yh = self.upper[1].min(other.upper[1]);

        // Finally, handle the last axis
        if other.lower[2] > self.lower[2] {
            new_cubes.push(Self::new_internal(
                [overlap_xl, overlap_yl, self.lower[2]],
                [overlap_xh, overlap_yh, other.lower[2]],
            ))
        }
        if other.upper[2] < self.upper[2] {
            new_cubes.push(Self::new_internal(
                [overlap_xl, overlap_yl, other.upper[2]],
                [overlap_xh, overlap_yh, self.upper[2]],
            ))
        }

        false
    }
}

fn parse_tuple(input: &[u8]) -> IResult<&[u8], (i32, i32)> {
    use nom::character::complete::i32;
    separated_pair(i32, tag(".."), i32)(input)
}

fn parse_cuboid(input: &[u8]) -> IResult<&[u8], Cuboid> {
    map(
        tuple((
            parse_tuple,
            preceded(tag(",y="), parse_tuple),
            preceded(tag(",z="), parse_tuple),
        )),
        |((xl, xh), (yl, yh), (zl, zh))| Cuboid::new([xl, yl, zl], [xh, yh, zh]),
    )(input)
}

fn parse_input(input: &[u8]) -> IResult<&[u8], Vec<(bool, Cuboid)>> {
    let parse_state = alt((map(tag("on x="), |_| true), map(tag("off x="), |_| false)));
    let parse_line = tuple((parse_state, parse_cuboid));

    separated_list1(newline, parse_line)(input)
}

pub fn part1(input: &mut dyn Read) -> String {
    let commands = read_input(input, parse_input);
    let mut cubes = Vec::new();
    let mut new_cubes = Vec::new();

    for (state, cube) in commands.into_iter().filter(|(_, c)| c.is_small()) {
        cubes.retain(|existing: &Cuboid| existing.retain_nonoverlapping(&cube, &mut new_cubes));

        // Add new cubes to the end
        cubes.append(&mut new_cubes);

        if state {
            cubes.push(cube);
        }
    }

    cubes.iter().map(Cuboid::volume).sum::<i64>().to_string()
}

pub fn part2(input: &mut dyn Read) -> String {
    let commands = read_input(input, parse_input);
    let mut cubes = Vec::new();
    let mut new_cubes = Vec::new();

    for (state, cube) in commands {
        cubes.retain(|existing: &Cuboid| existing.retain_nonoverlapping(&cube, &mut new_cubes));

        // Add new cubes to the end
        cubes.append(&mut new_cubes);

        if state {
            cubes.push(cube);
        }
    }

    cubes.iter().map(Cuboid::volume).sum::<i64>().to_string()
}

#[cfg(test)]
mod tests {
    use crate::test_implementation;

    use super::*;

    const SAMPLE1: &[u8] = include_bytes!("samples/22.1.txt");
    const SAMPLE2: &[u8] = include_bytes!("samples/22.2.txt");

    #[test]
    fn test_overlap() {
        let cube_a = Cuboid {
            lower: [1, 1, 1],
            upper: [4, 4, 4],
        };

        let cube_b = Cuboid {
            lower: [2, 2, 2],
            upper: [3, 3, 3],
        };

        let mut new_cubes = Vec::new();

        // B is fully inside A so it should overlap and the result should be empty
        assert!(!cube_b.retain_nonoverlapping(&cube_a, &mut new_cubes));
        assert_eq!(new_cubes, Vec::new());

        // In the reverse case, we should have lots of new cubes
        assert!(!cube_a.retain_nonoverlapping(&cube_b, &mut new_cubes));
        assert_eq!(new_cubes.len(), 6);
    }

    #[test]
    fn sample_part1() {
        test_implementation(part1, SAMPLE1, 590784);
    }

    #[test]
    fn sample_part2() {
        test_implementation(part2, SAMPLE2, 2758514936282235u64);
    }
}
<commit_msg>Better code reuse, almost generic over dimensions<commit_after>use std::io::Read;

use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::newline;
use nom::combinator::map;
use nom::multi::separated_list1;
use nom::sequence::preceded;
use nom::sequence::separated_pair;
use nom::sequence::tuple;
use nom::IResult;

use crate::common::read_input;

type Point3 = [i32; 3];

#[derive(Debug, Eq, PartialEq)]
struct Cuboid {
    lower: Point3,
    upper: Point3,
}

impl Cuboid {
    pub fn new(lower: Point3, upper: Point3) -> Self {
        // The input uses an inclusive range for representation, but an exclusive one simplifies a
        // lot of computations, so we convert here.
        Self::new_internal(lower, upper.map(|c| c + 1))
    }

    fn new_internal(lower: Point3, upper: Point3) -> Self {
        debug_assert!(lower.iter().zip(&upper).all(|(a, b)| a < b));
        Self { lower, upper }
    }

    pub fn is_small(&self) -> bool {
        self.lower
            .iter()
            .chain(&self.upper.map(|c| c - 1)) // begrudgingly convert back
            .all(|c| c.abs() <= 50)
    }

    pub fn volume(&self) -> i64 {
        self.lower
            .iter()
            .zip(&self.upper)
            .map(|(&l, &h)| (h - l) as i64)
            .product()
    }

    fn overlaps(&self, other: &Self) -> bool {
        self.lower
            .iter()
            .zip(&self.upper)
            .zip(other.lower.iter().zip(&other.upper))
            .all(|((&al, &ah), (&bl, &bh))| al < bh && bl < ah)
    }

    pub fn retain_nonoverlapping(&self, other: &Self, new_cubes: &mut Vec<Self>) -> bool {
        if !self.overlaps(other) {
            // Cube can be kept as-is.
            return true;
        }

        let mut lower = self.lower;
        let mut upper = self.upper;

        for axis in 0..3 {
            if other.lower[axis] > self.lower[axis] {
                let mut new_upper = upper;
                new_upper[axis] = other.lower[axis];

                new_cubes.push(Cuboid {
                    lower,
                    upper: new_upper,
                });

                lower[axis] = other.lower[axis];
            }

            if other.upper[axis] < self.upper[axis] {
                let mut new_lower = lower;
                new_lower[axis] = other.upper[axis];

                new_cubes.push(Cuboid {
                    lower: new_lower,
                    upper,
                });

                upper[axis] = other.upper[axis];
            }
        }

        false
    }
}

fn parse_tuple(input: &[u8]) -> IResult<&[u8], (i32, i32)> {
    use nom::character::complete::i32;
    separated_pair(i32, tag(".."), i32)(input)
}

fn parse_cuboid(input: &[u8]) -> IResult<&[u8], Cuboid> {
    map(
        tuple((
            parse_tuple,
            preceded(tag(",y="), parse_tuple),
            preceded(tag(",z="), parse_tuple),
        )),
        |((xl, xh), (yl, yh), (zl, zh))| Cuboid::new([xl, yl, zl], [xh, yh, zh]),
    )(input)
}

fn parse_input(input: &[u8]) -> IResult<&[u8], Vec<(bool, Cuboid)>> {
    let parse_state = alt((map(tag("on x="), |_| true), map(tag("off x="), |_| false)));
    let parse_line = tuple((parse_state, parse_cuboid));

    separated_list1(newline, parse_line)(input)
}

pub fn part1(input: &mut dyn Read) -> String {
    let commands = read_input(input, parse_input);
    let mut cubes = Vec::new();
    let mut new_cubes = Vec::new();

    for (state, cube) in commands.into_iter().filter(|(_, c)| c.is_small()) {
        cubes.retain(|existing: &Cuboid| existing.retain_nonoverlapping(&cube, &mut new_cubes));

        // Add new cubes to the end
        cubes.append(&mut new_cubes);

        if state {
            cubes.push(cube);
        }
    }

    cubes.iter().map(Cuboid::volume).sum::<i64>().to_string()
}

pub fn part2(input: &mut dyn Read) -> String {
    let commands = read_input(input, parse_input);
    let mut cubes = Vec::new();
    let mut new_cubes = Vec::new();

    for (state, cube) in commands {
        cubes.retain(|existing: &Cuboid| existing.retain_nonoverlapping(&cube, &mut new_cubes));

        // Add new cubes to the end
        cubes.append(&mut new_cubes);

        if state {
            cubes.push(cube);
        }
    }

    cubes.iter().map(Cuboid::volume).sum::<i64>().to_string()
}

#[cfg(test)]
mod tests {
    use crate::test_implementation;

    use super::*;

    const SAMPLE1: &[u8] = include_bytes!("samples/22.1.txt");
    const SAMPLE2: &[u8] = include_bytes!("samples/22.2.txt");

    #[test]
    fn test_overlap() {
        let cube_a = Cuboid {
            lower: [1, 1, 1],
            upper: [4, 4, 4],
        };

        let cube_b = Cuboid {
            lower: [2, 2, 2],
            upper: [3, 3, 3],
        };

        let mut new_cubes = Vec::new();

        // B is fully inside A so it should overlap and the result should be empty
        assert!(!cube_b.retain_nonoverlapping(&cube_a, &mut new_cubes));
        assert_eq!(new_cubes, Vec::new());

        // In the reverse case, we should have lots of new cubes
        assert!(!cube_a.retain_nonoverlapping(&cube_b, &mut new_cubes));
        assert_eq!(new_cubes.len(), 6);
    }

    #[test]
    fn sample_part1() {
        test_implementation(part1, SAMPLE1, 590784);
    }

    #[test]
    fn sample_part2() {
        test_implementation(part2, SAMPLE2, 2758514936282235u64);
    }
}
<|endoftext|>


<commit_before>//! Read configurations files.

#![deny(missing_docs_in_private_items)]

use std::{env, fmt, fs, io, path};
use std::io::Read;
use syntax::{ast, codemap};
use toml;

/// Get the configuration file from arguments.
pub fn file_from_args(args: &[codemap::Spanned<ast::NestedMetaItemKind>])
    -> Result<Option<path::PathBuf>, (&'static str, codemap::Span)> {
    for arg in args.iter().filter_map(|a| a.meta_item()) {
        if arg.name() == "conf_file" {
            return match arg.node {
                ast::MetaItemKind::Word |
                ast::MetaItemKind::List(_) => Err(("`conf_file` must be a named value", arg.span)),
                ast::MetaItemKind::NameValue(ref value) => {
                    if let ast::LitKind::Str(ref file, _) = value.node {
                        Ok(Some(file.to_string().into()))
                    } else {
                        Err(("`conf_file` value must be a string", value.span))
                    }
                },
            };
        }
    }

    Ok(None)
}

/// Error from reading a configuration file.
#[derive(Debug)]
pub enum Error {
    /// An I/O error.
    Io(io::Error),
    /// The file is not valid TOML.
    Toml(Vec<toml::ParserError>),
    /// Type error.
    Type(/// The name of the key.
         &'static str,
         /// The expected type.
         &'static str,
         /// The type we got instead.
         &'static str),
    /// There is an unknown key is the file.
    UnknownKey(String),
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        match *self {
            Error::Io(ref err) => err.fmt(f),
            Error::Toml(ref errs) => {
                let mut first = true;
                for err in errs {
                    if !first {
                        try!(", ".fmt(f));
                        first = false;
                    }

                    try!(err.fmt(f));
                }

                Ok(())
            },
            Error::Type(key, expected, got) => {
                write!(f, "`{}` is expected to be a `{}` but is a `{}`", key, expected, got)
            },
            Error::UnknownKey(ref key) => write!(f, "unknown key `{}`", key),
        }
    }
}

impl From<io::Error> for Error {
    fn from(e: io::Error) -> Self {
        Error::Io(e)
    }
}

macro_rules! define_Conf {
    ($(#[$doc: meta] ($toml_name: tt, $rust_name: ident, $default: expr => $($ty: tt)+),)+) => {
        /// Type used to store lint configuration.
        pub struct Conf {
            $(#[$doc] pub $rust_name: define_Conf!(TY $($ty)+),)+
        }

        impl Default for Conf {
            fn default() -> Conf {
                Conf {
                    $($rust_name: define_Conf!(DEFAULT $($ty)+, $default),)+
                }
            }
        }

        impl Conf {
            /// Set the property `name` (which must be the `toml` name) to the given value
            #[allow(cast_sign_loss)]
            fn set(&mut self, name: String, value: toml::Value) -> Result<(), Error> {
                match name.as_str() {
                    $(
                        define_Conf!(PAT $toml_name) => {
                            if let Some(value) = define_Conf!(CONV $($ty)+, value) {
                                self.$rust_name = value;
                            }
                            else {
                                return Err(Error::Type(define_Conf!(EXPR $toml_name),
                                                       stringify!($($ty)+),
                                                       value.type_str()));
                            }
                        },
                    )+
                    "third-party" => {
                        // for external tools such as clippy-service
                        return Ok(());
                    }
                    _ => {
                        return Err(Error::UnknownKey(name));
                    }
                }

                Ok(())
            }
        }
    };

    // hack to convert tts
    (PAT $pat: pat) => { $pat };
    (EXPR $e: expr) => { $e };
    (TY $ty: ty) => { $ty };

    // how to read the value?
    (CONV i64, $value: expr) => { $value.as_integer() };
    (CONV u64, $value: expr) => {
        $value.as_integer()
        .iter()
        .filter_map(|&i| if i >= 0 { Some(i as u64) } else { None })
        .next()
    };
    (CONV String, $value: expr) => { $value.as_str().map(Into::into) };
    (CONV Vec<String>, $value: expr) => {{
        let slice = $value.as_slice();

        if let Some(slice) = slice {
            if slice.iter().any(|v| v.as_str().is_none()) {
                None
            } else {
                Some(slice.iter().map(|v| v.as_str().expect("already checked").to_owned()).collect())
            }
        } else {
            None
        }
    }};

    // provide a nicer syntax to declare the default value of `Vec<String>` variables
    (DEFAULT Vec<String>, $e: expr) => { $e.iter().map(|&e| e.to_owned()).collect() };
    (DEFAULT $ty: ty, $e: expr) => { $e };
}

define_Conf! {
    /// Lint: BLACKLISTED_NAME. The list of blacklisted names to lint about
    ("blacklisted-names", blacklisted_names, ["foo", "bar", "baz", "quux"] => Vec<String>),
    /// Lint: CYCLOMATIC_COMPLEXITY. The maximum cyclomatic complexity a function can have
    ("cyclomatic-complexity-threshold", cyclomatic_complexity_threshold, 25 => u64),
    /// Lint: DOC_MARKDOWN. The list of words this lint should not consider as identifiers needing ticks
    ("doc-valid-idents", doc_valid_idents, [
        "MiB", "GiB", "TiB", "PiB", "EiB",
        "DirectX",
        "ECMAScript",
        "GPLv2", "GPLv3",
        "GitHub",
        "IPv4", "IPv6",
        "JavaScript",
        "NaN",
        "OAuth",
        "OpenGL",
        "TrueType",
        "iOS", "macOS",
        "TeX", "LaTeX", "BibTex", "BibLaTex",
        "MinGW",
    ] => Vec<String>),
    /// Lint: TOO_MANY_ARGUMENTS. The maximum number of argument a function or method can have
    ("too-many-arguments-threshold", too_many_arguments_threshold, 7 => u64),
    /// Lint: TYPE_COMPLEXITY. The maximum complexity a type can have
    ("type-complexity-threshold", type_complexity_threshold, 250 => u64),
    /// Lint: MANY_SINGLE_CHAR_NAMES. The maximum number of single char bindings a scope may have
    ("single-char-binding-names-threshold", max_single_char_names, 5 => u64),
    /// Lint: BOXED_LOCAL. The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
    ("too-large-for-stack", too_large_for_stack, 200 => u64),
    /// Lint: ENUM_VARIANT_NAMES. The minimum number of enum variants for the lints about variant names to trigger
    ("enum-variant-name-threshold", enum_variant_name_threshold, 3 => u64),
    /// Lint: LARGE_ENUM_VARIANT. The maximum size of a emum's variant to avoid box suggestion
    ("enum-variant-size-threshold", enum_variant_size_threshold, 200 => u64),
}

/// Search for the configuration file.
pub fn lookup_conf_file() -> io::Result<Option<path::PathBuf>> {
    /// Possible filename to search for.
    const CONFIG_FILE_NAMES: [&'static str; 2] = [".clippy.toml", "clippy.toml"];

    let mut current = try!(env::current_dir());

    loop {
        for config_file_name in &CONFIG_FILE_NAMES {
            let config_file = current.join(config_file_name);
            match fs::metadata(&config_file) {
                // Only return if it's a file to handle the unlikely situation of a directory named
                // `clippy.toml`.
                Ok(ref md) if md.is_file() => return Ok(Some(config_file)),
                // Return the error if it's something other than `NotFound`; otherwise we didn't
                // find the project file yet, and continue searching.
                Err(e) => {
                    if e.kind() != io::ErrorKind::NotFound {
                        return Err(e);
                    }
                },
                _ => (),
            }
        }

        // If the current directory has no parent, we're done searching.
        if !current.pop() {
            return Ok(None);
        }
    }
}

/// Read the `toml` configuration file.
///
/// In case of error, the function tries to continue as much as possible.
pub fn read(path: Option<&path::Path>) -> (Conf, Vec<Error>) {
    let mut conf = Conf::default();
    let mut errors = Vec::new();

    let path = if let Some(path) = path {
        path
    } else {
        return (conf, errors);
    };

    let file = match fs::File::open(path) {
        Ok(mut file) => {
            let mut buf = String::new();

            if let Err(err) = file.read_to_string(&mut buf) {
                errors.push(err.into());
                return (conf, errors);
            }

            buf
        },
        Err(err) => {
            errors.push(err.into());
            return (conf, errors);
        },
    };

    let mut parser = toml::Parser::new(&file);
    let toml = if let Some(toml) = parser.parse() {
        toml
    } else {
        errors.push(Error::Toml(parser.errors));
        return (conf, errors);
    };

    for (key, value) in toml {
        if let Err(err) = conf.set(key, value) {
            errors.push(err);
        }
    }

    (conf, errors)
}
<commit_msg>Add KiB to doc-valid-idents<commit_after>//! Read configurations files.

#![deny(missing_docs_in_private_items)]

use std::{env, fmt, fs, io, path};
use std::io::Read;
use syntax::{ast, codemap};
use toml;

/// Get the configuration file from arguments.
pub fn file_from_args(args: &[codemap::Spanned<ast::NestedMetaItemKind>])
    -> Result<Option<path::PathBuf>, (&'static str, codemap::Span)> {
    for arg in args.iter().filter_map(|a| a.meta_item()) {
        if arg.name() == "conf_file" {
            return match arg.node {
                ast::MetaItemKind::Word |
                ast::MetaItemKind::List(_) => Err(("`conf_file` must be a named value", arg.span)),
                ast::MetaItemKind::NameValue(ref value) => {
                    if let ast::LitKind::Str(ref file, _) = value.node {
                        Ok(Some(file.to_string().into()))
                    } else {
                        Err(("`conf_file` value must be a string", value.span))
                    }
                },
            };
        }
    }

    Ok(None)
}

/// Error from reading a configuration file.
#[derive(Debug)]
pub enum Error {
    /// An I/O error.
    Io(io::Error),
    /// The file is not valid TOML.
    Toml(Vec<toml::ParserError>),
    /// Type error.
    Type(/// The name of the key.
         &'static str,
         /// The expected type.
         &'static str,
         /// The type we got instead.
         &'static str),
    /// There is an unknown key is the file.
    UnknownKey(String),
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        match *self {
            Error::Io(ref err) => err.fmt(f),
            Error::Toml(ref errs) => {
                let mut first = true;
                for err in errs {
                    if !first {
                        try!(", ".fmt(f));
                        first = false;
                    }

                    try!(err.fmt(f));
                }

                Ok(())
            },
            Error::Type(key, expected, got) => {
                write!(f, "`{}` is expected to be a `{}` but is a `{}`", key, expected, got)
            },
            Error::UnknownKey(ref key) => write!(f, "unknown key `{}`", key),
        }
    }
}

impl From<io::Error> for Error {
    fn from(e: io::Error) -> Self {
        Error::Io(e)
    }
}

macro_rules! define_Conf {
    ($(#[$doc: meta] ($toml_name: tt, $rust_name: ident, $default: expr => $($ty: tt)+),)+) => {
        /// Type used to store lint configuration.
        pub struct Conf {
            $(#[$doc] pub $rust_name: define_Conf!(TY $($ty)+),)+
        }

        impl Default for Conf {
            fn default() -> Conf {
                Conf {
                    $($rust_name: define_Conf!(DEFAULT $($ty)+, $default),)+
                }
            }
        }

        impl Conf {
            /// Set the property `name` (which must be the `toml` name) to the given value
            #[allow(cast_sign_loss)]
            fn set(&mut self, name: String, value: toml::Value) -> Result<(), Error> {
                match name.as_str() {
                    $(
                        define_Conf!(PAT $toml_name) => {
                            if let Some(value) = define_Conf!(CONV $($ty)+, value) {
                                self.$rust_name = value;
                            }
                            else {
                                return Err(Error::Type(define_Conf!(EXPR $toml_name),
                                                       stringify!($($ty)+),
                                                       value.type_str()));
                            }
                        },
                    )+
                    "third-party" => {
                        // for external tools such as clippy-service
                        return Ok(());
                    }
                    _ => {
                        return Err(Error::UnknownKey(name));
                    }
                }

                Ok(())
            }
        }
    };

    // hack to convert tts
    (PAT $pat: pat) => { $pat };
    (EXPR $e: expr) => { $e };
    (TY $ty: ty) => { $ty };

    // how to read the value?
    (CONV i64, $value: expr) => { $value.as_integer() };
    (CONV u64, $value: expr) => {
        $value.as_integer()
        .iter()
        .filter_map(|&i| if i >= 0 { Some(i as u64) } else { None })
        .next()
    };
    (CONV String, $value: expr) => { $value.as_str().map(Into::into) };
    (CONV Vec<String>, $value: expr) => {{
        let slice = $value.as_slice();

        if let Some(slice) = slice {
            if slice.iter().any(|v| v.as_str().is_none()) {
                None
            } else {
                Some(slice.iter().map(|v| v.as_str().expect("already checked").to_owned()).collect())
            }
        } else {
            None
        }
    }};

    // provide a nicer syntax to declare the default value of `Vec<String>` variables
    (DEFAULT Vec<String>, $e: expr) => { $e.iter().map(|&e| e.to_owned()).collect() };
    (DEFAULT $ty: ty, $e: expr) => { $e };
}

define_Conf! {
    /// Lint: BLACKLISTED_NAME. The list of blacklisted names to lint about
    ("blacklisted-names", blacklisted_names, ["foo", "bar", "baz", "quux"] => Vec<String>),
    /// Lint: CYCLOMATIC_COMPLEXITY. The maximum cyclomatic complexity a function can have
    ("cyclomatic-complexity-threshold", cyclomatic_complexity_threshold, 25 => u64),
    /// Lint: DOC_MARKDOWN. The list of words this lint should not consider as identifiers needing ticks
    ("doc-valid-idents", doc_valid_idents, [
        "KiB", "MiB", "GiB", "TiB", "PiB", "EiB",
        "DirectX",
        "ECMAScript",
        "GPLv2", "GPLv3",
        "GitHub",
        "IPv4", "IPv6",
        "JavaScript",
        "NaN",
        "OAuth",
        "OpenGL",
        "TrueType",
        "iOS", "macOS",
        "TeX", "LaTeX", "BibTex", "BibLaTex",
        "MinGW",
    ] => Vec<String>),
    /// Lint: TOO_MANY_ARGUMENTS. The maximum number of argument a function or method can have
    ("too-many-arguments-threshold", too_many_arguments_threshold, 7 => u64),
    /// Lint: TYPE_COMPLEXITY. The maximum complexity a type can have
    ("type-complexity-threshold", type_complexity_threshold, 250 => u64),
    /// Lint: MANY_SINGLE_CHAR_NAMES. The maximum number of single char bindings a scope may have
    ("single-char-binding-names-threshold", max_single_char_names, 5 => u64),
    /// Lint: BOXED_LOCAL. The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
    ("too-large-for-stack", too_large_for_stack, 200 => u64),
    /// Lint: ENUM_VARIANT_NAMES. The minimum number of enum variants for the lints about variant names to trigger
    ("enum-variant-name-threshold", enum_variant_name_threshold, 3 => u64),
    /// Lint: LARGE_ENUM_VARIANT. The maximum size of a emum's variant to avoid box suggestion
    ("enum-variant-size-threshold", enum_variant_size_threshold, 200 => u64),
}

/// Search for the configuration file.
pub fn lookup_conf_file() -> io::Result<Option<path::PathBuf>> {
    /// Possible filename to search for.
    const CONFIG_FILE_NAMES: [&'static str; 2] = [".clippy.toml", "clippy.toml"];

    let mut current = try!(env::current_dir());

    loop {
        for config_file_name in &CONFIG_FILE_NAMES {
            let config_file = current.join(config_file_name);
            match fs::metadata(&config_file) {
                // Only return if it's a file to handle the unlikely situation of a directory named
                // `clippy.toml`.
                Ok(ref md) if md.is_file() => return Ok(Some(config_file)),
                // Return the error if it's something other than `NotFound`; otherwise we didn't
                // find the project file yet, and continue searching.
                Err(e) => {
                    if e.kind() != io::ErrorKind::NotFound {
                        return Err(e);
                    }
                },
                _ => (),
            }
        }

        // If the current directory has no parent, we're done searching.
        if !current.pop() {
            return Ok(None);
        }
    }
}

/// Read the `toml` configuration file.
///
/// In case of error, the function tries to continue as much as possible.
pub fn read(path: Option<&path::Path>) -> (Conf, Vec<Error>) {
    let mut conf = Conf::default();
    let mut errors = Vec::new();

    let path = if let Some(path) = path {
        path
    } else {
        return (conf, errors);
    };

    let file = match fs::File::open(path) {
        Ok(mut file) => {
            let mut buf = String::new();

            if let Err(err) = file.read_to_string(&mut buf) {
                errors.push(err.into());
                return (conf, errors);
            }

            buf
        },
        Err(err) => {
            errors.push(err.into());
            return (conf, errors);
        },
    };

    let mut parser = toml::Parser::new(&file);
    let toml = if let Some(toml) = parser.parse() {
        toml
    } else {
        errors.push(Error::Toml(parser.errors));
        return (conf, errors);
    };

    for (key, value) in toml {
        if let Err(err) = conf.set(key, value) {
            errors.push(err);
        }
    }

    (conf, errors)
}
<|endoftext|>


<commit_before>use crate::{api_client,
            hcore::{self,
                    package::{FullyQualifiedPackageIdent,
                              PackageIdent}}};
#[cfg(windows)]
use habitat_core::os::process::windows_child::ExitStatus;
#[cfg(not(windows))]
use std::process::ExitStatus;
use std::{env,
          error,
          fmt,
          io,
          net,
          path::PathBuf,
          result,
          str,
          string};

pub const DEFAULT_ERROR_EXIT_CODE: i32 = 1;

pub type Result<T> = result::Result<T, Error>;

#[derive(Debug)]
pub enum CommandExecutionError {
    RunError(Box<Error>),
    ExitStatus(ExitStatus),
}

impl CommandExecutionError {
    pub fn run_error(e: Error) -> Self { Self::RunError(Box::new(e)) }

    pub fn exit_status(e: ExitStatus) -> Self { Self::ExitStatus(e) }

    pub fn exit_code(&self) -> i32 {
        if let Self::ExitStatus(exit_status) = self {
            exit_status.code().unwrap_or(DEFAULT_ERROR_EXIT_CODE)
        } else {
            DEFAULT_ERROR_EXIT_CODE
        }
    }
}

impl fmt::Display for CommandExecutionError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::RunError(e) => write!(f, "execution failed: {}", e),
            Self::ExitStatus(exit_status) => {
                if let Some(code) = exit_status.code() {
                    write!(f, "exited with status {}", code)
                } else {
                    write!(f, "exited without status")
                }
            }
        }
    }
}

#[derive(Debug)]
pub enum Error {
    APIClient(api_client::Error),
    ArtifactIdentMismatch((String, String, String)),
    /// Occurs when there is no valid toml of json in the environment variable
    BadEnvConfig(String),
    BadGlyphStyle(String),
    CantUploadGossipToml,
    ChannelNotFound,
    CryptoKeyError(String),
    DownloadFailed(String),
    EditorEnv(env::VarError),
    EditStatus,
    FileNameError,
    /// Occurs when a file that should exist does not or could not be read.
    FileNotFound(String),
    GossipFileRelativePath(String),
    HabitatCore(hcore::Error),
    HookFailed {
        package_ident: FullyQualifiedPackageIdent,
        hook:          &'static str,
        error:         CommandExecutionError,
    },
    InvalidEventStreamToken(String),
    /// Occurs when making lower level IO calls.
    IO(io::Error),
    /// Errors when joining paths :)
    JoinPathsError(env::JoinPathsError),
    ListenCtlResolutionError(String, io::Error),
    MissingCLIInputError(String),
    NamedPipeTimeoutOnStart(String, String, io::Error),
    NativeTls(native_tls::Error),
    NetParseError(net::AddrParseError),
    OfflineArtifactNotFound(PackageIdent),
    OfflineOriginKeyNotFound(String),
    OfflinePackageNotFound(PackageIdent),
    PackageFailedToInstall(PackageIdent, Box<Self>),
    PackageNotFound(String),
    /// Occurs upon errors related to file or directory permissions.
    PermissionFailed(String),
    /// When an error occurs serializing rendering context
    RenderContextSerialization(serde_json::Error),
    RootRequired,
    StatusFileCorrupt(PathBuf),
    StrFromUtf8Error(str::Utf8Error),
    StringFromUtf8Error(string::FromUtf8Error),
    /// When an error occurs registering template file
    // Boxed due to clippy::large_enum_variant
    TemplateFileError(Box<handlebars::TemplateFileError>),
    /// When an error occurs rendering template
    /// The error is constructed with a handlebars::RenderError's format string instead
    /// of the handlebars::RenderError itself because the cause field of the
    /// handlebars::RenderError in the handlebars crate version we use implements send
    /// and not sync which can lead to upstream compile errors when dealing with the
    /// failure crate. We should change this to a RenderError after we update the
    /// handlebars crate. See https://github.com/sunng87/handlebars-rust/issues/194
    TemplateRenderError(String),
    /// When an error occurs merging toml
    TomlMergeError(String),
    /// When an error occurs parsing toml
    TomlParser(toml::de::Error),
    TomlSerializeError(toml::ser::Error),
    WireDecode(String),
}

impl Error {
    pub fn hook_run_error(package_ident: FullyQualifiedPackageIdent,
                          hook: &'static str,
                          error: Error)
                          -> Self {
        Self::HookFailed { package_ident,
                           hook,
                           error: CommandExecutionError::run_error(error) }
    }

    pub fn hook_exit_status(package_ident: FullyQualifiedPackageIdent,
                            hook: &'static str,
                            exit_status: ExitStatus)
                            -> Self {
        Self::HookFailed { package_ident,
                           hook,
                           error: CommandExecutionError::exit_status(exit_status) }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let msg = match *self {
            Error::APIClient(ref err) => format!("{}", err),
            Error::ArtifactIdentMismatch((ref a, ref ai, ref i)) => {
                format!("Artifact ident {} for `{}' does not match expected ident {}",
                        ai, a, i)
            }
            Error::BadEnvConfig(ref varname) => {
                format!("Unable to find valid TOML or JSON in {} ENVVAR", varname)
            }
            Error::BadGlyphStyle(ref style) => format!("Unknown symbol style '{}'", style),
            Error::CantUploadGossipToml => {
                "Can't upload gossip.toml, it's a reserved file name".to_string()
            }
            Error::ChannelNotFound => "Channel not found".to_string(),
            Error::CryptoKeyError(ref s) => format!("Missing or invalid key: {}", s),
            Error::DownloadFailed(ref msg) => msg.to_string(),
            Error::EditorEnv(ref e) => format!("Missing EDITOR environment variable: {}", e),
            Error::EditStatus => "Failed edit text command".to_string(),
            Error::FileNameError => "Failed to extract a filename".to_string(),
            Error::FileNotFound(ref e) => format!("File not found at: {}", e),
            Error::GossipFileRelativePath(ref s) => {
                format!("Path for gossip file cannot have relative components (eg: ..): {}",
                        s)
            }
            Error::HabitatCore(ref e) => format!("{}", e),
            Error::MissingCLIInputError(ref arg) => {
                format!("Missing required CLI argument!: {}", arg)
            }
            Error::HookFailed { ref package_ident,
                                ref hook,
                                ref error, } => {
                format!("{} {} hook failed: {}", package_ident, hook, error)
            }
            Error::InvalidEventStreamToken(ref s) => {
                format!("Invalid event stream token provided: '{}'", s)
            }
            Error::IO(ref err) => format!("{}", err),
            Error::JoinPathsError(ref err) => format!("{}", err),
            Error::NamedPipeTimeoutOnStart(ref group, ref hook, ref err) => {
                format!("Unable to start powershell named pipe for {} hook of {}: {}",
                        hook, group, err)
            }
            Error::NativeTls(ref err) => format!("TLS error '{}'", err),
            Error::NetParseError(ref err) => format!("{}", err),
            Error::OfflineArtifactNotFound(ref ident) => {
                format!("Cached artifact not found in offline mode: {}", ident)
            }
            Error::OfflineOriginKeyNotFound(ref name_with_rev) => {
                format!("Cached origin key not found in offline mode: {}",
                        name_with_rev)
            }
            Error::OfflinePackageNotFound(ref ident) => {
                format!("No installed package or cached artifact could be found locally in \
                         offline mode: {}",
                        ident)
            }
            Error::PackageFailedToInstall(ref ident, ref e) => {
                format!("Failed to install package {} - {}", ident, e)
            }
            Error::PackageNotFound(ref e) => format!("Package not found. {}", e),
            Error::PermissionFailed(ref e) => e.to_string(),
            Error::RenderContextSerialization(ref e) => {
                format!("Unable to serialize rendering context, {}", e)
            }
            Error::ListenCtlResolutionError(ref sup_addr, ref err) => {
                format!("Failed to resolve ctl address '{}': {}", sup_addr, err,)
            }
            Error::RootRequired => {
                "Root or administrator permissions required to complete operation".to_string()
            }
            Error::StatusFileCorrupt(ref path) => {
                format!("Unable to decode contents of INSTALL_STATUS file, {}",
                        path.display())
            }
            Error::StrFromUtf8Error(ref e) => format!("{}", e),
            Error::StringFromUtf8Error(ref e) => format!("{}", e),
            Error::TemplateFileError(ref err) => format!("{:?}", err),
            Error::TemplateRenderError(ref err) => err.to_string(),
            Error::TomlMergeError(ref e) => format!("Failed to merge TOML: {}", e),
            Error::TomlParser(ref err) => format!("Failed to parse TOML: {}", err),
            Error::TomlSerializeError(ref e) => format!("Can't serialize TOML: {}", e),
            Error::WireDecode(ref m) => format!("Failed to decode wire message: {}", m),
        };
        write!(f, "{}", msg)
    }
}

impl error::Error for Error {}

impl Error {
    pub fn exit_code(&self) -> i32 {
        match self {
            Self::HookFailed { error, .. } => error.exit_code(),
            _ => DEFAULT_ERROR_EXIT_CODE,
        }
    }
}

impl From<api_client::Error> for Error {
    fn from(err: api_client::Error) -> Self { Error::APIClient(err) }
}

impl From<handlebars::TemplateFileError> for Error {
    fn from(err: handlebars::TemplateFileError) -> Self { Error::TemplateFileError(Box::new(err)) }
}

impl From<hcore::Error> for Error {
    fn from(err: hcore::Error) -> Self { Error::HabitatCore(err) }
}

impl From<io::Error> for Error {
    fn from(err: io::Error) -> Self { Error::IO(err) }
}

impl From<env::JoinPathsError> for Error {
    fn from(err: env::JoinPathsError) -> Self { Error::JoinPathsError(err) }
}

impl From<str::Utf8Error> for Error {
    fn from(err: str::Utf8Error) -> Self { Error::StrFromUtf8Error(err) }
}

impl From<string::FromUtf8Error> for Error {
    fn from(err: string::FromUtf8Error) -> Self { Error::StringFromUtf8Error(err) }
}

impl From<toml::ser::Error> for Error {
    fn from(err: toml::ser::Error) -> Self { Error::TomlSerializeError(err) }
}

impl From<net::AddrParseError> for Error {
    fn from(err: net::AddrParseError) -> Self { Error::NetParseError(err) }
}

impl From<native_tls::Error> for Error {
    fn from(error: native_tls::Error) -> Self { Error::NativeTls(error) }
}
<commit_msg>[Refactor] Remove uneeded error type<commit_after>use crate::{api_client,
            hcore::{self,
                    package::{FullyQualifiedPackageIdent,
                              PackageIdent}}};
#[cfg(windows)]
use habitat_core::os::process::windows_child::ExitStatus;
#[cfg(not(windows))]
use std::process::ExitStatus;
use std::{env,
          error,
          fmt,
          io,
          net,
          path::PathBuf,
          result,
          str,
          string};

pub const DEFAULT_ERROR_EXIT_CODE: i32 = 1;

pub type Result<T> = result::Result<T, Error>;

#[derive(Debug)]
pub enum CommandExecutionError {
    RunError(Box<Error>),
    ExitStatus(ExitStatus),
}

impl CommandExecutionError {
    pub fn run_error(e: Error) -> Self { Self::RunError(Box::new(e)) }

    pub fn exit_status(e: ExitStatus) -> Self { Self::ExitStatus(e) }

    pub fn exit_code(&self) -> i32 {
        if let Self::ExitStatus(exit_status) = self {
            exit_status.code().unwrap_or(DEFAULT_ERROR_EXIT_CODE)
        } else {
            DEFAULT_ERROR_EXIT_CODE
        }
    }
}

impl fmt::Display for CommandExecutionError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::RunError(e) => write!(f, "execution failed: {}", e),
            Self::ExitStatus(exit_status) => {
                if let Some(code) = exit_status.code() {
                    write!(f, "exited with status {}", code)
                } else {
                    write!(f, "exited without status")
                }
            }
        }
    }
}

#[derive(Debug)]
pub enum Error {
    APIClient(api_client::Error),
    ArtifactIdentMismatch((String, String, String)),
    /// Occurs when there is no valid toml of json in the environment variable
    BadEnvConfig(String),
    BadGlyphStyle(String),
    CantUploadGossipToml,
    ChannelNotFound,
    CryptoKeyError(String),
    EditorEnv(env::VarError),
    EditStatus,
    FileNameError,
    /// Occurs when a file that should exist does not or could not be read.
    FileNotFound(String),
    GossipFileRelativePath(String),
    HabitatCore(hcore::Error),
    HookFailed {
        package_ident: FullyQualifiedPackageIdent,
        hook:          &'static str,
        error:         CommandExecutionError,
    },
    InvalidEventStreamToken(String),
    /// Occurs when making lower level IO calls.
    IO(io::Error),
    /// Errors when joining paths :)
    JoinPathsError(env::JoinPathsError),
    ListenCtlResolutionError(String, io::Error),
    MissingCLIInputError(String),
    NamedPipeTimeoutOnStart(String, String, io::Error),
    NativeTls(native_tls::Error),
    NetParseError(net::AddrParseError),
    OfflineArtifactNotFound(PackageIdent),
    OfflineOriginKeyNotFound(String),
    OfflinePackageNotFound(PackageIdent),
    PackageFailedToInstall(PackageIdent, Box<Self>),
    PackageNotFound(String),
    /// Occurs upon errors related to file or directory permissions.
    PermissionFailed(String),
    /// When an error occurs serializing rendering context
    RenderContextSerialization(serde_json::Error),
    RootRequired,
    StatusFileCorrupt(PathBuf),
    StrFromUtf8Error(str::Utf8Error),
    StringFromUtf8Error(string::FromUtf8Error),
    /// When an error occurs registering template file
    // Boxed due to clippy::large_enum_variant
    TemplateFileError(Box<handlebars::TemplateFileError>),
    /// When an error occurs rendering template
    /// The error is constructed with a handlebars::RenderError's format string instead
    /// of the handlebars::RenderError itself because the cause field of the
    /// handlebars::RenderError in the handlebars crate version we use implements send
    /// and not sync which can lead to upstream compile errors when dealing with the
    /// failure crate. We should change this to a RenderError after we update the
    /// handlebars crate. See https://github.com/sunng87/handlebars-rust/issues/194
    TemplateRenderError(String),
    /// When an error occurs merging toml
    TomlMergeError(String),
    /// When an error occurs parsing toml
    TomlParser(toml::de::Error),
    TomlSerializeError(toml::ser::Error),
    WireDecode(String),
}

impl Error {
    pub fn hook_run_error(package_ident: FullyQualifiedPackageIdent,
                          hook: &'static str,
                          error: Error)
                          -> Self {
        Self::HookFailed { package_ident,
                           hook,
                           error: CommandExecutionError::run_error(error) }
    }

    pub fn hook_exit_status(package_ident: FullyQualifiedPackageIdent,
                            hook: &'static str,
                            exit_status: ExitStatus)
                            -> Self {
        Self::HookFailed { package_ident,
                           hook,
                           error: CommandExecutionError::exit_status(exit_status) }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let msg = match *self {
            Error::APIClient(ref err) => format!("{}", err),
            Error::ArtifactIdentMismatch((ref a, ref ai, ref i)) => {
                format!("Artifact ident {} for `{}' does not match expected ident {}",
                        ai, a, i)
            }
            Error::BadEnvConfig(ref varname) => {
                format!("Unable to find valid TOML or JSON in {} ENVVAR", varname)
            }
            Error::BadGlyphStyle(ref style) => format!("Unknown symbol style '{}'", style),
            Error::CantUploadGossipToml => {
                "Can't upload gossip.toml, it's a reserved file name".to_string()
            }
            Error::ChannelNotFound => "Channel not found".to_string(),
            Error::CryptoKeyError(ref s) => format!("Missing or invalid key: {}", s),
            Error::EditorEnv(ref e) => format!("Missing EDITOR environment variable: {}", e),
            Error::EditStatus => "Failed edit text command".to_string(),
            Error::FileNameError => "Failed to extract a filename".to_string(),
            Error::FileNotFound(ref e) => format!("File not found at: {}", e),
            Error::GossipFileRelativePath(ref s) => {
                format!("Path for gossip file cannot have relative components (eg: ..): {}",
                        s)
            }
            Error::HabitatCore(ref e) => format!("{}", e),
            Error::MissingCLIInputError(ref arg) => {
                format!("Missing required CLI argument!: {}", arg)
            }
            Error::HookFailed { ref package_ident,
                                ref hook,
                                ref error, } => {
                format!("{} {} hook failed: {}", package_ident, hook, error)
            }
            Error::InvalidEventStreamToken(ref s) => {
                format!("Invalid event stream token provided: '{}'", s)
            }
            Error::IO(ref err) => format!("{}", err),
            Error::JoinPathsError(ref err) => format!("{}", err),
            Error::NamedPipeTimeoutOnStart(ref group, ref hook, ref err) => {
                format!("Unable to start powershell named pipe for {} hook of {}: {}",
                        hook, group, err)
            }
            Error::NativeTls(ref err) => format!("TLS error '{}'", err),
            Error::NetParseError(ref err) => format!("{}", err),
            Error::OfflineArtifactNotFound(ref ident) => {
                format!("Cached artifact not found in offline mode: {}", ident)
            }
            Error::OfflineOriginKeyNotFound(ref name_with_rev) => {
                format!("Cached origin key not found in offline mode: {}",
                        name_with_rev)
            }
            Error::OfflinePackageNotFound(ref ident) => {
                format!("No installed package or cached artifact could be found locally in \
                         offline mode: {}",
                        ident)
            }
            Error::PackageFailedToInstall(ref ident, ref e) => {
                format!("Failed to install package {} - {}", ident, e)
            }
            Error::PackageNotFound(ref e) => format!("Package not found. {}", e),
            Error::PermissionFailed(ref e) => e.to_string(),
            Error::RenderContextSerialization(ref e) => {
                format!("Unable to serialize rendering context, {}", e)
            }
            Error::ListenCtlResolutionError(ref sup_addr, ref err) => {
                format!("Failed to resolve ctl address '{}': {}", sup_addr, err,)
            }
            Error::RootRequired => {
                "Root or administrator permissions required to complete operation".to_string()
            }
            Error::StatusFileCorrupt(ref path) => {
                format!("Unable to decode contents of INSTALL_STATUS file, {}",
                        path.display())
            }
            Error::StrFromUtf8Error(ref e) => format!("{}", e),
            Error::StringFromUtf8Error(ref e) => format!("{}", e),
            Error::TemplateFileError(ref err) => format!("{:?}", err),
            Error::TemplateRenderError(ref err) => err.to_string(),
            Error::TomlMergeError(ref e) => format!("Failed to merge TOML: {}", e),
            Error::TomlParser(ref err) => format!("Failed to parse TOML: {}", err),
            Error::TomlSerializeError(ref e) => format!("Can't serialize TOML: {}", e),
            Error::WireDecode(ref m) => format!("Failed to decode wire message: {}", m),
        };
        write!(f, "{}", msg)
    }
}

impl error::Error for Error {}

impl Error {
    pub fn exit_code(&self) -> i32 {
        match self {
            Self::HookFailed { error, .. } => error.exit_code(),
            _ => DEFAULT_ERROR_EXIT_CODE,
        }
    }
}

impl From<api_client::Error> for Error {
    fn from(err: api_client::Error) -> Self { Error::APIClient(err) }
}

impl From<handlebars::TemplateFileError> for Error {
    fn from(err: handlebars::TemplateFileError) -> Self { Error::TemplateFileError(Box::new(err)) }
}

impl From<hcore::Error> for Error {
    fn from(err: hcore::Error) -> Self { Error::HabitatCore(err) }
}

impl From<io::Error> for Error {
    fn from(err: io::Error) -> Self { Error::IO(err) }
}

impl From<env::JoinPathsError> for Error {
    fn from(err: env::JoinPathsError) -> Self { Error::JoinPathsError(err) }
}

impl From<str::Utf8Error> for Error {
    fn from(err: str::Utf8Error) -> Self { Error::StrFromUtf8Error(err) }
}

impl From<string::FromUtf8Error> for Error {
    fn from(err: string::FromUtf8Error) -> Self { Error::StringFromUtf8Error(err) }
}

impl From<toml::ser::Error> for Error {
    fn from(err: toml::ser::Error) -> Self { Error::TomlSerializeError(err) }
}

impl From<net::AddrParseError> for Error {
    fn from(err: net::AddrParseError) -> Self { Error::NetParseError(err) }
}

impl From<native_tls::Error> for Error {
    fn from(error: native_tls::Error) -> Self { Error::NativeTls(error) }
}
<|endoftext|>


<commit_before>//!
//! The API for communicating with devices.
//!
//! This API is provided as Traits to be implemented:
//!
//! - by the low-level layers of the FoxBox, including the adapters;
//! - by test suites and tools that need to simulate connected devices.
//!
//! In turn, this API is used to implement:
//!
//! - the public-facing REST and WebSocket API;
//! - the rules API (ThinkerBell).
//!
//!

use devices::*;
use requests::*;
use values::Value;

/// An error produced by one of the APIs in this module.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum Error {
    /// There is no such node connected to the Foxbox, even indirectly.
    NoSuchNode(NodeId),

    /// There is no such service connected to the Foxbox, even indirectly.
    NoSuchService(ServiceId),

    /// Attempting to set a value with the wrong type
    TypeError,
}

/// An event during watching.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum WatchEvent {
    /// A new value is available.
    Value {
        /// The service that sent the value.
        from: ServiceId,

        /// The actual value.
        value: Value
    },

    /// The set of devices being watched has changed, typically either
    /// because a tag was edited or because a device was
    /// removed. Payload is the id of the device that was removed.
    InputRemoved(ServiceId),

    /// The set of devices being watched has changed, typically either
    /// because a tag was edited or because a device was
    /// added. Payload is the id of the device that was added.
    InputAdded(ServiceId),
}

/// A handle to the public API.
pub trait API: Send {
    /// Get the metadata on nodes matching some conditions.
    ///
    /// A call to `API::get_nodes(vec![req1, req2, ...])` will return
    /// the metadata on all nodes matching _either_ `req1` or `req2`
    /// or ...
    ///
    /// # REST API
    ///
    /// `GET /api/v1/nodes`
    ///
    /// ## Inputs
    ///
    /// Any JSON that can be deserialized to a `Vec<NodeRequest>`. See
    /// the implementation of `NodeRequest` for details.
    ///
    /// ### Example
    ///
    /// Request all doors in the entrance (tags `door`, `entrance`)
    /// that support output service `OpenClose`
    ///
    /// ```json
    /// [{
    ///   "tags": ["entrance", "door"],
    ///   "inputs": [
    ///     {
    ///       "kind": {
    ///         "Exactly": {
    ///           "OpenClose": []
    ///         }
    ///       }
    ///     }
    ///   ]
    /// }]
    /// ```
    ///
    ///
    /// ## Errors
    ///
    /// In case of syntax error, Error 400, accompanied with a
    /// somewhat human-readable JSON string detailing the error.
    ///
    /// ## Success
    ///
    /// A JSON representing an array of `Node`. See the implementation
    /// of `Node` for details.
    ///
    /// ### Example
    ///
    /// ```json
    /// [{
    ///   "tags": ["entrance", "door", "somevendor"],
    ///   "id: "some-node-id",
    ///   "inputs": [],
    ///   "outputs": [
    ///     "tags": [...],
    ///     "id": "some-service-id",
    ///     "node": "some-node-id",
    ///     "last_seen": "some-date",
    ///     "mechanism": {
    ///       "Output":  {
    ///         "kind": {
    ///           "OnOff": []
    ///         },
    ///         "push": [5000],
    ///         "updated": "some-date",
    ///       }
    ///     }
    ///   ]
    /// }]
    /// ```
    fn get_nodes(&self, &Vec<NodeRequest>) -> Vec<Node>;

    /// Label a set of nodes with a set of tags.
    ///
    /// A call to `API::put_node_tag(vec![req1, req2, ...], vec![tag1,
    /// ...])` will label all the nodes matching _either_ `req1` or
    /// `req2` or ... with `tag1`, ... and return the number of nodes
    /// matching any of the requests.
    ///
    /// Some of the nodes may already be labelled with `tag1`, or
    /// `tag2`, ... They will not change state. They are counted in
    /// the resulting `usize` nevertheless.
    ///
    /// Note that this call is _not live_. In other words, if nodes
    /// are added after the call, they will not be affected.
    ///
    /// # REST API
    ///
    /// `POST /api/v1/nodes/tag`
    ///
    /// ## Inputs
    ///
    /// Any JSON that can be deserialized to
    ///
    /// ```ignore
    /// {
    ///   set: Vec<NodeRequest>,
    ///   tags: Vec<String>,
    /// }
    /// ```
    ///
    /// ## Errors
    ///
    /// In case of syntax error, Error 400, accompanied with a
    /// somewhat human-readable JSON string detailing the error.
    ///
    /// ## Success
    ///
    /// A JSON string representing a number.
    fn put_node_tag(&self, set: &Vec<NodeRequest>, tags: &Vec<String>) -> usize;

    /// Remove a set of tags from a set of nodes.
    ///
    /// A call to `API::delete_node_tag(vec![req1, req2, ...], vec![tag1,
    /// ...])` will remove from all the nodes matching _either_ `req1` or
    /// `req2` or ... all of the tags `tag1`, ... and return the number of nodes
    /// matching any of the requests.
    ///
    /// Some of the nodes may not be labelled with `tag1`, or `tag2`,
    /// ... They will not change state. They are counted in the
    /// resulting `usize` nevertheless.
    ///
    /// Note that this call is _not live_. In other words, if nodes
    /// are added after the call, they will not be affected.
    ///
    /// # REST API
    ///
    /// `DELETE /api/v1/nodes/tag`
    ///
    /// ## Inputs
    ///
    /// Any JSON that can be deserialized to
    ///
    /// ```ignore
    /// {
    ///   set: Vec<NodeRequest>,
    ///   tags: Vec<String>,
    /// }
    /// ```
    ///
    /// ## Errors
    ///
    /// In case of syntax error, Error 400, accompanied with a
    /// somewhat human-readable JSON string detailing the error.
    ///
    /// ## Success
    ///
    /// A JSON representing a number.
    fn delete_node_tag(&self, set: &Vec<NodeRequest>, tags: String) -> usize;

    /// Get a list of inputs matching some conditions
    ///
    /// # REST API
    ///
    /// `GET /api/v1/services`
    fn get_input_services(&self, &Vec<InputRequest>) -> Vec<Service<Input>>;
    fn get_output_services(&self, &Vec<OutputRequest>) -> Vec<Service<Output>>;

    /// Label a set of services with a set of tags.
    ///
    /// A call to `API::put_{input, output}_tag(vec![req1, req2, ...], vec![tag1,
    /// ...])` will label all the services matching _either_ `req1` or
    /// `req2` or ... with `tag1`, ... and return the number of services
    /// matching any of the requests.
    ///
    /// Some of the services may already be labelled with `tag1`, or
    /// `tag2`, ... They will not change state. They are counted in
    /// the resulting `usize` nevertheless.
    ///
    /// Note that this call is _not live_. In other words, if services
    /// are added after the call, they will not be affected.
    ///
    /// # REST API
    ///
    /// `POST /api/v1/services/tag`
    ///
    /// ## Inputs
    ///
    /// Any JSON that can be deserialized to
    ///
    /// ```ignore
    /// {
    ///   set: Vec<InputRequest>,
    ///   tags: Vec<String>,
    /// }
    /// ```
    /// or
    /// ```ignore
    /// {
    ///   set: Vec<OutputRequest>,
    ///   tags: Vec<String>,
    /// }
    /// ```
    ///
    /// ## Errors
    ///
    /// In case of syntax error, Error 400, accompanied with a
    /// somewhat human-readable JSON string detailing the error.
    ///
    /// ## Success
    ///
    /// A JSON representing a number.
    fn put_input_tag(&self, &Vec<InputRequest>, &Vec<String>) -> usize;
    fn put_output_tag(&self, &Vec<OutputRequest>, &Vec<String>) -> usize;

    /// Remove a set of tags from a set of services.
    ///
    /// A call to `API::delete_{input, output}_tag(vec![req1, req2, ...], vec![tag1,
    /// ...])` will remove from all the services matching _either_ `req1` or
    /// `req2` or ... all of the tags `tag1`, ... and return the number of services
    /// matching any of the requests.
    ///
    /// Some of the services may not be labelled with `tag1`, or `tag2`,
    /// ... They will not change state. They are counted in the
    /// resulting `usize` nevertheless.
    ///
    /// Note that this call is _not live_. In other words, if services
    /// are added after the call, they will not be affected.
    ///
    /// # REST API
    ///
    /// `DELETE /api/v1/services/tag`
    ///
    /// ## Inputs
    ///
    /// Any JSON that can be deserialized to
    ///
    /// ```ignore
    /// {
    ///   set: Vec<InputRequest>,
    ///   tags: Vec<String>,
    /// }
    /// ```
    /// or
    /// ```ignore
    /// {
    ///   set: Vec<OutputRequest>,
    ///   tags: Vec<String>,
    /// }
    /// ```
    ///
    /// ## Errors
    ///
    /// In case of syntax error, Error 400, accompanied with a
    /// somewhat human-readable JSON string detailing the error.
    ///
    /// ## Success
    ///
    /// A JSON representing a number.
    fn delete_input_tag(&self, &Vec<InputRequest>, &Vec<String>) -> usize;
    fn delete_output_tag(&self, &Vec<InputRequest>, &Vec<String>) -> usize;

    /// Read the latest value from a set of services
    ///
    /// # REST API
    ///
    /// `GET /api/v1/services/value`
    fn get_service_value(&self, &Vec<InputRequest>) -> Vec<(ServiceId, Result<Value, Error>)>;

    /// Send one value to a set of services
    ///
    /// # REST API
    ///
    /// `POST /api/v1/services/value`
    fn put_service_value(&self, &Vec<OutputRequest>, Value) -> Vec<(ServiceId, Result<(), Error>)>;

    /// Watch for any change
    ///
    /// # WebSocket API
    ///
    /// `/api/v1/services/watch`
    fn register_service_watch(&self, Vec<WatchOptions>, cb: Box<FnMut(WatchEvent) + Send + 'static>) -> Self::WatchGuard;

    /// A value that causes a disconnection once it is dropped.
    type WatchGuard;
}

/// Options for watching changes in one or more services.
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct WatchOptions {
    /// The set of inputs to watch. Note that the actual inputs in the
    /// set may change over time.
    pub source: InputRequest,

    /// If `true`, watch as new values become available.
    pub should_watch_values: bool,

    /// If `true`, watch as nodes are connected/disconnected.
    pub should_watch_topology: bool,

    /// Make sure that we can't instantiate from another crate.
    #[serde(default, skip_serializing)]
    private: (),
}

impl WatchOptions {
    pub fn new() -> Self {
        WatchOptions {
            source: InputRequest::new(),
            should_watch_values: false,
            should_watch_topology: false,
            private: (),
        }
    }

    /// Restrict to input services in a given set.
    ///
    /// Also note that the actual input services that are part of the
    /// set may change with time, for instance if devices are added
    /// ore removed.  The request _is live_, i.e. the service watch
    /// will continue watching any input services that match `req`.
    pub fn with_inputs(self, req: InputRequest) -> Self {
        WatchOptions {
            source: self.source.and(req),
            ..self
        }
    }

    pub fn with_watch_values(self, should: bool) -> Self {
        WatchOptions {
            should_watch_values: should,
            ..self
        }
    }

    pub fn with_watch_topology(self, should: bool) -> Self {
        WatchOptions {
            should_watch_topology: should,
            ..self
        }
    }
}
<commit_msg>[fxbox/taxonomy] register_service_watch now takes an Fn instead of an FnMut<commit_after>//!
//! The API for communicating with devices.
//!
//! This API is provided as Traits to be implemented:
//!
//! - by the low-level layers of the FoxBox, including the adapters;
//! - by test suites and tools that need to simulate connected devices.
//!
//! In turn, this API is used to implement:
//!
//! - the public-facing REST and WebSocket API;
//! - the rules API (ThinkerBell).
//!
//!

use devices::*;
use requests::*;
use values::Value;

/// An error produced by one of the APIs in this module.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum Error {
    /// There is no such node connected to the Foxbox, even indirectly.
    NoSuchNode(NodeId),

    /// There is no such service connected to the Foxbox, even indirectly.
    NoSuchService(ServiceId),

    /// Attempting to set a value with the wrong type
    TypeError,
}

/// An event during watching.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum WatchEvent {
    /// A new value is available.
    Value {
        /// The service that sent the value.
        from: ServiceId,

        /// The actual value.
        value: Value
    },

    /// The set of devices being watched has changed, typically either
    /// because a tag was edited or because a device was
    /// removed. Payload is the id of the device that was removed.
    InputRemoved(ServiceId),

    /// The set of devices being watched has changed, typically either
    /// because a tag was edited or because a device was
    /// added. Payload is the id of the device that was added.
    InputAdded(ServiceId),
}

/// A handle to the public API.
pub trait API: Send {
    /// Get the metadata on nodes matching some conditions.
    ///
    /// A call to `API::get_nodes(vec![req1, req2, ...])` will return
    /// the metadata on all nodes matching _either_ `req1` or `req2`
    /// or ...
    ///
    /// # REST API
    ///
    /// `GET /api/v1/nodes`
    ///
    /// ## Inputs
    ///
    /// Any JSON that can be deserialized to a `Vec<NodeRequest>`. See
    /// the implementation of `NodeRequest` for details.
    ///
    /// ### Example
    ///
    /// Request all doors in the entrance (tags `door`, `entrance`)
    /// that support output service `OpenClose`
    ///
    /// ```json
    /// [{
    ///   "tags": ["entrance", "door"],
    ///   "inputs": [
    ///     {
    ///       "kind": {
    ///         "Exactly": {
    ///           "OpenClose": []
    ///         }
    ///       }
    ///     }
    ///   ]
    /// }]
    /// ```
    ///
    ///
    /// ## Errors
    ///
    /// In case of syntax error, Error 400, accompanied with a
    /// somewhat human-readable JSON string detailing the error.
    ///
    /// ## Success
    ///
    /// A JSON representing an array of `Node`. See the implementation
    /// of `Node` for details.
    ///
    /// ### Example
    ///
    /// ```json
    /// [{
    ///   "tags": ["entrance", "door", "somevendor"],
    ///   "id: "some-node-id",
    ///   "inputs": [],
    ///   "outputs": [
    ///     "tags": [...],
    ///     "id": "some-service-id",
    ///     "node": "some-node-id",
    ///     "last_seen": "some-date",
    ///     "mechanism": {
    ///       "Output":  {
    ///         "kind": {
    ///           "OnOff": []
    ///         },
    ///         "push": [5000],
    ///         "updated": "some-date",
    ///       }
    ///     }
    ///   ]
    /// }]
    /// ```
    fn get_nodes(&self, &Vec<NodeRequest>) -> Vec<Node>;

    /// Label a set of nodes with a set of tags.
    ///
    /// A call to `API::put_node_tag(vec![req1, req2, ...], vec![tag1,
    /// ...])` will label all the nodes matching _either_ `req1` or
    /// `req2` or ... with `tag1`, ... and return the number of nodes
    /// matching any of the requests.
    ///
    /// Some of the nodes may already be labelled with `tag1`, or
    /// `tag2`, ... They will not change state. They are counted in
    /// the resulting `usize` nevertheless.
    ///
    /// Note that this call is _not live_. In other words, if nodes
    /// are added after the call, they will not be affected.
    ///
    /// # REST API
    ///
    /// `POST /api/v1/nodes/tag`
    ///
    /// ## Inputs
    ///
    /// Any JSON that can be deserialized to
    ///
    /// ```ignore
    /// {
    ///   set: Vec<NodeRequest>,
    ///   tags: Vec<String>,
    /// }
    /// ```
    ///
    /// ## Errors
    ///
    /// In case of syntax error, Error 400, accompanied with a
    /// somewhat human-readable JSON string detailing the error.
    ///
    /// ## Success
    ///
    /// A JSON string representing a number.
    fn put_node_tag(&self, set: &Vec<NodeRequest>, tags: &Vec<String>) -> usize;

    /// Remove a set of tags from a set of nodes.
    ///
    /// A call to `API::delete_node_tag(vec![req1, req2, ...], vec![tag1,
    /// ...])` will remove from all the nodes matching _either_ `req1` or
    /// `req2` or ... all of the tags `tag1`, ... and return the number of nodes
    /// matching any of the requests.
    ///
    /// Some of the nodes may not be labelled with `tag1`, or `tag2`,
    /// ... They will not change state. They are counted in the
    /// resulting `usize` nevertheless.
    ///
    /// Note that this call is _not live_. In other words, if nodes
    /// are added after the call, they will not be affected.
    ///
    /// # REST API
    ///
    /// `DELETE /api/v1/nodes/tag`
    ///
    /// ## Inputs
    ///
    /// Any JSON that can be deserialized to
    ///
    /// ```ignore
    /// {
    ///   set: Vec<NodeRequest>,
    ///   tags: Vec<String>,
    /// }
    /// ```
    ///
    /// ## Errors
    ///
    /// In case of syntax error, Error 400, accompanied with a
    /// somewhat human-readable JSON string detailing the error.
    ///
    /// ## Success
    ///
    /// A JSON representing a number.
    fn delete_node_tag(&self, set: &Vec<NodeRequest>, tags: String) -> usize;

    /// Get a list of inputs matching some conditions
    ///
    /// # REST API
    ///
    /// `GET /api/v1/services`
    fn get_input_services(&self, &Vec<InputRequest>) -> Vec<Service<Input>>;
    fn get_output_services(&self, &Vec<OutputRequest>) -> Vec<Service<Output>>;

    /// Label a set of services with a set of tags.
    ///
    /// A call to `API::put_{input, output}_tag(vec![req1, req2, ...], vec![tag1,
    /// ...])` will label all the services matching _either_ `req1` or
    /// `req2` or ... with `tag1`, ... and return the number of services
    /// matching any of the requests.
    ///
    /// Some of the services may already be labelled with `tag1`, or
    /// `tag2`, ... They will not change state. They are counted in
    /// the resulting `usize` nevertheless.
    ///
    /// Note that this call is _not live_. In other words, if services
    /// are added after the call, they will not be affected.
    ///
    /// # REST API
    ///
    /// `POST /api/v1/services/tag`
    ///
    /// ## Inputs
    ///
    /// Any JSON that can be deserialized to
    ///
    /// ```ignore
    /// {
    ///   set: Vec<InputRequest>,
    ///   tags: Vec<String>,
    /// }
    /// ```
    /// or
    /// ```ignore
    /// {
    ///   set: Vec<OutputRequest>,
    ///   tags: Vec<String>,
    /// }
    /// ```
    ///
    /// ## Errors
    ///
    /// In case of syntax error, Error 400, accompanied with a
    /// somewhat human-readable JSON string detailing the error.
    ///
    /// ## Success
    ///
    /// A JSON representing a number.
    fn put_input_tag(&self, &Vec<InputRequest>, &Vec<String>) -> usize;
    fn put_output_tag(&self, &Vec<OutputRequest>, &Vec<String>) -> usize;

    /// Remove a set of tags from a set of services.
    ///
    /// A call to `API::delete_{input, output}_tag(vec![req1, req2, ...], vec![tag1,
    /// ...])` will remove from all the services matching _either_ `req1` or
    /// `req2` or ... all of the tags `tag1`, ... and return the number of services
    /// matching any of the requests.
    ///
    /// Some of the services may not be labelled with `tag1`, or `tag2`,
    /// ... They will not change state. They are counted in the
    /// resulting `usize` nevertheless.
    ///
    /// Note that this call is _not live_. In other words, if services
    /// are added after the call, they will not be affected.
    ///
    /// # REST API
    ///
    /// `DELETE /api/v1/services/tag`
    ///
    /// ## Inputs
    ///
    /// Any JSON that can be deserialized to
    ///
    /// ```ignore
    /// {
    ///   set: Vec<InputRequest>,
    ///   tags: Vec<String>,
    /// }
    /// ```
    /// or
    /// ```ignore
    /// {
    ///   set: Vec<OutputRequest>,
    ///   tags: Vec<String>,
    /// }
    /// ```
    ///
    /// ## Errors
    ///
    /// In case of syntax error, Error 400, accompanied with a
    /// somewhat human-readable JSON string detailing the error.
    ///
    /// ## Success
    ///
    /// A JSON representing a number.
    fn delete_input_tag(&self, &Vec<InputRequest>, &Vec<String>) -> usize;
    fn delete_output_tag(&self, &Vec<InputRequest>, &Vec<String>) -> usize;

    /// Read the latest value from a set of services
    ///
    /// # REST API
    ///
    /// `GET /api/v1/services/value`
    fn get_service_value(&self, &Vec<InputRequest>) -> Vec<(ServiceId, Result<Value, Error>)>;

    /// Send one value to a set of services
    ///
    /// # REST API
    ///
    /// `POST /api/v1/services/value`
    fn put_service_value(&self, &Vec<OutputRequest>, Value) -> Vec<(ServiceId, Result<(), Error>)>;

    /// Watch for any change
    ///
    /// # WebSocket API
    ///
    /// `/api/v1/services/watch`
    fn register_service_watch(&self, Vec<WatchOptions>, cb: Box<Fn(WatchEvent) + Send + 'static>) -> Self::WatchGuard;

    /// A value that causes a disconnection once it is dropped.
    type WatchGuard;
}

/// Options for watching changes in one or more services.
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct WatchOptions {
    /// The set of inputs to watch. Note that the actual inputs in the
    /// set may change over time.
    pub source: InputRequest,

    /// If `true`, watch as new values become available.
    pub should_watch_values: bool,

    /// If `true`, watch as nodes are connected/disconnected.
    pub should_watch_topology: bool,

    /// Make sure that we can't instantiate from another crate.
    #[serde(default, skip_serializing)]
    private: (),
}

impl WatchOptions {
    pub fn new() -> Self {
        WatchOptions {
            source: InputRequest::new(),
            should_watch_values: false,
            should_watch_topology: false,
            private: (),
        }
    }

    /// Restrict to input services in a given set.
    ///
    /// Also note that the actual input services that are part of the
    /// set may change with time, for instance if devices are added
    /// ore removed.  The request _is live_, i.e. the service watch
    /// will continue watching any input services that match `req`.
    pub fn with_inputs(self, req: InputRequest) -> Self {
        WatchOptions {
            source: self.source.and(req),
            ..self
        }
    }

    pub fn with_watch_values(self, should: bool) -> Self {
        WatchOptions {
            should_watch_values: should,
            ..self
        }
    }

    pub fn with_watch_topology(self, should: bool) -> Self {
        WatchOptions {
            should_watch_topology: should,
            ..self
        }
    }
}
<|endoftext|>


<commit_before>/// Postgres database functions using shell commands and db drivers
use std;
use std::path::Path;
use super::*;

#[cfg(feature="postgresql")]
use std::io::Read;
#[cfg(feature="postgresql")]
use postgres::{Connection, TlsMode};

#[cfg(not(feature="postgresql"))]
use std::process::Command;

#[cfg(not(feature="postgresql"))]
fn psql_cmd(conn_str: &str, cmd: &str) -> Result<String> {
    let out = Command::new("psql")
                    .arg(conn_str)
                    .arg("-t")      // no headers or footer
                    .arg("-A")      // un-aligned output
                    .arg("-F,")     // comma separator
                    .arg("-c")
                    .arg(cmd)
                    .output()
                    .chain_err(|| format_err!(ErrorKind::ShellCommand,
                                              "Error running command `psql`. Is it available on your PATH?"))?;
    if !out.status.success() {
        let stderr = std::str::from_utf8(&out.stderr)?;
        bail_fmt!(ErrorKind::Migration, "Error executing statement, stderr: `{}`", stderr);
    }
    let stdout = String::from_utf8(out.stdout)?;
    Ok(stdout)
}

// --
// Check connection
// --
#[cfg(not(feature="postgresql"))]
pub fn can_connect(conn_str: &str) -> Result<bool> {
    let out = Command::new("psql")
                    .arg(conn_str)
                    .arg("-c")
                    .arg("")
                    .output()
                    .chain_err(|| "Error running command `psql`. Is it available on your PATH?")?;
    Ok(out.status.success())
}

#[cfg(feature="postgresql")]
pub fn can_connect(conn_str: &str) -> Result<bool> {
    match Connection::connect(conn_str, TlsMode::None) {
        Ok(_)   => Ok(true),
        Err(_)  => Ok(false)
    }
}

// --
// Check `__migrant_migrations` table exists
// --
#[cfg(not(feature="postgresql"))]
pub fn migration_table_exists(conn_str: &str) -> Result<bool> {
    let stdout = psql_cmd(conn_str, sql::PG_MIGRATION_TABLE_EXISTS)?;
    Ok(stdout.trim() == "t")
}

#[cfg(feature="postgresql")]
pub fn migration_table_exists(conn_str: &str) -> Result<bool> {
    let conn = Connection::connect(conn_str, TlsMode::None)
        .map_err(|e| format_err!(ErrorKind::Migration, "{}", e))?;
    let rows = conn.query(sql::PG_MIGRATION_TABLE_EXISTS, &[])
        .map_err(|e| format_err!(ErrorKind::Migration, "{}", e))?;
    let exists: bool = rows.iter().next().unwrap().get(0);
    Ok(exists)
}

// --
// Create `__migrant_migrations` table
// --
#[cfg(not(feature="postgresql"))]
pub fn migration_setup(conn_str: &str) -> Result<bool> {
    if !migration_table_exists(conn_str)? {
        psql_cmd(conn_str, sql::CREATE_TABLE)?;
        return Ok(true)
    }
    Ok(false)
}

#[cfg(feature="postgresql")]
pub fn migration_setup(conn_str: &str) -> Result<bool> {
    if !migration_table_exists(conn_str)? {
        let conn = Connection::connect(conn_str, TlsMode::None)
            .map_err(|e| format_err!(ErrorKind::Migration, "{}", e))?;
        conn.execute(sql::CREATE_TABLE, &[])
            .map_err(|e| format_err!(ErrorKind::Migration, "{}", e))?;
        return Ok(true)
    }
    Ok(false)
}

// --
// Select all migrations from `__migrant_migrations` table
// --
#[cfg(not(feature="postgresql"))]
pub fn select_migrations(conn_str: &str) -> Result<Vec<String>> {
    let stdout = psql_cmd(conn_str, sql::GET_MIGRATIONS)?;
    Ok(stdout.trim().lines().map(String::from).collect())
}

#[cfg(feature="postgresql")]
pub fn select_migrations(conn_str: &str) -> Result<Vec<String>> {
    let conn = Connection::connect(conn_str, TlsMode::None)?;
    let rows = conn.query(sql::GET_MIGRATIONS, &[])?;
    Ok(rows.iter().map(|row| row.get(0)).collect())
}

// --
// Insert migration tag into `__migrant_migrations` table
// --
#[cfg(not(feature="postgresql"))]
pub fn insert_migration_tag(conn_str: &str, tag: &str) -> Result<()> {
    psql_cmd(conn_str, &sql::PG_ADD_MIGRATION.replace("__VAL__", tag))?;
    Ok(())
}

#[cfg(feature="postgresql")]
pub fn insert_migration_tag(conn_str: &str, tag: &str) -> Result<()> {
    let conn = Connection::connect(conn_str, TlsMode::None)?;
    conn.execute("insert into __migrant_migrations (tag) values ($1)", &[&tag])?;
    Ok(())
}

// --
// Delete migration tag from `__migrant_migrations` table
// --
#[cfg(not(feature="postgresql"))]
pub fn remove_migration_tag(conn_str: &str, tag: &str) -> Result<()> {
    psql_cmd(conn_str, &sql::PG_DELETE_MIGRATION.replace("__VAL__", tag))?;
    Ok(())
}

#[cfg(feature="postgresql")]
pub fn remove_migration_tag(conn_str: &str, tag: &str) -> Result<()> {
    let conn = Connection::connect(conn_str, TlsMode::None)?;
    conn.execute("delete from __migrant_migrations where tag = $1", &[&tag])?;
    Ok(())
}

// --
// Apply migration to database
// --
#[cfg(not(feature="postgresql"))]
pub fn run_migration(conn_str: &str, filename: &Path) -> Result<()> {
    let filename = filename.to_str().ok_or_else(|| format_err!(ErrorKind::PathError, "Invalid file path: {:?}", filename))?;
    let migrate = Command::new("psql")
            .arg(&conn_str)
            .arg("-f").arg(filename)
            .output()?;
    if !migrate.status.success() {
        let stderr = std::str::from_utf8(&migrate.stderr)?;
        bail_fmt!(ErrorKind::Migration, "Error executing statement, stderr: `{}`", stderr);
    }
    Ok(())
}

#[cfg(feature="postgresql")]
pub fn run_migration(conn_str: &str, filename: &Path) -> Result<()> {
    let mut file = std::fs::File::open(filename)?;
    let mut buf = String::new();
    file.read_to_string(&mut buf)?;

    let conn = Connection::connect(conn_str, TlsMode::None)
        .map_err(|e| format_err!(ErrorKind::Migration, "{}", e))?;
    conn.batch_execute(&buf)
        .map_err(|e| format_err!(ErrorKind::Migration, "{}", e))?;
    Ok(())
}

#[cfg(test)]
mod test {
    use std;
    use super::*;
    macro_rules! _try {
        ($exp:expr) => {
            match $exp {
                Ok(v) => v,
                Err(e) => {
                    eprintln!("Caught: {}", e);
                    panic!(e)
                }
            }
        }
    }

    #[test]
    fn postgres() {
        let conn_str = std::env::var("POSTGRES_TEST_CONN_STR").unwrap();

        // no table before setup
        let is_setup = _try!(migration_table_exists(&conn_str));
        assert_eq!(false, is_setup);

        // setup migration table
        let was_setup = _try!(migration_setup(&conn_str));
        assert_eq!(true, was_setup);
        let was_setup = _try!(migration_setup(&conn_str));
        assert_eq!(false, was_setup);

        // table exists after setup
        let is_setup = _try!(migration_table_exists(&conn_str));
        assert!(is_setup);

        // insert some tags
        _try!(insert_migration_tag(&conn_str, "initial"));
        _try!(insert_migration_tag(&conn_str, "alter1"));
        _try!(insert_migration_tag(&conn_str, "alter2"));

        // get applied
        let migs = _try!(select_migrations(&conn_str));
        assert_eq!(3, migs.len());

        // remove some tags
        _try!(remove_migration_tag(&conn_str, "alter2"));
        let migs = _try!(select_migrations(&conn_str));
        assert_eq!(2, migs.len());
    }
}
<commit_msg>psql error exit status<commit_after>/// Postgres database functions using shell commands and db drivers
use std;
use std::path::Path;
use super::*;

#[cfg(feature="postgresql")]
use std::io::Read;
#[cfg(feature="postgresql")]
use postgres::{Connection, TlsMode};

#[cfg(not(feature="postgresql"))]
use std::process::Command;

#[cfg(not(feature="postgresql"))]
fn psql_cmd(conn_str: &str, cmd: &str) -> Result<String> {
    let out = Command::new("psql")
                    .arg(conn_str)
                    .arg("-v").arg("ON_ERROR_STOP=1")
                    .arg("-t")      // no headers or footer
                    .arg("-A")      // un-aligned output
                    .arg("-F,")     // comma separator
                    .arg("-c")
                    .arg(cmd)
                    .output()
                    .chain_err(|| format_err!(ErrorKind::ShellCommand,
                                              "Error running command `psql`. Is it available on your PATH?"))?;
    if !out.status.success() {
        let stderr = std::str::from_utf8(&out.stderr)?;
        bail_fmt!(ErrorKind::Migration, "Error executing statement, stderr: `{}`", stderr);
    }
    let stdout = String::from_utf8(out.stdout)?;
    Ok(stdout)
}

// --
// Check connection
// --
#[cfg(not(feature="postgresql"))]
pub fn can_connect(conn_str: &str) -> Result<bool> {
    let out = Command::new("psql")
                    .arg(conn_str)
                    .arg("-c")
                    .arg("")
                    .output()
                    .chain_err(|| "Error running command `psql`. Is it available on your PATH?")?;
    Ok(out.status.success())
}

#[cfg(feature="postgresql")]
pub fn can_connect(conn_str: &str) -> Result<bool> {
    match Connection::connect(conn_str, TlsMode::None) {
        Ok(_)   => Ok(true),
        Err(_)  => Ok(false)
    }
}

// --
// Check `__migrant_migrations` table exists
// --
#[cfg(not(feature="postgresql"))]
pub fn migration_table_exists(conn_str: &str) -> Result<bool> {
    let stdout = psql_cmd(conn_str, sql::PG_MIGRATION_TABLE_EXISTS)?;
    Ok(stdout.trim() == "t")
}

#[cfg(feature="postgresql")]
pub fn migration_table_exists(conn_str: &str) -> Result<bool> {
    let conn = Connection::connect(conn_str, TlsMode::None)
        .map_err(|e| format_err!(ErrorKind::Migration, "{}", e))?;
    let rows = conn.query(sql::PG_MIGRATION_TABLE_EXISTS, &[])
        .map_err(|e| format_err!(ErrorKind::Migration, "{}", e))?;
    let exists: bool = rows.iter().next().unwrap().get(0);
    Ok(exists)
}

// --
// Create `__migrant_migrations` table
// --
#[cfg(not(feature="postgresql"))]
pub fn migration_setup(conn_str: &str) -> Result<bool> {
    if !migration_table_exists(conn_str)? {
        psql_cmd(conn_str, sql::CREATE_TABLE)?;
        return Ok(true)
    }
    Ok(false)
}

#[cfg(feature="postgresql")]
pub fn migration_setup(conn_str: &str) -> Result<bool> {
    if !migration_table_exists(conn_str)? {
        let conn = Connection::connect(conn_str, TlsMode::None)
            .map_err(|e| format_err!(ErrorKind::Migration, "{}", e))?;
        conn.execute(sql::CREATE_TABLE, &[])
            .map_err(|e| format_err!(ErrorKind::Migration, "{}", e))?;
        return Ok(true)
    }
    Ok(false)
}

// --
// Select all migrations from `__migrant_migrations` table
// --
#[cfg(not(feature="postgresql"))]
pub fn select_migrations(conn_str: &str) -> Result<Vec<String>> {
    let stdout = psql_cmd(conn_str, sql::GET_MIGRATIONS)?;
    Ok(stdout.trim().lines().map(String::from).collect())
}

#[cfg(feature="postgresql")]
pub fn select_migrations(conn_str: &str) -> Result<Vec<String>> {
    let conn = Connection::connect(conn_str, TlsMode::None)?;
    let rows = conn.query(sql::GET_MIGRATIONS, &[])?;
    Ok(rows.iter().map(|row| row.get(0)).collect())
}

// --
// Insert migration tag into `__migrant_migrations` table
// --
#[cfg(not(feature="postgresql"))]
pub fn insert_migration_tag(conn_str: &str, tag: &str) -> Result<()> {
    psql_cmd(conn_str, &sql::PG_ADD_MIGRATION.replace("__VAL__", tag))?;
    Ok(())
}

#[cfg(feature="postgresql")]
pub fn insert_migration_tag(conn_str: &str, tag: &str) -> Result<()> {
    let conn = Connection::connect(conn_str, TlsMode::None)?;
    conn.execute("insert into __migrant_migrations (tag) values ($1)", &[&tag])?;
    Ok(())
}

// --
// Delete migration tag from `__migrant_migrations` table
// --
#[cfg(not(feature="postgresql"))]
pub fn remove_migration_tag(conn_str: &str, tag: &str) -> Result<()> {
    psql_cmd(conn_str, &sql::PG_DELETE_MIGRATION.replace("__VAL__", tag))?;
    Ok(())
}

#[cfg(feature="postgresql")]
pub fn remove_migration_tag(conn_str: &str, tag: &str) -> Result<()> {
    let conn = Connection::connect(conn_str, TlsMode::None)?;
    conn.execute("delete from __migrant_migrations where tag = $1", &[&tag])?;
    Ok(())
}

// --
// Apply migration to database
// --
#[cfg(not(feature="postgresql"))]
pub fn run_migration(conn_str: &str, filename: &Path) -> Result<()> {
    let filename = filename.to_str().ok_or_else(|| format_err!(ErrorKind::PathError, "Invalid file path: {:?}", filename))?;
    let migrate = Command::new("psql")
            .arg(&conn_str)
            .arg("-v").arg("ON_ERROR_STOP=1")
            .arg("-f").arg(filename)
            .output()?;
    if !migrate.status.success() {
        let stderr = std::str::from_utf8(&migrate.stderr)?;
        bail_fmt!(ErrorKind::Migration, "Error executing statement, stderr: `{}`", stderr);
    }
    Ok(())
}

#[cfg(feature="postgresql")]
pub fn run_migration(conn_str: &str, filename: &Path) -> Result<()> {
    let mut file = std::fs::File::open(filename)?;
    let mut buf = String::new();
    file.read_to_string(&mut buf)?;

    let conn = Connection::connect(conn_str, TlsMode::None)
        .map_err(|e| format_err!(ErrorKind::Migration, "{}", e))?;
    conn.batch_execute(&buf)
        .map_err(|e| format_err!(ErrorKind::Migration, "{}", e))?;
    Ok(())
}

#[cfg(test)]
mod test {
    use std;
    use super::*;
    macro_rules! _try {
        ($exp:expr) => {
            match $exp {
                Ok(v) => v,
                Err(e) => {
                    eprintln!("Caught: {}", e);
                    panic!(e)
                }
            }
        }
    }

    #[test]
    fn postgres() {
        let conn_str = std::env::var("POSTGRES_TEST_CONN_STR").unwrap();

        // no table before setup
        let is_setup = _try!(migration_table_exists(&conn_str));
        assert_eq!(false, is_setup);

        // setup migration table
        let was_setup = _try!(migration_setup(&conn_str));
        assert_eq!(true, was_setup);
        let was_setup = _try!(migration_setup(&conn_str));
        assert_eq!(false, was_setup);

        // table exists after setup
        let is_setup = _try!(migration_table_exists(&conn_str));
        assert!(is_setup);

        // insert some tags
        _try!(insert_migration_tag(&conn_str, "initial"));
        _try!(insert_migration_tag(&conn_str, "alter1"));
        _try!(insert_migration_tag(&conn_str, "alter2"));

        // get applied
        let migs = _try!(select_migrations(&conn_str));
        assert_eq!(3, migs.len());

        // remove some tags
        _try!(remove_migration_tag(&conn_str, "alter2"));
        let migs = _try!(select_migrations(&conn_str));
        assert_eq!(2, migs.len());
    }
}
<|endoftext|>