<commit_before>//! `cargo upgrade`
#![warn(
    missing_docs,
    missing_debug_implementations,
    missing_copy_implementations,
    trivial_casts,
    trivial_numeric_casts,
    unsafe_code,
    unstable_features,
    unused_import_braces,
    unused_qualifications
)]

#[macro_use]
extern crate error_chain;

use crate::errors::*;
use cargo_edit::{
    find, get_latest_dependency, registry_url, update_registry_index, CrateName, Dependency,
    LocalManifest,
};
use failure::Fail;
use std::collections::{HashMap, HashSet};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process;
use structopt::StructOpt;
use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor};
use url::Url;

mod errors {
    error_chain! {
        links {
            CargoEditLib(::cargo_edit::Error, ::cargo_edit::ErrorKind);
        }
        foreign_links {
            CargoMetadata(::failure::Compat<::cargo_metadata::Error>);
        }
    }
}

#[derive(Debug, StructOpt)]
#[structopt(bin_name = "cargo")]
enum Command {
    /// Upgrade dependencies as specified in the local manifest file (i.e. Cargo.toml).
    #[structopt(name = "upgrade", author = "")]
    #[structopt(
        after_help = "This command differs from `cargo update`, which updates the dependency versions recorded in the
local lock file (Cargo.lock).

If `<dependency>`(s) are provided, only the specified dependencies will be upgraded. The version to
upgrade to for each can be specified with e.g. `docopt@0.8.0` or `serde@>=0.9,<2.0`.

Dev, build, and all target dependencies will also be upgraded. Only dependencies from crates.io are
supported. Git/path dependencies will be ignored.

All packages in the workspace will be upgraded if the `--all` flag is supplied. The `--all` flag may
be supplied in the presence of a virtual manifest.

If the '--to-lockfile' flag is supplied, all dependencies will be upgraded to the currently locked
version as recorded in the Cargo.lock file. This flag requires that the Cargo.lock file is
up-to-date. If the lock file is missing, or it needs to be updated, cargo-upgrade will exit with an
error. If the '--to-lockfile' flag is supplied then the network won't be accessed."
    )]
    Upgrade(Args),
}

#[derive(Debug, StructOpt)]
struct Args {
    /// Crates to be upgraded.
    dependency: Vec<String>,

    /// Path to the manifest to upgrade
    #[structopt(long = "manifest-path", value_name = "path")]
    manifest_path: Option<PathBuf>,

    /// Upgrade all packages in the workspace.
    #[structopt(long = "all")]
    all: bool,

    /// Include prerelease versions when fetching from crates.io (e.g. 0.6.0-alpha').
    #[structopt(long = "allow-prerelease")]
    allow_prerelease: bool,

    /// Print changes to be made without making them.
    #[structopt(long = "dry-run")]
    dry_run: bool,

    /// Run without accessing the network
    #[structopt(long = "offline")]
    pub offline: bool,

    /// Upgrade all packages to the version in the lockfile.
    #[structopt(long = "to-lockfile", conflicts_with = "dependency")]
    pub to_lockfile: bool,
}

/// A collection of manifests.
struct Manifests(Vec<(LocalManifest, cargo_metadata::Package)>);

/// Helper function to check whether a `cargo_metadata::Dependency` is a version dependency.
fn is_version_dep(dependency: &cargo_metadata::Dependency) -> bool {
    match dependency.source {
        // This is the criterion cargo uses (in `SourceId::from_url`) to decide whether a
        // dependency has the 'registry' kind.
        Some(ref s) => s.splitn(2, '+').next() == Some("registry"),
        _ => false,
    }
}

fn dry_run_message() -> Result<()> {
    let bufwtr = BufferWriter::stdout(ColorChoice::Always);
    let mut buffer = bufwtr.buffer();
    buffer
        .set_color(ColorSpec::new().set_fg(Some(Color::Cyan)).set_bold(true))
        .chain_err(|| "Failed to set output colour")?;
    write!(&mut buffer, "Starting dry run. ").chain_err(|| "Failed to write dry run message")?;
    buffer
        .set_color(&ColorSpec::new())
        .chain_err(|| "Failed to clear output colour")?;
    writeln!(&mut buffer, "Changes will not be saved.")
        .chain_err(|| "Failed to write dry run message")?;
    bufwtr
        .print(&buffer)
        .chain_err(|| "Failed to print dry run message")
}

impl Manifests {
    /// Get all manifests in the workspace.
    fn get_all(manifest_path: &Option<PathBuf>) -> Result<Self> {
        let mut cmd = cargo_metadata::MetadataCommand::new();
        cmd.no_deps();
        if let Some(path) = manifest_path {
            cmd.manifest_path(path);
        }
        let result = cmd.exec().map_err(|e| {
            Error::from(e.compat()).chain_err(|| "Failed to get workspace metadata")
        })?;
        result
            .packages
            .into_iter()
            .map(|package| {
                Ok((
                    LocalManifest::try_new(Path::new(&package.manifest_path))?,
                    package,
                ))
            })
            .collect::<Result<Vec<_>>>()
            .map(Manifests)
    }

    /// Get the manifest specified by the manifest path. Try to make an educated guess if no path is
    /// provided.
    fn get_local_one(manifest_path: &Option<PathBuf>) -> Result<Self> {
        let resolved_manifest_path: String = find(&manifest_path)?.to_string_lossy().into();

        let manifest = LocalManifest::find(&manifest_path)?;

        let mut cmd = cargo_metadata::MetadataCommand::new();
        cmd.no_deps();
        if let Some(path) = manifest_path {
            cmd.manifest_path(path);
        }
        let result = cmd
            .exec()
            .map_err(|e| Error::from(e.compat()).chain_err(|| "Invalid manifest"))?;
        let packages = result.packages;
        let package = packages
            .iter()
            .find(|p| p.manifest_path.to_string_lossy() == resolved_manifest_path)
            // If we have successfully got metadata, but our manifest path does not correspond to a
            // package, we must have been called against a virtual manifest.
            .chain_err(|| {
                "Found virtual manifest, but this command requires running against an \
                 actual package in this workspace. Try adding `--all`."
            })?;

        Ok(Manifests(vec![(manifest, package.to_owned())]))
    }

    /// Get the the combined set of dependencies to upgrade. If the user has specified
    /// per-dependency desired versions, extract those here.
    fn get_dependencies(&self, only_update: Vec<String>) -> Result<DesiredUpgrades> {
        // Map the names of user-specified dependencies to the (optionally) requested version.
        let selected_dependencies = only_update
            .into_iter()
            .map(|name| {
                if let Some(dependency) = CrateName::new(&name.clone()).parse_as_version()? {
                    Ok((
                        dependency.name.clone(),
                        dependency.version().map(String::from),
                    ))
                } else {
                    Ok((name, None))
                }
            })
            .collect::<Result<HashMap<_, _>>>()?;

        Ok(DesiredUpgrades(
            self.0
                .iter()
                .flat_map(|&(_, ref package)| package.dependencies.clone())
                .filter(is_version_dep)
                .filter_map(|dependency| {
                    if selected_dependencies.is_empty() {
                        // User hasn't asked for any specific dependencies to be upgraded,
                        // so upgrade all the dependencies.
                        let mut dep = Dependency::new(&dependency.name);
                        if let Some(rename) = dependency.rename {
                            dep = dep.set_rename(&rename);
                        }
                        let is_prerelease = dependency.req.to_string().contains("-");
                        Some((
                            dep,
                            UpgradeMetadata {
                                registry: dependency.registry,
                                version: None,
                                is_prerelease,
                            },
                        ))
                    } else {
                        // User has asked for specific dependencies. Check if this dependency
                        // was specified, populating the registry from the lockfile metadata.
                        match selected_dependencies.get(&dependency.name) {
                            Some(version) => Some((
                                Dependency::new(&dependency.name),
                                UpgradeMetadata {
                                    registry: dependency.registry,
                                    version: version.clone(),
                                    is_prerelease: false,
                                },
                            )),
                            None => None,
                        }
                    }
                })
                .collect(),
        ))
    }

    /// Upgrade the manifests on disk following the previously-determined upgrade schema.
    fn upgrade(self, upgraded_deps: &ActualUpgrades, dry_run: bool) -> Result<()> {
        if dry_run {
            dry_run_message()?;
        }

        for (mut manifest, package) in self.0 {
            println!("{}:", package.name);

            for (dep, version) in &upgraded_deps.0 {
                let mut new_dep = Dependency::new(&dep.name).set_version(version);
                if let Some(rename) = dep.rename() {
                    new_dep = new_dep.set_rename(&rename);
                }
                manifest.upgrade(&new_dep, dry_run)?;
            }
        }

        Ok(())
    }

    /// Update dependencies in Cargo.toml file(s) to match the corresponding
    /// version in Cargo.lock.
    fn sync_to_lockfile(self, dry_run: bool) -> Result<()> {
        // Get locked dependencies. For workspaces with multiple Cargo.toml
        // files, there is only a single lockfile, so it suffices to get
        // metadata for any one of Cargo.toml files.
        let (manifest, _package) =
            self.0.iter().next().ok_or_else(|| {
                ErrorKind::CargoEditLib(::cargo_edit::ErrorKind::InvalidCargoConfig)
            })?;
        let mut cmd = cargo_metadata::MetadataCommand::new();
        cmd.manifest_path(manifest.path.clone());
        cmd.other_options(vec!["--locked".to_string()]);

        let result = cmd
            .exec()
            .map_err(|e| Error::from(e.compat()).chain_err(|| "Invalid manifest"))?;

        let locked = result
            .packages
            .into_iter()
            .filter(|p| p.source.is_some()) // Source is none for local packages
            .collect::<Vec<_>>();

        if dry_run {
            dry_run_message()?;
        }

        for (mut manifest, package) in self.0 {
            println!("{}:", package.name);

            // Upgrade the manifests one at a time, as multiple manifests may
            // request the same dependency at differing versions.
            for (name, version) in package
                .dependencies
                .clone()
                .into_iter()
                .filter(is_version_dep)
                .filter_map(|d| {
                    for p in &locked {
                        // The requested dependency may be present in the lock file with different versions,
                        // but only one will be semver-compatible with the requested version.
                        if d.name == p.name && d.req.matches(&p.version) {
                            return Some((d.name, p.version.to_string()));
                        }
                    }
                    None
                })
            {
                manifest.upgrade(&Dependency::new(&name).set_version(&version), dry_run)?;
            }
        }
        Ok(())
    }
}

// Some metadata about the dependency
// we're trying to upgrade.
struct UpgradeMetadata {
    registry: Option<String>,
    // `Some` if the user has specified an explicit
    // version to upgrade to.
    version: Option<String>,
    is_prerelease: bool,
}

/// The set of dependencies to be upgraded, alongside the registries returned from cargo metadata, and
/// the desired versions, if specified by the user.
struct DesiredUpgrades(HashMap<Dependency, UpgradeMetadata>);

/// The complete specification of the upgrades that will be performed. Map of the dependency names
/// to the new versions.
struct ActualUpgrades(HashMap<Dependency, String>);

impl DesiredUpgrades {
    /// Transform the dependencies into their upgraded forms. If a version is specified, all
    /// dependencies will get that version.
    fn get_upgraded(self, allow_prerelease: bool, manifest_path: &Path) -> Result<ActualUpgrades> {
        self.0
            .into_iter()
            .map(
                |(
                    dep,
                    UpgradeMetadata {
                        registry,
                        version,
                        is_prerelease,
                    },
                )| {
                    if let Some(v) = version {
                        Ok((dep, v))
                    } else {
                        let registry_url = match registry {
                            Some(x) => Some(Url::parse(&x).map_err(|_| {
                                ErrorKind::CargoEditLib(::cargo_edit::ErrorKind::InvalidCargoConfig)
                            })?),
                            None => None,
                        };
                        let allow_prerelease = allow_prerelease || is_prerelease;
                        get_latest_dependency(
                            &dep.name,
                            allow_prerelease,
                            manifest_path,
                            &registry_url,
                        )
                        .map(|new_dep| {
                            (
                                dep,
                                new_dep
                                    .version()
                                    .expect("Invalid dependency type")
                                    .to_string(),
                            )
                        })
                        .chain_err(|| "Failed to get new version")
                    }
                },
            )
            .collect::<Result<_>>()
            .map(ActualUpgrades)
    }
}

/// Main processing function. Allows us to return a `Result` so that `main` can print pretty error
/// messages.
fn process(args: Args) -> Result<()> {
    let Args {
        dependency,
        manifest_path,
        all,
        allow_prerelease,
        dry_run,
        to_lockfile,
        ..
    } = args;

    if !args.offline && !to_lockfile {
        let url = registry_url(&find(&manifest_path)?, None)?;
        update_registry_index(&url)?;
    }

    let manifests = if all {
        Manifests::get_all(&manifest_path)
    } else {
        Manifests::get_local_one(&manifest_path)
    }?;

    if to_lockfile {
        manifests.sync_to_lockfile(dry_run)
    } else {
        let existing_dependencies = manifests.get_dependencies(dependency)?;

        // Update indices for any alternative registries, unless
        // we're offline.
        if !args.offline {
            for registry_url in existing_dependencies
                .0
                .values()
                .filter_map(|UpgradeMetadata { registry, .. }| registry.as_ref())
                .collect::<HashSet<_>>()
            {
                update_registry_index(&Url::parse(registry_url).map_err(|_| {
                    ErrorKind::CargoEditLib(::cargo_edit::ErrorKind::InvalidCargoConfig)
                })?)?;
            }
        }

        let upgraded_dependencies =
            existing_dependencies.get_upgraded(allow_prerelease, &find(&manifest_path)?)?;

        manifests.upgrade(&upgraded_dependencies, dry_run)
    }
}

fn main() {
    let args: Command = Command::from_args();
    let Command::Upgrade(args) = args;

    if let Err(err) = process(args) {
        eprintln!("Command failed due to unhandled error: {}\n", err);

        for e in err.iter().skip(1) {
            eprintln!("Caused by: {}", e);
        }

        if let Some(backtrace) = err.backtrace() {
            eprintln!("Backtrace: {:?}", backtrace);
        }

        process::exit(1);
    }
}
<commit_msg>upgrade: detect prerelease for a single dependency<commit_after>//! `cargo upgrade`
#![warn(
    missing_docs,
    missing_debug_implementations,
    missing_copy_implementations,
    trivial_casts,
    trivial_numeric_casts,
    unsafe_code,
    unstable_features,
    unused_import_braces,
    unused_qualifications
)]

#[macro_use]
extern crate error_chain;

use crate::errors::*;
use cargo_edit::{
    find, get_latest_dependency, registry_url, update_registry_index, CrateName, Dependency,
    LocalManifest,
};
use failure::Fail;
use std::collections::{HashMap, HashSet};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process;
use structopt::StructOpt;
use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor};
use url::Url;

mod errors {
    error_chain! {
        links {
            CargoEditLib(::cargo_edit::Error, ::cargo_edit::ErrorKind);
        }
        foreign_links {
            CargoMetadata(::failure::Compat<::cargo_metadata::Error>);
        }
    }
}

#[derive(Debug, StructOpt)]
#[structopt(bin_name = "cargo")]
enum Command {
    /// Upgrade dependencies as specified in the local manifest file (i.e. Cargo.toml).
    #[structopt(name = "upgrade", author = "")]
    #[structopt(
        after_help = "This command differs from `cargo update`, which updates the dependency versions recorded in the
local lock file (Cargo.lock).

If `<dependency>`(s) are provided, only the specified dependencies will be upgraded. The version to
upgrade to for each can be specified with e.g. `docopt@0.8.0` or `serde@>=0.9,<2.0`.

Dev, build, and all target dependencies will also be upgraded. Only dependencies from crates.io are
supported. Git/path dependencies will be ignored.

All packages in the workspace will be upgraded if the `--all` flag is supplied. The `--all` flag may
be supplied in the presence of a virtual manifest.

If the '--to-lockfile' flag is supplied, all dependencies will be upgraded to the currently locked
version as recorded in the Cargo.lock file. This flag requires that the Cargo.lock file is
up-to-date. If the lock file is missing, or it needs to be updated, cargo-upgrade will exit with an
error. If the '--to-lockfile' flag is supplied then the network won't be accessed."
    )]
    Upgrade(Args),
}

#[derive(Debug, StructOpt)]
struct Args {
    /// Crates to be upgraded.
    dependency: Vec<String>,

    /// Path to the manifest to upgrade
    #[structopt(long = "manifest-path", value_name = "path")]
    manifest_path: Option<PathBuf>,

    /// Upgrade all packages in the workspace.
    #[structopt(long = "all")]
    all: bool,

    /// Include prerelease versions when fetching from crates.io (e.g. 0.6.0-alpha').
    #[structopt(long = "allow-prerelease")]
    allow_prerelease: bool,

    /// Print changes to be made without making them.
    #[structopt(long = "dry-run")]
    dry_run: bool,

    /// Run without accessing the network
    #[structopt(long = "offline")]
    pub offline: bool,

    /// Upgrade all packages to the version in the lockfile.
    #[structopt(long = "to-lockfile", conflicts_with = "dependency")]
    pub to_lockfile: bool,
}

/// A collection of manifests.
struct Manifests(Vec<(LocalManifest, cargo_metadata::Package)>);

/// Helper function to check whether a `cargo_metadata::Dependency` is a version dependency.
fn is_version_dep(dependency: &cargo_metadata::Dependency) -> bool {
    match dependency.source {
        // This is the criterion cargo uses (in `SourceId::from_url`) to decide whether a
        // dependency has the 'registry' kind.
        Some(ref s) => s.splitn(2, '+').next() == Some("registry"),
        _ => false,
    }
}

fn dry_run_message() -> Result<()> {
    let bufwtr = BufferWriter::stdout(ColorChoice::Always);
    let mut buffer = bufwtr.buffer();
    buffer
        .set_color(ColorSpec::new().set_fg(Some(Color::Cyan)).set_bold(true))
        .chain_err(|| "Failed to set output colour")?;
    write!(&mut buffer, "Starting dry run. ").chain_err(|| "Failed to write dry run message")?;
    buffer
        .set_color(&ColorSpec::new())
        .chain_err(|| "Failed to clear output colour")?;
    writeln!(&mut buffer, "Changes will not be saved.")
        .chain_err(|| "Failed to write dry run message")?;
    bufwtr
        .print(&buffer)
        .chain_err(|| "Failed to print dry run message")
}

impl Manifests {
    /// Get all manifests in the workspace.
    fn get_all(manifest_path: &Option<PathBuf>) -> Result<Self> {
        let mut cmd = cargo_metadata::MetadataCommand::new();
        cmd.no_deps();
        if let Some(path) = manifest_path {
            cmd.manifest_path(path);
        }
        let result = cmd.exec().map_err(|e| {
            Error::from(e.compat()).chain_err(|| "Failed to get workspace metadata")
        })?;
        result
            .packages
            .into_iter()
            .map(|package| {
                Ok((
                    LocalManifest::try_new(Path::new(&package.manifest_path))?,
                    package,
                ))
            })
            .collect::<Result<Vec<_>>>()
            .map(Manifests)
    }

    /// Get the manifest specified by the manifest path. Try to make an educated guess if no path is
    /// provided.
    fn get_local_one(manifest_path: &Option<PathBuf>) -> Result<Self> {
        let resolved_manifest_path: String = find(&manifest_path)?.to_string_lossy().into();

        let manifest = LocalManifest::find(&manifest_path)?;

        let mut cmd = cargo_metadata::MetadataCommand::new();
        cmd.no_deps();
        if let Some(path) = manifest_path {
            cmd.manifest_path(path);
        }
        let result = cmd
            .exec()
            .map_err(|e| Error::from(e.compat()).chain_err(|| "Invalid manifest"))?;
        let packages = result.packages;
        let package = packages
            .iter()
            .find(|p| p.manifest_path.to_string_lossy() == resolved_manifest_path)
            // If we have successfully got metadata, but our manifest path does not correspond to a
            // package, we must have been called against a virtual manifest.
            .chain_err(|| {
                "Found virtual manifest, but this command requires running against an \
                 actual package in this workspace. Try adding `--all`."
            })?;

        Ok(Manifests(vec![(manifest, package.to_owned())]))
    }

    /// Get the the combined set of dependencies to upgrade. If the user has specified
    /// per-dependency desired versions, extract those here.
    fn get_dependencies(&self, only_update: Vec<String>) -> Result<DesiredUpgrades> {
        // Map the names of user-specified dependencies to the (optionally) requested version.
        let selected_dependencies = only_update
            .into_iter()
            .map(|name| {
                if let Some(dependency) = CrateName::new(&name.clone()).parse_as_version()? {
                    Ok((
                        dependency.name.clone(),
                        dependency.version().map(String::from),
                    ))
                } else {
                    Ok((name, None))
                }
            })
            .collect::<Result<HashMap<_, _>>>()?;

        Ok(DesiredUpgrades(
            self.0
                .iter()
                .flat_map(|&(_, ref package)| package.dependencies.clone())
                .filter(is_version_dep)
                .filter_map(|dependency| {
                    let is_prerelease = dependency.req.to_string().contains("-");
                    if selected_dependencies.is_empty() {
                        // User hasn't asked for any specific dependencies to be upgraded,
                        // so upgrade all the dependencies.
                        let mut dep = Dependency::new(&dependency.name);
                        if let Some(rename) = dependency.rename {
                            dep = dep.set_rename(&rename);
                        }
                        Some((
                            dep,
                            UpgradeMetadata {
                                registry: dependency.registry,
                                version: None,
                                is_prerelease,
                            },
                        ))
                    } else {
                        // User has asked for specific dependencies. Check if this dependency
                        // was specified, populating the registry from the lockfile metadata.
                        match selected_dependencies.get(&dependency.name) {
                            Some(version) => Some((
                                Dependency::new(&dependency.name),
                                UpgradeMetadata {
                                    registry: dependency.registry,
                                    version: version.clone(),
                                    is_prerelease,
                                },
                            )),
                            None => None,
                        }
                    }
                })
                .collect(),
        ))
    }

    /// Upgrade the manifests on disk following the previously-determined upgrade schema.
    fn upgrade(self, upgraded_deps: &ActualUpgrades, dry_run: bool) -> Result<()> {
        if dry_run {
            dry_run_message()?;
        }

        for (mut manifest, package) in self.0 {
            println!("{}:", package.name);

            for (dep, version) in &upgraded_deps.0 {
                let mut new_dep = Dependency::new(&dep.name).set_version(version);
                if let Some(rename) = dep.rename() {
                    new_dep = new_dep.set_rename(&rename);
                }
                manifest.upgrade(&new_dep, dry_run)?;
            }
        }

        Ok(())
    }

    /// Update dependencies in Cargo.toml file(s) to match the corresponding
    /// version in Cargo.lock.
    fn sync_to_lockfile(self, dry_run: bool) -> Result<()> {
        // Get locked dependencies. For workspaces with multiple Cargo.toml
        // files, there is only a single lockfile, so it suffices to get
        // metadata for any one of Cargo.toml files.
        let (manifest, _package) =
            self.0.iter().next().ok_or_else(|| {
                ErrorKind::CargoEditLib(::cargo_edit::ErrorKind::InvalidCargoConfig)
            })?;
        let mut cmd = cargo_metadata::MetadataCommand::new();
        cmd.manifest_path(manifest.path.clone());
        cmd.other_options(vec!["--locked".to_string()]);

        let result = cmd
            .exec()
            .map_err(|e| Error::from(e.compat()).chain_err(|| "Invalid manifest"))?;

        let locked = result
            .packages
            .into_iter()
            .filter(|p| p.source.is_some()) // Source is none for local packages
            .collect::<Vec<_>>();

        if dry_run {
            dry_run_message()?;
        }

        for (mut manifest, package) in self.0 {
            println!("{}:", package.name);

            // Upgrade the manifests one at a time, as multiple manifests may
            // request the same dependency at differing versions.
            for (name, version) in package
                .dependencies
                .clone()
                .into_iter()
                .filter(is_version_dep)
                .filter_map(|d| {
                    for p in &locked {
                        // The requested dependency may be present in the lock file with different versions,
                        // but only one will be semver-compatible with the requested version.
                        if d.name == p.name && d.req.matches(&p.version) {
                            return Some((d.name, p.version.to_string()));
                        }
                    }
                    None
                })
            {
                manifest.upgrade(&Dependency::new(&name).set_version(&version), dry_run)?;
            }
        }
        Ok(())
    }
}

// Some metadata about the dependency
// we're trying to upgrade.
struct UpgradeMetadata {
    registry: Option<String>,
    // `Some` if the user has specified an explicit
    // version to upgrade to.
    version: Option<String>,
    is_prerelease: bool,
}

/// The set of dependencies to be upgraded, alongside the registries returned from cargo metadata, and
/// the desired versions, if specified by the user.
struct DesiredUpgrades(HashMap<Dependency, UpgradeMetadata>);

/// The complete specification of the upgrades that will be performed. Map of the dependency names
/// to the new versions.
struct ActualUpgrades(HashMap<Dependency, String>);

impl DesiredUpgrades {
    /// Transform the dependencies into their upgraded forms. If a version is specified, all
    /// dependencies will get that version.
    fn get_upgraded(self, allow_prerelease: bool, manifest_path: &Path) -> Result<ActualUpgrades> {
        self.0
            .into_iter()
            .map(
                |(
                    dep,
                    UpgradeMetadata {
                        registry,
                        version,
                        is_prerelease,
                    },
                )| {
                    if let Some(v) = version {
                        Ok((dep, v))
                    } else {
                        let registry_url = match registry {
                            Some(x) => Some(Url::parse(&x).map_err(|_| {
                                ErrorKind::CargoEditLib(::cargo_edit::ErrorKind::InvalidCargoConfig)
                            })?),
                            None => None,
                        };
                        let allow_prerelease = allow_prerelease || is_prerelease;
                        get_latest_dependency(
                            &dep.name,
                            allow_prerelease,
                            manifest_path,
                            &registry_url,
                        )
                        .map(|new_dep| {
                            (
                                dep,
                                new_dep
                                    .version()
                                    .expect("Invalid dependency type")
                                    .to_string(),
                            )
                        })
                        .chain_err(|| "Failed to get new version")
                    }
                },
            )
            .collect::<Result<_>>()
            .map(ActualUpgrades)
    }
}

/// Main processing function. Allows us to return a `Result` so that `main` can print pretty error
/// messages.
fn process(args: Args) -> Result<()> {
    let Args {
        dependency,
        manifest_path,
        all,
        allow_prerelease,
        dry_run,
        to_lockfile,
        ..
    } = args;

    if !args.offline && !to_lockfile {
        let url = registry_url(&find(&manifest_path)?, None)?;
        update_registry_index(&url)?;
    }

    let manifests = if all {
        Manifests::get_all(&manifest_path)
    } else {
        Manifests::get_local_one(&manifest_path)
    }?;

    if to_lockfile {
        manifests.sync_to_lockfile(dry_run)
    } else {
        let existing_dependencies = manifests.get_dependencies(dependency)?;

        // Update indices for any alternative registries, unless
        // we're offline.
        if !args.offline {
            for registry_url in existing_dependencies
                .0
                .values()
                .filter_map(|UpgradeMetadata { registry, .. }| registry.as_ref())
                .collect::<HashSet<_>>()
            {
                update_registry_index(&Url::parse(registry_url).map_err(|_| {
                    ErrorKind::CargoEditLib(::cargo_edit::ErrorKind::InvalidCargoConfig)
                })?)?;
            }
        }

        let upgraded_dependencies =
            existing_dependencies.get_upgraded(allow_prerelease, &find(&manifest_path)?)?;

        manifests.upgrade(&upgraded_dependencies, dry_run)
    }
}

fn main() {
    let args: Command = Command::from_args();
    let Command::Upgrade(args) = args;

    if let Err(err) = process(args) {
        eprintln!("Command failed due to unhandled error: {}\n", err);

        for e in err.iter().skip(1) {
            eprintln!("Caused by: {}", e);
        }

        if let Some(backtrace) = err.backtrace() {
            eprintln!("Backtrace: {:?}", backtrace);
        }

        process::exit(1);
    }
}
<|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 std::collections::{HashMap, HashSet};
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};

use syn;

use bindgen::cargo::{Cargo, PackageRef};
use bindgen::ir::Cfg;

const STD_CRATES: &'static [&'static str] = &["std",
                                              "std_unicode",
                                              "alloc",
                                              "collections",
                                              "core",
                                              "proc_macro"];

type ParseResult = Result<(), String>;

/// Parses a single rust source file, not following `mod` or `extern crate`.
pub fn parse_src<F>(src_file: &Path,
                    items_callback: &mut F) -> ParseResult
    where F: FnMut(&str, &Vec<syn::Item>)
{
    let src_parsed = {
        let mut s = String::new();
        let mut f = File::open(src_file).map_err(|_| format!("Parsing: cannot open file `{:?}`.", src_file))?;
        f.read_to_string(&mut s).map_err(|_| format!("Parsing: cannot open file `{:?}`.", src_file))?;
        syn::parse_crate(&s).map_err(|msg| format!("Parsing:\n{}.", msg))?
    };

    items_callback("", &src_parsed.items);

    Ok(())
}

/// Recursively parses a rust library starting at the root crate's directory.
///
/// Inside a crate, `mod` and `extern crate` declarations are followed
/// and parsed. To find an external crate, the parser uses the `cargo metadata`
/// command to find the location of dependencies.
pub fn parse_lib<F>(lib: Cargo,
                    parse_deps: bool,
                    include: &Option<Vec<String>>,
                    exclude: &Vec<String>,
                    expand: &Vec<String>,
                    items_callback: &mut F) -> ParseResult
    where F: FnMut(&str, &str, Option<Cfg>, &Vec<syn::Item>)
{
    let mut context = ParseLibContext {
        lib: lib,
        parse_deps: parse_deps,
        include: include.clone(),
        exclude: exclude.clone(),
        expand: expand.clone(),
        parsed_crates: HashSet::new(),
        cache_src: HashMap::new(),
        cache_expanded_crate: HashMap::new(),

        cfg_stack: Vec::new(),

        items_callback: items_callback,
    };

    parse_crate(&context.lib.binding_crate_ref(), &mut context)
}

struct ParseLibContext<F>
    where F: FnMut(&str, &str, Option<Cfg>, &Vec<syn::Item>)
{
    lib: Cargo,
    parse_deps: bool,

    include: Option<Vec<String>>,
    exclude: Vec<String>,
    expand: Vec<String>,

    parsed_crates: HashSet<String>,
    cache_src: HashMap<PathBuf, Vec<syn::Item>>,
    cache_expanded_crate: HashMap<String, Vec<syn::Item>>,

    cfg_stack: Vec<Cfg>,

    items_callback: F,
}

impl<F> ParseLibContext<F>
    where F: FnMut(&str, &str, Option<Cfg>, &Vec<syn::Item>)
{
    fn should_parse_dependency(&self, pkg_name: &String) -> bool {
        if self.parsed_crates.contains(pkg_name) {
            return false;
        }

        if !self.parse_deps {
            return false;
        }

        // Skip any whitelist or blacklist for expand
        if self.expand.contains(&pkg_name) {
            return true;
        }

        // If we have a whitelist, check it
        if let Some(ref include) = self.include {
            if !include.contains(&pkg_name) {
                return false;
            }
        }

        // Check the blacklist
        return !STD_CRATES.contains(&pkg_name.as_ref()) &&
               !self.exclude.contains(&pkg_name);
    }
}

fn parse_crate<F>(pkg: &PackageRef, context: &mut ParseLibContext<F>) -> ParseResult
    where F: FnMut(&str, &str, Option<Cfg>, &Vec<syn::Item>)
{
    context.parsed_crates.insert(pkg.name.clone());

    // Check if we should use cargo expand for this crate
    if context.expand.contains(&pkg.name) {
        return parse_expand_crate(pkg, context);
    }

    // Otherwise do our normal parse
    let crate_src = context.lib.find_crate_src(pkg);

    match crate_src {
        Some(crate_src) => {
            parse_mod(pkg, crate_src.as_path(), context)
        },
        None => {
            // This should be an error, but is common enough to just elicit a warning
            warn!("Parsing crate `{}`: can't find lib.rs with `cargo metadata`.", pkg.name);
            Ok(())
        },
    }
}

fn parse_expand_crate<F>(pkg: &PackageRef, context: &mut ParseLibContext<F>) -> ParseResult
    where F: FnMut(&str, &str, Option<Cfg>, &Vec<syn::Item>)
{
    let mod_parsed = {
        if !context.cache_expanded_crate.contains_key(&pkg.name) {
            let s = context.lib.expand_crate(pkg)?;
            let i = syn::parse_crate(&s).map_err(|msg| format!("Parsing crate `{}`:\n{}.", pkg.name, msg))?;
            context.cache_expanded_crate.insert(pkg.name.clone(), i.items);
        }

        context.cache_expanded_crate.get(&pkg.name).unwrap().clone()
    };

    process_expanded_mod(pkg, &mod_parsed, context)
}

fn process_expanded_mod<F>(pkg: &PackageRef,
                           items: &Vec<syn::Item>,
                           context: &mut ParseLibContext<F>) -> ParseResult
    where F: FnMut(&str, &str, Option<Cfg>, &Vec<syn::Item>)
{
    (context.items_callback)(context.lib.binding_crate_name(),
                             &pkg.name,
                             Cfg::join(&context.cfg_stack),
                             items);

    for item in items {
        match item.node {
            syn::ItemKind::Mod(ref inline_items) => {
                let cfg = Cfg::load(&item.attrs);
                if let &Some(ref cfg) = &cfg {
                    context.cfg_stack.push(cfg.clone());
                }

                if let &Some(ref inline_items) = inline_items {
                    process_expanded_mod(pkg, inline_items, context)?;
                } else {
                    error!("Parsing crate `{}`: external mod found in expanded source, this shouldn't be possible.", pkg.name);
                }

                if cfg.is_some() {
                    context.cfg_stack.pop();
                }
            }
            syn::ItemKind::ExternCrate(_) => {
                let dep_pkg_name = item.ident.to_string();

                let cfg = Cfg::load(&item.attrs);
                if let &Some(ref cfg) = &cfg {
                    context.cfg_stack.push(cfg.clone());
                }

                if context.should_parse_dependency(&dep_pkg_name) {
                    let dep_pkg_ref = context.lib.find_dep_ref(pkg, &dep_pkg_name);

                    if let Some(dep_pkg_ref) = dep_pkg_ref {
                        parse_crate(&dep_pkg_ref, context)?;
                    } else {
                        error!("Parsing crate `{}`: can't find dependency version for {}`.", pkg.name, dep_pkg_name);
                    }
                }

                if cfg.is_some() {
                    context.cfg_stack.pop();
                }
            }
            _ => {}
        }
    }

    Ok(())
}

fn parse_mod<F>(pkg: &PackageRef,
                mod_path: &Path,
                context: &mut ParseLibContext<F>) -> ParseResult
    where F: FnMut(&str, &str, Option<Cfg>, &Vec<syn::Item>)
{
    let mod_parsed = {
        let owned_mod_path = mod_path.to_path_buf();

        if !context.cache_src.contains_key(&owned_mod_path) {
            let mut s = String::new();
            let mut f = File::open(mod_path).map_err(|_| format!("Parsing crate `{}`: cannot open file `{:?}`.", pkg.name, mod_path))?;
            f.read_to_string(&mut s).map_err(|_| format!("Parsing crate `{}`: cannot open file `{:?}`.", pkg.name, mod_path))?;
            let i = syn::parse_crate(&s).map_err(|msg| format!("Parsing crate `{}`:\n{}.", pkg.name, msg))?;
            context.cache_src.insert(owned_mod_path.clone(), i.items);
        }

        context.cache_src.get(&owned_mod_path).unwrap().clone()
    };

    let mod_dir = mod_path.parent().unwrap();

    process_mod(pkg,
                mod_dir,
                &mod_parsed,
                context)
}

fn process_mod<F>(pkg: &PackageRef,
                  mod_dir: &Path,
                  items: &Vec<syn::Item>,
                  context: &mut ParseLibContext<F>) -> ParseResult
    where F: FnMut(&str, &str, Option<Cfg>, &Vec<syn::Item>)
{
    (context.items_callback)(context.lib.binding_crate_name(),
                             &pkg.name,
                             Cfg::join(&context.cfg_stack),
                             items);

    for item in items {
        match item.node {
            syn::ItemKind::Mod(ref inline_items) => {
                let next_mod_name = item.ident.to_string();

                let cfg = Cfg::load(&item.attrs);
                if let &Some(ref cfg) = &cfg {
                    context.cfg_stack.push(cfg.clone());
                }

                if let &Some(ref inline_items) = inline_items {
                    process_mod(pkg,
                                &mod_dir.join(&next_mod_name),
                                inline_items,
                                context)?;
                } else {
                    let next_mod_path1 = mod_dir.join(next_mod_name.clone() + ".rs");
                    let next_mod_path2 = mod_dir.join(next_mod_name.clone()).join("mod.rs");

                    if next_mod_path1.exists() {
                        parse_mod(pkg,
                                  next_mod_path1.as_path(),
                                  context)?;
                    } else if next_mod_path2.exists() {
                        parse_mod(pkg,
                                  next_mod_path2.as_path(),
                                  context)?;
                    } else {
                        // This should be an error, but is common enough to just elicit a warning
                        warn!("Parsing crate `{}`: can't find mod {}`.", pkg.name, next_mod_name);
                    }
                }

                if cfg.is_some() {
                    context.cfg_stack.pop();
                }
            }
            syn::ItemKind::ExternCrate(_) => {
                let dep_pkg_name = item.ident.to_string();

                let cfg = Cfg::load(&item.attrs);
                if let &Some(ref cfg) = &cfg {
                    context.cfg_stack.push(cfg.clone());
                }

                if context.should_parse_dependency(&dep_pkg_name) {
                    let dep_pkg_ref = context.lib.find_dep_ref(pkg, &dep_pkg_name);

                    if let Some(dep_pkg_ref) = dep_pkg_ref {
                        parse_crate(&dep_pkg_ref, context)?;
                    } else {
                        error!("Parsing crate `{}`: can't find dependency version for {}`.", pkg.name, dep_pkg_name);
                    }
                }

                if cfg.is_some() {
                    context.cfg_stack.pop();
                }
            }
            _ => {}
        }
    }

    Ok(())
}
<commit_msg>Support extern crate ... as ...; imports<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::collections::{HashMap, HashSet};
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};

use syn;

use bindgen::cargo::{Cargo, PackageRef};
use bindgen::ir::Cfg;

const STD_CRATES: &'static [&'static str] = &["std",
                                              "std_unicode",
                                              "alloc",
                                              "collections",
                                              "core",
                                              "proc_macro"];

type ParseResult = Result<(), String>;

/// Parses a single rust source file, not following `mod` or `extern crate`.
pub fn parse_src<F>(src_file: &Path,
                    items_callback: &mut F) -> ParseResult
    where F: FnMut(&str, &Vec<syn::Item>)
{
    let src_parsed = {
        let mut s = String::new();
        let mut f = File::open(src_file).map_err(|_| format!("Parsing: cannot open file `{:?}`.", src_file))?;
        f.read_to_string(&mut s).map_err(|_| format!("Parsing: cannot open file `{:?}`.", src_file))?;
        syn::parse_crate(&s).map_err(|msg| format!("Parsing:\n{}.", msg))?
    };

    items_callback("", &src_parsed.items);

    Ok(())
}

/// Recursively parses a rust library starting at the root crate's directory.
///
/// Inside a crate, `mod` and `extern crate` declarations are followed
/// and parsed. To find an external crate, the parser uses the `cargo metadata`
/// command to find the location of dependencies.
pub fn parse_lib<F>(lib: Cargo,
                    parse_deps: bool,
                    include: &Option<Vec<String>>,
                    exclude: &Vec<String>,
                    expand: &Vec<String>,
                    items_callback: &mut F) -> ParseResult
    where F: FnMut(&str, &str, Option<Cfg>, &Vec<syn::Item>)
{
    let mut context = ParseLibContext {
        lib: lib,
        parse_deps: parse_deps,
        include: include.clone(),
        exclude: exclude.clone(),
        expand: expand.clone(),
        parsed_crates: HashSet::new(),
        cache_src: HashMap::new(),
        cache_expanded_crate: HashMap::new(),

        cfg_stack: Vec::new(),

        items_callback: items_callback,
    };

    parse_crate(&context.lib.binding_crate_ref(), &mut context)
}

struct ParseLibContext<F>
    where F: FnMut(&str, &str, Option<Cfg>, &Vec<syn::Item>)
{
    lib: Cargo,
    parse_deps: bool,

    include: Option<Vec<String>>,
    exclude: Vec<String>,
    expand: Vec<String>,

    parsed_crates: HashSet<String>,
    cache_src: HashMap<PathBuf, Vec<syn::Item>>,
    cache_expanded_crate: HashMap<String, Vec<syn::Item>>,

    cfg_stack: Vec<Cfg>,

    items_callback: F,
}

impl<F> ParseLibContext<F>
    where F: FnMut(&str, &str, Option<Cfg>, &Vec<syn::Item>)
{
    fn should_parse_dependency(&self, pkg_name: &String) -> bool {
        if self.parsed_crates.contains(pkg_name) {
            return false;
        }

        if !self.parse_deps {
            return false;
        }

        // Skip any whitelist or blacklist for expand
        if self.expand.contains(&pkg_name) {
            return true;
        }

        // If we have a whitelist, check it
        if let Some(ref include) = self.include {
            if !include.contains(&pkg_name) {
                return false;
            }
        }

        // Check the blacklist
        return !STD_CRATES.contains(&pkg_name.as_ref()) &&
               !self.exclude.contains(&pkg_name);
    }
}

fn parse_crate<F>(pkg: &PackageRef, context: &mut ParseLibContext<F>) -> ParseResult
    where F: FnMut(&str, &str, Option<Cfg>, &Vec<syn::Item>)
{
    context.parsed_crates.insert(pkg.name.clone());

    // Check if we should use cargo expand for this crate
    if context.expand.contains(&pkg.name) {
        return parse_expand_crate(pkg, context);
    }

    // Otherwise do our normal parse
    let crate_src = context.lib.find_crate_src(pkg);

    match crate_src {
        Some(crate_src) => {
            parse_mod(pkg, crate_src.as_path(), context)
        },
        None => {
            // This should be an error, but is common enough to just elicit a warning
            warn!("Parsing crate `{}`: can't find lib.rs with `cargo metadata`.", pkg.name);
            Ok(())
        },
    }
}

fn parse_expand_crate<F>(pkg: &PackageRef, context: &mut ParseLibContext<F>) -> ParseResult
    where F: FnMut(&str, &str, Option<Cfg>, &Vec<syn::Item>)
{
    let mod_parsed = {
        if !context.cache_expanded_crate.contains_key(&pkg.name) {
            let s = context.lib.expand_crate(pkg)?;
            let i = syn::parse_crate(&s).map_err(|msg| format!("Parsing crate `{}`:\n{}.", pkg.name, msg))?;
            context.cache_expanded_crate.insert(pkg.name.clone(), i.items);
        }

        context.cache_expanded_crate.get(&pkg.name).unwrap().clone()
    };

    process_expanded_mod(pkg, &mod_parsed, context)
}

fn process_expanded_mod<F>(pkg: &PackageRef,
                           items: &Vec<syn::Item>,
                           context: &mut ParseLibContext<F>) -> ParseResult
    where F: FnMut(&str, &str, Option<Cfg>, &Vec<syn::Item>)
{
    (context.items_callback)(context.lib.binding_crate_name(),
                             &pkg.name,
                             Cfg::join(&context.cfg_stack),
                             items);

    for item in items {
        match item.node {
            syn::ItemKind::Mod(ref inline_items) => {
                let cfg = Cfg::load(&item.attrs);
                if let &Some(ref cfg) = &cfg {
                    context.cfg_stack.push(cfg.clone());
                }

                if let &Some(ref inline_items) = inline_items {
                    process_expanded_mod(pkg, inline_items, context)?;
                } else {
                    error!("Parsing crate `{}`: external mod found in expanded source, this shouldn't be possible.", pkg.name);
                }

                if cfg.is_some() {
                    context.cfg_stack.pop();
                }
            }
            syn::ItemKind::ExternCrate(_) => {
                let dep_pkg_name = item.ident.to_string();

                let cfg = Cfg::load(&item.attrs);
                if let &Some(ref cfg) = &cfg {
                    context.cfg_stack.push(cfg.clone());
                }

                if context.should_parse_dependency(&dep_pkg_name) {
                    let dep_pkg_ref = context.lib.find_dep_ref(pkg, &dep_pkg_name);

                    if let Some(dep_pkg_ref) = dep_pkg_ref {
                        parse_crate(&dep_pkg_ref, context)?;
                    } else {
                        error!("Parsing crate `{}`: can't find dependency version for {}`.", pkg.name, dep_pkg_name);
                    }
                }

                if cfg.is_some() {
                    context.cfg_stack.pop();
                }
            }
            _ => {}
        }
    }

    Ok(())
}

fn parse_mod<F>(pkg: &PackageRef,
                mod_path: &Path,
                context: &mut ParseLibContext<F>) -> ParseResult
    where F: FnMut(&str, &str, Option<Cfg>, &Vec<syn::Item>)
{
    let mod_parsed = {
        let owned_mod_path = mod_path.to_path_buf();

        if !context.cache_src.contains_key(&owned_mod_path) {
            let mut s = String::new();
            let mut f = File::open(mod_path).map_err(|_| format!("Parsing crate `{}`: cannot open file `{:?}`.", pkg.name, mod_path))?;
            f.read_to_string(&mut s).map_err(|_| format!("Parsing crate `{}`: cannot open file `{:?}`.", pkg.name, mod_path))?;
            let i = syn::parse_crate(&s).map_err(|msg| format!("Parsing crate `{}`:\n{}.", pkg.name, msg))?;
            context.cache_src.insert(owned_mod_path.clone(), i.items);
        }

        context.cache_src.get(&owned_mod_path).unwrap().clone()
    };

    let mod_dir = mod_path.parent().unwrap();

    process_mod(pkg,
                mod_dir,
                &mod_parsed,
                context)
}

fn process_mod<F>(pkg: &PackageRef,
                  mod_dir: &Path,
                  items: &Vec<syn::Item>,
                  context: &mut ParseLibContext<F>) -> ParseResult
    where F: FnMut(&str, &str, Option<Cfg>, &Vec<syn::Item>)
{
    (context.items_callback)(context.lib.binding_crate_name(),
                             &pkg.name,
                             Cfg::join(&context.cfg_stack),
                             items);

    for item in items {
        match item.node {
            syn::ItemKind::Mod(ref inline_items) => {
                let next_mod_name = item.ident.to_string();

                let cfg = Cfg::load(&item.attrs);
                if let &Some(ref cfg) = &cfg {
                    context.cfg_stack.push(cfg.clone());
                }

                if let &Some(ref inline_items) = inline_items {
                    process_mod(pkg,
                                &mod_dir.join(&next_mod_name),
                                inline_items,
                                context)?;
                } else {
                    let next_mod_path1 = mod_dir.join(next_mod_name.clone() + ".rs");
                    let next_mod_path2 = mod_dir.join(next_mod_name.clone()).join("mod.rs");

                    if next_mod_path1.exists() {
                        parse_mod(pkg,
                                  next_mod_path1.as_path(),
                                  context)?;
                    } else if next_mod_path2.exists() {
                        parse_mod(pkg,
                                  next_mod_path2.as_path(),
                                  context)?;
                    } else {
                        // This should be an error, but is common enough to just elicit a warning
                        warn!("Parsing crate `{}`: can't find mod {}`.", pkg.name, next_mod_name);
                    }
                }

                if cfg.is_some() {
                    context.cfg_stack.pop();
                }
            }
            syn::ItemKind::ExternCrate(ref cr) => {
                let dep_pkg_name = if let Some(ref name) = *cr {
                    name.to_string()
                } else {
                    item.ident.to_string()
                };

                let cfg = Cfg::load(&item.attrs);
                if let &Some(ref cfg) = &cfg {
                    context.cfg_stack.push(cfg.clone());
                }

                if context.should_parse_dependency(&dep_pkg_name) {
                    let dep_pkg_ref = context.lib.find_dep_ref(pkg, &dep_pkg_name);

                    if let Some(dep_pkg_ref) = dep_pkg_ref {
                        parse_crate(&dep_pkg_ref, context)?;
                    } else {
                        error!("Parsing crate `{}`: can't find dependency version for {}`.", pkg.name, dep_pkg_name);
                    }
                }

                if cfg.is_some() {
                    context.cfg_stack.pop();
                }
            }
            _ => {}
        }
    }

    Ok(())
}
<|endoftext|>


<commit_before>use super::error::{Error, RepoError};
use std::result;
use chrono::*;
use entities::*;
use super::db::Repo;
use super::validate::Validate;
use uuid::Uuid;

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

trait Id {
    fn id(&self) -> &str;
}

impl Id for Entry {
    fn id(&self) -> &str {
        &self.id
    }
}

impl Id for Category {
    fn id(&self) -> &str {
        &self.id
    }
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct NewEntry {
    title       : String,
    description : String,
    lat         : f64,
    lng         : f64,
    street      : Option<String>,
    zip         : Option<String>,
    city        : Option<String>,
    country     : Option<String>,
    email       : Option<String>,
    telephone   : Option<String>,
    homepage    : Option<String>,
    categories  : Vec<String>,
    license     : String,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct UpdateEntry {
    id          : String,
    version     : u64,
    title       : String,
    description : String,
    lat         : f64,
    lng         : f64,
    street      : Option<String>,
    zip         : Option<String>,
    city        : Option<String>,
    country     : Option<String>,
    email       : Option<String>,
    telephone   : Option<String>,
    homepage    : Option<String>,
    categories  : Vec<String>,
}

pub fn create_new_entry<R: Repo<Entry>>(r: &mut R, e: NewEntry) -> Result<String>
 {
    let e = Entry{
        id          :  Uuid::new_v4().simple().to_string(),
        created     :  UTC::now().timestamp() as u64,
        version     :  0,
        title       :  e.title,
        description :  e.description,
        lat         :  e.lat,
        lng         :  e.lng,
        street      :  e.street,
        zip         :  e.zip,
        city        :  e.city,
        country     :  e.country,
        email       :  e.email,
        telephone   :  e.telephone,
        homepage    :  e.homepage,
        categories  :  e.categories,
        license     :  Some(e.license)
    };
    e.validate()?;
    r.create(&e)?;
    Ok(e.id)
}

pub fn update_entry<R: Repo<Entry>>(r: &mut R, e: UpdateEntry) -> Result<()> {
    let old : Entry = r.get(&e.id)?;
    if old.version != e.version {
        return Err(Error::Repo(RepoError::InvalidVersion))
    }
    let e = Entry{
        id          :  e.id,
        created     :  UTC::now().timestamp() as u64,
        version     :  e.version+1,
        title       :  e.title,
        description :  e.description,
        lat         :  e.lat,
        lng         :  e.lng,
        street      :  e.street,
        zip         :  e.zip,
        city        :  e.city,
        country     :  e.country,
        email       :  e.email,
        telephone   :  e.telephone,
        homepage    :  e.homepage,
        categories  :  e.categories,
        license     :  old.license
    };
    r.update(&e)?;
    Ok(())
}

#[cfg(test)]
pub mod tests {

    use super::*;

    type RepoResult<T> = result::Result<T, RepoError>;

    pub struct MockRepo<T> {
        objects: Vec<T>,
    }

    impl<T> MockRepo<T> {
        pub fn new() -> MockRepo<T> {
            MockRepo {
                objects: vec![]
            }
        }

        pub fn clear(&mut self) {
            self.objects = vec![];
        }
    }

    impl<T:Id + Clone> Repo<T> for MockRepo<T> {

        fn get(&self, id: &str) -> RepoResult<T> {
            match self.objects.iter().find(|x| x.id() == id) {
                Some(x) => Ok(x.clone()),
                None => Err(RepoError::NotFound),
            }
        }

        fn all(&self) -> RepoResult<Vec<T>> {
            Ok(self.objects.clone())
        }

        fn create(&mut self, e: &T) -> RepoResult<()> {
            if self.objects.iter().any(|x| x.id() == e.id()) {
                return Err(RepoError::AlreadyExists)
            } else {
                self.objects.push(e.clone());
            }
            Ok(())
        }

        fn update(&mut self, e: &T) -> RepoResult<()> {
            if let Some(pos) = self.objects.iter().position(|x| x.id() == e.id()) {
                self.objects[pos] = e.clone();
            } else {
                return Err(RepoError::NotFound)
            }
            Ok(())
        }
    }

    #[test]
    fn create_new_valid_entry() {
        let x = NewEntry {
            title       : "foo".into(),
            description : "bar".into(),
            lat         : 0.0,
            lng         : 0.0,
            street      : None,
            zip         : None,
            city        : None,
            country     : None,
            email       : None,
            telephone   : None,
            homepage    : None,
            categories  : vec![],
            license     : "CC0-1.0".into()
        };
        let mut mock_db: MockRepo<Entry> = MockRepo { objects: vec![] };
        let now = UTC::now();
        let id = create_new_entry(&mut mock_db, x).unwrap();
        assert!(Uuid::parse_str(&id).is_ok());
        assert_eq!(mock_db.objects.len(),1);
        let x = &mock_db.objects[0];
        assert_eq!(x.title, "foo");
        assert_eq!(x.description, "bar");
        assert_eq!(x.version, 0);
        assert!(x.created as i64 >= now.timestamp());
        assert!(Uuid::parse_str(&x.id).is_ok());
        assert_eq!(x.id, id);
    }

    #[test]
    fn create_entry_with_invalid_email() {
        let x = NewEntry {
            title       : "foo".into(),
            description : "bar".into(),
            lat         : 0.0,
            lng         : 0.0,
            street      : None,
            zip         : None,
            city        : None,
            country     : None,
            email       : Some("fooo-not-ok".into()),
            telephone   : None,
            homepage    : None,
            categories  : vec![],
            license     : "CC0-1.0".into()
        };
        let mut mock_db: MockRepo<Entry> = MockRepo { objects: vec![] };
        assert!(create_new_entry(&mut mock_db, x).is_err());
    }

    #[test]
    fn update_valid_entry(){
        let id = Uuid::new_v4().simple().to_string();
        let old = Entry {
            id          : id.clone(),
            version     : 1,
            created     : 0,
            title       : "foo".into(),
            description : "bar".into(),
            lat         : 0.0,
            lng         : 0.0,
            street      : None,
            zip         : None,
            city        : None,
            country     : None,
            email       : None,
            telephone   : None,
            homepage    : None,
            categories  : vec![],
            license     : None
        };
        let new = UpdateEntry {
            id          : id.clone(),
            version     : 1,
            title       : "foo".into(),
            description : "bar".into(),
            lat         : 0.0,
            lng         : 0.0,
            street      : Some("street".into()),
            zip         : None,
            city        : None,
            country     : None,
            email       : None,
            telephone   : None,
            homepage    : None,
            categories  : vec![],
        };
        let mut mock_db : MockRepo<Entry> = MockRepo{ objects: vec![old]};
        let now = UTC::now();
        assert!(update_entry(&mut mock_db, new).is_ok());
        assert_eq!(mock_db.objects.len(),1);
        let x = &mock_db.objects[0];
        assert_eq!(x.street, Some("street".into()));
        assert_eq!(x.description, "bar");
        assert_eq!(x.version, 2);
        assert!(x.created as i64 >= now.timestamp());
        assert!(Uuid::parse_str(&x.id).is_ok());
    }

    #[test]
    fn update_entry_with_invalid_version(){
        let id = Uuid::new_v4().simple().to_string();
        let old = Entry {
            id          : id.clone(),
            version     : 3,
            created     : 0,
            title       : "foo".into(),
            description : "bar".into(),
            lat         : 0.0,
            lng         : 0.0,
            street      : None,
            zip         : None,
            city        : None,
            country     : None,
            email       : None,
            telephone   : None,
            homepage    : None,
            categories  : vec![],
            license     : None
        };
        let new = UpdateEntry {
            id          : id.clone(),
            version     : 4,
            title       : "foo".into(),
            description : "bar".into(),
            lat         : 0.0,
            lng         : 0.0,
            street      : Some("street".into()),
            zip         : None,
            city        : None,
            country     : None,
            email       : None,
            telephone   : None,
            homepage    : None,
            categories  : vec![],
        };
        let mut mock_db : MockRepo<Entry> = MockRepo{ objects: vec![old]};
        let result = update_entry(&mut mock_db, new);
        assert!(result.is_err());
        match result.err().unwrap() {
            Error::Repo(err) => {
                match err {
                    RepoError::InvalidVersion => { },
                    _ => {
                        panic!("invalid error type");
                    }
                }
            },
            _ => {
                panic!("invalid error type");
            }
        }
        assert_eq!(mock_db.objects.len(),1);
    }

    #[test]
    fn update_non_existing_entry(){
        let id = Uuid::new_v4().simple().to_string();
        let new = UpdateEntry {
            id          : id.clone(),
            version     : 4,
            title       : "foo".into(),
            description : "bar".into(),
            lat         : 0.0,
            lng         : 0.0,
            street      : Some("street".into()),
            zip         : None,
            city        : None,
            country     : None,
            email       : None,
            telephone   : None,
            homepage    : None,
            categories  : vec![],
        };
        let mut mock_db : MockRepo<Entry> = MockRepo{ objects: vec![]};
        let result = update_entry(&mut mock_db, new);
        assert!(result.is_err());
        match result.err().unwrap() {
            Error::Repo(err) => {
                match err {
                    RepoError::NotFound => { },
                    _ => {
                        panic!("invalid error type");
                    }
                }
            },
            _ => {
                panic!("invalid error type");
            }
        }
        assert_eq!(mock_db.objects.len(),0);
    }
}
<commit_msg>fix(update_entry): client must increase the version property<commit_after>use super::error::{Error, RepoError};
use std::result;
use chrono::*;
use entities::*;
use super::db::Repo;
use super::validate::Validate;
use uuid::Uuid;

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

trait Id {
    fn id(&self) -> &str;
}

impl Id for Entry {
    fn id(&self) -> &str {
        &self.id
    }
}

impl Id for Category {
    fn id(&self) -> &str {
        &self.id
    }
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct NewEntry {
    title       : String,
    description : String,
    lat         : f64,
    lng         : f64,
    street      : Option<String>,
    zip         : Option<String>,
    city        : Option<String>,
    country     : Option<String>,
    email       : Option<String>,
    telephone   : Option<String>,
    homepage    : Option<String>,
    categories  : Vec<String>,
    license     : String,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct UpdateEntry {
    id          : String,
    version     : u64,
    title       : String,
    description : String,
    lat         : f64,
    lng         : f64,
    street      : Option<String>,
    zip         : Option<String>,
    city        : Option<String>,
    country     : Option<String>,
    email       : Option<String>,
    telephone   : Option<String>,
    homepage    : Option<String>,
    categories  : Vec<String>,
}

pub fn create_new_entry<R: Repo<Entry>>(r: &mut R, e: NewEntry) -> Result<String>
 {
    let e = Entry{
        id          :  Uuid::new_v4().simple().to_string(),
        created     :  UTC::now().timestamp() as u64,
        version     :  0,
        title       :  e.title,
        description :  e.description,
        lat         :  e.lat,
        lng         :  e.lng,
        street      :  e.street,
        zip         :  e.zip,
        city        :  e.city,
        country     :  e.country,
        email       :  e.email,
        telephone   :  e.telephone,
        homepage    :  e.homepage,
        categories  :  e.categories,
        license     :  Some(e.license)
    };
    e.validate()?;
    r.create(&e)?;
    Ok(e.id)
}

pub fn update_entry<R: Repo<Entry>>(r: &mut R, e: UpdateEntry) -> Result<()> {
    let old : Entry = r.get(&e.id)?;
    if (old.version + 1) != e.version {
        return Err(Error::Repo(RepoError::InvalidVersion))
    }
    let e = Entry{
        id          :  e.id,
        created     :  UTC::now().timestamp() as u64,
        version     :  e.version+1,
        title       :  e.title,
        description :  e.description,
        lat         :  e.lat,
        lng         :  e.lng,
        street      :  e.street,
        zip         :  e.zip,
        city        :  e.city,
        country     :  e.country,
        email       :  e.email,
        telephone   :  e.telephone,
        homepage    :  e.homepage,
        categories  :  e.categories,
        license     :  old.license
    };
    r.update(&e)?;
    Ok(())
}

#[cfg(test)]
pub mod tests {

    use super::*;

    type RepoResult<T> = result::Result<T, RepoError>;

    pub struct MockRepo<T> {
        objects: Vec<T>,
    }

    impl<T> MockRepo<T> {
        pub fn new() -> MockRepo<T> {
            MockRepo {
                objects: vec![]
            }
        }

        pub fn clear(&mut self) {
            self.objects = vec![];
        }
    }

    impl<T:Id + Clone> Repo<T> for MockRepo<T> {

        fn get(&self, id: &str) -> RepoResult<T> {
            match self.objects.iter().find(|x| x.id() == id) {
                Some(x) => Ok(x.clone()),
                None => Err(RepoError::NotFound),
            }
        }

        fn all(&self) -> RepoResult<Vec<T>> {
            Ok(self.objects.clone())
        }

        fn create(&mut self, e: &T) -> RepoResult<()> {
            if self.objects.iter().any(|x| x.id() == e.id()) {
                return Err(RepoError::AlreadyExists)
            } else {
                self.objects.push(e.clone());
            }
            Ok(())
        }

        fn update(&mut self, e: &T) -> RepoResult<()> {
            if let Some(pos) = self.objects.iter().position(|x| x.id() == e.id()) {
                self.objects[pos] = e.clone();
            } else {
                return Err(RepoError::NotFound)
            }
            Ok(())
        }
    }

    #[test]
    fn create_new_valid_entry() {
        let x = NewEntry {
            title       : "foo".into(),
            description : "bar".into(),
            lat         : 0.0,
            lng         : 0.0,
            street      : None,
            zip         : None,
            city        : None,
            country     : None,
            email       : None,
            telephone   : None,
            homepage    : None,
            categories  : vec![],
            license     : "CC0-1.0".into()
        };
        let mut mock_db: MockRepo<Entry> = MockRepo { objects: vec![] };
        let now = UTC::now();
        let id = create_new_entry(&mut mock_db, x).unwrap();
        assert!(Uuid::parse_str(&id).is_ok());
        assert_eq!(mock_db.objects.len(),1);
        let x = &mock_db.objects[0];
        assert_eq!(x.title, "foo");
        assert_eq!(x.description, "bar");
        assert_eq!(x.version, 0);
        assert!(x.created as i64 >= now.timestamp());
        assert!(Uuid::parse_str(&x.id).is_ok());
        assert_eq!(x.id, id);
    }

    #[test]
    fn create_entry_with_invalid_email() {
        let x = NewEntry {
            title       : "foo".into(),
            description : "bar".into(),
            lat         : 0.0,
            lng         : 0.0,
            street      : None,
            zip         : None,
            city        : None,
            country     : None,
            email       : Some("fooo-not-ok".into()),
            telephone   : None,
            homepage    : None,
            categories  : vec![],
            license     : "CC0-1.0".into()
        };
        let mut mock_db: MockRepo<Entry> = MockRepo { objects: vec![] };
        assert!(create_new_entry(&mut mock_db, x).is_err());
    }

    #[test]
    fn update_valid_entry(){
        let id = Uuid::new_v4().simple().to_string();
        let old = Entry {
            id          : id.clone(),
            version     : 1,
            created     : 0,
            title       : "foo".into(),
            description : "bar".into(),
            lat         : 0.0,
            lng         : 0.0,
            street      : None,
            zip         : None,
            city        : None,
            country     : None,
            email       : None,
            telephone   : None,
            homepage    : None,
            categories  : vec![],
            license     : None
        };
        let new = UpdateEntry {
            id          : id.clone(),
            version     : 1,
            title       : "foo".into(),
            description : "bar".into(),
            lat         : 0.0,
            lng         : 0.0,
            street      : Some("street".into()),
            zip         : None,
            city        : None,
            country     : None,
            email       : None,
            telephone   : None,
            homepage    : None,
            categories  : vec![],
        };
        let mut mock_db : MockRepo<Entry> = MockRepo{ objects: vec![old]};
        let now = UTC::now();
        assert!(update_entry(&mut mock_db, new).is_ok());
        assert_eq!(mock_db.objects.len(),1);
        let x = &mock_db.objects[0];
        assert_eq!(x.street, Some("street".into()));
        assert_eq!(x.description, "bar");
        assert_eq!(x.version, 2);
        assert!(x.created as i64 >= now.timestamp());
        assert!(Uuid::parse_str(&x.id).is_ok());
    }

    #[test]
    fn update_entry_with_invalid_version(){
        let id = Uuid::new_v4().simple().to_string();
        let old = Entry {
            id          : id.clone(),
            version     : 3,
            created     : 0,
            title       : "foo".into(),
            description : "bar".into(),
            lat         : 0.0,
            lng         : 0.0,
            street      : None,
            zip         : None,
            city        : None,
            country     : None,
            email       : None,
            telephone   : None,
            homepage    : None,
            categories  : vec![],
            license     : None
        };
        let new = UpdateEntry {
            id          : id.clone(),
            version     : 4,
            title       : "foo".into(),
            description : "bar".into(),
            lat         : 0.0,
            lng         : 0.0,
            street      : Some("street".into()),
            zip         : None,
            city        : None,
            country     : None,
            email       : None,
            telephone   : None,
            homepage    : None,
            categories  : vec![],
        };
        let mut mock_db : MockRepo<Entry> = MockRepo{ objects: vec![old]};
        let result = update_entry(&mut mock_db, new);
        assert!(result.is_err());
        match result.err().unwrap() {
            Error::Repo(err) => {
                match err {
                    RepoError::InvalidVersion => { },
                    _ => {
                        panic!("invalid error type");
                    }
                }
            },
            _ => {
                panic!("invalid error type");
            }
        }
        assert_eq!(mock_db.objects.len(),1);
    }

    #[test]
    fn update_non_existing_entry(){
        let id = Uuid::new_v4().simple().to_string();
        let new = UpdateEntry {
            id          : id.clone(),
            version     : 4,
            title       : "foo".into(),
            description : "bar".into(),
            lat         : 0.0,
            lng         : 0.0,
            street      : Some("street".into()),
            zip         : None,
            city        : None,
            country     : None,
            email       : None,
            telephone   : None,
            homepage    : None,
            categories  : vec![],
        };
        let mut mock_db : MockRepo<Entry> = MockRepo{ objects: vec![]};
        let result = update_entry(&mut mock_db, new);
        assert!(result.is_err());
        match result.err().unwrap() {
            Error::Repo(err) => {
                match err {
                    RepoError::NotFound => { },
                    _ => {
                        panic!("invalid error type");
                    }
                }
            },
            _ => {
                panic!("invalid error type");
            }
        }
        assert_eq!(mock_db.objects.len(),0);
    }
}
<|endoftext|>


<commit_before>use std::{mem, convert, sync};
use std::fmt::Debug;

use byteorder::{ByteOrder, LittleEndian};

use super::super::crypto::{PublicKey, SecretKey, Signature, sign, verify, Hash, hash,
                           SIGNATURE_LENGTH};

use super::{Field, Error};

pub const HEADER_SIZE: usize = 8; // TODO: rename to HEADER_LENGTH?

pub const TEST_NETWORK_ID: u8 = 0;
pub const PROTOCOL_MAJOR_VERSION: u8 = 0;

pub type RawMessage = sync::Arc<MessageBuffer>;

// TODO: reduce `to` argument from `write`, `read` and `check` methods
// TODO: payload_length as a first value into message header
// TODO: make sure that message length is enougth when using mem::transmute

#[derive(Debug, PartialEq)]
pub struct MessageBuffer {
    pub raw: Vec<u8>,
}

impl MessageBuffer {
    pub fn from_vec(raw: Vec<u8>) -> MessageBuffer {
        // TODO: check that size >= HEADER_SIZE
        // TODO: check that payload_length == raw.len()
        MessageBuffer { raw: raw }
    }

    pub fn len(&self) -> usize {
        self.raw.len()
    }

    pub fn network_id(&self) -> u8 {
        self.raw[0]
    }

    pub fn version(&self) -> u8 {
        self.raw[1]
    }

    pub fn message_type(&self) -> u16 {
        LittleEndian::read_u16(&self.raw[2..4])
    }

    fn body(&self) -> &[u8] {
        &self.raw[..self.raw.len() - SIGNATURE_LENGTH]
    }

    pub fn check<'a, F: Field<'a>>(&'a self, from: usize, to: usize) -> Result<(), Error> {
        F::check(self.body(), from + HEADER_SIZE, to + HEADER_SIZE)
    }

    pub fn read<'a, F: Field<'a>>(&'a self, from: usize, to: usize) -> F {
        F::read(self.body(), from + HEADER_SIZE, to + HEADER_SIZE)
    }

    pub fn signature(&self) -> &Signature {
        let sign_idx = self.raw.len() - SIGNATURE_LENGTH;
        unsafe { mem::transmute(&self.raw[sign_idx]) }
    }

    pub fn verify(&self, pub_key: &PublicKey) -> bool {
        verify(self.signature(), self.body(), pub_key)
    }
}

impl convert::AsRef<[u8]> for MessageBuffer {
    fn as_ref(&self) -> &[u8] {
        &self.raw
    }
}

#[derive(Debug, PartialEq)]
pub struct MessageWriter {
    raw: Vec<u8>,
}

impl MessageWriter {
    pub fn new(message_type: u16, payload_length: usize) -> MessageWriter {
        let mut raw = MessageWriter { raw: vec![0; HEADER_SIZE + payload_length] };
        raw.set_network_id(TEST_NETWORK_ID);
        raw.set_version(PROTOCOL_MAJOR_VERSION);
        raw.set_message_type(message_type);
        raw
    }

    fn set_network_id(&mut self, network_id: u8) {
        self.raw[0] = network_id
    }

    fn set_version(&mut self, version: u8) {
        self.raw[1] = version
    }

    fn set_message_type(&mut self, message_type: u16) {
        LittleEndian::write_u16(&mut self.raw[2..4], message_type)
    }

    fn set_payload_length(&mut self, length: usize) {
        LittleEndian::write_u32(&mut self.raw[4..8], length as u32)
    }

    pub fn write<'a, F: Field<'a>>(&'a mut self, field: F, from: usize, to: usize) {
        field.write(&mut self.raw, from + HEADER_SIZE, to + HEADER_SIZE);
    }

    pub fn sign(mut self, secret_key: &SecretKey) -> MessageBuffer {
        let payload_length = self.raw.len() + SIGNATURE_LENGTH;
        self.set_payload_length(payload_length);
        let signature = sign(&self.raw, secret_key);
        self.raw.extend_from_slice(signature.as_ref());
        MessageBuffer { raw: self.raw }
    }
}

pub trait Message: Debug + Clone + PartialEq + Sized + Send {
    fn raw(&self) -> &RawMessage;
    fn from_raw(raw: RawMessage) -> Result<Self, Error>;

    fn hash(&self) -> Hash {
        hash(self.raw().as_ref().as_ref())
    }

    fn verify(&self, pub_key: &PublicKey) -> bool {
        self.raw().verify(pub_key)
    }
}

// #[test]
// fn test_empty_message() {
//     let raw = MessageBuffer::empty();
//     assert_eq!(raw.network_id(), 0);
//     assert_eq!(raw.version(), 0);
//     assert_eq!(raw.message_type(), 0);
//     assert_eq!(raw.payload_length(), 0);
// }

// #[test]
// fn test_as_mut() {
//     let mut raw = MessageBuffer::empty();
//     {
//         let bytes = raw.as_mut();
//         bytes[0] = 1;
//         bytes[1] = 2;
//         bytes[2] = 3;
//         bytes[3] = 0;
//         bytes[4] = 5;
//         bytes[5] = 6;
//     }
//     assert_eq!(raw.network_id(), 1);
//     assert_eq!(raw.version(), 2);
//     assert_eq!(raw.message_type(), 3);
//     assert_eq!(raw.payload_length(), 1541);
// }
<commit_msg>Remove pub from MessageBuffer<commit_after>use std::{mem, convert, sync};
use std::fmt::Debug;

use byteorder::{ByteOrder, LittleEndian};

use super::super::crypto::{PublicKey, SecretKey, Signature, sign, verify, Hash, hash,
                           SIGNATURE_LENGTH};

use super::{Field, Error};

pub const HEADER_SIZE: usize = 8; // TODO: rename to HEADER_LENGTH?

pub const TEST_NETWORK_ID: u8 = 0;
pub const PROTOCOL_MAJOR_VERSION: u8 = 0;

pub type RawMessage = sync::Arc<MessageBuffer>;

// TODO: reduce `to` argument from `write`, `read` and `check` methods
// TODO: payload_length as a first value into message header
// TODO: make sure that message length is enougth when using mem::transmute

#[derive(Debug, PartialEq)]
pub struct MessageBuffer {
    raw: Vec<u8>,
}

impl MessageBuffer {
    pub fn from_vec(raw: Vec<u8>) -> MessageBuffer {
        // TODO: check that size >= HEADER_SIZE
        // TODO: check that payload_length == raw.len()
        MessageBuffer { raw: raw }
    }

    pub fn len(&self) -> usize {
        self.raw.len()
    }

    pub fn network_id(&self) -> u8 {
        self.raw[0]
    }

    pub fn version(&self) -> u8 {
        self.raw[1]
    }

    pub fn message_type(&self) -> u16 {
        LittleEndian::read_u16(&self.raw[2..4])
    }

    fn body(&self) -> &[u8] {
        &self.raw[..self.raw.len() - SIGNATURE_LENGTH]
    }

    pub fn check<'a, F: Field<'a>>(&'a self, from: usize, to: usize) -> Result<(), Error> {
        F::check(self.body(), from + HEADER_SIZE, to + HEADER_SIZE)
    }

    pub fn read<'a, F: Field<'a>>(&'a self, from: usize, to: usize) -> F {
        F::read(self.body(), from + HEADER_SIZE, to + HEADER_SIZE)
    }

    pub fn signature(&self) -> &Signature {
        let sign_idx = self.raw.len() - SIGNATURE_LENGTH;
        unsafe { mem::transmute(&self.raw[sign_idx]) }
    }

    pub fn verify(&self, pub_key: &PublicKey) -> bool {
        verify(self.signature(), self.body(), pub_key)
    }
}

impl convert::AsRef<[u8]> for MessageBuffer {
    fn as_ref(&self) -> &[u8] {
        &self.raw
    }
}

#[derive(Debug, PartialEq)]
pub struct MessageWriter {
    raw: Vec<u8>,
}

impl MessageWriter {
    pub fn new(message_type: u16, payload_length: usize) -> MessageWriter {
        let mut raw = MessageWriter { raw: vec![0; HEADER_SIZE + payload_length] };
        raw.set_network_id(TEST_NETWORK_ID);
        raw.set_version(PROTOCOL_MAJOR_VERSION);
        raw.set_message_type(message_type);
        raw
    }

    fn set_network_id(&mut self, network_id: u8) {
        self.raw[0] = network_id
    }

    fn set_version(&mut self, version: u8) {
        self.raw[1] = version
    }

    fn set_message_type(&mut self, message_type: u16) {
        LittleEndian::write_u16(&mut self.raw[2..4], message_type)
    }

    fn set_payload_length(&mut self, length: usize) {
        LittleEndian::write_u32(&mut self.raw[4..8], length as u32)
    }

    pub fn write<'a, F: Field<'a>>(&'a mut self, field: F, from: usize, to: usize) {
        field.write(&mut self.raw, from + HEADER_SIZE, to + HEADER_SIZE);
    }

    pub fn sign(mut self, secret_key: &SecretKey) -> MessageBuffer {
        let payload_length = self.raw.len() + SIGNATURE_LENGTH;
        self.set_payload_length(payload_length);
        let signature = sign(&self.raw, secret_key);
        self.raw.extend_from_slice(signature.as_ref());
        MessageBuffer { raw: self.raw }
    }
}

pub trait Message: Debug + Clone + PartialEq + Sized + Send {
    fn raw(&self) -> &RawMessage;
    fn from_raw(raw: RawMessage) -> Result<Self, Error>;

    fn hash(&self) -> Hash {
        hash(self.raw().as_ref().as_ref())
    }

    fn verify(&self, pub_key: &PublicKey) -> bool {
        self.raw().verify(pub_key)
    }
}

// #[test]
// fn test_empty_message() {
//     let raw = MessageBuffer::empty();
//     assert_eq!(raw.network_id(), 0);
//     assert_eq!(raw.version(), 0);
//     assert_eq!(raw.message_type(), 0);
//     assert_eq!(raw.payload_length(), 0);
// }

// #[test]
// fn test_as_mut() {
//     let mut raw = MessageBuffer::empty();
//     {
//         let bytes = raw.as_mut();
//         bytes[0] = 1;
//         bytes[1] = 2;
//         bytes[2] = 3;
//         bytes[3] = 0;
//         bytes[4] = 5;
//         bytes[5] = 6;
//     }
//     assert_eq!(raw.network_id(), 1);
//     assert_eq!(raw.version(), 2);
//     assert_eq!(raw.message_type(), 3);
//     assert_eq!(raw.payload_length(), 1541);
// }
<|endoftext|>


<commit_before>extern mod win32;

use win32::window::*;
use win32::ll::*;

struct MainWindow {
    raw: HWND,
}

impl win32::window::Window for MainWindow {
    fn hwnd(&self) -> HWND {
        self.raw
    }
}

fn main() {
    let non = win32::window::null();
    non.message_box("Hello World", "hello world");
}
<commit_msg>Update Hello World example<commit_after>extern mod win32;

use std::ptr;
use std::os::win32::as_utf16_p;
use win32::window::*;
use win32::ll::*;

struct MainWindow {
    raw: HWND,
    title: ~str,
}

impl win32::window::Window for MainWindow {
    fn hwnd(&self) -> HWND {
        self.raw
    }
}

extern "stdcall" fn main_wnd_proc(hwnd: HWND, msg: UINT, w: WPARAM, l: LPARAM) -> LRESULT {
    debug!("main_wnd_proc: hwnd %? / msg 0x%x / w %? / l %?", hwnd, msg as uint, w, l);

    if msg == 0x0001 { // WM_CREATE
        return 0 as LRESULT;
    }
    if msg == 0x0002 { // WM_DESTROY
        unsafe {
            user32::PostQuitMessage(0 as c_int);
        }
        return 0 as LRESULT;
    }
    if msg == 0x000F { // WM_PAINT
        let rgb_res: [BYTE, ..32] = [0 as BYTE, ..32];
        let ps = PAINTSTRUCT {
            hdc: ptr::null(),
            fErase: 0 as BOOL,
            rcPaint: RECT {
                left: 0 as LONG, top: 0 as LONG,
                right: 0 as LONG, bottom: 0 as LONG
            },
            fRestore: 0 as BOOL,
            fIncUpdate: 0 as BOOL,
            rgbReserved: &rgb_res,
        };
        unsafe {
            let dc = user32::BeginPaint(hwnd, &ps);

            let hello = "hello world";
            let mut hello_p = hello.to_utf16();
            hello_p.push(0u16);
            do std::vec::as_mut_buf(hello_p) |buf, len| {
                let len = len - 1;
                gdi32::TextOutW(dc, 0 as c_int, 0 as c_int, buf, len as i32);
            }
            user32::EndPaint(hwnd, &ps);
        }

        return 0 as LRESULT;
    }
    unsafe { user32::DefWindowProcW(hwnd, msg, w, l) }
}

impl MainWindow {
    fn register(instance: HINSTANCE, clsname: &str) {
        do as_utf16_p(clsname) |clsname_p| {
            let wcex = WNDCLASSEX {
                cbSize: std::sys::size_of::<WNDCLASSEX>() as UINT,
                style: 0x0001 | 0x0002, // CS_HREDRAW | CS_VREDRAW
                lpfnWndProc: main_wnd_proc,
                cbClsExtra: 0,
                cbWndExtra: 0,
                hInstance: instance,
                hIcon: ptr::null(),
                hCursor: ptr::null(),
                hbrBackground: (5 + 1) as HBRUSH,
                lpszMenuName: ptr::null(),
                lpszClassName: clsname_p,
                hIconSm: ptr::null(),
            };

            unsafe {
                let _res = user32::RegisterClassExW(&wcex);
            }
        }
    }

    fn new(instance: HINSTANCE) -> MainWindow {
        let clsname = "MainWindow";
        MainWindow::register(instance, clsname);

        let mut window = MainWindow {
            raw: ptr::null(),
            title: ~"Hello",
        };

        let WS_OVERLAPPED = 0x00000000u32;
        let WS_CAPTION = 0x00C00000u32;
        let WS_SYSMENU = 0x00080000u32;
        let WS_THICKFRAME = 0x00040000u32;
        let WS_MINIMIZEBOX = 0x00020000u32;
        let WS_MAXIMIZEBOX = 0x00010000u32;
        let WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU |
                WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;

        do as_utf16_p(clsname) |clsname_p| {
            do as_utf16_p(window.title) |title_p| {
                unsafe {
                    let hwnd = user32::CreateWindowExW(
                        0, clsname_p, title_p, WS_OVERLAPPEDWINDOW as DWORD,
                        0 as c_int, 0 as c_int, 400 as c_int, 400 as c_int,
                        ptr::null(), ptr::null(), instance,
                        ptr::null::<*c_void>() as *mut c_void
                    );
                    window.raw = hwnd;
                }
            }
        };

        window
    }
}

fn main() {
    let instance = unsafe {
        kernel32::GetModuleHandleW(ptr::null()) as HINSTANCE
    };
    let main = MainWindow::new(instance);

    main.show(1);
    main.update();

    let msg = MSG {
        hwnd: ptr::null(),
        message: 0 as UINT,
        wParam: 0 as WPARAM,
        lParam: 0 as LPARAM,
        time: 0 as DWORD,
        pt: POINT { x: 0 as LONG, y: 0 as LONG },
    };
    loop {
        let ret = unsafe {
            user32::GetMessageW(&msg as *MSG, ptr::null(),
                    0 as UINT, 0 as UINT)
        };

        if ret == 0 {
            let exit_code = msg.wParam;
            std::os::set_exit_status(exit_code as int);
            return;
        }
        else {
            unsafe {
                user32::TranslateMessage(&msg as *MSG);
                user32::DispatchMessageW(&msg as *MSG);
            }
        }
    }
}
<|endoftext|>


<commit_before>use std::io::{BufRead, BufReader};
use std::io;
use std::io::prelude::*;
use std::iter;
use std::str;

pub enum TokenKind {
    Identifier,
    IntNumber,
    FloatNumber,
    String,
    Char,
    Symbol,
    Newline,
    End,
}

pub struct Token {
    pub kind: TokenKind,
    pub val: String,
    pub line: i32,
}

impl Token {
    pub fn new(kind: TokenKind, val: &str, line: i32) -> Token {
        Token {
            kind: kind,
            val: val.to_string(),
            line: line,
        }
    }
}

pub struct Lexer<'a> {
    cur_line: i32,
    filename: String,
    peek: iter::Peekable<str::Chars<'a>>,
    peek_pos: usize,
}

impl<'a> Lexer<'a> {
    pub fn new(filename: String, input: &'a str) -> Lexer<'a> {
        let l = Lexer {
            cur_line: 0,
            filename: filename.to_string(),
            peek: input.chars().peekable(),
            peek_pos: 0,
        };
        l
    }
    pub fn get_filename(self) -> String {
        self.filename
    }

    fn peek_next(&mut self) -> char {
        self.peek_pos += 1;
        self.peek.next().unwrap()
    }

    pub fn read_identifier(&mut self) -> Token {
        let mut ident = String::new();
        loop {
            match self.peek.peek() {
                Some(&c) => {
                    match c {
                        'a'...'z' | 'A'...'Z' | '_' | '0'...'9' => ident.push(c),
                        _ => break,
                    }
                }
                _ => break,
            };
            self.peek_next();
        }
        Token::new(TokenKind::Identifier, ident.as_str(), self.cur_line)
    }
    pub fn read_newline(&mut self) -> Token {
        self.peek_next();
        Token::new(TokenKind::Newline, "", self.cur_line)
    }
    pub fn read_symbol(&mut self) -> Token {
        let c = self.peek_next();
        let mut sym = String::new();
        sym.push(c);
        Token::new(TokenKind::Symbol, sym.as_str(), self.cur_line)
    }

    pub fn read_token(&mut self) -> Option<Token> {
        match self.peek.peek() {
            Some(&c) => {
                match c {
                    'a'...'z' | 'A'...'Z' | '_' => Some(self.read_identifier()),
                    ' ' | '\t' => {
                        self.peek_next();
                        self.read_token()
                    }
                    '\n' => Some(self.read_newline()),
                    _ => Some(self.read_symbol()),
                }
            }
            None => None as Option<Token>,
        }
    }
}
<commit_msg>refine code, add next_char_is and anything related to it<commit_after>use std::io::{BufRead, BufReader};
use std::io;
use std::io::prelude::*;
use std::iter;
use std::str;
use std::collections::VecDeque;

pub enum TokenKind {
    Identifier,
    IntNumber,
    FloatNumber,
    String,
    Char,
    Symbol,
    Newline,
    End,
}

pub struct Token {
    pub kind: TokenKind,
    pub val: String,
    pub line: i32,
}

impl Token {
    pub fn new(kind: TokenKind, val: &str, line: i32) -> Token {
        Token {
            kind: kind,
            val: val.to_string(),
            line: line,
        }
    }
}

pub struct Lexer<'a> {
    cur_line: i32,
    filename: String,
    peek: iter::Peekable<str::Chars<'a>>,
    buf: VecDeque<char>,
}

impl<'a> Lexer<'a> {
    pub fn new(filename: String, input: &'a str) -> Lexer<'a> {
        Lexer {
            cur_line: 0,
            filename: filename.to_string(),
            peek: input.chars().peekable(),
            buf: VecDeque::new(),
        }
    }
    pub fn get_filename(self) -> String {
        self.filename
    }

    fn peek_get(&mut self) -> Option<&char> {
        if self.buf.len() > 0 {
            self.buf.front()
        } else {
            self.peek.peek()
        }
    }
    fn peek_next(&mut self) -> char {
        if self.buf.len() > 0 {
            self.buf.pop_front().unwrap()
        } else {
            self.peek.next().unwrap()
        }
    }
    fn peek_unget(&mut self, ch: char) {
        self.buf.push_back(ch);
    }
    fn peek_next_char_is(&mut self, ch: char) -> bool {
        let nextc = self.peek_next();
        nextc == ch
    }

    pub fn read_identifier(&mut self) -> Token {
        let mut ident = String::new();
        loop {
            match self.peek_get() {
                Some(&c) => {
                    match c {
                        'a'...'z' | 'A'...'Z' | '_' | '0'...'9' => ident.push(c),
                        _ => break,
                    }
                }
                _ => break,
            };
            self.peek_next();
        }
        Token::new(TokenKind::Identifier, ident.as_str(), self.cur_line)
    }
    pub fn read_newline(&mut self) -> Token {
        self.peek_next();
        Token::new(TokenKind::Newline, "", self.cur_line)
    }
    pub fn read_symbol(&mut self) -> Token {
        let c = self.peek_next();
        let mut sym = String::new();
        sym.push(c);
        Token::new(TokenKind::Symbol, sym.as_str(), self.cur_line)
    }

    pub fn read_token(&mut self) -> Option<Token> {
        match self.peek_get() {
            Some(&c) => {
                match c {
                    'a'...'z' | 'A'...'Z' | '_' => Some(self.read_identifier()),
                    ' ' | '\t' => {
                        self.peek_next();
                        self.read_token()
                    }
                    '\n' => Some(self.read_newline()),
                    _ => Some(self.read_symbol()),
                }
            }
            None => None as Option<Token>,
        }
    }
}
<|endoftext|>