<commit_before>use std::{
    collections::BTreeMap,
    fs,
    path::{Path, PathBuf},
};

use defaults::Defaults;
use eyre::{bail, Result};
use futures::future;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use tokio::sync::mpsc;

use files::{File, Files};
use graph_triples::{
    relations::{self, NULL_RANGE},
    resources, Resource, Triple,
};
use providers::provider::{ImportOptions, SyncOptions, WatchMode};
use stencila_schema::Node;

/// A source-destination combination
///
/// Each source by destination combination should be unique to a project.
/// It is possible to have the same source being imported to multiple
/// destinations within a project and for multiple sources to used the same
/// destination (e.g. the root directory of the project).
#[skip_serializing_none]
#[derive(Clone, Debug, Defaults, Deserialize, Serialize)]
#[serde(default)]
pub struct Source {
    /// The name of the source
    ///
    /// Useful for providing a shorthand way to refer to the source rather than using it's URL
    pub name: Option<String>,

    /// The URL of the source
    pub url: String,

    /// The providers that matched the URL
    pub provider: Option<String>,

    /// The node parsed from the URL by the provider
    pub node: Option<Node>,

    /// The destination path within the project
    pub dest: Option<PathBuf>,

    /// Run a cron schedule to import and/or export the source
    pub cron: Option<SourceCron>,

    /// Synchronize the source
    pub watch: Option<SourceWatch>,

    /// The name of the secret required to access the source
    ///
    /// To improve the security of API access tokens, secrets are only ever read from
    /// environment variables. Source providers usually have a default secret name
    /// e.g. `GITHUB_TOKEN`. However, this field allows setting of custom secret names
    /// which may be necessary, for example, if a project uses two sources from the
    /// same provider, requiring different secrets.
    pub secret_name: Option<String>,

    /// A list of file paths currently associated with the source (relative to the project root)
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub files: Vec<PathBuf>,

    /// A running cron task
    #[serde(skip)]
    #[def = "None"]
    cron_task: Option<SourceTask>,

    /// A running watch task
    #[serde(skip)]
    #[def = "None"]
    watch_task: Option<SourceTask>,
}

#[skip_serializing_none]
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct SourceCron {
    /// The schedule on which to perform the action
    ///
    /// Can be a cron phrase (e.g. "every 10mins") or cron expression (e.g. "0 0 */10 * * *").
    schedule: String,

    /// The cron expression/s parsed from the `schedule`
    expressions: Option<Vec<String>>,

    /// The timezone parsed from the `schedule`
    timezone: Option<String>,

    /// The action to perform at each scheduled time
    action: Option<String>,
}

#[skip_serializing_none]
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct SourceWatch {
    /// The synchronization mode
    mode: Option<WatchMode>,
}

#[derive(Debug, Clone)]
struct SourceTask {
    /// The channel sender used to cancel the source task
    ///
    /// Note that if the source is dropped (ie. removed from a list of sources) with a running task
    /// then this canceller will be dropped and so the task will also end (an explicit `.send()`
    /// is not required).
    canceller: mpsc::Sender<()>,
}

impl Source {
    /// Create a new source
    pub async fn new(
        url: String,
        name: Option<String>,
        dest: Option<PathBuf>,
        cron: Option<String>,
        watch: Option<WatchMode>,
    ) -> Result<Source> {
        let (provider, node) = providers::resolve(&url).await?;

        let cron = if let Some(schedule) = cron {
            let (schedules, timezone) = cron_utils::parse(&schedule)?;
            let (expressions, timezone) = if !schedules.is_empty() {
                (
                    Some(
                        schedules
                            .iter()
                            .map(|schedule| schedule.to_string())
                            .collect(),
                    ),
                    Some(timezone.to_string()),
                )
            } else {
                (None, None)
            };

            Some(SourceCron {
                schedule,
                expressions,
                timezone,
                action: None,
            })
        } else {
            None
        };

        let watch = watch.map(|mode| SourceWatch { mode: Some(mode) });

        Ok(Source {
            name,
            url,
            provider: Some(provider),
            node: Some(node),
            dest,
            cron,
            watch,
            ..Default::default()
        })
    }

    /// Generate a label for the source
    pub fn label(&self) -> String {
        self.name.clone().unwrap_or_else(|| self.url.clone())
    }

    /// Does the source match a string identifier
    ///
    /// Matches the string against the `name`, the `url` , or the filename (of the `dest` with or without extension).
    pub fn matches(&self, identifier: &str) -> bool {
        if let Some(name) = &self.name {
            if name == identifier {
                return true;
            }
        }

        if identifier == self.url {
            return true;
        }

        if let Some(dest) = &self.dest {
            if dest.to_string_lossy() == identifier {
                return true;
            }
            if let Some(file_stem) = dest.file_stem() {
                if file_stem == identifier {
                    return true;
                }
            }
        }

        false
    }

    /// Read a source from a JSON file
    ///
    /// Only changes the properties that are NOT saved in the project.json file.
    pub fn read<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
        let path = path.as_ref();
        if !path.exists() {
            bail!("Project source file does not exist: {}", path.display())
        }
        let path = path.canonicalize()?;

        let json = fs::read_to_string(path)?;
        let source: Source = serde_json::from_str(&json)?;
        self.files = source.files;

        Ok(())
    }

    /// Create a graph resource for the source
    pub fn resource(&self) -> Resource {
        resources::url(&self.url)
    }

    /// Create a set of graph triples describing relation between the source it's associated files.
    pub fn triples(&self, project: &Path) -> Vec<Triple> {
        let this = self.resource();
        self.files
            .iter()
            .map(|file| {
                (
                    this.clone(),
                    relations::imports(NULL_RANGE),
                    resources::file(&project.join(file)),
                )
            })
            .collect()
    }

    /// Import the source
    ///
    /// # Arguments
    ///
    /// - `path`: The path to import the source into
    ///
    /// # Returns
    ///
    /// A map of the [`File`]s that were imported by the source.
    pub async fn import(&self, path: &Path) -> Result<BTreeMap<PathBuf, File>> {
        let (.., node) = providers::resolve(&self.url).await?;
        let dest = match &self.dest {
            Some(dest) => path.join(dest),
            None => path.to_path_buf(),
        };
        let options = ImportOptions {
            secret_name: self.secret_name.clone(),
        };

        let (pre, ..) = Files::walk(path, false);

        providers::import(&node, &dest, Some(options)).await?;

        let (post, ..) = Files::walk(path, false);
        let files = post
            .into_iter()
            .filter_map(|(path, file)| match pre.get(&path) {
                None => Some((path, file)),
                Some(existing) => {
                    if existing.size != file.size && existing.modified != file.modified {
                        Some((path, file))
                    } else {
                        None
                    }
                }
            });

        Ok(files.collect())
    }

    /// Start cron and watch tasks (as applicable and as needed) for the source
    pub async fn start(&mut self, dest: &Path) -> Result<()> {
        self.cron_task_start(dest).await?;
        self.watch_task_start(dest).await?;

        Ok(())
    }

    /// Start a background cron task for the source
    ///
    /// A task has a `cron` spec and there is no tasks currently running for it.
    pub async fn cron_task_start(&mut self, dest: &Path) -> Result<()> {
        let cron = match (&self.cron, &self.cron_task) {
            (Some(cron), None) => cron,
            _ => return Ok(()),
        };

        tracing::info!("Starting cron task for source `{}`", self.label());
        let action = cron.action.clone().unwrap_or_default();
        let schedule = cron.schedule.clone();
        let (.., node) = providers::resolve(&self.url).await?;
        let dest = dest.to_path_buf();
        let (canceller, cancellee) = mpsc::channel(1);
        tokio::spawn(
            async move { providers::cron(&action, &schedule, &node, &dest, cancellee).await },
        );
        self.cron_task = Some(SourceTask { canceller });

        Ok(())
    }

    /// Start a background watch task for the source
    ///
    /// A task has a `watch` spec and there is no tasks currently running for it.
    pub async fn watch_task_start(&mut self, dest: &Path) -> Result<()> {
        let watch = match (&self.watch, &self.watch_task) {
            (Some(watch), None) => watch,
            _ => return Ok(()),
        };

        tracing::info!("Starting watch task for source `{}`", self.label());
        let (.., node) = providers::resolve(&self.url).await?;
        let dest = dest.to_path_buf();
        let (canceller, cancellee) = mpsc::channel(1);
        let options = SyncOptions {
            mode: watch.mode.clone(),
            secret_name: self.secret_name.clone(),
            ..Default::default()
        };
        tokio::spawn(async move { providers::watch(&node, &dest, cancellee, Some(options)).await });
        self.watch_task = Some(SourceTask { canceller });

        Ok(())
    }

    /// Start cron and watch tasks (if started) for the source
    pub async fn stop(&mut self) -> Result<()> {
        if let Some(task) = &self.cron_task {
            tracing::info!("Stopping cron task for source `{}`", self.label());
            task.canceller.send(()).await?
        }
        self.cron_task = None;

        if let Some(task) = &self.watch_task {
            tracing::info!("Stopping watch task for source `{}`", self.label());
            task.canceller.send(()).await?
        }
        self.watch_task = None;

        Ok(())
    }
}

/// A set of sources, usually associated with a project
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
#[serde(transparent)]
pub struct Sources {
    /// The list of sources
    pub inner: Vec<Source>,

    /// If in a running state, the path that cron and watch tasks were started with
    ///
    /// If tasks are running then any new sources that are added will have
    /// tasks automatically started for them.
    #[serde(skip)]
    running: Option<PathBuf>,
}

impl Sources {
    /// Return a list of sources
    pub fn list(&self) -> Vec<String> {
        self.inner.iter().map(|source| source.label()).collect()
    }

    /// Find a source
    ///
    /// # Arguments
    ///
    /// - `identifier`: An identifier for a source.
    ///
    /// The first source that matches the identifier is returned. Will error if no match is found.
    pub fn find(&self, identifier: &str) -> Result<&Source> {
        if let Some(source) = self.inner.iter().find(|source| source.matches(identifier)) {
            return Ok(source);
        }

        bail!("Unable to find a source matching `{}`", identifier)
    }

    /// Add a source
    ///
    /// # Arguments
    ///
    /// - `source`: The source to add
    ///
    /// Warns if there is already a source with the same `url`.
    /// Errors if there is already a source with the same `name` or the same `url` and `dest`.
    ///
    /// If the `start` method has been called previously then the sources are in a "running"
    /// state and the `start` will be called on the source.
    pub async fn add(&mut self, mut source: Source) -> Result<()> {
        let source_dest = source.dest.clone().unwrap_or_else(|| PathBuf::from("."));
        for existing in self.inner.iter() {
            if let (Some(existing_name), Some(source_name)) = (&existing.name, &source.name) {
                if existing_name == source_name {
                    bail!("There is already a source with the name `{}`", source_name);
                }
            }
            let existing_dest = existing.dest.clone().unwrap_or_else(|| PathBuf::from("."));
            if existing.url == source.url && existing_dest == source_dest {
                bail!(
                    "There is already a source with the URL `{}` and destination `{}`",
                    source.url,
                    source_dest.display()
                );
            }
            if existing.url == source.url {
                tracing::warn!("There is already a source with the URL `{}`", source.url);
            }
        }
        if let Some(path) = &self.running {
            source.start(path).await?;
        }
        self.inner.push(source);

        Ok(())
    }

    /// Remove a source
    ///
    /// # Arguments
    ///
    /// - `identifier`: An identifier for a source.
    ///
    /// Any running tasks for the source will be stopped.
    ///
    /// The first source that matches the identifier is removed. See [`Source::matches`] for
    /// details. Will error if no match is found.
    pub async fn remove(&mut self, identifier: &str) -> Result<Source> {
        if let Some(index) = self
            .inner
            .iter()
            .position(|source| source.matches(identifier))
        {
            let mut removed = self.inner.remove(index);
            removed.stop().await?;
            return Ok(removed);
        }

        bail!("Unable to find a matching source to remove")
    }

    /// Import all sources
    ///
    /// # Arguments
    ///
    /// - `path`: The path to import the sources into (usually a project directory)
    ///
    /// Imports sources in parallel.
    pub async fn import(&self, path: &Path) -> Result<()> {
        let futures = self.inner.iter().map(|source| {
            let source = source.clone();
            let path = path.to_path_buf();
            tokio::spawn(async move { source.import(&path).await })
        });
        future::try_join_all(futures).await?;

        Ok(())
    }

    /// Import all sources synchronously
    ///
    /// # Arguments
    ///
    /// - `path`: The path to import the sources into (usually a project directory)
    ///
    /// Imports sources in parallel but blocks until all are complete.
    pub fn import_sync(&self, path: &Path) -> Result<()> {
        futures::executor::block_on(async { self.import(path).await })
    }

    /// Start cron and/or watch tasks for each source
    pub async fn start(&mut self, path: &Path) -> Result<()> {
        for source in self.inner.iter_mut() {
            let dest = match &source.dest {
                Some(dest) => path.join(dest),
                None => path.to_path_buf(),
            };
            source.start(&dest).await?;
        }
        self.running = Some(path.to_path_buf());

        Ok(())
    }

    /// Stop any cron or watch tasks for each source
    pub async fn stop(&mut self) -> Result<()> {
        for source in self.inner.iter_mut() {
            source.stop().await?;
        }
        self.running = None;

        Ok(())
    }
}
<commit_msg>fix(Sources): Check for invalid destination path when creating a new `Source`<commit_after>use std::{
    collections::BTreeMap,
    fs,
    path::{Path, PathBuf},
};

use defaults::Defaults;
use eyre::{bail, Result};
use futures::future;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use tokio::sync::mpsc;

use files::{File, Files};
use graph_triples::{
    relations::{self, NULL_RANGE},
    resources, Resource, Triple,
};
use providers::provider::{ImportOptions, SyncOptions, WatchMode};
use stencila_schema::Node;

/// A source-destination combination
///
/// Each source by destination combination should be unique to a project.
/// It is possible to have the same source being imported to multiple
/// destinations within a project and for multiple sources to used the same
/// destination (e.g. the root directory of the project).
#[skip_serializing_none]
#[derive(Clone, Debug, Defaults, Deserialize, Serialize)]
#[serde(default)]
pub struct Source {
    /// The name of the source
    ///
    /// Useful for providing a shorthand way to refer to the source rather than using it's URL
    pub name: Option<String>,

    /// The URL of the source
    pub url: String,

    /// The providers that matched the URL
    pub provider: Option<String>,

    /// The node parsed from the URL by the provider
    pub node: Option<Node>,

    /// The destination path within the project
    pub dest: Option<PathBuf>,

    /// Run a cron schedule to import and/or export the source
    pub cron: Option<SourceCron>,

    /// Synchronize the source
    pub watch: Option<SourceWatch>,

    /// The name of the secret required to access the source
    ///
    /// To improve the security of API access tokens, secrets are only ever read from
    /// environment variables. Source providers usually have a default secret name
    /// e.g. `GITHUB_TOKEN`. However, this field allows setting of custom secret names
    /// which may be necessary, for example, if a project uses two sources from the
    /// same provider, requiring different secrets.
    pub secret_name: Option<String>,

    /// A list of file paths currently associated with the source (relative to the project root)
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub files: Vec<PathBuf>,

    /// A running cron task
    #[serde(skip)]
    #[def = "None"]
    cron_task: Option<SourceTask>,

    /// A running watch task
    #[serde(skip)]
    #[def = "None"]
    watch_task: Option<SourceTask>,
}

#[skip_serializing_none]
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct SourceCron {
    /// The schedule on which to perform the action
    ///
    /// Can be a cron phrase (e.g. "every 10mins") or cron expression (e.g. "0 0 */10 * * *").
    schedule: String,

    /// The cron expression/s parsed from the `schedule`
    expressions: Option<Vec<String>>,

    /// The timezone parsed from the `schedule`
    timezone: Option<String>,

    /// The action to perform at each scheduled time
    action: Option<String>,
}

#[skip_serializing_none]
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct SourceWatch {
    /// The synchronization mode
    mode: Option<WatchMode>,
}

#[derive(Debug, Clone)]
struct SourceTask {
    /// The channel sender used to cancel the source task
    ///
    /// Note that if the source is dropped (ie. removed from a list of sources) with a running task
    /// then this canceller will be dropped and so the task will also end (an explicit `.send()`
    /// is not required).
    canceller: mpsc::Sender<()>,
}

impl Source {
    /// Create a new source
    ///
    /// Resolves a [`Provider`] for the source.
    /// Ensures that `dest` is a relative path and does not include any traversal (i.e. `..`).
    /// Parses the `cron` into an expression and timezone.
    pub async fn new(
        url: String,
        name: Option<String>,
        dest: Option<PathBuf>,
        cron: Option<String>,
        watch: Option<WatchMode>,
    ) -> Result<Source> {
        let (provider, node) = providers::resolve(&url).await?;

        if let Some(dest) = dest.as_ref() {
            if dest.is_absolute() {
                bail!("Source destination must be a relative path; try removing any leading slash (MacOS or Linux) or drive letter (e.g. `C:/` on Windows)")
            }
            if dest.to_string_lossy().contains("..") {
                bail!("Source destination must not have any path traversal (a.k.a directory climbing); try removing any `..` in the destination path")
            }
        }

        let cron = if let Some(schedule) = cron {
            let (schedules, timezone) = cron_utils::parse(&schedule)?;
            let (expressions, timezone) = if !schedules.is_empty() {
                (
                    Some(
                        schedules
                            .iter()
                            .map(|schedule| schedule.to_string())
                            .collect(),
                    ),
                    Some(timezone.to_string()),
                )
            } else {
                (None, None)
            };

            Some(SourceCron {
                schedule,
                expressions,
                timezone,
                action: None,
            })
        } else {
            None
        };

        let watch = watch.map(|mode| SourceWatch { mode: Some(mode) });

        Ok(Source {
            name,
            url,
            provider: Some(provider),
            node: Some(node),
            dest,
            cron,
            watch,
            ..Default::default()
        })
    }

    /// Generate a label for the source
    pub fn label(&self) -> String {
        self.name.clone().unwrap_or_else(|| self.url.clone())
    }

    /// Does the source match a string identifier
    ///
    /// Matches the string against the `name`, the `url` , or the filename (of the `dest` with or without extension).
    pub fn matches(&self, identifier: &str) -> bool {
        if let Some(name) = &self.name {
            if name == identifier {
                return true;
            }
        }

        if identifier == self.url {
            return true;
        }

        if let Some(dest) = &self.dest {
            if dest.to_string_lossy() == identifier {
                return true;
            }
            if let Some(file_stem) = dest.file_stem() {
                if file_stem == identifier {
                    return true;
                }
            }
        }

        false
    }

    /// Read a source from a JSON file
    ///
    /// Only changes the properties that are NOT saved in the project.json file.
    pub fn read<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
        let path = path.as_ref();
        if !path.exists() {
            bail!("Project source file does not exist: {}", path.display())
        }
        let path = path.canonicalize()?;

        let json = fs::read_to_string(path)?;
        let source: Source = serde_json::from_str(&json)?;
        self.files = source.files;

        Ok(())
    }

    /// Create a graph resource for the source
    pub fn resource(&self) -> Resource {
        resources::url(&self.url)
    }

    /// Create a set of graph triples describing relation between the source it's associated files.
    pub fn triples(&self, project: &Path) -> Vec<Triple> {
        let this = self.resource();
        self.files
            .iter()
            .map(|file| {
                (
                    this.clone(),
                    relations::imports(NULL_RANGE),
                    resources::file(&project.join(file)),
                )
            })
            .collect()
    }

    /// Import the source
    ///
    /// # Arguments
    ///
    /// - `path`: The path to import the source into
    ///
    /// # Returns
    ///
    /// A map of the [`File`]s that were imported by the source.
    pub async fn import(&self, path: &Path) -> Result<BTreeMap<PathBuf, File>> {
        let (.., node) = providers::resolve(&self.url).await?;
        let dest = match &self.dest {
            Some(dest) => path.join(dest),
            None => path.to_path_buf(),
        };
        let options = ImportOptions {
            secret_name: self.secret_name.clone(),
        };

        let (pre, ..) = Files::walk(path, false);

        providers::import(&node, &dest, Some(options)).await?;

        let (post, ..) = Files::walk(path, false);
        let files = post
            .into_iter()
            .filter_map(|(path, file)| match pre.get(&path) {
                None => Some((path, file)),
                Some(existing) => {
                    if existing.size != file.size && existing.modified != file.modified {
                        Some((path, file))
                    } else {
                        None
                    }
                }
            });

        Ok(files.collect())
    }

    /// Start cron and watch tasks (as applicable and as needed) for the source
    pub async fn start(&mut self, dest: &Path) -> Result<()> {
        self.cron_task_start(dest).await?;
        self.watch_task_start(dest).await?;

        Ok(())
    }

    /// Start a background cron task for the source
    ///
    /// A task has a `cron` spec and there is no tasks currently running for it.
    pub async fn cron_task_start(&mut self, dest: &Path) -> Result<()> {
        let cron = match (&self.cron, &self.cron_task) {
            (Some(cron), None) => cron,
            _ => return Ok(()),
        };

        tracing::info!("Starting cron task for source `{}`", self.label());
        let action = cron.action.clone().unwrap_or_default();
        let schedule = cron.schedule.clone();
        let (.., node) = providers::resolve(&self.url).await?;
        let dest = dest.to_path_buf();
        let (canceller, cancellee) = mpsc::channel(1);
        tokio::spawn(
            async move { providers::cron(&action, &schedule, &node, &dest, cancellee).await },
        );
        self.cron_task = Some(SourceTask { canceller });

        Ok(())
    }

    /// Start a background watch task for the source
    ///
    /// A task has a `watch` spec and there is no tasks currently running for it.
    pub async fn watch_task_start(&mut self, dest: &Path) -> Result<()> {
        let watch = match (&self.watch, &self.watch_task) {
            (Some(watch), None) => watch,
            _ => return Ok(()),
        };

        tracing::info!("Starting watch task for source `{}`", self.label());
        let (.., node) = providers::resolve(&self.url).await?;
        let dest = dest.to_path_buf();
        let (canceller, cancellee) = mpsc::channel(1);
        let options = SyncOptions {
            mode: watch.mode.clone(),
            secret_name: self.secret_name.clone(),
            ..Default::default()
        };
        tokio::spawn(async move { providers::watch(&node, &dest, cancellee, Some(options)).await });
        self.watch_task = Some(SourceTask { canceller });

        Ok(())
    }

    /// Start cron and watch tasks (if started) for the source
    pub async fn stop(&mut self) -> Result<()> {
        if let Some(task) = &self.cron_task {
            tracing::info!("Stopping cron task for source `{}`", self.label());
            task.canceller.send(()).await?
        }
        self.cron_task = None;

        if let Some(task) = &self.watch_task {
            tracing::info!("Stopping watch task for source `{}`", self.label());
            task.canceller.send(()).await?
        }
        self.watch_task = None;

        Ok(())
    }
}

/// A set of sources, usually associated with a project
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
#[serde(transparent)]
pub struct Sources {
    /// The list of sources
    pub inner: Vec<Source>,

    /// If in a running state, the path that cron and watch tasks were started with
    ///
    /// If tasks are running then any new sources that are added will have
    /// tasks automatically started for them.
    #[serde(skip)]
    running: Option<PathBuf>,
}

impl Sources {
    /// Return a list of sources
    pub fn list(&self) -> Vec<String> {
        self.inner.iter().map(|source| source.label()).collect()
    }

    /// Find a source
    ///
    /// # Arguments
    ///
    /// - `identifier`: An identifier for a source.
    ///
    /// The first source that matches the identifier is returned. Will error if no match is found.
    pub fn find(&self, identifier: &str) -> Result<&Source> {
        if let Some(source) = self.inner.iter().find(|source| source.matches(identifier)) {
            return Ok(source);
        }

        bail!("Unable to find a source matching `{}`", identifier)
    }

    /// Add a source
    ///
    /// # Arguments
    ///
    /// - `source`: The source to add
    ///
    /// Warns if there is already a source with the same `url`.
    /// Errors if there is already a source with the same `name` or the same `url` and `dest`.
    ///
    /// If the `start` method has been called previously then the sources are in a "running"
    /// state and the `start` will be called on the source.
    pub async fn add(&mut self, mut source: Source) -> Result<()> {
        let source_dest = source.dest.clone().unwrap_or_else(|| PathBuf::from("."));
        for existing in self.inner.iter() {
            if let (Some(existing_name), Some(source_name)) = (&existing.name, &source.name) {
                if existing_name == source_name {
                    bail!("There is already a source with the name `{}`", source_name);
                }
            }
            let existing_dest = existing.dest.clone().unwrap_or_else(|| PathBuf::from("."));
            if existing.url == source.url && existing_dest == source_dest {
                bail!(
                    "There is already a source with the URL `{}` and destination `{}`",
                    source.url,
                    source_dest.display()
                );
            }
            if existing.url == source.url {
                tracing::warn!("There is already a source with the URL `{}`", source.url);
            }
        }
        if let Some(path) = &self.running {
            source.start(path).await?;
        }
        self.inner.push(source);

        Ok(())
    }

    /// Remove a source
    ///
    /// # Arguments
    ///
    /// - `identifier`: An identifier for a source.
    ///
    /// Any running tasks for the source will be stopped.
    ///
    /// The first source that matches the identifier is removed. See [`Source::matches`] for
    /// details. Will error if no match is found.
    pub async fn remove(&mut self, identifier: &str) -> Result<Source> {
        if let Some(index) = self
            .inner
            .iter()
            .position(|source| source.matches(identifier))
        {
            let mut removed = self.inner.remove(index);
            removed.stop().await?;
            return Ok(removed);
        }

        bail!("Unable to find a matching source to remove")
    }

    /// Import all sources
    ///
    /// # Arguments
    ///
    /// - `path`: The path to import the sources into (usually a project directory)
    ///
    /// Imports sources in parallel.
    pub async fn import(&self, path: &Path) -> Result<()> {
        let futures = self.inner.iter().map(|source| {
            let source = source.clone();
            let path = path.to_path_buf();
            tokio::spawn(async move { source.import(&path).await })
        });
        future::try_join_all(futures).await?;

        Ok(())
    }

    /// Import all sources synchronously
    ///
    /// # Arguments
    ///
    /// - `path`: The path to import the sources into (usually a project directory)
    ///
    /// Imports sources in parallel but blocks until all are complete.
    pub fn import_sync(&self, path: &Path) -> Result<()> {
        futures::executor::block_on(async { self.import(path).await })
    }

    /// Start cron and/or watch tasks for each source
    pub async fn start(&mut self, path: &Path) -> Result<()> {
        for source in self.inner.iter_mut() {
            let dest = match &source.dest {
                Some(dest) => path.join(dest),
                None => path.to_path_buf(),
            };
            source.start(&dest).await?;
        }
        self.running = Some(path.to_path_buf());

        Ok(())
    }

    /// Stop any cron or watch tasks for each source
    pub async fn stop(&mut self) -> Result<()> {
        for source in self.inner.iter_mut() {
            source.stop().await?;
        }
        self.running = None;

        Ok(())
    }
}
<|endoftext|>


<commit_before>use std;
use std::fmt;
use std::fs::Metadata;
use std::path::PathBuf;

use chrono::prelude::*;
use lazy_init::Lazy;
use sysinfo;
use sysinfo::{Process, System, SystemExt};

use command_line_arguments::CommandLineArguments;
use datetime::*;

// Information about the program we are running (qork.exe), the invocation.
// This information is derived at runtime.

pub struct ProgramInfo {
    parsed_args: CommandLineArguments,
    process: Lazy<Process>,

    path: Option<PathBuf>,
    raw_args: Vec<String>,
    meta_data: Option<Metadata>
}

impl ProgramInfo {
    pub fn new() -> ProgramInfo {
        let path = std::env::current_exe().ok();
        let md = path.as_ref().and_then(|e| e.metadata().ok());

        ProgramInfo {
            parsed_args: CommandLineArguments::new(),
            process: Lazy::new(),
            path: path,
            raw_args: std::env::args().collect(),

            meta_data: md
        }
    }

    pub fn path(&self) -> &Option<PathBuf> {
        &self.path
    }

    pub fn process(&self) -> &Process {
        self.process.get_or_create(|| {
            let system = System::new();
            let pid = sysinfo::get_current_pid();
            let ref_to_process = system.get_process(pid).unwrap();
            ref_to_process.clone()
        })
    }

    pub fn raw_args(&self) -> &Vec<String> {
        &self.raw_args
    }

    pub fn parsed_args(&self) -> &CommandLineArguments {
        &self.parsed_args
    }

    pub fn meta_data(&self) -> &Option<Metadata> {
        &self.meta_data
    }

    pub fn size(&self) -> Option<u64> {
        self.meta_data.as_ref().map(|m| m.len())
    }

    pub fn modified_date(&self) -> Option<DateTime<Utc>> {
         self.meta_data.as_ref().map(|m| m.modified().ok()).map(|m| system_time_to_date_time(m.unwrap()))
     }
}

impl fmt::Debug for ProgramInfo {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let p = match &self.path {
            &Some(ref pb) => pb.to_str().unwrap_or("unknown"),
            &None => "unknown"
        };

        let mdate = match &self.modified_date() {
            &Some(t) => t.format("%Y-%m-%d %H:%M:%S%.3f UTC").to_string(),
            &None => String::from("unknown")
        };

        write!(f, r#"ProgramInfo {{ path: "{}", size: {}, modified_date: "{}" }}"#,
            p,
            self.size().unwrap_or(0),
            mdate
        )
    }
}

impl fmt::Display for ProgramInfo {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        fmt::Debug::fmt(self, f)
    }
}
<commit_msg>Call libc directly rather than using sysinfo.<commit_after>use std;
use std::fmt;
use std::fs::Metadata;
use std::path::PathBuf;

use chrono::prelude::*;
use lazy_init::Lazy;
use libc;
use libc::{pid_t, uid_t, gid_t};
use sysinfo;
use sysinfo::{Process, System, SystemExt};

use command_line_arguments::CommandLineArguments;
use datetime::*;

// Information about the program we are running (qork.exe), the invocation.
// This information is derived at runtime.

pub struct ProgramInfo {
    path: Option<PathBuf>,
    meta_data: Option<Metadata>,
    raw_args: Vec<String>,
    parsed_args: CommandLineArguments,
    process: Lazy<Process>,
    pub pid: pid_t,
    pub parent_pid: pid_t,
    pub uid: uid_t,
    pub effective_uid: uid_t,
    pub gid: gid_t,
    pub effective_gid: gid_t
}

/*
pub struct Process {
    pub name: String,
    pub cmd: Vec<String>,
    pub exe: String,
    pub pid: pid_t,                    WANT
    pub parent: Option<pid_t>,         WANT
    pub environ: Vec<String>,
    pub cwd: String,
    pub root: String,
    pub memory: u64,
    pub start_time: u64,
    pub cpu_usage: f32,
    pub uid: uid_t,                    WANT
    pub gid: gid_t,                    WANT
    pub status: Option<ProcessStatus>,
    pub tasks: HashMap<pid_t, Process>,
}
*/

impl ProgramInfo {
    pub fn new() -> ProgramInfo {
        let path = std::env::current_exe().ok();
        let md = path.as_ref().and_then(|e| e.metadata().ok());

        ProgramInfo {
            path: path,
            meta_data: md,
            raw_args: std::env::args().collect(),
            parsed_args: CommandLineArguments::new(),
            process: Lazy::new(),
            pid: unsafe { libc::getpid() },
            parent_pid: unsafe { libc::getppid() },
            uid: unsafe { libc::getuid() },
            effective_uid: unsafe { libc::getuid() },
            gid: unsafe { libc::getgid() },
            effective_gid: unsafe { libc::getegid() },
        }
    }

    pub fn path(&self) -> &Option<PathBuf> {
        &self.path
    }

    pub fn process(&self) -> &Process {
        self.process.get_or_create(|| {
            let system = System::new();
            let pid = sysinfo::get_current_pid();
            let ref_to_process = system.get_process(pid).unwrap();
            ref_to_process.clone()
        })
    }

    pub fn raw_args(&self) -> &Vec<String> {
        &self.raw_args
    }

    pub fn parsed_args(&self) -> &CommandLineArguments {
        &self.parsed_args
    }

    pub fn meta_data(&self) -> &Option<Metadata> {
        &self.meta_data
    }

    pub fn size(&self) -> Option<u64> {
        self.meta_data.as_ref().map(|m| m.len())
    }

    pub fn modified_date(&self) -> Option<DateTime<Utc>> {
         self.meta_data.as_ref().map(|m| m.modified().ok()).map(|m| system_time_to_date_time(m.unwrap()))
    }
}

impl fmt::Debug for ProgramInfo {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let p = match &self.path {
            &Some(ref pb) => pb.to_str().unwrap_or("unknown"),
            &None => "unknown"
        };

        let mdate = match &self.modified_date() {
            &Some(t) => t.format("%Y-%m-%d %H:%M:%S%.3f UTC").to_string(),
            &None => String::from("unknown")
        };

        write!(f, r#"ProgramInfo {{ path: "{}", size: {}, modified_date: "{}", pid: {}, parent_pid: {}, uid: {}, effective_uid: {}, gid: {}, effective_gid: {} }}"#,
            p,
            self.size().unwrap_or(0),
            mdate,
            self.pid, self.parent_pid,
            self.uid, self.effective_uid,
            self.gid, self.effective_gid
        )
    }
}

impl fmt::Display for ProgramInfo {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        fmt::Debug::fmt(self, f)
    }
}
<|endoftext|>


<commit_before>// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#![allow(non_snake_case)]

register_diagnostic!(E0001, r##"
    This error suggests that the expression arm corresponding to the noted pattern
    will never be reached as for all possible values of the expression being matched,
    one of the preceeding patterns will match.

    This means that perhaps some of the preceeding patterns are too general, this
    one is too specific or the ordering is incorrect.
"##)

register_diagnostic!(E0162, r##"
    This error is produced by an `if let` expression where the pattern is irrefutable.
    An `if let` that can never fail is considered an error.
"##)

register_diagnostics!(
    E0002,
    E0003,
    E0004,
    E0005,
    E0006,
    E0007,
    E0008,
    E0009,
    E0010,
    E0011,
    E0012,
    E0013,
    E0014,
    E0015,
    E0016,
    E0017,
    E0019,
    E0020,
    E0021,
    E0022,
    E0023,
    E0024,
    E0025,
    E0026,
    E0027,
    E0028,
    E0029,
    E0030,
    E0031,
    E0032,
    E0033,
    E0034,
    E0035,
    E0036,
    E0038,
    E0039,
    E0040,
    E0041,
    E0044,
    E0045,
    E0046,
    E0047,
    E0048,
    E0049,
    E0050,
    E0051,
    E0052,
    E0053,
    E0054,
    E0055,
    E0056,
    E0057,
    E0058,
    E0059,
    E0060,
    E0061,
    E0062,
    E0063,
    E0066,
    E0067,
    E0068,
    E0069,
    E0070,
    E0071,
    E0072,
    E0073,
    E0074,
    E0075,
    E0076,
    E0077,
    E0079,
    E0080,
    E0081,
    E0082,
    E0083,
    E0084,
    E0085,
    E0086,
    E0087,
    E0088,
    E0089,
    E0090,
    E0091,
    E0092,
    E0093,
    E0094,
    E0100,
    E0101,
    E0102,
    E0103,
    E0104,
    E0106,
    E0107,
    E0108,
    E0109,
    E0110,
    E0113,
    E0114,
    E0115,
    E0116,
    E0117,
    E0118,
    E0119,
    E0120,
    E0121,
    E0122,
    E0124,
    E0126,
    E0127,
    E0128,
    E0129,
    E0130,
    E0131,
    E0132,
    E0133,
    E0134,
    E0135,
    E0136,
    E0137,
    E0138,
    E0139,
    E0140,
    E0141,
    E0152,
    E0153,
    E0154,
    E0155,
    E0156,
    E0157,
    E0158,
    E0159,
    E0161
)
<commit_msg>Temporarily remove the description for the diagnostic E0162<commit_after>// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#![allow(non_snake_case)]

register_diagnostic!(E0001, r##"
    This error suggests that the expression arm corresponding to the noted pattern
    will never be reached as for all possible values of the expression being matched,
    one of the preceeding patterns will match.

    This means that perhaps some of the preceeding patterns are too general, this
    one is too specific or the ordering is incorrect.
"##)

register_diagnostics!(
    E0002,
    E0003,
    E0004,
    E0005,
    E0006,
    E0007,
    E0008,
    E0009,
    E0010,
    E0011,
    E0012,
    E0013,
    E0014,
    E0015,
    E0016,
    E0017,
    E0019,
    E0020,
    E0021,
    E0022,
    E0023,
    E0024,
    E0025,
    E0026,
    E0027,
    E0028,
    E0029,
    E0030,
    E0031,
    E0032,
    E0033,
    E0034,
    E0035,
    E0036,
    E0038,
    E0039,
    E0040,
    E0041,
    E0044,
    E0045,
    E0046,
    E0047,
    E0048,
    E0049,
    E0050,
    E0051,
    E0052,
    E0053,
    E0054,
    E0055,
    E0056,
    E0057,
    E0058,
    E0059,
    E0060,
    E0061,
    E0062,
    E0063,
    E0066,
    E0067,
    E0068,
    E0069,
    E0070,
    E0071,
    E0072,
    E0073,
    E0074,
    E0075,
    E0076,
    E0077,
    E0079,
    E0080,
    E0081,
    E0082,
    E0083,
    E0084,
    E0085,
    E0086,
    E0087,
    E0088,
    E0089,
    E0090,
    E0091,
    E0092,
    E0093,
    E0094,
    E0100,
    E0101,
    E0102,
    E0103,
    E0104,
    E0106,
    E0107,
    E0108,
    E0109,
    E0110,
    E0113,
    E0114,
    E0115,
    E0116,
    E0117,
    E0118,
    E0119,
    E0120,
    E0121,
    E0122,
    E0124,
    E0126,
    E0127,
    E0128,
    E0129,
    E0130,
    E0131,
    E0132,
    E0133,
    E0134,
    E0135,
    E0136,
    E0137,
    E0138,
    E0139,
    E0140,
    E0141,
    E0152,
    E0153,
    E0154,
    E0155,
    E0156,
    E0157,
    E0158,
    E0159,
    E0161,
    E0162
)
<|endoftext|>


<commit_before>// -*- rust -*-
// xfail-pretty

/*
  A parallel version of fibonacci numbers.

  This version is meant mostly as a way of stressing and benchmarking
  the task system. It supports a lot of command-line arguments to
  control how it runs.

*/

use std;

import std::{time, getopts};
import io::writer_util;
import int::range;
import comm::port;
import comm::chan;
import comm::send;
import comm::recv;

import core::result;
import result::{ok, err};

fn fib(n: int) -> int {
    fn pfib(c: chan<int>, n: int) {
        if n == 0 {
            send(c, 0);
        } else if n <= 2 {
            send(c, 1);
        } else {
            let p = port();
            let ch = chan(p);
            task::spawn(|| pfib(ch, n - 1) );
            task::spawn(|| pfib(ch, n - 2) );
            send(c, recv(p) + recv(p));
        }
    }

    let p = port();
    let ch = chan(p);
    let t = task::spawn(|| pfib(ch, n) );
    return recv(p);
}

type config = {stress: bool};

fn parse_opts(argv: ~[~str]) -> config {
    let opts = ~[getopts::optflag(~"stress")];

    let opt_args = vec::slice(argv, 1u, vec::len(argv));

    alt getopts::getopts(opt_args, opts) {
      ok(m) { return {stress: getopts::opt_present(m, ~"stress")} }
      err(_) { fail; }
    }
}

fn stress_task(&&id: int) {
    let mut i = 0;
    loop {
        let n = 15;
        assert (fib(n) == fib(n));
        i += 1;
        error!{"%d: Completed %d iterations", id, i};
    }
}

fn stress(num_tasks: int) {
    let mut results = ~[];
    for range(0, num_tasks) |i| {
        do task::task().future_result(|-r| {
            results += ~[r];
        }).spawn {
            stress_task(i);
        }
    }
    for results.each |r| { future::get(r); }
}

fn main(args: ~[~str]) {
    let args = if os::getenv(~"RUST_BENCH").is_some() {
        ~[~"", ~"20"]
    } else if args.len() <= 1u {
        ~[~"", ~"8"]
    } else {
        args
    };

    let opts = parse_opts(args);

    if opts.stress {
        stress(2);
    } else {
        let max = option::get(uint::parse_buf(str::bytes(args[1]),
                                              10u)) as int;

        let num_trials = 10;

        let out = io::stdout();

        for range(1, max + 1) |n| {
            for range(0, num_trials) |i| {
                let start = time::precise_time_ns();
                let fibn = fib(n);
                let stop = time::precise_time_ns();

                let elapsed = stop - start;

                out.write_line(fmt!{"%d\t%d\t%s", n, fibn,
                                    u64::str(elapsed)});
            }
        }
    }
}
<commit_msg>Convert pfib to pipes. This is a useful stress test.<commit_after>// -*- rust -*-
// xfail-pretty

/*
  A parallel version of fibonacci numbers.

  This version is meant mostly as a way of stressing and benchmarking
  the task system. It supports a lot of command-line arguments to
  control how it runs.

*/

use std;

import std::{time, getopts};
import io::writer_util;
import int::range;
import pipes::port;
import pipes::chan;
import pipes::send;
import pipes::recv;

import core::result;
import result::{ok, err};

fn fib(n: int) -> int {
    fn pfib(c: chan<int>, n: int) {
        if n == 0 {
            c.send(0);
        } else if n <= 2 {
            c.send(1);
        } else {
            let p = pipes::port_set();
            let ch = p.chan();
            task::spawn(|| pfib(ch, n - 1) );
            let ch = p.chan();
            task::spawn(|| pfib(ch, n - 2) );
            c.send(p.recv() + p.recv());
        }
    }

    let (ch, p) = pipes::stream();
    let t = task::spawn(|| pfib(ch, n) );
    p.recv()
}

type config = {stress: bool};

fn parse_opts(argv: ~[~str]) -> config {
    let opts = ~[getopts::optflag(~"stress")];

    let opt_args = vec::slice(argv, 1u, vec::len(argv));

    alt getopts::getopts(opt_args, opts) {
      ok(m) { return {stress: getopts::opt_present(m, ~"stress")} }
      err(_) { fail; }
    }
}

fn stress_task(&&id: int) {
    let mut i = 0;
    loop {
        let n = 15;
        assert (fib(n) == fib(n));
        i += 1;
        error!{"%d: Completed %d iterations", id, i};
    }
}

fn stress(num_tasks: int) {
    let mut results = ~[];
    for range(0, num_tasks) |i| {
        do task::task().future_result(|-r| {
            results += ~[r];
        }).spawn {
            stress_task(i);
        }
    }
    for results.each |r| { future::get(r); }
}

fn main(args: ~[~str]) {
    let args = if os::getenv(~"RUST_BENCH").is_some() {
        ~[~"", ~"20"]
    } else if args.len() <= 1u {
        ~[~"", ~"8"]
    } else {
        args
    };

    let opts = parse_opts(args);

    if opts.stress {
        stress(2);
    } else {
        let max = option::get(uint::parse_buf(str::bytes(args[1]),
                                              10u)) as int;

        let num_trials = 10;

        let out = io::stdout();

        for range(1, max + 1) |n| {
            for range(0, num_trials) |i| {
                let start = time::precise_time_ns();
                let fibn = fib(n);
                let stop = time::precise_time_ns();

                let elapsed = stop - start;

                out.write_line(fmt!{"%d\t%d\t%s", n, fibn,
                                    u64::str(elapsed)});
            }
        }
    }
}
<|endoftext|>


<commit_before>#![feature(staged_api)]
#![doc(issue_tracker_base_url = "http://issue_url/")]

#![unstable(feature="test", issue = "32374")]

// @matches issue_32374/index.html '//*[@class="docblock-short"]/span[@class="stab deprecated"]' \
//      'Deprecated'
// @matches issue_32374/index.html '//*[@class="docblock-short"]/span[@class="stab unstable"]' \
//      'Experimental'
// @matches issue_32374/index.html '//*[@class="docblock-short"]/text()' 'Docs'

// @has issue_32374/struct.T.html '//*[@class="stab deprecated"]' \
//      'Deprecated since 1.0.0: text'
// @has - 'test #32374'
// @matches issue_32374/struct.T.html '//*[@class="stab unstable"]' \
//      '🔬 This is a nightly-only experimental API. \(test\s#32374\)$'
/// Docs
#[rustc_deprecated(since = "1.0.0", reason = "text")]
#[unstable(feature = "test", issue = "32374")]
pub struct T;

// @has issue_32374/struct.U.html '//*[@class="stab deprecated"]' \
//      'Deprecated since 1.0.0: deprecated'
// @has issue_32374/struct.U.html '//*[@class="stab unstable"]' \
//      '🔬 This is a nightly-only experimental API. (test #32374)'
// @has issue_32374/struct.U.html '//details' \
//      '🔬 This is a nightly-only experimental API. (test #32374)'
// @has issue_32374/struct.U.html '//summary' \
//      '🔬 This is a nightly-only experimental API. (test #32374)'
// @has issue_32374/struct.U.html '//details/p' \
//      'unstable'
#[rustc_deprecated(since = "1.0.0", reason = "deprecated")]
#[unstable(feature = "test", issue = "32374", reason = "unstable")]
pub struct U;
<commit_msg>Add test for deprecated emoji<commit_after>#![feature(staged_api)]
#![doc(issue_tracker_base_url = "http://issue_url/")]

#![unstable(feature="test", issue = "32374")]

// @matches issue_32374/index.html '//*[@class="docblock-short"]/span[@class="stab deprecated"]' \
//      'Deprecated'
// @matches issue_32374/index.html '//*[@class="docblock-short"]/span[@class="stab unstable"]' \
//      'Experimental'
// @matches issue_32374/index.html '//*[@class="docblock-short"]/text()' 'Docs'

// @has issue_32374/struct.T.html '//*[@class="stab deprecated"]' \
//      '👎 Deprecated since 1.0.0: text'
// @has - 'test #32374'
// @matches issue_32374/struct.T.html '//*[@class="stab unstable"]' \
//      '🔬 This is a nightly-only experimental API. \(test\s#32374\)$'
/// Docs
#[rustc_deprecated(since = "1.0.0", reason = "text")]
#[unstable(feature = "test", issue = "32374")]
pub struct T;

// @has issue_32374/struct.U.html '//*[@class="stab deprecated"]' \
//      '👎 Deprecated since 1.0.0: deprecated'
// @has issue_32374/struct.U.html '//*[@class="stab unstable"]' \
//      '🔬 This is a nightly-only experimental API. (test #32374)'
// @has issue_32374/struct.U.html '//details' \
//      '🔬 This is a nightly-only experimental API. (test #32374)'
// @has issue_32374/struct.U.html '//summary' \
//      '🔬 This is a nightly-only experimental API. (test #32374)'
// @has issue_32374/struct.U.html '//details/p' \
//      'unstable'
#[rustc_deprecated(since = "1.0.0", reason = "deprecated")]
#[unstable(feature = "test", issue = "32374", reason = "unstable")]
pub struct U;
<|endoftext|>


<commit_before>use std::collections::{HashMap, HashSet};
use std::thread;
use std::net::SocketAddr;
use std::net::TcpStream;
use std::sync::{Arc, Mutex, RwLock, mpsc};
use std::time::Duration;

use rocksdb::DB;

use super::cluster::{Simulator, Cluster};
use tikv::raftserver::server::*;
use tikv::raftserver::Result;
use tikv::util::codec::rpc;
use kvproto::raft_serverpb::{self, Message, MessageType};
use kvproto::raft_cmdpb::*;
use tikv::pd::PdClient;
use super::util;
use super::pd::TestPdClient;
use super::pd_ask::run_ask_loop;

pub struct ServerCluster {
    cluster_id: u64,
    senders: HashMap<u64, SendCh>,
    handles: HashMap<u64, thread::JoinHandle<()>>,
    addrs: HashMap<u64, SocketAddr>,
    conns: Mutex<HashMap<SocketAddr, Vec<TcpStream>>>,

    msg_id: Mutex<u64>,
    pd_client: Arc<RwLock<TestPdClient>>,
}

impl ServerCluster {
    pub fn new(cluster_id: u64, pd_client: Arc<RwLock<TestPdClient>>) -> ServerCluster {
        ServerCluster {
            cluster_id: cluster_id,
            senders: HashMap::new(),
            handles: HashMap::new(),
            addrs: HashMap::new(),
            conns: Mutex::new(HashMap::new()),
            msg_id: Mutex::new(0),
            pd_client: pd_client,
        }
    }

    fn alloc_msg_id(&self) -> u64 {
        let mut msg_id = self.msg_id.lock().unwrap();
        *msg_id += 1;
        *msg_id
    }

    fn get_conn(&self, addr: &SocketAddr) -> Result<TcpStream> {
        {
            let mut conns = self.conns
                                .lock()
                                .unwrap();
            let conn = conns.get_mut(addr);
            if let Some(mut pool) = conn {
                if !pool.is_empty() {
                    return Ok(pool.pop().unwrap());
                }
            }
        }

        let conn = TcpStream::connect(addr).unwrap();
        conn.set_nodelay(true);
        Ok(conn)
    }
}

impl Simulator for ServerCluster {
    #[allow(useless_format)]
    fn run_node(&mut self, node_id: u64, cfg: Config, engine: Arc<DB>) -> u64 {
        assert!(node_id == 0 || !self.handles.contains_key(&node_id));
        assert!(node_id == 0 || !self.senders.contains_key(&node_id));

        let mut event_loop = create_event_loop().unwrap();
        let mut server = Server::new(&mut event_loop, cfg, vec![engine], self.pd_client.clone())
                             .unwrap();
        let addr = server.listening_addr().unwrap();

        assert!(node_id == 0 || node_id == server.get_node_id());
        let node_id = server.get_node_id();

        // We must get real listening address and re-update pd again.
        self.pd_client
            .write()
            .unwrap()
            .put_node(self.cluster_id, util::new_node(node_id, addr.to_string()))
            .unwrap();

        let ch = server.get_sendch();

        let t = thread::spawn(move || {
            server.run(&mut event_loop).unwrap();
        });

        self.handles.insert(node_id, t);
        self.senders.insert(node_id, ch);
        self.addrs.insert(node_id, addr);

        node_id
    }

    fn stop_node(&mut self, node_id: u64) {
        let h = self.handles.remove(&node_id).unwrap();
        let ch = self.senders.remove(&node_id).unwrap();
        self.addrs.remove(&node_id).unwrap();

        ch.send(Msg::Quit).unwrap();
        h.join().unwrap();
    }

    fn get_node_ids(&self) -> HashSet<u64> {
        self.senders.keys().cloned().collect()
    }

    fn call_command(&self, request: RaftCmdRequest, timeout: Duration) -> Result<RaftCmdResponse> {
        let node_id = request.get_header().get_peer().get_node_id();
        let addr = self.addrs.get(&node_id).unwrap();
        let mut conn = self.get_conn(addr).unwrap();

        let mut msg = Message::new();
        msg.set_msg_type(MessageType::Cmd);
        msg.set_cmd_req(request);

        let msg_id = self.alloc_msg_id();
        try!(rpc::encode_msg(&mut conn, msg_id, &msg));

        let mut resp_msg = Message::new();
        let get_msg_id = try!(rpc::decode_msg(&mut conn, &mut resp_msg));

        {
            let mut conns = self.conns
                                .lock()
                                .unwrap();
            let p = conns.entry(*addr).or_insert(vec![]);
            p.push(conn);
        }

        assert_eq!(resp_msg.get_msg_type(), MessageType::CmdResp);
        assert_eq!(msg_id, get_msg_id);

        Ok(resp_msg.take_cmd_resp())
    }

    fn send_raft_msg(&self, raft_msg: raft_serverpb::RaftMessage) -> Result<()> {
        let node_id = raft_msg.get_to_peer().get_node_id();
        let addr = self.addrs.get(&node_id).unwrap();

        let mut conn = TcpStream::connect(addr).unwrap();

        conn.set_write_timeout(Some(Duration::from_secs(3))).unwrap();

        let mut msg = Message::new();
        msg.set_msg_type(MessageType::Raft);
        msg.set_raft(raft_msg);

        let msg_id = self.alloc_msg_id();
        try!(rpc::encode_msg(&mut conn, msg_id, &msg));

        Ok(())
    }
}

pub fn new_server_cluster(id: u64, count: usize) -> Cluster<ServerCluster> {
    let (tx, rx) = mpsc::channel();
    let pd_client = Arc::new(RwLock::new(TestPdClient::new(tx)));
    let sim = Arc::new(RwLock::new(ServerCluster::new(id, pd_client.clone())));
    run_ask_loop(pd_client.clone(), sim.clone(), rx);
    Cluster::new(id, count, sim, pd_client)
}
<commit_msg>Make clippy happy<commit_after>use std::collections::{HashMap, HashSet};
use std::thread;
use std::net::SocketAddr;
use std::net::TcpStream;
use std::sync::{Arc, Mutex, RwLock, mpsc};
use std::time::Duration;

use rocksdb::DB;

use super::cluster::{Simulator, Cluster};
use tikv::raftserver::server::*;
use tikv::raftserver::Result;
use tikv::util::codec::rpc;
use kvproto::raft_serverpb::{self, Message, MessageType};
use kvproto::raft_cmdpb::*;
use tikv::pd::PdClient;
use super::util;
use super::pd::TestPdClient;
use super::pd_ask::run_ask_loop;

pub struct ServerCluster {
    cluster_id: u64,
    senders: HashMap<u64, SendCh>,
    handles: HashMap<u64, thread::JoinHandle<()>>,
    addrs: HashMap<u64, SocketAddr>,
    conns: Mutex<HashMap<SocketAddr, Vec<TcpStream>>>,

    msg_id: Mutex<u64>,
    pd_client: Arc<RwLock<TestPdClient>>,
}

impl ServerCluster {
    pub fn new(cluster_id: u64, pd_client: Arc<RwLock<TestPdClient>>) -> ServerCluster {
        ServerCluster {
            cluster_id: cluster_id,
            senders: HashMap::new(),
            handles: HashMap::new(),
            addrs: HashMap::new(),
            conns: Mutex::new(HashMap::new()),
            msg_id: Mutex::new(0),
            pd_client: pd_client,
        }
    }

    fn alloc_msg_id(&self) -> u64 {
        let mut msg_id = self.msg_id.lock().unwrap();
        *msg_id += 1;
        *msg_id
    }

    fn get_conn(&self, addr: &SocketAddr) -> Result<TcpStream> {
        {
            let mut conns = self.conns
                                .lock()
                                .unwrap();
            let conn = conns.get_mut(addr);
            if let Some(mut pool) = conn {
                if !pool.is_empty() {
                    return Ok(pool.pop().unwrap());
                }
            }
        }

        let conn = TcpStream::connect(addr).unwrap();
        conn.set_nodelay(true).unwrap();
        Ok(conn)
    }
}

impl Simulator for ServerCluster {
    #[allow(useless_format)]
    fn run_node(&mut self, node_id: u64, cfg: Config, engine: Arc<DB>) -> u64 {
        assert!(node_id == 0 || !self.handles.contains_key(&node_id));
        assert!(node_id == 0 || !self.senders.contains_key(&node_id));

        let mut event_loop = create_event_loop().unwrap();
        let mut server = Server::new(&mut event_loop, cfg, vec![engine], self.pd_client.clone())
                             .unwrap();
        let addr = server.listening_addr().unwrap();

        assert!(node_id == 0 || node_id == server.get_node_id());
        let node_id = server.get_node_id();

        // We must get real listening address and re-update pd again.
        self.pd_client
            .write()
            .unwrap()
            .put_node(self.cluster_id, util::new_node(node_id, addr.to_string()))
            .unwrap();

        let ch = server.get_sendch();

        let t = thread::spawn(move || {
            server.run(&mut event_loop).unwrap();
        });

        self.handles.insert(node_id, t);
        self.senders.insert(node_id, ch);
        self.addrs.insert(node_id, addr);

        node_id
    }

    fn stop_node(&mut self, node_id: u64) {
        let h = self.handles.remove(&node_id).unwrap();
        let ch = self.senders.remove(&node_id).unwrap();
        self.addrs.remove(&node_id).unwrap();

        ch.send(Msg::Quit).unwrap();
        h.join().unwrap();
    }

    fn get_node_ids(&self) -> HashSet<u64> {
        self.senders.keys().cloned().collect()
    }

    #[allow(or_fun_call)]
    fn call_command(&self, request: RaftCmdRequest, timeout: Duration) -> Result<RaftCmdResponse> {
        let node_id = request.get_header().get_peer().get_node_id();
        let addr = self.addrs.get(&node_id).unwrap();
        let mut conn = self.get_conn(addr).unwrap();

        let mut msg = Message::new();
        msg.set_msg_type(MessageType::Cmd);
        msg.set_cmd_req(request);

        let msg_id = self.alloc_msg_id();
        conn.set_write_timeout(Some(timeout)).unwrap();
        try!(rpc::encode_msg(&mut conn, msg_id, &msg));

        conn.set_read_timeout(Some(timeout)).unwrap();
        let mut resp_msg = Message::new();
        let get_msg_id = try!(rpc::decode_msg(&mut conn, &mut resp_msg));

        {
            let mut conns = self.conns
                                .lock()
                                .unwrap();
            let p = conns.entry(*addr).or_insert(vec![]);
            p.push(conn);
        }

        assert_eq!(resp_msg.get_msg_type(), MessageType::CmdResp);
        assert_eq!(msg_id, get_msg_id);

        Ok(resp_msg.take_cmd_resp())
    }

    fn send_raft_msg(&self, raft_msg: raft_serverpb::RaftMessage) -> Result<()> {
        let node_id = raft_msg.get_to_peer().get_node_id();
        let addr = self.addrs.get(&node_id).unwrap();

        let mut conn = TcpStream::connect(addr).unwrap();

        conn.set_write_timeout(Some(Duration::from_secs(3))).unwrap();

        let mut msg = Message::new();
        msg.set_msg_type(MessageType::Raft);
        msg.set_raft(raft_msg);

        let msg_id = self.alloc_msg_id();
        try!(rpc::encode_msg(&mut conn, msg_id, &msg));

        Ok(())
    }
}

pub fn new_server_cluster(id: u64, count: usize) -> Cluster<ServerCluster> {
    let (tx, rx) = mpsc::channel();
    let pd_client = Arc::new(RwLock::new(TestPdClient::new(tx)));
    let sim = Arc::new(RwLock::new(ServerCluster::new(id, pd_client.clone())));
    run_ask_loop(pd_client.clone(), sim.clone(), rx);
    Cluster::new(id, count, sim, pd_client)
}
<|endoftext|>


<commit_before>// Implements http://rosettacode.org/wiki/100_doors
#![cfg(not_tested)]
fn main() {
	for i in std::iter::range_inclusive(1,100) {
		let x = (i as f64).powf(&0.5);
		let state = if x == x.round() {"open"} else {"closed"};
		println!("Door {} is {}", i, state);
	}
}
<commit_msg>Fix #101<commit_after>// Implements http://rosettacode.org/wiki/100_doors
#![cfg(not_tested)]
fn main() {
	for i in std::iter::range_inclusive(1,100) {
		let x = (i as f64).powf(0.5);
		let state = if x == x.round() {"open"} else {"closed"};
		println!("Door {} is {}", i, state);
	}
}
<|endoftext|>


<commit_before>//! Tests for the `cargo rustdoc` command.

use cargo_test_support::{basic_manifest, project};

#[cargo_test]
fn rustdoc_simple() {
    let p = project().file("src/lib.rs", "").build();

    p.cargo("rustdoc -v")
        .with_stderr(
            "\
[DOCUMENTING] foo v0.0.1 ([CWD])
[RUNNING] `rustdoc [..]--crate-name foo src/lib.rs [..]\
        -o [CWD]/target/doc \
        [..] \
        -L dependency=[CWD]/target/debug/deps`
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
",
        )
        .run();
}

#[cargo_test]
fn rustdoc_args() {
    let p = project().file("src/lib.rs", "").build();

    p.cargo("rustdoc -v -- --cfg=foo")
        .with_stderr(
            "\
[DOCUMENTING] foo v0.0.1 ([CWD])
[RUNNING] `rustdoc [..]--crate-name foo src/lib.rs [..]\
        -o [CWD]/target/doc \
        [..] \
        --cfg=foo \
        -L dependency=[CWD]/target/debug/deps`
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
",
        )
        .run();
}

#[cargo_test]
fn rustdoc_binary_args_passed() {
    let p = project().file("src/main.rs", "").build();

    p.cargo("rustdoc -v")
        .arg("--")
        .arg("--theme")
        .arg("dark")
        .with_stderr_contains("[RUNNING] `rustdoc [..] --theme dark[..]`")
        .with_status(101)
        .run();
}

#[cargo_test]
fn rustdoc_foo_with_bar_dependency() {
    let foo = project()
        .file(
            "Cargo.toml",
            r#"
            [package]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies.bar]
            path = "../bar"
        "#,
        )
        .file("src/lib.rs", "extern crate bar; pub fn foo() {}")
        .build();
    let _bar = project()
        .at("bar")
        .file("Cargo.toml", &basic_manifest("bar", "0.0.1"))
        .file("src/lib.rs", "pub fn baz() {}")
        .build();

    foo.cargo("rustdoc -v -- --cfg=foo")
        .with_stderr(
            "\
[CHECKING] bar v0.0.1 ([..])
[RUNNING] `rustc [..]bar/src/lib.rs [..]`
[DOCUMENTING] foo v0.0.1 ([CWD])
[RUNNING] `rustdoc [..]--crate-name foo src/lib.rs [..]\
        -o [CWD]/target/doc \
        [..] \
        --cfg=foo \
        -L dependency=[CWD]/target/debug/deps \
        --extern [..]`
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
",
        )
        .run();
}

#[cargo_test]
fn rustdoc_only_bar_dependency() {
    let foo = project()
        .file(
            "Cargo.toml",
            r#"
            [package]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies.bar]
            path = "../bar"
        "#,
        )
        .file("src/main.rs", "extern crate bar; fn main() { bar::baz() }")
        .build();
    let _bar = project()
        .at("bar")
        .file("Cargo.toml", &basic_manifest("bar", "0.0.1"))
        .file("src/lib.rs", "pub fn baz() {}")
        .build();

    foo.cargo("rustdoc -v -p bar -- --cfg=foo")
        .with_stderr(
            "\
[DOCUMENTING] bar v0.0.1 ([..])
[RUNNING] `rustdoc [..]--crate-name bar [..]bar/src/lib.rs [..]\
        -o [CWD]/target/doc \
        [..] \
        --cfg=foo \
        -L dependency=[CWD]/target/debug/deps`
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
",
        )
        .run();
}

#[cargo_test]
fn rustdoc_same_name_documents_lib() {
    let p = project()
        .file("src/main.rs", "fn main() {}")
        .file("src/lib.rs", r#" "#)
        .build();

    p.cargo("rustdoc -v -- --cfg=foo")
        .with_stderr(
            "\
[DOCUMENTING] foo v0.0.1 ([..])
[RUNNING] `rustdoc [..]--crate-name foo src/lib.rs [..]\
        -o [CWD]/target/doc \
        [..] \
        --cfg=foo \
        -L dependency=[CWD]/target/debug/deps`
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
",
        )
        .run();
}

#[cargo_test]
fn features() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
            [package]
            name = "foo"
            version = "0.0.1"
            authors = []

            [features]
            quux = []
        "#,
        )
        .file("src/lib.rs", "")
        .build();

    p.cargo("rustdoc --verbose --features quux")
        .with_stderr_contains("[..]feature=[..]quux[..]")
        .run();
}

#[cargo_test]
fn proc_macro_crate_type() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
            [package]
            name = "foo"
            version = "0.0.1"
            authors = []

            [lib]
            proc-macro = true

        "#,
        )
        .file("src/lib.rs", "")
        .build();

    p.cargo("rustdoc --verbose")
        .with_stderr_contains(
            "\
[RUNNING] `rustdoc --crate-type proc-macro [..]`
",
        )
        .run();
}

#[cargo_test]
#[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))]
fn rustdoc_target() {
    let p = project().file("src/lib.rs", "").build();

    p.cargo("rustdoc --verbose --target x86_64-unknown-linux-gnu")
        .with_stderr(
            "\
[DOCUMENTING] foo v0.0.1 ([..])
[RUNNING] `rustdoc [..]--crate-name foo src/lib.rs [..]\
    --target x86_64-unknown-linux-gnu \
    -o [CWD]/target/x86_64-unknown-linux-gnu/doc \
    [..] \
    -L dependency=[CWD]/target/x86_64-unknown-linux-gnu/debug/deps \
    -L dependency=[CWD]/target/debug/deps`
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
        )
        .run();
}
<commit_msg>Use a stable rustdoc option for testing<commit_after>//! Tests for the `cargo rustdoc` command.

use cargo_test_support::{basic_manifest, project};

#[cargo_test]
fn rustdoc_simple() {
    let p = project().file("src/lib.rs", "").build();

    p.cargo("rustdoc -v")
        .with_stderr(
            "\
[DOCUMENTING] foo v0.0.1 ([CWD])
[RUNNING] `rustdoc [..]--crate-name foo src/lib.rs [..]\
        -o [CWD]/target/doc \
        [..] \
        -L dependency=[CWD]/target/debug/deps`
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
",
        )
        .run();
}

#[cargo_test]
fn rustdoc_args() {
    let p = project().file("src/lib.rs", "").build();

    p.cargo("rustdoc -v -- --cfg=foo")
        .with_stderr(
            "\
[DOCUMENTING] foo v0.0.1 ([CWD])
[RUNNING] `rustdoc [..]--crate-name foo src/lib.rs [..]\
        -o [CWD]/target/doc \
        [..] \
        --cfg=foo \
        -L dependency=[CWD]/target/debug/deps`
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
",
        )
        .run();
}

#[cargo_test]
fn rustdoc_binary_args_passed() {
    let p = project().file("src/main.rs", "").build();

    p.cargo("rustdoc -v")
        .arg("--")
        .arg("--markdown-no-toc")
        .with_stderr_contains("[RUNNING] `rustdoc [..] --markdown-no-toc[..]`")
        .run();
}

#[cargo_test]
fn rustdoc_foo_with_bar_dependency() {
    let foo = project()
        .file(
            "Cargo.toml",
            r#"
            [package]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies.bar]
            path = "../bar"
        "#,
        )
        .file("src/lib.rs", "extern crate bar; pub fn foo() {}")
        .build();
    let _bar = project()
        .at("bar")
        .file("Cargo.toml", &basic_manifest("bar", "0.0.1"))
        .file("src/lib.rs", "pub fn baz() {}")
        .build();

    foo.cargo("rustdoc -v -- --cfg=foo")
        .with_stderr(
            "\
[CHECKING] bar v0.0.1 ([..])
[RUNNING] `rustc [..]bar/src/lib.rs [..]`
[DOCUMENTING] foo v0.0.1 ([CWD])
[RUNNING] `rustdoc [..]--crate-name foo src/lib.rs [..]\
        -o [CWD]/target/doc \
        [..] \
        --cfg=foo \
        -L dependency=[CWD]/target/debug/deps \
        --extern [..]`
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
",
        )
        .run();
}

#[cargo_test]
fn rustdoc_only_bar_dependency() {
    let foo = project()
        .file(
            "Cargo.toml",
            r#"
            [package]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies.bar]
            path = "../bar"
        "#,
        )
        .file("src/main.rs", "extern crate bar; fn main() { bar::baz() }")
        .build();
    let _bar = project()
        .at("bar")
        .file("Cargo.toml", &basic_manifest("bar", "0.0.1"))
        .file("src/lib.rs", "pub fn baz() {}")
        .build();

    foo.cargo("rustdoc -v -p bar -- --cfg=foo")
        .with_stderr(
            "\
[DOCUMENTING] bar v0.0.1 ([..])
[RUNNING] `rustdoc [..]--crate-name bar [..]bar/src/lib.rs [..]\
        -o [CWD]/target/doc \
        [..] \
        --cfg=foo \
        -L dependency=[CWD]/target/debug/deps`
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
",
        )
        .run();
}

#[cargo_test]
fn rustdoc_same_name_documents_lib() {
    let p = project()
        .file("src/main.rs", "fn main() {}")
        .file("src/lib.rs", r#" "#)
        .build();

    p.cargo("rustdoc -v -- --cfg=foo")
        .with_stderr(
            "\
[DOCUMENTING] foo v0.0.1 ([..])
[RUNNING] `rustdoc [..]--crate-name foo src/lib.rs [..]\
        -o [CWD]/target/doc \
        [..] \
        --cfg=foo \
        -L dependency=[CWD]/target/debug/deps`
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
",
        )
        .run();
}

#[cargo_test]
fn features() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
            [package]
            name = "foo"
            version = "0.0.1"
            authors = []

            [features]
            quux = []
        "#,
        )
        .file("src/lib.rs", "")
        .build();

    p.cargo("rustdoc --verbose --features quux")
        .with_stderr_contains("[..]feature=[..]quux[..]")
        .run();
}

#[cargo_test]
fn proc_macro_crate_type() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
            [package]
            name = "foo"
            version = "0.0.1"
            authors = []

            [lib]
            proc-macro = true

        "#,
        )
        .file("src/lib.rs", "")
        .build();

    p.cargo("rustdoc --verbose")
        .with_stderr_contains(
            "\
[RUNNING] `rustdoc --crate-type proc-macro [..]`
",
        )
        .run();
}

#[cargo_test]
#[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))]
fn rustdoc_target() {
    let p = project().file("src/lib.rs", "").build();

    p.cargo("rustdoc --verbose --target x86_64-unknown-linux-gnu")
        .with_stderr(
            "\
[DOCUMENTING] foo v0.0.1 ([..])
[RUNNING] `rustdoc [..]--crate-name foo src/lib.rs [..]\
    --target x86_64-unknown-linux-gnu \
    -o [CWD]/target/x86_64-unknown-linux-gnu/doc \
    [..] \
    -L dependency=[CWD]/target/x86_64-unknown-linux-gnu/debug/deps \
    -L dependency=[CWD]/target/debug/deps`
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
        )
        .run();
}
<|endoftext|>


<commit_before>use serde::ser::{Serialize, Serializer, SerializeStruct};
use serde::de::{Deserialize, Deserializer, Visitor, Error as DeError};
use std::iter::Iterator;
use std::fmt;
use std::ops::Add;
use std::convert::From;
use std::str::FromStr;

#[derive(Debug)]
pub struct AmazonMetaData {
    pub ami_launch_index: String,
    pub local_hostname: String,
    pub availability_zone: String,
    pub instance_id: String,
    pub public_ip4: String,
    pub public_hostname: String,
    pub ami_manifest_path: String,
    pub local_ip4: String,
    pub hostname: String,
    pub ami_id: String,
    pub instance_type: String
}

impl Serialize for AmazonMetaData {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where
        S: Serializer {
        let mut s = serializer.serialize_struct("AmazonMetaData", 11)?;
        s.serialize_field("ami-launch-index", &self.ami_launch_index)?;
        s.serialize_field("local-hostname", &self.local_hostname)?;
        s.serialize_field("availability-zone", &self.availability_zone)?;
        s.serialize_field("instance-id", &self.instance_id)?;
        s.serialize_field("public-ipv4", &self.public_ip4)?;
        s.serialize_field("public-hostname", &self.public_hostname)?;
        s.serialize_field("ami-manifest-path", &self.ami_manifest_path)?;
        s.serialize_field("local-ipv4", &self.local_ip4)?;
        s.serialize_field("hostname", &self.hostname)?;
        s.serialize_field("ami-id", &self.ami_id)?;
        s.serialize_field("instance-type", &self.instance_type)?;
        s.end()
    }
}

mod test {
    use super::*;
    use serde_json;

    #[test]
    fn test_serialize_amazon_meta_data() {
        let md = AmazonMetaData {
            ami_launch_index: "001a".to_string(),
            local_hostname: "localhost0".to_string(),
            availability_zone: "US_East1a".to_string(),
            instance_id: "instance1a".to_string(),
            public_ip4: "32.23.21.212".to_string(),
            public_hostname: "foo.coma".to_string(),
            ami_manifest_path: "/dev/nulla".to_string(),
            local_ip4: "127.0.0.12".to_string(),
            hostname: "privatefoo.coma".to_string(),
            ami_id: "ami0023".to_string(),
            instance_type: "c4xlarged".to_string()
        };
        let json = sample_meta_data();

        let result = serde_json::to_string(&md).unwrap();
        assert_eq!(json, result);
    }

    fn sample_meta_data() -> String {
        r#"{ "ami-launch-index": "001a",
            "local-hostname": "localhost0",
            "availability-zone": "US_East1a",
            "instance-id": "instance1a",
            "public-ipv4": "32.23.21.212",
            "public-hostname": "foo.coma",
            "ami-manifest-path": "/dev/nulla",
            "local-ipv4": "127.0.0.12",
            "hostname": "privatefoo.coma",
            "ami-id": "ami0023",
            "instance-type": "c4xlarged" }"#
            .to_string()
            .replace(" ", "")
            .replace("\n", "")
    }

}<commit_msg>implemented deserialization for AmazonMetaData<commit_after>use serde::ser::{Serialize, Serializer, SerializeStruct};
use serde::de::{Deserialize, Deserializer, Visitor, Error as DeError, MapAccess};
use std::iter::Iterator;
use std::fmt;
use std::ops::Add;
use std::convert::From;
use std::str::FromStr;

const AMI_LAUNCH_INDEX: &'static str = "ami-launch-index";
const LOCAL_HOSTNAME: &'static str = "local-hostname";
const AVAILABILITY_ZONE: &'static str = "availability-zone";
const INSTANCE_ID: &'static str = "instance-id";
const PUBLIC_IPV4: &'static str = "public-ipv4";
const PUBLIC_HOSTNAME: &'static str = "public-hostname";
const AMI_MANIFEST_PATH: &'static str = "ami-manifest-path";
const LOCAL_IPV4: &'static str = "local-ipv4";
const HOSTNAME: &'static str = "hostname";
const AMI_ID: &'static str = "ami-id";
const INSTANCE_TYPE: &'static str = "instance-type";
const JSON_FIELDS: &'static [&'static str] = &[AMI_LAUNCH_INDEX, LOCAL_HOSTNAME, AVAILABILITY_ZONE, INSTANCE_ID,
    PUBLIC_IPV4, PUBLIC_HOSTNAME, AMI_MANIFEST_PATH, LOCAL_IPV4,
    HOSTNAME, AMI_ID, INSTANCE_TYPE];
const RUST_FIELDS: &'static [&'static str] = &["ami_launch_index", "local_hostname", "availability_zone", "instance_id",
    "public_ip4", "public_hostname", "ami_manifest_path", "local_ip4", "hostname", "ami_id", "instance_type"];
const AMAZON_META_DATA: &'static str = "AmazonMetaData";

#[derive(Debug, PartialEq)]
pub struct AmazonMetaData {
    pub ami_launch_index: String,
    pub local_hostname: String,
    pub availability_zone: String,
    pub instance_id: String,
    pub public_ip4: String,
    pub public_hostname: String,
    pub ami_manifest_path: String,
    pub local_ip4: String,
    pub hostname: String,
    pub ami_id: String,
    pub instance_type: String
}

impl Serialize for AmazonMetaData {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where
        S: Serializer {
        let mut s = serializer.serialize_struct(AMAZON_META_DATA, 11)?;
        s.serialize_field(AMI_LAUNCH_INDEX, &self.ami_launch_index)?;
        s.serialize_field(LOCAL_HOSTNAME, &self.local_hostname)?;
        s.serialize_field(AVAILABILITY_ZONE, &self.availability_zone)?;
        s.serialize_field(INSTANCE_ID, &self.instance_id)?;
        s.serialize_field(PUBLIC_IPV4, &self.public_ip4)?;
        s.serialize_field(PUBLIC_HOSTNAME, &self.public_hostname)?;
        s.serialize_field(AMI_MANIFEST_PATH, &self.ami_manifest_path)?;
        s.serialize_field(LOCAL_IPV4, &self.local_ip4)?;
        s.serialize_field(HOSTNAME, &self.hostname)?;
        s.serialize_field(AMI_ID, &self.ami_id)?;
        s.serialize_field(INSTANCE_TYPE, &self.instance_type)?;
        s.end()
    }
}

impl<'de> Deserialize<'de> for AmazonMetaData {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where
        D: Deserializer<'de> {
        enum Field { AmiLaunchIndex, LocalHostname, AvailabilityZone, InstanceId, PublicIp4, PublicHostname, AmiManifestPath, LocalIp4, Hostname, AmiId, InstanceType };

        impl<'de> Deserialize<'de> for Field {
            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where
                D: Deserializer<'de> {
                struct FieldVisitor;

                impl<'de> Visitor<'de> for FieldVisitor {
                    type Value = Field;

                    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                        formatter.write_str("A AmazonMetaData field (see schema)")
                    }

                    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> where
                        E: DeError {
                        match v {
                            AMI_LAUNCH_INDEX => Ok(Field::AmiLaunchIndex),
                            LOCAL_HOSTNAME => Ok(Field::LocalHostname),
                            AVAILABILITY_ZONE => Ok(Field::AvailabilityZone),
                            INSTANCE_ID => Ok(Field::InstanceId),
                            PUBLIC_IPV4 => Ok(Field::PublicIp4),
                            PUBLIC_HOSTNAME => Ok(Field::PublicHostname),
                            AMI_MANIFEST_PATH => Ok(Field::AmiManifestPath),
                            LOCAL_IPV4 => Ok(Field::LocalIp4),
                            HOSTNAME => Ok(Field::Hostname),
                            AMI_ID => Ok(Field::AmiId),
                            INSTANCE_TYPE => Ok(Field::InstanceType),
                            _ => Err(DeError::unknown_field(v, JSON_FIELDS))
                        }
                    }
                }

                deserializer.deserialize_identifier(FieldVisitor)
            }
        }

        struct AmazonMetaDataVisitor;

        impl<'de> Visitor<'de> for AmazonMetaDataVisitor {
            type Value = AmazonMetaData;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("struct AmazonMetaDataVisitor")
            }

            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> where
                A: MapAccess<'de> {
                let mut maybe_ami_launch_index = None;
                let mut maybe_local_hostname = None;
                let mut maybe_availability_zone = None;
                let mut maybe_instance_id = None;
                let mut maybe_public_ip4 = None;
                let mut maybe_public_hostname = None;
                let mut maybe_ami_manifest_path = None;
                let mut maybe_local_ip4 = None;
                let mut maybe_hostname = None;
                let mut maybe_ami_id = None;
                let mut maybe_instance_type = None;

                while let Some(key) = map.next_key()? {
                    match key {
                        Field::AmiLaunchIndex => {
                            if maybe_ami_launch_index.is_some() {
                                return Err(DeError::duplicate_field(AMI_LAUNCH_INDEX));
                            }
                            maybe_ami_launch_index = Some(map.next_value()?)
                        },
                        Field::LocalHostname => {
                            if maybe_local_hostname.is_some() {
                                return Err(DeError::duplicate_field(LOCAL_HOSTNAME));
                            }
                            maybe_local_hostname = Some(map.next_value()?)
                        },
                        Field::AvailabilityZone => {
                            if maybe_availability_zone.is_some() {
                                return Err(DeError::duplicate_field(AVAILABILITY_ZONE));
                            }
                            maybe_availability_zone= Some(map.next_value()?)
                        },
                        Field::InstanceId => {
                            if maybe_instance_id.is_some() {
                                return Err(DeError::duplicate_field(INSTANCE_ID));
                            }
                            maybe_instance_id= Some(map.next_value()?)
                        },
                        Field::PublicIp4 => {
                            if maybe_public_ip4.is_some() {
                                return Err(DeError::duplicate_field(PUBLIC_IPV4));
                            }
                            maybe_public_ip4= Some(map.next_value()?)
                        },
                        Field::PublicHostname => {
                            if maybe_public_hostname.is_some() {
                                return Err(DeError::duplicate_field(PUBLIC_HOSTNAME));
                            }
                            maybe_public_hostname= Some(map.next_value()?)
                        },
                        Field::AmiManifestPath => {
                            if maybe_ami_manifest_path.is_some() {
                                return Err(DeError::duplicate_field(AMI_MANIFEST_PATH));
                            }
                            maybe_ami_manifest_path= Some(map.next_value()?)
                        },
                        Field::LocalIp4 => {
                            if maybe_local_ip4.is_some() {
                                return Err(DeError::duplicate_field(LOCAL_IPV4));
                            }
                            maybe_local_ip4= Some(map.next_value()?)
                        },
                        Field::Hostname => {
                            if maybe_hostname.is_some() {
                                return Err(DeError::duplicate_field(HOSTNAME));
                            }
                            maybe_hostname= Some(map.next_value()?)
                        },
                        Field::AmiId => {
                            if maybe_ami_id.is_some() {
                                return Err(DeError::duplicate_field(AMI_ID));
                            }
                            maybe_ami_id= Some(map.next_value()?)
                        },
                        Field::InstanceType => {
                            if maybe_instance_type.is_some() {
                                return Err(DeError::duplicate_field(INSTANCE_TYPE));
                            }
                            maybe_instance_type= Some(map.next_value()?)
                        }
                    }
                }

                let ami_launch_index = maybe_ami_launch_index.ok_or_else(|| DeError::missing_field(AMI_LAUNCH_INDEX));
                let local_hostname = maybe_local_hostname.ok_or_else(|| DeError::missing_field(LOCAL_HOSTNAME));
                let availability_zone = maybe_availability_zone.ok_or_else(|| DeError::missing_field(AVAILABILITY_ZONE));
                let instance_id = maybe_instance_id.ok_or_else(|| DeError::missing_field(INSTANCE_ID));
                let public_ip4 = maybe_public_ip4.ok_or_else(|| DeError::missing_field(PUBLIC_IPV4));
                let public_hostname = maybe_public_hostname.ok_or_else(|| DeError::missing_field(PUBLIC_HOSTNAME));
                let ami_manifest_path = maybe_ami_manifest_path.ok_or_else(|| DeError::missing_field(AMI_MANIFEST_PATH));
                let local_ip4 = maybe_local_ip4.ok_or_else(|| DeError::missing_field(LOCAL_IPV4));
                let hostname = maybe_hostname.ok_or_else(|| DeError::missing_field(HOSTNAME));
                let ami_id = maybe_ami_id.ok_or_else(|| DeError::missing_field(AMI_ID));
                let instance_type = maybe_instance_type.ok_or_else(|| DeError::missing_field(INSTANCE_TYPE));

                Ok(AmazonMetaData {
                    ami_launch_index: ami_launch_index?,
                    local_hostname: local_hostname?,
                    availability_zone: availability_zone?,
                    instance_id: instance_id?,
                    public_ip4: public_ip4?,
                    public_hostname: public_hostname?,
                    ami_manifest_path: ami_manifest_path?,
                    local_ip4: local_ip4?,
                    hostname: hostname?,
                    ami_id: ami_id?,
                    instance_type: instance_type?
                })
            }
        }
        deserializer.deserialize_struct(AMAZON_META_DATA, RUST_FIELDS, AmazonMetaDataVisitor)
    }
}

mod test {
    use super::*;
    use serde_json;

    #[test]
    fn test_serialize_amazon_meta_data() {
        let md = AmazonMetaData {
            ami_launch_index: "001a".to_string(),
            local_hostname: "localhost0".to_string(),
            availability_zone: "US_East1a".to_string(),
            instance_id: "instance1a".to_string(),
            public_ip4: "32.23.21.212".to_string(),
            public_hostname: "foo.coma".to_string(),
            ami_manifest_path: "/dev/nulla".to_string(),
            local_ip4: "127.0.0.12".to_string(),
            hostname: "privatefoo.coma".to_string(),
            ami_id: "ami0023".to_string(),
            instance_type: "c4xlarged".to_string()
        };
        let json = sample_meta_data();

        let result = serde_json::to_string(&md).unwrap();
        assert_eq!(json, result);
    }

    #[test]
    fn test_deserialize_amazon_meta_data() {
        let md = AmazonMetaData {
            ami_launch_index: "001a".to_string(),
            local_hostname: "localhost0".to_string(),
            availability_zone: "US_East1a".to_string(),
            instance_id: "instance1a".to_string(),
            public_ip4: "32.23.21.212".to_string(),
            public_hostname: "foo.coma".to_string(),
            ami_manifest_path: "/dev/nulla".to_string(),
            local_ip4: "127.0.0.12".to_string(),
            hostname: "privatefoo.coma".to_string(),
            ami_id: "ami0023".to_string(),
            instance_type: "c4xlarged".to_string()
        };
        let json = sample_meta_data();
        let result = serde_json::from_str(&json).unwrap();
        assert_eq!(md, result);
    }

    fn sample_meta_data() -> String {
        r#"{ "ami-launch-index": "001a",
            "local-hostname": "localhost0",
            "availability-zone": "US_East1a",
            "instance-id": "instance1a",
            "public-ipv4": "32.23.21.212",
            "public-hostname": "foo.coma",
            "ami-manifest-path": "/dev/nulla",
            "local-ipv4": "127.0.0.12",
            "hostname": "privatefoo.coma",
            "ami-id": "ami0023",
            "instance-type": "c4xlarged" }"#
            .to_string()
            .replace(" ", "")
            .replace("\n", "")
    }
}<|endoftext|>