<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

use std::{
    clone::Clone,
    collections::HashMap,
    path::{Path, PathBuf},
};

use libudev;

use devicemapper::{Device, DmNameBuf};

#[cfg(test)]
use crate::engine::strat_engine::cleanup::teardown_pools;

use crate::{
    engine::{
        engine::Eventable,
        event::get_engine_listener_list,
        shared::create_pool_idempotent_or_err,
        strat_engine::{
            backstore::{find_all, get_metadata, identify_block_device},
            cmd::verify_binaries,
            devlinks,
            dm::{get_dm, get_dm_init},
            names::validate_name,
            pool::{check_metadata, StratPool},
        },
        structures::Table,
        types::{CreateAction, DeleteAction, RenameAction},
        Engine, EngineEvent, Name, Pool, PoolUuid,
    },
    stratis::{ErrorEnum, StratisError, StratisResult},
};

const REQUIRED_DM_MINOR_VERSION: u32 = 37;

/// Setup a pool from constituent devices in the context of some already
/// setup pools. Return an error on anything that prevents the pool
/// being set up.
/// Precondition: every device in devices has already been determined to belong
/// to the pool with pool_uuid.
fn setup_pool(
    pool_uuid: PoolUuid,
    devices: &HashMap<Device, PathBuf>,
    pools: &Table<StratPool>,
) -> StratisResult<(Name, StratPool)> {
    // FIXME: In this method, various errors are assembled from various
    // sources and combined into strings, so that they
    // can be printed as log messages if necessary. Instead, some kind of
    // error-chaining should be used here and if it is necessary
    // to log the error, the log code should be able to reduce the error
    // chain to something that can be sensibly logged.
    let info_string = || {
        let dev_paths = devices
            .values()
            .map(|p| p.to_str().expect("Unix is utf-8"))
            .collect::<Vec<&str>>()
            .join(" ,");
        format!("(pool UUID: {}, devnodes: {})", pool_uuid, dev_paths)
    };

    let (timestamp, metadata) = get_metadata(pool_uuid, devices)?.ok_or_else(|| {
        let err_msg = format!("no metadata found for {}", info_string());
        StratisError::Engine(ErrorEnum::NotFound, err_msg)
    })?;

    if pools.contains_name(&metadata.name) {
        let err_msg = format!(
            "pool with name \"{}\" set up; metadata specifies same name for {}",
            &metadata.name,
            info_string()
        );
        return Err(StratisError::Engine(ErrorEnum::AlreadyExists, err_msg));
    }

    check_metadata(&metadata)
        .or_else(|e| {
            let err_msg = format!(
                "inconsistent metadata for {}: reason: {:?}",
                info_string(),
                e
            );
            Err(StratisError::Engine(ErrorEnum::Error, err_msg))
        })
        .and_then(|_| {
            StratPool::setup(pool_uuid, devices, timestamp, &metadata).or_else(|e| {
                let err_msg = format!(
                    "failed to set up pool for {}: reason: {:?}",
                    info_string(),
                    e
                );
                Err(StratisError::Engine(ErrorEnum::Error, err_msg))
            })
        })
        .and_then(|(pool_name, pool)| {
            devlinks::setup_pool_devlinks(&pool_name, &pool);
            Ok((pool_name, pool))
        })
}

#[derive(Debug)]
pub struct StratEngine {
    pools: Table<StratPool>,

    // Map of stratis devices that have been found but one or more stratis block devices are missing
    // which prevents the associated pools from being setup.
    incomplete_pools: HashMap<PoolUuid, HashMap<Device, PathBuf>>,

    // Maps name of DM devices we are watching to the most recent event number
    // we've handled for each
    watched_dev_last_event_nrs: HashMap<DmNameBuf, u32>,
}

impl StratEngine {
    /// Setup a StratEngine.
    /// 1. Verify the existence of Stratis /dev directory.
    /// 2. Setup all the pools belonging to the engine.
    ///    a. Places any devices which belong to a pool, but are not complete
    ///       in the incomplete pools data structure.
    ///
    /// Returns an error if the kernel doesn't support required DM features.
    /// Returns an error if there was an error reading device nodes.
    /// Returns an error if the binaries on which it depends can not be found.
    pub fn initialize() -> StratisResult<StratEngine> {
        let dm = get_dm_init()?;
        verify_binaries()?;
        let minor_dm_version = dm.version()?.1;
        if minor_dm_version < REQUIRED_DM_MINOR_VERSION {
            let err_msg = format!(
                "Requires DM minor version {} but kernel only supports {}",
                REQUIRED_DM_MINOR_VERSION, minor_dm_version
            );
            return Err(StratisError::Engine(ErrorEnum::Error, err_msg));
        }

        devlinks::setup_dev_path()?;

        let pools = find_all()?;

        let mut table = Table::default();
        let mut incomplete_pools = HashMap::new();
        for (pool_uuid, devices) in pools {
            match setup_pool(pool_uuid, &devices, &table) {
                Ok((pool_name, pool)) => {
                    table.insert(pool_name, pool_uuid, pool);
                }
                Err(err) => {
                    warn!("no pool set up, reason: {:?}", err);
                    incomplete_pools.insert(pool_uuid, devices);
                }
            }
        }

        let engine = StratEngine {
            pools: table,
            incomplete_pools,
            watched_dev_last_event_nrs: HashMap::new(),
        };

        devlinks::cleanup_devlinks(engine.pools().iter());

        Ok(engine)
    }

    /// Teardown Stratis, preparatory to a shutdown.
    #[cfg(test)]
    pub fn teardown(self) -> StratisResult<()> {
        teardown_pools(self.pools)
    }

    /// Given a udev database entry, process the entry.
    ///
    /// If all the devices are present in the pool and the pool isn't already
    /// up and running, it will get setup and the newly created pool and UUID
    /// will be returned.
    ///
    /// Logs a warning if the block devices appears to be a Stratis block
    /// device and no pool is set up.
    fn block_evaluate(&mut self, device: &libudev::Device) -> Option<(PoolUuid, &mut dyn Pool)> {
        if let Some((pool_uuid, _, device, dev_node)) = identify_block_device(device) {
            if self.pools.contains_uuid(pool_uuid) {
                None
            } else {
                let mut devices = self
                    .incomplete_pools
                    .remove(&pool_uuid)
                    .or_else(|| Some(HashMap::new()))
                    .expect("We just retrieved or created a HashMap");
                devices.insert(device, dev_node);
                match setup_pool(pool_uuid, &devices, &self.pools) {
                    Ok((pool_name, pool)) => {
                        self.pools.insert(pool_name, pool_uuid, pool);
                        Some((
                            pool_uuid,
                            self.get_mut_pool(pool_uuid)
                                .expect("pool was just inserted")
                                .1,
                        ))
                    }
                    Err(err) => {
                        warn!("no pool set up, reason: {:?}", err);
                        self.incomplete_pools.insert(pool_uuid, devices);
                        None
                    }
                }
            }
        } else {
            None
        }
    }
}

impl Engine for StratEngine {
    fn handle_event(&mut self, event: &libudev::Event) -> Option<(PoolUuid, &mut dyn Pool)> {
        let event_type = event.event_type();
        if event_type == libudev::EventType::Add || event_type == libudev::EventType::Change {
            self.block_evaluate(event.device())
        } else {
            None
        }
    }

    fn create_pool(
        &mut self,
        name: &str,
        blockdev_paths: &[&Path],
        redundancy: Option<u16>,
    ) -> StratisResult<CreateAction<PoolUuid>> {
        let redundancy = calculate_redundancy!(redundancy);

        validate_name(name)?;

        match self.pools.get_by_name(name) {
            Some((_, pool)) => create_pool_idempotent_or_err(pool, name, blockdev_paths),
            None => {
                let (uuid, pool) = StratPool::initialize(name, blockdev_paths, redundancy)?;

                let name = Name::new(name.to_owned());
                devlinks::pool_added(&name);
                self.pools.insert(name, uuid, pool);
                Ok(CreateAction::Created(uuid))
            }
        }
    }

    fn destroy_pool(&mut self, uuid: PoolUuid) -> StratisResult<DeleteAction<PoolUuid>> {
        if let Some((_, pool)) = self.pools.get_by_uuid(uuid) {
            if pool.has_filesystems() {
                return Err(StratisError::Engine(
                    ErrorEnum::Busy,
                    "filesystems remaining on pool".into(),
                ));
            };
        } else {
            return Ok(DeleteAction::Identity);
        }

        let (pool_name, mut pool) = self
            .pools
            .remove_by_uuid(uuid)
            .expect("Must succeed since self.pools.get_by_uuid() returned a value");

        if let Err(err) = pool.destroy() {
            self.pools.insert(pool_name, uuid, pool);
            Err(err)
        } else {
            devlinks::pool_removed(&pool_name);
            Ok(DeleteAction::Deleted(uuid))
        }
    }

    fn rename_pool(
        &mut self,
        uuid: PoolUuid,
        new_name: &str,
    ) -> StratisResult<RenameAction<PoolUuid>> {
        validate_name(new_name)?;
        let old_name = rename_pool_pre_idem!(self; uuid; new_name);

        let (_, mut pool) = self
            .pools
            .remove_by_uuid(uuid)
            .expect("Must succeed since self.pools.get_by_uuid() returned a value");

        let new_name = Name::new(new_name.to_owned());
        if let Err(err) = pool.write_metadata(&new_name) {
            self.pools.insert(old_name, uuid, pool);
            Err(err)
        } else {
            get_engine_listener_list().notify(&EngineEvent::PoolRenamed {
                dbus_path: pool.get_dbus_path(),
                from: &*old_name,
                to: &*new_name,
            });

            self.pools.insert(new_name.clone(), uuid, pool);
            devlinks::pool_renamed(&old_name, &new_name);
            Ok(RenameAction::Renamed(uuid))
        }
    }

    fn get_pool(&self, uuid: PoolUuid) -> Option<(Name, &dyn Pool)> {
        get_pool!(self; uuid)
    }

    fn get_mut_pool(&mut self, uuid: PoolUuid) -> Option<(Name, &mut dyn Pool)> {
        get_mut_pool!(self; uuid)
    }

    fn configure_simulator(&mut self, _denominator: u32) -> StratisResult<()> {
        Ok(()) // we're not the simulator and not configurable, so just say ok
    }

    fn pools(&self) -> Vec<(Name, PoolUuid, &dyn Pool)> {
        self.pools
            .iter()
            .map(|(name, uuid, pool)| (name.clone(), *uuid, pool as &dyn Pool))
            .collect()
    }

    fn pools_mut(&mut self) -> Vec<(Name, PoolUuid, &mut dyn Pool)> {
        self.pools
            .iter_mut()
            .map(|(name, uuid, pool)| (name.clone(), *uuid, pool as &mut dyn Pool))
            .collect()
    }

    fn get_eventable(&self) -> Option<&'static dyn Eventable> {
        Some(get_dm())
    }

    fn evented(&mut self) -> StratisResult<()> {
        let device_list: HashMap<_, _> = get_dm()
            .list_devices()?
            .into_iter()
            .map(|(dm_name, _, event_nr)| {
                (
                    dm_name,
                    event_nr.expect("Supported DM versions always provide a value"),
                )
            })
            .collect();

        for (pool_name, pool_uuid, pool) in &mut self.pools {
            for dm_name in pool.get_eventing_dev_names(*pool_uuid) {
                if device_list.get(&dm_name) > self.watched_dev_last_event_nrs.get(&dm_name) {
                    pool.event_on(*pool_uuid, pool_name, &dm_name)?;
                }
            }
        }

        self.watched_dev_last_event_nrs = device_list;

        Ok(())
    }
}

#[cfg(test)]
mod test {
    use std::fs::remove_dir_all;

    use crate::engine::engine::DEV_PATH;

    use crate::engine::strat_engine::tests::{loopbacked, real};

    use crate::engine::types::EngineAction;

    use super::*;

    /// Verify that a pool rename causes the pool metadata to get the new name.
    fn test_pool_rename(paths: &[&Path]) {
        let mut engine = StratEngine::initialize().unwrap();

        let name1 = "name1";
        let uuid1 = engine
            .create_pool(name1, paths, None)
            .unwrap()
            .changed()
            .unwrap();

        let name2 = "name2";
        let action = engine.rename_pool(uuid1, name2).unwrap();

        assert_eq!(action, RenameAction::Renamed(uuid1));
        engine.teardown().unwrap();

        let engine = StratEngine::initialize().unwrap();
        let pool_name: String = engine.get_pool(uuid1).unwrap().0.to_owned();
        assert_eq!(pool_name, name2);
    }

    #[test]
    pub fn loop_test_pool_rename() {
        loopbacked::test_with_spec(
            &loopbacked::DeviceLimits::Range(1, 3, None),
            test_pool_rename,
        );
    }

    #[test]
    pub fn real_test_pool_rename() {
        real::test_with_spec(
            &real::DeviceLimits::AtLeast(1, None, None),
            test_pool_rename,
        );
    }

    /// Test engine setup.
    /// 1. Create two pools.
    /// 2. Verify that both exist.
    /// 3. Teardown the engine.
    /// 4. Initialize the engine.
    /// 5. Verify that pools can be found again.
    /// 6. Teardown the engine and remove "/stratis".
    /// 7. Initialize the engine one more time.
    /// 8. Verify that both pools are found and that there are no incomplete pools.
    fn test_setup(paths: &[&Path]) {
        assert!(paths.len() > 1);

        let (paths1, paths2) = paths.split_at(paths.len() / 2);

        let mut engine = StratEngine::initialize().unwrap();

        let name1 = "name1";
        let uuid1 = engine
            .create_pool(name1, paths1, None)
            .unwrap()
            .changed()
            .unwrap();

        let name2 = "name2";
        let uuid2 = engine
            .create_pool(name2, paths2, None)
            .unwrap()
            .changed()
            .unwrap();

        assert!(engine.get_pool(uuid1).is_some());
        assert!(engine.get_pool(uuid2).is_some());

        engine.teardown().unwrap();

        let engine = StratEngine::initialize().unwrap();

        assert!(engine.get_pool(uuid1).is_some());
        assert!(engine.get_pool(uuid2).is_some());

        engine.teardown().unwrap();
        remove_dir_all(DEV_PATH).unwrap();

        let engine = StratEngine::initialize().unwrap();
        assert_eq!(engine.incomplete_pools, HashMap::new());

        assert!(engine.get_pool(uuid1).is_some());
        assert!(engine.get_pool(uuid2).is_some());

        engine.teardown().unwrap();
    }

    #[test]
    pub fn loop_test_setup() {
        loopbacked::test_with_spec(&loopbacked::DeviceLimits::Range(2, 3, None), test_setup);
    }

    #[test]
    pub fn real_test_setup() {
        real::test_with_spec(&real::DeviceLimits::AtLeast(2, None, None), test_setup);
    }
}
<commit_msg>Use standard Option method and_then<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

use std::{
    clone::Clone,
    collections::HashMap,
    path::{Path, PathBuf},
};

use libudev;

use devicemapper::{Device, DmNameBuf};

#[cfg(test)]
use crate::engine::strat_engine::cleanup::teardown_pools;

use crate::{
    engine::{
        engine::Eventable,
        event::get_engine_listener_list,
        shared::create_pool_idempotent_or_err,
        strat_engine::{
            backstore::{find_all, get_metadata, identify_block_device},
            cmd::verify_binaries,
            devlinks,
            dm::{get_dm, get_dm_init},
            names::validate_name,
            pool::{check_metadata, StratPool},
        },
        structures::Table,
        types::{CreateAction, DeleteAction, RenameAction},
        Engine, EngineEvent, Name, Pool, PoolUuid,
    },
    stratis::{ErrorEnum, StratisError, StratisResult},
};

const REQUIRED_DM_MINOR_VERSION: u32 = 37;

/// Setup a pool from constituent devices in the context of some already
/// setup pools. Return an error on anything that prevents the pool
/// being set up.
/// Precondition: every device in devices has already been determined to belong
/// to the pool with pool_uuid.
fn setup_pool(
    pool_uuid: PoolUuid,
    devices: &HashMap<Device, PathBuf>,
    pools: &Table<StratPool>,
) -> StratisResult<(Name, StratPool)> {
    // FIXME: In this method, various errors are assembled from various
    // sources and combined into strings, so that they
    // can be printed as log messages if necessary. Instead, some kind of
    // error-chaining should be used here and if it is necessary
    // to log the error, the log code should be able to reduce the error
    // chain to something that can be sensibly logged.
    let info_string = || {
        let dev_paths = devices
            .values()
            .map(|p| p.to_str().expect("Unix is utf-8"))
            .collect::<Vec<&str>>()
            .join(" ,");
        format!("(pool UUID: {}, devnodes: {})", pool_uuid, dev_paths)
    };

    let (timestamp, metadata) = get_metadata(pool_uuid, devices)?.ok_or_else(|| {
        let err_msg = format!("no metadata found for {}", info_string());
        StratisError::Engine(ErrorEnum::NotFound, err_msg)
    })?;

    if pools.contains_name(&metadata.name) {
        let err_msg = format!(
            "pool with name \"{}\" set up; metadata specifies same name for {}",
            &metadata.name,
            info_string()
        );
        return Err(StratisError::Engine(ErrorEnum::AlreadyExists, err_msg));
    }

    check_metadata(&metadata)
        .or_else(|e| {
            let err_msg = format!(
                "inconsistent metadata for {}: reason: {:?}",
                info_string(),
                e
            );
            Err(StratisError::Engine(ErrorEnum::Error, err_msg))
        })
        .and_then(|_| {
            StratPool::setup(pool_uuid, devices, timestamp, &metadata).or_else(|e| {
                let err_msg = format!(
                    "failed to set up pool for {}: reason: {:?}",
                    info_string(),
                    e
                );
                Err(StratisError::Engine(ErrorEnum::Error, err_msg))
            })
        })
        .and_then(|(pool_name, pool)| {
            devlinks::setup_pool_devlinks(&pool_name, &pool);
            Ok((pool_name, pool))
        })
}

#[derive(Debug)]
pub struct StratEngine {
    pools: Table<StratPool>,

    // Map of stratis devices that have been found but one or more stratis block devices are missing
    // which prevents the associated pools from being setup.
    incomplete_pools: HashMap<PoolUuid, HashMap<Device, PathBuf>>,

    // Maps name of DM devices we are watching to the most recent event number
    // we've handled for each
    watched_dev_last_event_nrs: HashMap<DmNameBuf, u32>,
}

impl StratEngine {
    /// Setup a StratEngine.
    /// 1. Verify the existence of Stratis /dev directory.
    /// 2. Setup all the pools belonging to the engine.
    ///    a. Places any devices which belong to a pool, but are not complete
    ///       in the incomplete pools data structure.
    ///
    /// Returns an error if the kernel doesn't support required DM features.
    /// Returns an error if there was an error reading device nodes.
    /// Returns an error if the binaries on which it depends can not be found.
    pub fn initialize() -> StratisResult<StratEngine> {
        let dm = get_dm_init()?;
        verify_binaries()?;
        let minor_dm_version = dm.version()?.1;
        if minor_dm_version < REQUIRED_DM_MINOR_VERSION {
            let err_msg = format!(
                "Requires DM minor version {} but kernel only supports {}",
                REQUIRED_DM_MINOR_VERSION, minor_dm_version
            );
            return Err(StratisError::Engine(ErrorEnum::Error, err_msg));
        }

        devlinks::setup_dev_path()?;

        let pools = find_all()?;

        let mut table = Table::default();
        let mut incomplete_pools = HashMap::new();
        for (pool_uuid, devices) in pools {
            match setup_pool(pool_uuid, &devices, &table) {
                Ok((pool_name, pool)) => {
                    table.insert(pool_name, pool_uuid, pool);
                }
                Err(err) => {
                    warn!("no pool set up, reason: {:?}", err);
                    incomplete_pools.insert(pool_uuid, devices);
                }
            }
        }

        let engine = StratEngine {
            pools: table,
            incomplete_pools,
            watched_dev_last_event_nrs: HashMap::new(),
        };

        devlinks::cleanup_devlinks(engine.pools().iter());

        Ok(engine)
    }

    /// Teardown Stratis, preparatory to a shutdown.
    #[cfg(test)]
    pub fn teardown(self) -> StratisResult<()> {
        teardown_pools(self.pools)
    }

    /// Given a udev database entry, process the entry.
    ///
    /// If all the devices are present in the pool and the pool isn't already
    /// up and running, it will get setup and the newly created pool and UUID
    /// will be returned.
    ///
    /// Logs a warning if the block devices appears to be a Stratis block
    /// device and no pool is set up.
    fn block_evaluate(&mut self, device: &libudev::Device) -> Option<(PoolUuid, &mut dyn Pool)> {
        identify_block_device(device).and_then(move |(pool_uuid, _, device, dev_node)| {
            if self.pools.contains_uuid(pool_uuid) {
                None
            } else {
                let mut devices = self
                    .incomplete_pools
                    .remove(&pool_uuid)
                    .or_else(|| Some(HashMap::new()))
                    .expect("We just retrieved or created a HashMap");
                devices.insert(device, dev_node);
                match setup_pool(pool_uuid, &devices, &self.pools) {
                    Ok((pool_name, pool)) => {
                        self.pools.insert(pool_name, pool_uuid, pool);
                        Some((
                            pool_uuid,
                            self.get_mut_pool(pool_uuid)
                                .expect("pool was just inserted")
                                .1,
                        ))
                    }
                    Err(err) => {
                        warn!("no pool set up, reason: {:?}", err);
                        self.incomplete_pools.insert(pool_uuid, devices);
                        None
                    }
                }
            }
        })
    }
}

impl Engine for StratEngine {
    fn handle_event(&mut self, event: &libudev::Event) -> Option<(PoolUuid, &mut dyn Pool)> {
        let event_type = event.event_type();
        if event_type == libudev::EventType::Add || event_type == libudev::EventType::Change {
            self.block_evaluate(event.device())
        } else {
            None
        }
    }

    fn create_pool(
        &mut self,
        name: &str,
        blockdev_paths: &[&Path],
        redundancy: Option<u16>,
    ) -> StratisResult<CreateAction<PoolUuid>> {
        let redundancy = calculate_redundancy!(redundancy);

        validate_name(name)?;

        match self.pools.get_by_name(name) {
            Some((_, pool)) => create_pool_idempotent_or_err(pool, name, blockdev_paths),
            None => {
                let (uuid, pool) = StratPool::initialize(name, blockdev_paths, redundancy)?;

                let name = Name::new(name.to_owned());
                devlinks::pool_added(&name);
                self.pools.insert(name, uuid, pool);
                Ok(CreateAction::Created(uuid))
            }
        }
    }

    fn destroy_pool(&mut self, uuid: PoolUuid) -> StratisResult<DeleteAction<PoolUuid>> {
        if let Some((_, pool)) = self.pools.get_by_uuid(uuid) {
            if pool.has_filesystems() {
                return Err(StratisError::Engine(
                    ErrorEnum::Busy,
                    "filesystems remaining on pool".into(),
                ));
            };
        } else {
            return Ok(DeleteAction::Identity);
        }

        let (pool_name, mut pool) = self
            .pools
            .remove_by_uuid(uuid)
            .expect("Must succeed since self.pools.get_by_uuid() returned a value");

        if let Err(err) = pool.destroy() {
            self.pools.insert(pool_name, uuid, pool);
            Err(err)
        } else {
            devlinks::pool_removed(&pool_name);
            Ok(DeleteAction::Deleted(uuid))
        }
    }

    fn rename_pool(
        &mut self,
        uuid: PoolUuid,
        new_name: &str,
    ) -> StratisResult<RenameAction<PoolUuid>> {
        validate_name(new_name)?;
        let old_name = rename_pool_pre_idem!(self; uuid; new_name);

        let (_, mut pool) = self
            .pools
            .remove_by_uuid(uuid)
            .expect("Must succeed since self.pools.get_by_uuid() returned a value");

        let new_name = Name::new(new_name.to_owned());
        if let Err(err) = pool.write_metadata(&new_name) {
            self.pools.insert(old_name, uuid, pool);
            Err(err)
        } else {
            get_engine_listener_list().notify(&EngineEvent::PoolRenamed {
                dbus_path: pool.get_dbus_path(),
                from: &*old_name,
                to: &*new_name,
            });

            self.pools.insert(new_name.clone(), uuid, pool);
            devlinks::pool_renamed(&old_name, &new_name);
            Ok(RenameAction::Renamed(uuid))
        }
    }

    fn get_pool(&self, uuid: PoolUuid) -> Option<(Name, &dyn Pool)> {
        get_pool!(self; uuid)
    }

    fn get_mut_pool(&mut self, uuid: PoolUuid) -> Option<(Name, &mut dyn Pool)> {
        get_mut_pool!(self; uuid)
    }

    fn configure_simulator(&mut self, _denominator: u32) -> StratisResult<()> {
        Ok(()) // we're not the simulator and not configurable, so just say ok
    }

    fn pools(&self) -> Vec<(Name, PoolUuid, &dyn Pool)> {
        self.pools
            .iter()
            .map(|(name, uuid, pool)| (name.clone(), *uuid, pool as &dyn Pool))
            .collect()
    }

    fn pools_mut(&mut self) -> Vec<(Name, PoolUuid, &mut dyn Pool)> {
        self.pools
            .iter_mut()
            .map(|(name, uuid, pool)| (name.clone(), *uuid, pool as &mut dyn Pool))
            .collect()
    }

    fn get_eventable(&self) -> Option<&'static dyn Eventable> {
        Some(get_dm())
    }

    fn evented(&mut self) -> StratisResult<()> {
        let device_list: HashMap<_, _> = get_dm()
            .list_devices()?
            .into_iter()
            .map(|(dm_name, _, event_nr)| {
                (
                    dm_name,
                    event_nr.expect("Supported DM versions always provide a value"),
                )
            })
            .collect();

        for (pool_name, pool_uuid, pool) in &mut self.pools {
            for dm_name in pool.get_eventing_dev_names(*pool_uuid) {
                if device_list.get(&dm_name) > self.watched_dev_last_event_nrs.get(&dm_name) {
                    pool.event_on(*pool_uuid, pool_name, &dm_name)?;
                }
            }
        }

        self.watched_dev_last_event_nrs = device_list;

        Ok(())
    }
}

#[cfg(test)]
mod test {
    use std::fs::remove_dir_all;

    use crate::engine::engine::DEV_PATH;

    use crate::engine::strat_engine::tests::{loopbacked, real};

    use crate::engine::types::EngineAction;

    use super::*;

    /// Verify that a pool rename causes the pool metadata to get the new name.
    fn test_pool_rename(paths: &[&Path]) {
        let mut engine = StratEngine::initialize().unwrap();

        let name1 = "name1";
        let uuid1 = engine
            .create_pool(name1, paths, None)
            .unwrap()
            .changed()
            .unwrap();

        let name2 = "name2";
        let action = engine.rename_pool(uuid1, name2).unwrap();

        assert_eq!(action, RenameAction::Renamed(uuid1));
        engine.teardown().unwrap();

        let engine = StratEngine::initialize().unwrap();
        let pool_name: String = engine.get_pool(uuid1).unwrap().0.to_owned();
        assert_eq!(pool_name, name2);
    }

    #[test]
    pub fn loop_test_pool_rename() {
        loopbacked::test_with_spec(
            &loopbacked::DeviceLimits::Range(1, 3, None),
            test_pool_rename,
        );
    }

    #[test]
    pub fn real_test_pool_rename() {
        real::test_with_spec(
            &real::DeviceLimits::AtLeast(1, None, None),
            test_pool_rename,
        );
    }

    /// Test engine setup.
    /// 1. Create two pools.
    /// 2. Verify that both exist.
    /// 3. Teardown the engine.
    /// 4. Initialize the engine.
    /// 5. Verify that pools can be found again.
    /// 6. Teardown the engine and remove "/stratis".
    /// 7. Initialize the engine one more time.
    /// 8. Verify that both pools are found and that there are no incomplete pools.
    fn test_setup(paths: &[&Path]) {
        assert!(paths.len() > 1);

        let (paths1, paths2) = paths.split_at(paths.len() / 2);

        let mut engine = StratEngine::initialize().unwrap();

        let name1 = "name1";
        let uuid1 = engine
            .create_pool(name1, paths1, None)
            .unwrap()
            .changed()
            .unwrap();

        let name2 = "name2";
        let uuid2 = engine
            .create_pool(name2, paths2, None)
            .unwrap()
            .changed()
            .unwrap();

        assert!(engine.get_pool(uuid1).is_some());
        assert!(engine.get_pool(uuid2).is_some());

        engine.teardown().unwrap();

        let engine = StratEngine::initialize().unwrap();

        assert!(engine.get_pool(uuid1).is_some());
        assert!(engine.get_pool(uuid2).is_some());

        engine.teardown().unwrap();
        remove_dir_all(DEV_PATH).unwrap();

        let engine = StratEngine::initialize().unwrap();
        assert_eq!(engine.incomplete_pools, HashMap::new());

        assert!(engine.get_pool(uuid1).is_some());
        assert!(engine.get_pool(uuid2).is_some());

        engine.teardown().unwrap();
    }

    #[test]
    pub fn loop_test_setup() {
        loopbacked::test_with_spec(&loopbacked::DeviceLimits::Range(2, 3, None), test_setup);
    }

    #[test]
    pub fn real_test_setup() {
        real::test_with_spec(&real::DeviceLimits::AtLeast(2, None, None), test_setup);
    }
}
<|endoftext|>


<commit_before>//! Optional handlers for the Iron framework. Requires the `iron-handlers` feature enabled.

use iron::prelude::*;
use iron::middleware::Handler;
use iron::mime::Mime;
use iron::status;
use iron::method;

use std::collections::{HashMap, BTreeMap};

use rustc_serialize::json::{ToJson, Json};

use ::{InputValue, GraphQLType, RootNode, execute};

/// Handler that executes GraphQL queries in the given schema
///
/// The handler responds to GET requests and POST requests only. In GET
/// requests, the query should be supplied in the `query` URL parameter, e.g.
/// `http://localhost:3000/graphql?query={hero{name}}`.
///
/// POST requests support both queries and variables. POST a JSON document to
/// this endpoint containing the field `"query"` and optionally `"variables"`.
/// The variables should be a JSON object containing the variable to value
/// mapping.
pub struct GraphQLHandler<'a, CtxFactory, Query, Mutation, CtxT>
    where CtxFactory: Fn(&mut Request) -> CtxT + Send + Sync + 'static,
          CtxT: 'static,
          Query: GraphQLType<Context=CtxT> + Send + Sync + 'static,
          Mutation: GraphQLType<Context=CtxT> + Send + Sync + 'static,
{
    context_factory: CtxFactory,
    root_node: RootNode<'a, Query, Mutation>,
}

/// Handler that renders GraphiQL - a graphical query editor interface
pub struct GraphiQLHandler {
    graphql_url: String,
}

impl<'a, CtxFactory, Query, Mutation, CtxT>
    GraphQLHandler<'a, CtxFactory, Query, Mutation, CtxT>
    where CtxFactory: Fn(&mut Request) -> CtxT + Send + Sync + 'static,
          CtxT: 'static,
          Query: GraphQLType<Context=CtxT> + Send + Sync + 'static,
          Mutation: GraphQLType<Context=CtxT> + Send + Sync + 'static,
{
    /// Build a new GraphQL handler
    ///
    /// The context factory will receive the Iron request object and is
    /// expected to construct a context object for the given schema. This can
    /// be used to construct e.g. database connections or similar data that
    /// the schema needs to execute the query.
    pub fn new(context_factory: CtxFactory, query: Query, mutation: Mutation) -> Self {
        GraphQLHandler {
            context_factory: context_factory,
            root_node: RootNode::new(query, mutation),
        }
    }

    fn handle_get(&self, req: &mut Request) -> IronResult<Response> {
        let url = req.url.clone().into_generic_url();

        let mut query = None;
        let variables = HashMap::new();

        for (k, v) in url.query_pairs() {
            if k == "query" {
                query = Some(v.into_owned());
            }
        }

        let query = iexpect!(query);

        self.execute(req, &query, &variables)
    }

    fn handle_post(&self, req: &mut Request) -> IronResult<Response> {
        let json_data = itry!(Json::from_reader(&mut req.body));

        let json_obj = match json_data {
            Json::Object(o) => o,
            _ => return Ok(Response::with((status::BadRequest, "No JSON object was decoded"))),
        };

        let mut query = None;
        let mut variables = HashMap::new();

        for (k, v) in json_obj.into_iter() {
            if k == "query" {
                query = v.as_string().map(|s| s.to_owned());
            }
            else if k == "variables" {
                variables = match InputValue::from_json(v).to_object_value() {
                    Some(o) => o.into_iter().map(|(k, v)| (k.to_owned(), v.clone())).collect(),
                    _ => HashMap::new(),
                };
            }
        }

        let query = iexpect!(query);

        self.execute(req, &query, &variables)
    }

    fn execute(&self, req: &mut Request, query: &str, variables: &HashMap<String, InputValue>) -> IronResult<Response> {
        let context = (self.context_factory)(req);
        let result = execute(query, None, &self.root_node, variables, &context);

        let content_type = "application/json".parse::<Mime>().unwrap();

        match result {
            Ok((result, errors)) => {
                let mut map = BTreeMap::new();
                map.insert("data".to_owned(), result.to_json());
                if !errors.is_empty() {
                    map.insert("errors".to_owned(), errors.to_json());
                }

                let data = Json::Object(map);
                let json = data.pretty();

                Ok(Response::with((content_type, status::Ok, json.to_string())))
            }

            Err(err) => {
                let data = err.to_json();
                let json = data.pretty();

                Ok(Response::with((content_type, status::BadRequest, json.to_string())))
            }
        }
    }
}

impl GraphiQLHandler {
    /// Build a new GraphiQL handler targeting the specified URL.
    ///
    /// The provided URL should point to the URL of the attached `GraphQLHandler`. It can be
    /// relative, so a common value could be `"/graphql"`.
    pub fn new(graphql_url: &str) -> GraphiQLHandler {
        GraphiQLHandler {
            graphql_url: graphql_url.to_owned(),
        }
    }
}

impl<'a, CtxFactory, Query, Mutation, CtxT>
    Handler
    for GraphQLHandler<'a, CtxFactory, Query, Mutation, CtxT>
    where CtxFactory: Fn(&mut Request) -> CtxT + Send + Sync + 'static,
          CtxT: 'static,
          Query: GraphQLType<Context=CtxT> + Send + Sync + 'static,
          Mutation: GraphQLType<Context=CtxT> + Send + Sync + 'static, 'a: 'static,
{
    fn handle(&self, req: &mut Request) -> IronResult<Response> {
        match req.method {
            method::Get => self.handle_get(req),
            method::Post => self.handle_post(req),
            _ => Ok(Response::with((status::MethodNotAllowed)))
        }
    }
}

impl Handler for GraphiQLHandler {
    fn handle(&self, _: &mut Request) -> IronResult<Response> {
        let content_type = "text/html".parse::<Mime>().unwrap();

        let stylesheet_source = r#"

        "#;

        let fetcher_source = r#"

        "#;

        let source = format!(r#"
<!DOCTYPE html>

        <title>GraphQL</title>
        {stylesheet_source}
        <link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/graphiql/0.7.3/graphiql.css">

        {fetcher_source}

"#,
            graphql_url = self.graphql_url,
            stylesheet_source = stylesheet_source,
            fetcher_source = fetcher_source);

        Ok(Response::with((content_type, status::Ok, source)))
    }
}

#[cfg(test)]
mod tests {
    use rustc_serialize::json::Json;

    use iron::prelude::*;
    use iron::status;
    use iron::headers;
    use iron_test::{request, response};
    use iron::{Handler, Headers};

    use ::tests::model::Database;
    use types::scalars::EmptyMutation;

    use super::GraphQLHandler;

    fn context_factory(_: &mut Request) -> Database {
        Database::new()
    }

    fn make_handler() -> Box<Handler> {
        Box::new(GraphQLHandler::new(
            context_factory,
            Database::new(),
            EmptyMutation::<Database>::new(),
        ))
    }

    fn unwrap_json_response(resp: Response) -> Json {
        let result = response::extract_body_to_string(resp);

        Json::from_str(&result).expect("Could not parse JSON object")
    }

    #[test]
    fn test_simple_get() {
        let response = request::get(
            "http://localhost:3000/?query={hero{name}}",
            Headers::new(),
            &make_handler())
            .expect("Unexpected IronError");

        assert_eq!(response.status, Some(status::Ok));
        assert_eq!(response.headers.get::<headers::ContentType>(),
                   Some(&headers::ContentType::json()));

        let json = unwrap_json_response(response);

        assert_eq!(
            json,
            Json::from_str(r#"{"data": {"hero": {"name": "R2-D2"}}}"#)
                .expect("Invalid JSON constant in test"));
    }

    #[test]
    fn test_simple_post() {
        let response = request::post(
            "http://localhost:3000/",
            Headers::new(),
            r#"{"query": "{hero{name}}"}"#,
            &make_handler())
            .expect("Unexpected IronError");

        assert_eq!(response.status, Some(status::Ok));
        assert_eq!(response.headers.get::<headers::ContentType>(),
                   Some(&headers::ContentType::json()));

        let json = unwrap_json_response(response);

        assert_eq!(
            json,
            Json::from_str(r#"{"data": {"hero": {"name": "R2-D2"}}}"#)
                .expect("Invalid JSON constant in test"));
    }

    #[test]
    fn test_unsupported_method() {
        let response = request::options(
            "http://localhost:3000/?query={hero{name}}",
            Headers::new(),
            &make_handler())
            .expect("Unexpected IronError");

        assert_eq!(response.status, Some(status::MethodNotAllowed));
    }
}
<commit_msg>Update to graphiql 0.8.1<commit_after>//! Optional handlers for the Iron framework. Requires the `iron-handlers` feature enabled.

use iron::prelude::*;
use iron::middleware::Handler;
use iron::mime::Mime;
use iron::status;
use iron::method;

use std::collections::{HashMap, BTreeMap};

use rustc_serialize::json::{ToJson, Json};

use ::{InputValue, GraphQLType, RootNode, execute};

/// Handler that executes GraphQL queries in the given schema
///
/// The handler responds to GET requests and POST requests only. In GET
/// requests, the query should be supplied in the `query` URL parameter, e.g.
/// `http://localhost:3000/graphql?query={hero{name}}`.
///
/// POST requests support both queries and variables. POST a JSON document to
/// this endpoint containing the field `"query"` and optionally `"variables"`.
/// The variables should be a JSON object containing the variable to value
/// mapping.
pub struct GraphQLHandler<'a, CtxFactory, Query, Mutation, CtxT>
    where CtxFactory: Fn(&mut Request) -> CtxT + Send + Sync + 'static,
          CtxT: 'static,
          Query: GraphQLType<Context=CtxT> + Send + Sync + 'static,
          Mutation: GraphQLType<Context=CtxT> + Send + Sync + 'static,
{
    context_factory: CtxFactory,
    root_node: RootNode<'a, Query, Mutation>,
}

/// Handler that renders GraphiQL - a graphical query editor interface
pub struct GraphiQLHandler {
    graphql_url: String,
}

impl<'a, CtxFactory, Query, Mutation, CtxT>
    GraphQLHandler<'a, CtxFactory, Query, Mutation, CtxT>
    where CtxFactory: Fn(&mut Request) -> CtxT + Send + Sync + 'static,
          CtxT: 'static,
          Query: GraphQLType<Context=CtxT> + Send + Sync + 'static,
          Mutation: GraphQLType<Context=CtxT> + Send + Sync + 'static,
{
    /// Build a new GraphQL handler
    ///
    /// The context factory will receive the Iron request object and is
    /// expected to construct a context object for the given schema. This can
    /// be used to construct e.g. database connections or similar data that
    /// the schema needs to execute the query.
    pub fn new(context_factory: CtxFactory, query: Query, mutation: Mutation) -> Self {
        GraphQLHandler {
            context_factory: context_factory,
            root_node: RootNode::new(query, mutation),
        }
    }

    fn handle_get(&self, req: &mut Request) -> IronResult<Response> {
        let url = req.url.clone().into_generic_url();

        let mut query = None;
        let variables = HashMap::new();

        for (k, v) in url.query_pairs() {
            if k == "query" {
                query = Some(v.into_owned());
            }
        }

        let query = iexpect!(query);

        self.execute(req, &query, &variables)
    }

    fn handle_post(&self, req: &mut Request) -> IronResult<Response> {
        let json_data = itry!(Json::from_reader(&mut req.body));

        let json_obj = match json_data {
            Json::Object(o) => o,
            _ => return Ok(Response::with((status::BadRequest, "No JSON object was decoded"))),
        };

        let mut query = None;
        let mut variables = HashMap::new();

        for (k, v) in json_obj.into_iter() {
            if k == "query" {
                query = v.as_string().map(|s| s.to_owned());
            }
            else if k == "variables" {
                variables = match InputValue::from_json(v).to_object_value() {
                    Some(o) => o.into_iter().map(|(k, v)| (k.to_owned(), v.clone())).collect(),
                    _ => HashMap::new(),
                };
            }
        }

        let query = iexpect!(query);

        self.execute(req, &query, &variables)
    }

    fn execute(&self, req: &mut Request, query: &str, variables: &HashMap<String, InputValue>) -> IronResult<Response> {
        let context = (self.context_factory)(req);
        let result = execute(query, None, &self.root_node, variables, &context);

        let content_type = "application/json".parse::<Mime>().unwrap();

        match result {
            Ok((result, errors)) => {
                let mut map = BTreeMap::new();
                map.insert("data".to_owned(), result.to_json());
                if !errors.is_empty() {
                    map.insert("errors".to_owned(), errors.to_json());
                }

                let data = Json::Object(map);
                let json = data.pretty();

                Ok(Response::with((content_type, status::Ok, json.to_string())))
            }

            Err(err) => {
                let data = err.to_json();
                let json = data.pretty();

                Ok(Response::with((content_type, status::BadRequest, json.to_string())))
            }
        }
    }
}

impl GraphiQLHandler {
    /// Build a new GraphiQL handler targeting the specified URL.
    ///
    /// The provided URL should point to the URL of the attached `GraphQLHandler`. It can be
    /// relative, so a common value could be `"/graphql"`.
    pub fn new(graphql_url: &str) -> GraphiQLHandler {
        GraphiQLHandler {
            graphql_url: graphql_url.to_owned(),
        }
    }
}

impl<'a, CtxFactory, Query, Mutation, CtxT>
    Handler
    for GraphQLHandler<'a, CtxFactory, Query, Mutation, CtxT>
    where CtxFactory: Fn(&mut Request) -> CtxT + Send + Sync + 'static,
          CtxT: 'static,
          Query: GraphQLType<Context=CtxT> + Send + Sync + 'static,
          Mutation: GraphQLType<Context=CtxT> + Send + Sync + 'static, 'a: 'static,
{
    fn handle(&self, req: &mut Request) -> IronResult<Response> {
        match req.method {
            method::Get => self.handle_get(req),
            method::Post => self.handle_post(req),
            _ => Ok(Response::with((status::MethodNotAllowed)))
        }
    }
}

impl Handler for GraphiQLHandler {
    fn handle(&self, _: &mut Request) -> IronResult<Response> {
        let content_type = "text/html".parse::<Mime>().unwrap();

        let stylesheet_source = r#"

        "#;

        let fetcher_source = r#"

        "#;

        let source = format!(r#"
<!DOCTYPE html>

        <title>GraphQL</title>
        {stylesheet_source}
        <link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/graphiql/0.8.1/graphiql.css">

        {fetcher_source}

"#,
            graphql_url = self.graphql_url,
            stylesheet_source = stylesheet_source,
            fetcher_source = fetcher_source);

        Ok(Response::with((content_type, status::Ok, source)))
    }
}

#[cfg(test)]
mod tests {
    use rustc_serialize::json::Json;

    use iron::prelude::*;
    use iron::status;
    use iron::headers;
    use iron_test::{request, response};
    use iron::{Handler, Headers};

    use ::tests::model::Database;
    use types::scalars::EmptyMutation;

    use super::GraphQLHandler;

    fn context_factory(_: &mut Request) -> Database {
        Database::new()
    }

    fn make_handler() -> Box<Handler> {
        Box::new(GraphQLHandler::new(
            context_factory,
            Database::new(),
            EmptyMutation::<Database>::new(),
        ))
    }

    fn unwrap_json_response(resp: Response) -> Json {
        let result = response::extract_body_to_string(resp);

        Json::from_str(&result).expect("Could not parse JSON object")
    }

    #[test]
    fn test_simple_get() {
        let response = request::get(
            "http://localhost:3000/?query={hero{name}}",
            Headers::new(),
            &make_handler())
            .expect("Unexpected IronError");

        assert_eq!(response.status, Some(status::Ok));
        assert_eq!(response.headers.get::<headers::ContentType>(),
                   Some(&headers::ContentType::json()));

        let json = unwrap_json_response(response);

        assert_eq!(
            json,
            Json::from_str(r#"{"data": {"hero": {"name": "R2-D2"}}}"#)
                .expect("Invalid JSON constant in test"));
    }

    #[test]
    fn test_simple_post() {
        let response = request::post(
            "http://localhost:3000/",
            Headers::new(),
            r#"{"query": "{hero{name}}"}"#,
            &make_handler())
            .expect("Unexpected IronError");

        assert_eq!(response.status, Some(status::Ok));
        assert_eq!(response.headers.get::<headers::ContentType>(),
                   Some(&headers::ContentType::json()));

        let json = unwrap_json_response(response);

        assert_eq!(
            json,
            Json::from_str(r#"{"data": {"hero": {"name": "R2-D2"}}}"#)
                .expect("Invalid JSON constant in test"));
    }

    #[test]
    fn test_unsupported_method() {
        let response = request::options(
            "http://localhost:3000/?query={hero{name}}",
            Headers::new(),
            &make_handler())
            .expect("Unexpected IronError");

        assert_eq!(response.status, Some(status::MethodNotAllowed));
    }
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use cssparser::Parser;
use media_queries::CSSErrorReporterTest;
use style::parser::ParserContext;
use style::stylesheets::Origin;
use url::Url;

#[test]
fn font_feature_settings_should_parse_properly() {
    use style::properties::longhands::font_feature_settings;
    use style::properties::longhands::font_feature_settings::computed_value;
    use style::properties::longhands::font_feature_settings::computed_value::FeatureTagValue;

    let normal = parse_longhand!(font_feature_settings, "normal");
    let normal_computed = computed_value::T::Normal;
    assert_eq!(normal, normal_computed);

    let on = parse_longhand!(font_feature_settings, "\"abcd\" on");
    let on_computed = computed_value::T::Computed(vec![FeatureTagValue{ tag:String::from("abcd"), value: 1 }]);
    assert_eq!(on, on_computed);

    let off = parse_longhand!(font_feature_settings, "\"abcd\" off");
    let off_computed = computed_value::T::Computed(vec![FeatureTagValue{ tag:String::from("abcd"), value: 0 }]);
    assert_eq!(off, off_computed);

    let empty = parse_longhand!(font_feature_settings, "\"abcd\"");
    let empty_computed = computed_value::T::Computed(vec![FeatureTagValue{ tag:String::from("abcd"), value: 1 }]);
    assert_eq!(empty, empty_computed);

    let pos_integer = parse_longhand!(font_feature_settings, "\"abcd\" 100");
    let pos_integer_computed = computed_value::T::Computed(vec![FeatureTagValue{ tag:String::from("abcd"), value: 100 }]);
    assert_eq!(pos_integer, pos_integer_computed);

    let pos_integer = parse_longhand!(font_feature_settings, "\"abcd\" off, \"efgh\"");
    let pos_integer_computed = computed_value::T::Computed(vec![
        FeatureTagValue{ tag:String::from("abcd"), value: 0 },
        FeatureTagValue{ tag:String::from("efgh"), value: 1 }
    ]);
    assert_eq!(pos_integer, pos_integer_computed);
}
<commit_msg>Tests cleanup<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use cssparser::Parser;
use media_queries::CSSErrorReporterTest;
use style::parser::ParserContext;
use style::stylesheets::Origin;
use url::Url;

#[test]
fn font_feature_settings_should_parse_properly() {
    use style::properties::longhands::font_feature_settings;
    use style::properties::longhands::font_feature_settings::computed_value;
    use style::properties::longhands::font_feature_settings::computed_value::FeatureTagValue;

    let normal = parse_longhand!(font_feature_settings, "normal");
    let normal_computed = computed_value::T::Normal;
    assert_eq!(normal, normal_computed);

    let on = parse_longhand!(font_feature_settings, "\"abcd\" on");
    let on_computed = computed_value::T::Computed(vec![
        FeatureTagValue { tag: String::from("abcd"), value: 1 }
    ]);
    assert_eq!(on, on_computed);

    let off = parse_longhand!(font_feature_settings, "\"abcd\" off");
    let off_computed = computed_value::T::Computed(vec![
        FeatureTagValue { tag: String::from("abcd"), value: 0 }
    ]);
    assert_eq!(off, off_computed);

    let empty = parse_longhand!(font_feature_settings, "\"abcd\"");
    let empty_computed = computed_value::T::Computed(vec![
        FeatureTagValue { tag: String::from("abcd"), value: 1 }
    ]);
    assert_eq!(empty, empty_computed);

    let pos_integer = parse_longhand!(font_feature_settings, "\"abcd\" 100");
    let pos_integer_computed = computed_value::T::Computed(vec![
        FeatureTagValue { tag: String::from("abcd"), value: 100 }
    ]);
    assert_eq!(pos_integer, pos_integer_computed);

    let multiple = parse_longhand!(font_feature_settings, "\"abcd\" off, \"efgh\"");
    let multiple_computed = computed_value::T::Computed(vec![
        FeatureTagValue { tag: String::from("abcd"), value: 0 },
        FeatureTagValue { tag: String::from("efgh"), value: 1 }
    ]);
    assert_eq!(multiple, multiple_computed);
}
<|endoftext|>


<commit_before>use byteutils;
use columnvalueops::ColumnValueOps;
use types::DbType;
use types::F64NoNaN;
use std::borrow::{Cow, IntoCow};
use std::fmt;

#[derive(Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub enum Variant {
    Null,
    Bytes(Vec<u8>),
    StringLiteral(String),
    SignedInteger(i64),
    UnsignedInteger(u64),
    Float(F64NoNaN)
}

impl fmt::Display for Variant {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        match self {
            &Variant::Null => write!(f, "NULL"),
            &Variant::Bytes(ref v) => write!(f, "{:?}", v),
            &Variant::StringLiteral(ref s) => write!(f, "{}", s),
            &Variant::SignedInteger(n) => write!(f, "{}", n),
            &Variant::UnsignedInteger(n) => write!(f, "{}", n),
            &Variant::Float(n) => write!(f, "{}", *n),
        }
    }
}

impl fmt::Debug for Variant {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        write!(f, "{}", self)
    }
}

fn from_bool(value: bool) -> Variant {
    Variant::UnsignedInteger(if value { 1 } else { 0 })
}

impl ColumnValueOps for Variant {
    fn from_string_literal(s: Cow<str>) -> Result<Variant, Cow<str>> {
        Ok(Variant::StringLiteral(s.into_owned()))
    }

    fn from_number_literal(s: Cow<str>) -> Result<Variant, Cow<str>> {
        if let Ok(number) = s.parse() {
            Ok(Variant::UnsignedInteger(number))
        } else if let Ok(number) = s.parse() {
            Ok(Variant::SignedInteger(number))
        } else if let Ok(number) = s.parse() {
            Ok(Variant::Float(F64NoNaN::new(number).unwrap()))
        } else {
            Err(s)
        }
    }

    fn from_f64(value: f64) -> Variant {
        Variant::Float(F64NoNaN::new(value).unwrap())
    }

    fn to_f64(self) -> Result<f64, ()> {
        let num = self.cast(DbType::F64);
        if let Some(Variant::Float(float)) = num {
            Ok(*float)
        } else {
            Err(())
        }
    }

    fn from_u64(value: u64) -> Variant {
        Variant::UnsignedInteger(value)
    }

    fn to_u64(self) -> Result<u64, ()> {
        let num = self.cast(DbType::Integer { signed: false, bytes: 8 });
        if let Some(Variant::UnsignedInteger(i)) = num {
            Ok(i)
        } else {
            Err(())
        }
    }

    fn from_bytes(dbtype: DbType, bytes: Cow<[u8]>) -> Result<Variant, ()> {
        match dbtype {
            DbType::Null => Ok(Variant::Null),
            DbType::ByteDynamic => Ok(Variant::Bytes(bytes.into_owned())),
            DbType::ByteFixed(n) => {
                if bytes.len() as u64 != n {
                    Err(())
                } else {
                    Ok(Variant::Bytes(bytes.into_owned()))
                }
            },
            DbType::Integer { signed, bytes: n } => {
                if bytes.len() != n as usize {
                    Err(())
                } else {
                    if signed {
                        Ok(Variant::SignedInteger(byteutils::read_sdbinteger(&bytes)))
                    } else {
                        Ok(Variant::UnsignedInteger(byteutils::read_udbinteger(&bytes)))
                    }
                }
            },
            DbType::F64 => {
                let f = byteutils::read_dbfloat(&bytes);
                Ok(Variant::Float(F64NoNaN::new(f).unwrap()))
            },
            DbType::String => {
                let len = bytes.len();
                if len > 0 && bytes[len - 1] == 0 {
                    let s = String::from_utf8_lossy(&bytes[0..len - 1]);
                    Ok(Variant::StringLiteral(s.into_owned()))
                } else {
                    Err(())
                }
            }
        }
    }

    fn to_bytes(self, dbtype: DbType) -> Result<Box<[u8]>, ()> {
        let s = match self.cast(dbtype) {
            Some(s) => s,
            None => return Err(())
        };

        match (s, dbtype) {
            (Variant::Null, DbType::Null) => {
                // NULL has no data.
                Err(())
            },
            (Variant::Bytes(v), DbType::ByteDynamic) => {
                Ok(v.into_boxed_slice())
            },
            (Variant::StringLiteral(s), DbType::String) => {
                Ok((s + "\0").into_bytes().into_boxed_slice())
            },
            (Variant::SignedInteger(v), DbType::Integer { signed: true, bytes }) => {
                let mut buf = vec![0; bytes as usize];
                byteutils::write_sdbinteger(v, &mut buf);
                Ok(buf.into_boxed_slice())
            },
            (Variant::UnsignedInteger(v), DbType::Integer { signed: false, bytes }) => {
                let mut buf = vec![0; bytes as usize];
                byteutils::write_udbinteger(v, &mut buf);
                Ok(buf.into_boxed_slice())
            },
            (Variant::Float(v), DbType::F64) => {
                let mut buf = [0; 8];
                byteutils::write_dbfloat(*v, &mut buf);
                Ok(Box::new(buf))
            },
            _ => {
                Err(())
            }
        }
    }

    fn get_dbtype(&self) -> DbType {
        match self {
            &Variant::Null => DbType::Null,
            &Variant::Bytes(ref bytes) => DbType::ByteFixed(bytes.len() as u64),
            &Variant::StringLiteral(..) => DbType::String,
            &Variant::SignedInteger(..) => DbType::Integer { signed: true, bytes: 8 },
            &Variant::UnsignedInteger(..) => DbType::Integer { signed: false, bytes: 8 },
            &Variant::Float(..) => DbType::F64
        }
    }

    fn to_3vl(&self) -> i8 {
        fn b(value: bool) -> i8 { if value { 1 } else { -1 } }

        match self {
            &Variant::Null => 0,
            &Variant::Bytes(ref bytes) => b(!bytes.is_empty()),
            &Variant::StringLiteral(ref s) => b(!s.is_empty()),
            &Variant::SignedInteger(n) => b(n != 0),
            &Variant::UnsignedInteger(n) => b(n != 0),
            &Variant::Float(n) => b(*n != 0.0)
        }
    }

    fn from_3vl(value: i8) -> Self {
        match value {
            -1 => from_bool(false),
            0 => Variant::Null,
            1 => from_bool(true),
            _ => panic!()
        }
    }

    fn cast(self, dbtype: DbType) -> Option<Self> {
        match (self, dbtype) {
            (e@Variant::Null, DbType::Null)
            | (e@Variant::Bytes(_), DbType::ByteDynamic)
            | (e@Variant::StringLiteral(_), DbType::String)
            | (e@Variant::SignedInteger(_), DbType::Integer { signed: true, .. })
            | (e@Variant::UnsignedInteger(_), DbType::Integer { signed: false, .. })
            | (e@Variant::Float(_), DbType::F64) => {
                Some(e)
            },
            (e, DbType::String) => {
                // every variant can be converted to a string
                Some(Variant::StringLiteral(e.to_string()))
            },
            (e, DbType::ByteDynamic) => {
                // every variant can be converted to their byte representation
                let dbtype = e.get_dbtype();
                match e.to_bytes(dbtype) {
                    Ok(bytes) => Some(Variant::Bytes(bytes.into_vec())),
                    Err(()) => None
                }
            },
            (Variant::Bytes(bytes), v) => {
                // every variant can be converted from their byte representation
                let r: &[u8] = &bytes;
                match ColumnValueOps::from_bytes(v, r.into_cow()) {
                    Ok(s) => Some(s),
                    Err(()) => None
                }
            },
            (Variant::Float(float), DbType::Integer { signed, .. }) => {
                // truncates
                if signed {
                    Some(Variant::SignedInteger(*float as i64))
                } else {
                    Some(Variant::UnsignedInteger(*float as u64))
                }
            },
            // TODO: overflow checks!
            (Variant::UnsignedInteger(integer), DbType::F64) => {
                Some(Variant::Float(F64NoNaN::new(integer as f64).unwrap()))
            },
            (Variant::SignedInteger(integer), DbType::F64) => {
                Some(Variant::Float(F64NoNaN::new(integer as f64).unwrap()))
            },
            (Variant::UnsignedInteger(integer), DbType::Integer { signed: true, .. }) => {
                Some(Variant::SignedInteger(integer as i64))
            },
            (Variant::SignedInteger(integer), DbType::Integer { signed: false, .. }) => {
                Some(Variant::UnsignedInteger(integer as u64))
            },
            _ => None
        }
    }

    // None: self or rhs is NULL, or comparison is otherwise invalid
    // -1: self < rhs
    // 0: self == rhs
    // 1: self > rhs
    fn compare(&self, rhs: &Self) -> Option<i8> {
        let dbtype = self.get_dbtype();
        if let Some(r) = rhs.clone().cast(dbtype) {
            match (self, &r) {
                (&Variant::Null, _) | (_, &Variant::Null) => None,
                (&Variant::UnsignedInteger(l), &Variant::UnsignedInteger(r)) => {
                    Some(if l < r { -1 } else if l > r { 1 } else { 0 })
                },
                (&Variant::SignedInteger(l), &Variant::SignedInteger(r)) => {
                    Some(if l < r { -1 } else if l > r { 1 } else { 0 })
                },
                (&Variant::Float(l), &Variant::Float(r)) => {
                    Some(if l < r { -1 } else if l > r { 1 } else { 0 })
                },
                (&Variant::Bytes(ref l), &Variant::Bytes(ref r)) => {
                    Some(if l < r { -1 } else if l > r { 1 } else { 0 })
                },
                (&Variant::StringLiteral(ref l), &Variant::StringLiteral(ref r)) => {
                    Some(if l < r { -1 } else if l > r { 1 } else { 0 })
                },
                _ => unreachable!()
            }
        } else {
            None
        }
    }

    fn concat(&self, rhs: &Self) -> Self {
        match (self, rhs) {
            (&Variant::StringLiteral(ref l), &Variant::StringLiteral(ref r)) => {
                Variant::StringLiteral(format!("{}{}", l, r))
            },
            (e @ &Variant::StringLiteral(_), rhs) => {
                match rhs.clone().cast(DbType::String) {
                    Some(r) => e.concat(&r),
                    None => e.clone()
                }
            },
            (e, _) => e.clone()
        }
    }

    fn add(&self, rhs: &Self) -> Self {
        // TODO: treat overflow!
        let dbtype = self.get_dbtype();
        if let Some(r) = rhs.clone().cast(dbtype) {
            match (self, r) {
                (&Variant::UnsignedInteger(l), Variant::UnsignedInteger(r)) => {
                    Variant::UnsignedInteger(l + r)
                },
                (&Variant::SignedInteger(l), Variant::SignedInteger(r)) => {
                    Variant::SignedInteger(l + r)
                },
                (&Variant::Float(l), Variant::Float(r)) => {
                    Variant::Float(F64NoNaN::new(*l + *r).unwrap())
                },
                _ => self.clone()
            }
        } else {
            self.clone()
        }
    }

    fn sub(&self, rhs: &Self) -> Self {
        // TODO: treat overflow!
        let dbtype = self.get_dbtype();
        if let Some(r) = rhs.clone().cast(dbtype) {
            match (self, r) {
                (&Variant::UnsignedInteger(l), Variant::UnsignedInteger(r)) => {
                    Variant::UnsignedInteger(l - r)
                },
                (&Variant::SignedInteger(l), Variant::SignedInteger(r)) => {
                    Variant::SignedInteger(l - r)
                },
                (&Variant::Float(l), Variant::Float(r)) => {
                    Variant::Float(F64NoNaN::new(*l - *r).unwrap())
                },
                _ => self.clone()
            }
        } else {
            self.clone()
        }
    }

    fn mul(&self, rhs: &Self) -> Self {
        // TODO: treat overflow!
        let dbtype = self.get_dbtype();
        if let Some(r) = rhs.clone().cast(dbtype) {
            match (self, r) {
                (&Variant::UnsignedInteger(l), Variant::UnsignedInteger(r)) => {
                    Variant::UnsignedInteger(l * r)
                },
                (&Variant::SignedInteger(l), Variant::SignedInteger(r)) => {
                    Variant::SignedInteger(l * r)
                },
                (&Variant::Float(l), Variant::Float(r)) => {
                    Variant::Float(F64NoNaN::new(*l * *r).unwrap())
                },
                _ => self.clone()
            }
        } else {
            self.clone()
        }
    }

    fn div(&self, rhs: &Self) -> Self {
        // TODO: treat overflow!
        let dbtype = self.get_dbtype();
        if let Some(r) = rhs.clone().cast(dbtype) {
            match (self, r) {
                (&Variant::UnsignedInteger(_), Variant::UnsignedInteger(0)) => Variant::Null,
                (&Variant::SignedInteger(_), Variant::SignedInteger(0)) => Variant::Null,

                (&Variant::UnsignedInteger(l), Variant::UnsignedInteger(r)) => {
                    Variant::UnsignedInteger(l / r)
                },
                (&Variant::SignedInteger(l), Variant::SignedInteger(r)) => {
                    Variant::SignedInteger(l / r)
                },
                (&Variant::Float(l), Variant::Float(r)) => {
                    if r == F64NoNaN::new(0.0).unwrap() {
                        Variant::Null
                    } else {
                        Variant::Float(F64NoNaN::new(*l / *r).unwrap())
                    }
                },
                _ => self.clone()
            }
        } else {
            self.clone()
        }
    }

    fn negate(&self) -> Self {
        // TODO: treat overflow!
        match self {
            &Variant::SignedInteger(n) => Variant::SignedInteger(-n),
            &Variant::UnsignedInteger(n) => Variant::SignedInteger(-(n as i64)),
            &Variant::Float(n) => Variant::Float(F64NoNaN::new(-*n).unwrap()),
            &Variant::Null |
            &Variant::Bytes(..) |
            &Variant::StringLiteral(..) => {
                self.clone()
            }
        }
    }
}
<commit_msg>Parsed number literals as signed integer first<commit_after>use byteutils;
use columnvalueops::ColumnValueOps;
use types::DbType;
use types::F64NoNaN;
use std::borrow::{Cow, IntoCow};
use std::fmt;

#[derive(Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub enum Variant {
    Null,
    Bytes(Vec<u8>),
    StringLiteral(String),
    SignedInteger(i64),
    UnsignedInteger(u64),
    Float(F64NoNaN)
}

impl fmt::Display for Variant {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        match self {
            &Variant::Null => write!(f, "NULL"),
            &Variant::Bytes(ref v) => write!(f, "{:?}", v),
            &Variant::StringLiteral(ref s) => write!(f, "{}", s),
            &Variant::SignedInteger(n) => write!(f, "{}", n),
            &Variant::UnsignedInteger(n) => write!(f, "{}", n),
            &Variant::Float(n) => write!(f, "{}", *n),
        }
    }
}

impl fmt::Debug for Variant {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        write!(f, "{}", self)
    }
}

fn from_bool(value: bool) -> Variant {
    Variant::UnsignedInteger(if value { 1 } else { 0 })
}

impl ColumnValueOps for Variant {
    fn from_string_literal(s: Cow<str>) -> Result<Variant, Cow<str>> {
        Ok(Variant::StringLiteral(s.into_owned()))
    }

    fn from_number_literal(s: Cow<str>) -> Result<Variant, Cow<str>> {
        if let Ok(number) = s.parse() {
            Ok(Variant::SignedInteger(number))
        } else if let Ok(number) = s.parse() {
            Ok(Variant::UnsignedInteger(number))
        } else if let Ok(number) = s.parse() {
            Ok(Variant::Float(F64NoNaN::new(number).unwrap()))
        } else {
            Err(s)
        }
    }

    fn from_f64(value: f64) -> Variant {
        Variant::Float(F64NoNaN::new(value).unwrap())
    }

    fn to_f64(self) -> Result<f64, ()> {
        let num = self.cast(DbType::F64);
        if let Some(Variant::Float(float)) = num {
            Ok(*float)
        } else {
            Err(())
        }
    }

    fn from_u64(value: u64) -> Variant {
        Variant::UnsignedInteger(value)
    }

    fn to_u64(self) -> Result<u64, ()> {
        let num = self.cast(DbType::Integer { signed: false, bytes: 8 });
        if let Some(Variant::UnsignedInteger(i)) = num {
            Ok(i)
        } else {
            Err(())
        }
    }

    fn from_bytes(dbtype: DbType, bytes: Cow<[u8]>) -> Result<Variant, ()> {
        match dbtype {
            DbType::Null => Ok(Variant::Null),
            DbType::ByteDynamic => Ok(Variant::Bytes(bytes.into_owned())),
            DbType::ByteFixed(n) => {
                if bytes.len() as u64 != n {
                    Err(())
                } else {
                    Ok(Variant::Bytes(bytes.into_owned()))
                }
            },
            DbType::Integer { signed, bytes: n } => {
                if bytes.len() != n as usize {
                    Err(())
                } else {
                    if signed {
                        Ok(Variant::SignedInteger(byteutils::read_sdbinteger(&bytes)))
                    } else {
                        Ok(Variant::UnsignedInteger(byteutils::read_udbinteger(&bytes)))
                    }
                }
            },
            DbType::F64 => {
                let f = byteutils::read_dbfloat(&bytes);
                Ok(Variant::Float(F64NoNaN::new(f).unwrap()))
            },
            DbType::String => {
                let len = bytes.len();
                if len > 0 && bytes[len - 1] == 0 {
                    let s = String::from_utf8_lossy(&bytes[0..len - 1]);
                    Ok(Variant::StringLiteral(s.into_owned()))
                } else {
                    Err(())
                }
            }
        }
    }

    fn to_bytes(self, dbtype: DbType) -> Result<Box<[u8]>, ()> {
        let s = match self.cast(dbtype) {
            Some(s) => s,
            None => return Err(())
        };

        match (s, dbtype) {
            (Variant::Null, DbType::Null) => {
                // NULL has no data.
                Err(())
            },
            (Variant::Bytes(v), DbType::ByteDynamic) => {
                Ok(v.into_boxed_slice())
            },
            (Variant::StringLiteral(s), DbType::String) => {
                Ok((s + "\0").into_bytes().into_boxed_slice())
            },
            (Variant::SignedInteger(v), DbType::Integer { signed: true, bytes }) => {
                let mut buf = vec![0; bytes as usize];
                byteutils::write_sdbinteger(v, &mut buf);
                Ok(buf.into_boxed_slice())
            },
            (Variant::UnsignedInteger(v), DbType::Integer { signed: false, bytes }) => {
                let mut buf = vec![0; bytes as usize];
                byteutils::write_udbinteger(v, &mut buf);
                Ok(buf.into_boxed_slice())
            },
            (Variant::Float(v), DbType::F64) => {
                let mut buf = [0; 8];
                byteutils::write_dbfloat(*v, &mut buf);
                Ok(Box::new(buf))
            },
            _ => {
                Err(())
            }
        }
    }

    fn get_dbtype(&self) -> DbType {
        match self {
            &Variant::Null => DbType::Null,
            &Variant::Bytes(ref bytes) => DbType::ByteFixed(bytes.len() as u64),
            &Variant::StringLiteral(..) => DbType::String,
            &Variant::SignedInteger(..) => DbType::Integer { signed: true, bytes: 8 },
            &Variant::UnsignedInteger(..) => DbType::Integer { signed: false, bytes: 8 },
            &Variant::Float(..) => DbType::F64
        }
    }

    fn to_3vl(&self) -> i8 {
        fn b(value: bool) -> i8 { if value { 1 } else { -1 } }

        match self {
            &Variant::Null => 0,
            &Variant::Bytes(ref bytes) => b(!bytes.is_empty()),
            &Variant::StringLiteral(ref s) => b(!s.is_empty()),
            &Variant::SignedInteger(n) => b(n != 0),
            &Variant::UnsignedInteger(n) => b(n != 0),
            &Variant::Float(n) => b(*n != 0.0)
        }
    }

    fn from_3vl(value: i8) -> Self {
        match value {
            -1 => from_bool(false),
            0 => Variant::Null,
            1 => from_bool(true),
            _ => panic!()
        }
    }

    fn cast(self, dbtype: DbType) -> Option<Self> {
        match (self, dbtype) {
            (e@Variant::Null, DbType::Null)
            | (e@Variant::Bytes(_), DbType::ByteDynamic)
            | (e@Variant::StringLiteral(_), DbType::String)
            | (e@Variant::SignedInteger(_), DbType::Integer { signed: true, .. })
            | (e@Variant::UnsignedInteger(_), DbType::Integer { signed: false, .. })
            | (e@Variant::Float(_), DbType::F64) => {
                Some(e)
            },
            (e, DbType::String) => {
                // every variant can be converted to a string
                Some(Variant::StringLiteral(e.to_string()))
            },
            (e, DbType::ByteDynamic) => {
                // every variant can be converted to their byte representation
                let dbtype = e.get_dbtype();
                match e.to_bytes(dbtype) {
                    Ok(bytes) => Some(Variant::Bytes(bytes.into_vec())),
                    Err(()) => None
                }
            },
            (Variant::Bytes(bytes), v) => {
                // every variant can be converted from their byte representation
                let r: &[u8] = &bytes;
                match ColumnValueOps::from_bytes(v, r.into_cow()) {
                    Ok(s) => Some(s),
                    Err(()) => None
                }
            },
            (Variant::Float(float), DbType::Integer { signed, .. }) => {
                // truncates
                if signed {
                    Some(Variant::SignedInteger(*float as i64))
                } else {
                    Some(Variant::UnsignedInteger(*float as u64))
                }
            },
            // TODO: overflow checks!
            (Variant::UnsignedInteger(integer), DbType::F64) => {
                Some(Variant::Float(F64NoNaN::new(integer as f64).unwrap()))
            },
            (Variant::SignedInteger(integer), DbType::F64) => {
                Some(Variant::Float(F64NoNaN::new(integer as f64).unwrap()))
            },
            (Variant::UnsignedInteger(integer), DbType::Integer { signed: true, .. }) => {
                Some(Variant::SignedInteger(integer as i64))
            },
            (Variant::SignedInteger(integer), DbType::Integer { signed: false, .. }) => {
                Some(Variant::UnsignedInteger(integer as u64))
            },
            _ => None
        }
    }

    // None: self or rhs is NULL, or comparison is otherwise invalid
    // -1: self < rhs
    // 0: self == rhs
    // 1: self > rhs
    fn compare(&self, rhs: &Self) -> Option<i8> {
        let dbtype = self.get_dbtype();
        if let Some(r) = rhs.clone().cast(dbtype) {
            match (self, &r) {
                (&Variant::Null, _) | (_, &Variant::Null) => None,
                (&Variant::UnsignedInteger(l), &Variant::UnsignedInteger(r)) => {
                    Some(if l < r { -1 } else if l > r { 1 } else { 0 })
                },
                (&Variant::SignedInteger(l), &Variant::SignedInteger(r)) => {
                    Some(if l < r { -1 } else if l > r { 1 } else { 0 })
                },
                (&Variant::Float(l), &Variant::Float(r)) => {
                    Some(if l < r { -1 } else if l > r { 1 } else { 0 })
                },
                (&Variant::Bytes(ref l), &Variant::Bytes(ref r)) => {
                    Some(if l < r { -1 } else if l > r { 1 } else { 0 })
                },
                (&Variant::StringLiteral(ref l), &Variant::StringLiteral(ref r)) => {
                    Some(if l < r { -1 } else if l > r { 1 } else { 0 })
                },
                _ => unreachable!()
            }
        } else {
            None
        }
    }

    fn concat(&self, rhs: &Self) -> Self {
        match (self, rhs) {
            (&Variant::StringLiteral(ref l), &Variant::StringLiteral(ref r)) => {
                Variant::StringLiteral(format!("{}{}", l, r))
            },
            (e @ &Variant::StringLiteral(_), rhs) => {
                match rhs.clone().cast(DbType::String) {
                    Some(r) => e.concat(&r),
                    None => e.clone()
                }
            },
            (e, _) => e.clone()
        }
    }

    fn add(&self, rhs: &Self) -> Self {
        // TODO: treat overflow!
        let dbtype = self.get_dbtype();
        if let Some(r) = rhs.clone().cast(dbtype) {
            match (self, r) {
                (&Variant::UnsignedInteger(l), Variant::UnsignedInteger(r)) => {
                    Variant::UnsignedInteger(l + r)
                },
                (&Variant::SignedInteger(l), Variant::SignedInteger(r)) => {
                    Variant::SignedInteger(l + r)
                },
                (&Variant::Float(l), Variant::Float(r)) => {
                    Variant::Float(F64NoNaN::new(*l + *r).unwrap())
                },
                _ => self.clone()
            }
        } else {
            self.clone()
        }
    }

    fn sub(&self, rhs: &Self) -> Self {
        // TODO: treat overflow!
        let dbtype = self.get_dbtype();
        if let Some(r) = rhs.clone().cast(dbtype) {
            match (self, r) {
                (&Variant::UnsignedInteger(l), Variant::UnsignedInteger(r)) => {
                    Variant::UnsignedInteger(l - r)
                },
                (&Variant::SignedInteger(l), Variant::SignedInteger(r)) => {
                    Variant::SignedInteger(l - r)
                },
                (&Variant::Float(l), Variant::Float(r)) => {
                    Variant::Float(F64NoNaN::new(*l - *r).unwrap())
                },
                _ => self.clone()
            }
        } else {
            self.clone()
        }
    }

    fn mul(&self, rhs: &Self) -> Self {
        // TODO: treat overflow!
        let dbtype = self.get_dbtype();
        if let Some(r) = rhs.clone().cast(dbtype) {
            match (self, r) {
                (&Variant::UnsignedInteger(l), Variant::UnsignedInteger(r)) => {
                    Variant::UnsignedInteger(l * r)
                },
                (&Variant::SignedInteger(l), Variant::SignedInteger(r)) => {
                    Variant::SignedInteger(l * r)
                },
                (&Variant::Float(l), Variant::Float(r)) => {
                    Variant::Float(F64NoNaN::new(*l * *r).unwrap())
                },
                _ => self.clone()
            }
        } else {
            self.clone()
        }
    }

    fn div(&self, rhs: &Self) -> Self {
        // TODO: treat overflow!
        let dbtype = self.get_dbtype();
        if let Some(r) = rhs.clone().cast(dbtype) {
            match (self, r) {
                (&Variant::UnsignedInteger(_), Variant::UnsignedInteger(0)) => Variant::Null,
                (&Variant::SignedInteger(_), Variant::SignedInteger(0)) => Variant::Null,

                (&Variant::UnsignedInteger(l), Variant::UnsignedInteger(r)) => {
                    Variant::UnsignedInteger(l / r)
                },
                (&Variant::SignedInteger(l), Variant::SignedInteger(r)) => {
                    Variant::SignedInteger(l / r)
                },
                (&Variant::Float(l), Variant::Float(r)) => {
                    if r == F64NoNaN::new(0.0).unwrap() {
                        Variant::Null
                    } else {
                        Variant::Float(F64NoNaN::new(*l / *r).unwrap())
                    }
                },
                _ => self.clone()
            }
        } else {
            self.clone()
        }
    }

    fn negate(&self) -> Self {
        // TODO: treat overflow!
        match self {
            &Variant::SignedInteger(n) => Variant::SignedInteger(-n),
            &Variant::UnsignedInteger(n) => Variant::SignedInteger(-(n as i64)),
            &Variant::Float(n) => Variant::Float(F64NoNaN::new(-*n).unwrap()),
            &Variant::Null |
            &Variant::Bytes(..) |
            &Variant::StringLiteral(..) => {
                self.clone()
            }
        }
    }
}
<|endoftext|>