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

use app_dirs::{
    AppDataType,
    AppInfo,
    app_dir,
};
use cache::disk::DiskCache;
#[cfg(feature = "redis")]
use cache::redis::RedisCache;
#[cfg(feature = "s3")]
use cache::s3::S3Cache;
#[cfg(feature = "gcs")]
use cache::gcs::{self, GCSCache, GCSCredentialProvider, RWMode};
use futures_cpupool::CpuPool;
use regex::Regex;
#[cfg(feature = "gcs")]
use serde_json;
use std::env;
use std::fmt;
use std::io::{
    self,
    Read,
    Seek,
    Write,
};
#[cfg(feature = "gcs")]
use std::fs::File;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;
use tokio_core::reactor::Handle;
use zip::{CompressionMethod, ZipArchive, ZipWriter};
use zip::write::FileOptions;

use errors::*;

//TODO: might need to put this somewhere more central
const APP_INFO: AppInfo = AppInfo {
    name: "sccache",
    author: "Mozilla",
};

const TEN_GIGS: usize = 10 * 1024 * 1024 * 1024;

/// Result of a cache lookup.
pub enum Cache {
    /// Result was found in cache.
    Hit(CacheRead),
    /// Result was not found in cache.
    Miss,
    /// Cache entry should be ignored, force compilation.
    Recache,
}

impl fmt::Debug for Cache {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Cache::Hit(_) => write!(f, "Cache::Hit(...)"),
            Cache::Miss => write!(f, "Cache::Miss"),
            Cache::Recache => write!(f, "Cache::Recache"),
        }
    }
}

/// Trait objects can't be bounded by more than one non-builtin trait.
pub trait ReadSeek : Read + Seek + Send {}

impl<T: Read + Seek + Send> ReadSeek for T {}

/// Data stored in the compiler cache.
pub struct CacheRead {
    zip: ZipArchive<Box<ReadSeek>>,
}

impl CacheRead {
    /// Create a cache entry from `reader`.
    pub fn from<R>(reader: R) -> Result<CacheRead>
        where R: ReadSeek + 'static,
    {
        let z = ZipArchive::new(Box::new(reader) as Box<ReadSeek>).chain_err(|| {
            "Failed to parse cache entry"
        })?;
        Ok(CacheRead {
            zip: z,
        })
    }

    /// Get an object from this cache entry at `name` and write it to `to`.
    /// If the file has stored permissions, return them.
    pub fn get_object<T>(&mut self, name: &str, to: &mut T) -> Result<Option<u32>>
        where T: Write,
    {
        let mut file = self.zip.by_name(name).chain_err(|| {
            "Failed to read object from cache entry"
        })?;
        io::copy(&mut file, to)?;
        Ok(file.unix_mode())
    }
}

/// Data to be stored in the compiler cache.
pub struct CacheWrite {
    zip: ZipWriter<io::Cursor<Vec<u8>>>,
}

impl CacheWrite {
    /// Create a new, empty cache entry.
    pub fn new() -> CacheWrite
    {
        CacheWrite {
            zip: ZipWriter::new(io::Cursor::new(vec!())),
        }
    }

    /// Add an object containing the contents of `from` to this cache entry at `name`.
    /// If `mode` is `Some`, store the file entry with that mode.
    pub fn put_object<T>(&mut self, name: &str, from: &mut T, mode: Option<u32>) -> Result<()>
        where T: Read,
    {
        let opts = FileOptions::default().compression_method(CompressionMethod::Deflated);
        let opts = if let Some(mode) = mode { opts.unix_permissions(mode) } else { opts };
        self.zip.start_file(name, opts).chain_err(|| {
            "Failed to start cache entry object"
        })?;
        io::copy(from, &mut self.zip)?;
        Ok(())
    }

    /// Finish writing data to the cache entry writer, and return the data.
    pub fn finish(self) -> Result<Vec<u8>>
    {
        let CacheWrite { mut zip } = self;
        let cur = zip.finish().chain_err(|| "Failed to finish cache entry zip")?;
        Ok(cur.into_inner())
    }
}

/// An interface to cache storage.
pub trait Storage {
    /// Get a cache entry by `key`.
    ///
    /// If an error occurs, this method should return a `Cache::Error`.
    /// If nothing fails but the entry is not found in the cache,
    /// it should return a `Cache::Miss`.
    /// If the entry is successfully found in the cache, it should
    /// return a `Cache::Hit`.
    fn get(&self, key: &str) -> SFuture<Cache>;

    /// Put `entry` in the cache under `key`.
    ///
    /// Returns a `Future` that will provide the result or error when the put is
    /// finished.
    fn put(&self, key: &str, entry: CacheWrite) -> SFuture<Duration>;

    /// Get the storage location.
    fn location(&self) -> String;

    /// Get the current storage usage, if applicable.
    fn current_size(&self) -> Option<usize>;

    /// Get the maximum storage size, if applicable.
    fn max_size(&self) -> Option<usize>;
}

fn parse_size(val: &str) -> Option<usize> {
    let re = Regex::new(r"^(\d+)([KMGT])$").unwrap();
    re.captures(val)
        .and_then(|caps| {
            caps.get(1)
                .and_then(|size| usize::from_str(size.as_str()).ok())
                .and_then(|size| Some((size, caps.get(2))))
        })
        .and_then(|(size, suffix)| {
            match suffix.map(|s| s.as_str()) {
                Some("K") => Some(1024 * size),
                Some("M") => Some(1024 * 1024 * size),
                Some("G") => Some(1024 * 1024 * 1024 * size),
                Some("T") => Some(1024 * 1024 * 1024 * 1024 * size),
                _ => None,
            }
        })
}

/// Get a suitable `Storage` implementation from the environment.
pub fn storage_from_environment(pool: &CpuPool, _handle: &Handle) -> Arc<Storage> {
    if cfg!(feature = "s3") {
        if let Ok(bucket) = env::var("SCCACHE_BUCKET") {
            let endpoint = match env::var("SCCACHE_ENDPOINT") {
                Ok(endpoint) => format!("{}/{}", endpoint, bucket),
                _ => match env::var("SCCACHE_REGION") {
                    Ok(ref region) if region != "us-east-1" =>
                        format!("{}.s3-{}.amazonaws.com", bucket, region),
                    _ => format!("{}.s3.amazonaws.com", bucket),
                },
            };
            debug!("Trying S3Cache({})", endpoint);
            #[cfg(feature = "s3")]
            match S3Cache::new(&bucket, &endpoint, _handle) {
                Ok(s) => {
                    trace!("Using S3Cache");
                    return Arc::new(s);
                }
                Err(e) => warn!("Failed to create S3Cache: {:?}", e),
            }
        }
    }

    if cfg!(feature = "redis") {
        if let Ok(url) = env::var("SCCACHE_REDIS") {
            debug!("Trying Redis({})", url);
            #[cfg(feature = "redis")]
            match RedisCache::new(&url, pool) {
                Ok(s) => {
                    trace!("Using Redis: {}", url);
                    return Arc::new(s);
                }
                Err(e) => warn!("Failed to create RedisCache: {:?}", e),
            }
        }
    }

    if cfg!(feature = "gcs") {
        if let Ok(bucket) = env::var("SCCACHE_GCS_BUCKET")
        {
            debug!("Trying GCS bucket({})", bucket);
            #[cfg(feature = "gcs")]
            {
                let cred_path_res = env::var("SCCACHE_GCS_KEY_PATH");
                if cred_path_res.is_err() {
                    warn!("No SCCACHE_GCS_KEY_PATH specified-- no authentication will be used.");
                }

                let service_account_key_opt: Option<gcs::ServiceAccountKey> =
                    if let Ok(cred_path) = cred_path_res
                {
                    // Attempt to read the service account key from file
                    let service_account_key_res: Result<gcs::ServiceAccountKey> =
                        File::open(&cred_path)
                            .map_err(Into::into)
                            .and_then(|mut file| {
                                let mut service_account_json = String::new();
                                file.read_to_string(&mut service_account_json)?;
                                Ok(service_account_json)
                            }).and_then(|service_account_json|
                                serde_json::from_str(&service_account_json).map_err(Into::into)
                            );

                    // warn! if an error was encountered reading the key from the file
                    if let Err(ref e) = service_account_key_res {
                        warn!("Failed to parse service account credentials from file: {:?}. \
                            Continuing without authentication.", e);
                    }

                    service_account_key_res.ok()
                } else { None };

                let gcs_read_write_mode = match env::var("SCCACHE_GCS_RW_MODE")
                                          .as_ref().map(String::as_str)
                {
                    Ok("READ_ONLY") => RWMode::ReadOnly,
                    Ok("READ_WRITE") => RWMode::ReadWrite,
                    Ok(_) => {
                        warn!("Invalid SCCACHE_GCS_RW_MODE-- defaulting to READ_ONLY.");
                        RWMode::ReadOnly
                    },
                    _ => {
                        warn!("No SCCACHE_GCS_RW_MODE specified-- defaulting to READ_ONLY.");
                        RWMode::ReadOnly
                    }
                };

                let gcs_cred_provider =
                    service_account_key_opt.map(|path|
                        GCSCredentialProvider::new(gcs_read_write_mode, path));

                match GCSCache::new(bucket, gcs_cred_provider, gcs_read_write_mode, _handle) {
                    Ok(s) => {
                        trace!("Using GCSCache");
                        return Arc::new(s);
                    }
                    Err(e) => warn!("Failed to create GCS Cache: {:?}", e),
                }
            }
        }
    }

    let d = env::var_os("SCCACHE_DIR")
        .map(|p| PathBuf::from(p))
        .or_else(|| app_dir(AppDataType::UserCache, &APP_INFO, "").ok())
        // Fall back to something, even if it's not very good.
        .unwrap_or(env::temp_dir().join("sccache_cache"));
    trace!("Using DiskCache({:?})", d);
    let cache_size = env::var("SCCACHE_CACHE_SIZE")
        .ok()
        .and_then(|v| parse_size(&v))
        .unwrap_or(TEN_GIGS);
    trace!("DiskCache size: {}", cache_size);
    Arc::new(DiskCache::new(&d, cache_size, pool))
}

#[test]
fn test_parse_size() {
    assert_eq!(None, parse_size(""));
    assert_eq!(None, parse_size("100"));
    assert_eq!(Some(2048), parse_size("2K"));
    assert_eq!(Some(10 * 1024 * 1024), parse_size("10M"));
    assert_eq!(Some(TEN_GIGS), parse_size("10G"));
    assert_eq!(Some(1024 * TEN_GIGS), parse_size("10T"));
}
<commit_msg>Cleanup GCS service account key error handling<commit_after>// Copyright 2016 Mozilla Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use app_dirs::{
    AppDataType,
    AppInfo,
    app_dir,
};
use cache::disk::DiskCache;
#[cfg(feature = "redis")]
use cache::redis::RedisCache;
#[cfg(feature = "s3")]
use cache::s3::S3Cache;
#[cfg(feature = "gcs")]
use cache::gcs::{self, GCSCache, GCSCredentialProvider, RWMode};
use futures_cpupool::CpuPool;
use regex::Regex;
#[cfg(feature = "gcs")]
use serde_json;
use std::env;
use std::fmt;
use std::io::{
    self,
    Read,
    Seek,
    Write,
};
#[cfg(feature = "gcs")]
use std::fs::File;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;
use tokio_core::reactor::Handle;
use zip::{CompressionMethod, ZipArchive, ZipWriter};
use zip::write::FileOptions;

use errors::*;

//TODO: might need to put this somewhere more central
const APP_INFO: AppInfo = AppInfo {
    name: "sccache",
    author: "Mozilla",
};

const TEN_GIGS: usize = 10 * 1024 * 1024 * 1024;

/// Result of a cache lookup.
pub enum Cache {
    /// Result was found in cache.
    Hit(CacheRead),
    /// Result was not found in cache.
    Miss,
    /// Cache entry should be ignored, force compilation.
    Recache,
}

impl fmt::Debug for Cache {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Cache::Hit(_) => write!(f, "Cache::Hit(...)"),
            Cache::Miss => write!(f, "Cache::Miss"),
            Cache::Recache => write!(f, "Cache::Recache"),
        }
    }
}

/// Trait objects can't be bounded by more than one non-builtin trait.
pub trait ReadSeek : Read + Seek + Send {}

impl<T: Read + Seek + Send> ReadSeek for T {}

/// Data stored in the compiler cache.
pub struct CacheRead {
    zip: ZipArchive<Box<ReadSeek>>,
}

impl CacheRead {
    /// Create a cache entry from `reader`.
    pub fn from<R>(reader: R) -> Result<CacheRead>
        where R: ReadSeek + 'static,
    {
        let z = ZipArchive::new(Box::new(reader) as Box<ReadSeek>).chain_err(|| {
            "Failed to parse cache entry"
        })?;
        Ok(CacheRead {
            zip: z,
        })
    }

    /// Get an object from this cache entry at `name` and write it to `to`.
    /// If the file has stored permissions, return them.
    pub fn get_object<T>(&mut self, name: &str, to: &mut T) -> Result<Option<u32>>
        where T: Write,
    {
        let mut file = self.zip.by_name(name).chain_err(|| {
            "Failed to read object from cache entry"
        })?;
        io::copy(&mut file, to)?;
        Ok(file.unix_mode())
    }
}

/// Data to be stored in the compiler cache.
pub struct CacheWrite {
    zip: ZipWriter<io::Cursor<Vec<u8>>>,
}

impl CacheWrite {
    /// Create a new, empty cache entry.
    pub fn new() -> CacheWrite
    {
        CacheWrite {
            zip: ZipWriter::new(io::Cursor::new(vec!())),
        }
    }

    /// Add an object containing the contents of `from` to this cache entry at `name`.
    /// If `mode` is `Some`, store the file entry with that mode.
    pub fn put_object<T>(&mut self, name: &str, from: &mut T, mode: Option<u32>) -> Result<()>
        where T: Read,
    {
        let opts = FileOptions::default().compression_method(CompressionMethod::Deflated);
        let opts = if let Some(mode) = mode { opts.unix_permissions(mode) } else { opts };
        self.zip.start_file(name, opts).chain_err(|| {
            "Failed to start cache entry object"
        })?;
        io::copy(from, &mut self.zip)?;
        Ok(())
    }

    /// Finish writing data to the cache entry writer, and return the data.
    pub fn finish(self) -> Result<Vec<u8>>
    {
        let CacheWrite { mut zip } = self;
        let cur = zip.finish().chain_err(|| "Failed to finish cache entry zip")?;
        Ok(cur.into_inner())
    }
}

/// An interface to cache storage.
pub trait Storage {
    /// Get a cache entry by `key`.
    ///
    /// If an error occurs, this method should return a `Cache::Error`.
    /// If nothing fails but the entry is not found in the cache,
    /// it should return a `Cache::Miss`.
    /// If the entry is successfully found in the cache, it should
    /// return a `Cache::Hit`.
    fn get(&self, key: &str) -> SFuture<Cache>;

    /// Put `entry` in the cache under `key`.
    ///
    /// Returns a `Future` that will provide the result or error when the put is
    /// finished.
    fn put(&self, key: &str, entry: CacheWrite) -> SFuture<Duration>;

    /// Get the storage location.
    fn location(&self) -> String;

    /// Get the current storage usage, if applicable.
    fn current_size(&self) -> Option<usize>;

    /// Get the maximum storage size, if applicable.
    fn max_size(&self) -> Option<usize>;
}

fn parse_size(val: &str) -> Option<usize> {
    let re = Regex::new(r"^(\d+)([KMGT])$").unwrap();
    re.captures(val)
        .and_then(|caps| {
            caps.get(1)
                .and_then(|size| usize::from_str(size.as_str()).ok())
                .and_then(|size| Some((size, caps.get(2))))
        })
        .and_then(|(size, suffix)| {
            match suffix.map(|s| s.as_str()) {
                Some("K") => Some(1024 * size),
                Some("M") => Some(1024 * 1024 * size),
                Some("G") => Some(1024 * 1024 * 1024 * size),
                Some("T") => Some(1024 * 1024 * 1024 * 1024 * size),
                _ => None,
            }
        })
}

/// Get a suitable `Storage` implementation from the environment.
pub fn storage_from_environment(pool: &CpuPool, _handle: &Handle) -> Arc<Storage> {
    if cfg!(feature = "s3") {
        if let Ok(bucket) = env::var("SCCACHE_BUCKET") {
            let endpoint = match env::var("SCCACHE_ENDPOINT") {
                Ok(endpoint) => format!("{}/{}", endpoint, bucket),
                _ => match env::var("SCCACHE_REGION") {
                    Ok(ref region) if region != "us-east-1" =>
                        format!("{}.s3-{}.amazonaws.com", bucket, region),
                    _ => format!("{}.s3.amazonaws.com", bucket),
                },
            };
            debug!("Trying S3Cache({})", endpoint);
            #[cfg(feature = "s3")]
            match S3Cache::new(&bucket, &endpoint, _handle) {
                Ok(s) => {
                    trace!("Using S3Cache");
                    return Arc::new(s);
                }
                Err(e) => warn!("Failed to create S3Cache: {:?}", e),
            }
        }
    }

    if cfg!(feature = "redis") {
        if let Ok(url) = env::var("SCCACHE_REDIS") {
            debug!("Trying Redis({})", url);
            #[cfg(feature = "redis")]
            match RedisCache::new(&url, pool) {
                Ok(s) => {
                    trace!("Using Redis: {}", url);
                    return Arc::new(s);
                }
                Err(e) => warn!("Failed to create RedisCache: {:?}", e),
            }
        }
    }

    if cfg!(feature = "gcs") {
        if let Ok(bucket) = env::var("SCCACHE_GCS_BUCKET")
        {
            debug!("Trying GCS bucket({})", bucket);
            #[cfg(feature = "gcs")]
            {
                let cred_path_res = env::var("SCCACHE_GCS_KEY_PATH");
                if cred_path_res.is_err() {
                    warn!("No SCCACHE_GCS_KEY_PATH specified-- no authentication will be used.");
                }

                let service_account_key_opt: Option<gcs::ServiceAccountKey> =
                    if let Ok(cred_path) = cred_path_res
                {
                    // Attempt to read the service account key from file
                    let service_account_key_res: Result<gcs::ServiceAccountKey> = (|| {
                        let mut file = File::open(&cred_path)?;
                        let mut service_account_json = String::new();
                        file.read_to_string(&mut service_account_json)?;
                        Ok(serde_json::from_str(&service_account_json)?)
                    })();

                    // warn! if an error was encountered reading the key from the file
                    if let Err(ref e) = service_account_key_res {
                        warn!("Failed to parse service account credentials from file: {:?}. \
                            Continuing without authentication.", e);
                    }

                    service_account_key_res.ok()
                } else { None };

                let gcs_read_write_mode = match env::var("SCCACHE_GCS_RW_MODE")
                                          .as_ref().map(String::as_str)
                {
                    Ok("READ_ONLY") => RWMode::ReadOnly,
                    Ok("READ_WRITE") => RWMode::ReadWrite,
                    Ok(_) => {
                        warn!("Invalid SCCACHE_GCS_RW_MODE-- defaulting to READ_ONLY.");
                        RWMode::ReadOnly
                    },
                    _ => {
                        warn!("No SCCACHE_GCS_RW_MODE specified-- defaulting to READ_ONLY.");
                        RWMode::ReadOnly
                    }
                };

                let gcs_cred_provider =
                    service_account_key_opt.map(|path|
                        GCSCredentialProvider::new(gcs_read_write_mode, path));

                match GCSCache::new(bucket, gcs_cred_provider, gcs_read_write_mode, _handle) {
                    Ok(s) => {
                        trace!("Using GCSCache");
                        return Arc::new(s);
                    }
                    Err(e) => warn!("Failed to create GCS Cache: {:?}", e),
                }
            }
        }
    }

    let d = env::var_os("SCCACHE_DIR")
        .map(|p| PathBuf::from(p))
        .or_else(|| app_dir(AppDataType::UserCache, &APP_INFO, "").ok())
        // Fall back to something, even if it's not very good.
        .unwrap_or(env::temp_dir().join("sccache_cache"));
    trace!("Using DiskCache({:?})", d);
    let cache_size = env::var("SCCACHE_CACHE_SIZE")
        .ok()
        .and_then(|v| parse_size(&v))
        .unwrap_or(TEN_GIGS);
    trace!("DiskCache size: {}", cache_size);
    Arc::new(DiskCache::new(&d, cache_size, pool))
}

#[test]
fn test_parse_size() {
    assert_eq!(None, parse_size(""));
    assert_eq!(None, parse_size("100"));
    assert_eq!(Some(2048), parse_size("2K"));
    assert_eq!(Some(10 * 1024 * 1024), parse_size("10M"));
    assert_eq!(Some(TEN_GIGS), parse_size("10G"));
    assert_eq!(Some(1024 * TEN_GIGS), parse_size("10T"));
}
<|endoftext|>


<commit_before>// Bloom
//
// HTTP REST API caching middleware
// Copyright: 2017, Valerian Saliou <valerian@valeriansaliou.name>
// License: Mozilla Public License v2.0 (MPL v2.0)

use hyper::{Error, Method, HttpVersion, StatusCode, Headers, Body};
use futures::{future, Future, Stream};
use farmhash;

use super::route::CacheRoute;
use super::check::CacheCheck;
use APP_CONF;
use APP_CACHE_STORE;
use header::janitor::HeaderJanitor;
use header::response_buckets::HeaderResponseBloomResponseBuckets;
use header::response_ttl::HeaderResponseBloomResponseTTL;

pub struct CacheWrite;

pub struct CacheWriteResult {
    pub body: Result<String, Option<String>>,
    pub fingerprint: Option<String>,
    pub status: StatusCode,
    pub headers: Headers,
}

pub type CacheWriteResultFuture = Box<Future<Item = CacheWriteResult, Error = Error>>;

impl CacheWrite {
    pub fn save(
        key: String,
        key_mask: String,
        auth_hash: String,
        shard: u8,
        method: Method,
        version: HttpVersion,
        status: StatusCode,
        mut headers: Headers,
        body: Body,
    ) -> CacheWriteResultFuture {
        Box::new(
            body.concat2()
                .map(|raw_data| String::from_utf8(raw_data.to_vec()))
                .and_then(move |body_result| {
                    if let Ok(body_value) = body_result {
                        // Clean headers before they get stored
                        HeaderJanitor::clean(&mut headers);

                        // Generate storable value
                        let value = format!(
                            "{}\n{}\n{}",
                            CacheWrite::generate_chain_banner(&version, &status),
                            CacheWrite::generate_chain_headers(&headers),
                            body_value
                        );

                        debug!("checking whether to write cache for key: {}", &key);

                        if APP_CONF.cache.disable_write == false &&
                            CacheCheck::from_response(&method, &status, &headers) == true
                        {
                            debug!("key: {} cacheable, writing cache", &key);

                            // Acquire bucket from response, or fallback to no bucket
                            let mut key_tags =
                                match headers.get::<HeaderResponseBloomResponseBuckets>() {
                                    None => Vec::new(),
                                    Some(value) => {
                                        value
                                            .0
                                            .iter()
                                            .map(|value| {
                                                CacheRoute::gen_key_bucket_from_hash(
                                                    shard,
                                                    &CacheRoute::hash(value),
                                                )
                                            })
                                            .collect::<Vec<String>>()
                                    }
                                };

                            key_tags.push(CacheRoute::gen_key_auth_from_hash(shard, &auth_hash));

                            // Acquire TTL from response, or fallback to default TTL
                            let ttl = match headers.get::<HeaderResponseBloomResponseTTL>() {
                                None => APP_CONF.cache.ttl_default,
                                Some(value) => value.0,
                            };

                            // Process value fingerprint
                            let fingerprint = Self::process_body_fingerprint(&value);

                            // Write to cache
                            Box::new(
                                APP_CACHE_STORE
                                    .set(key, key_mask, value, fingerprint, ttl, key_tags)
                                    .or_else(|_| Err(Error::Incomplete))
                                    .and_then(move |result| {
                                        future::ok(match result {
                                            Ok(fingerprint) => {
                                                debug!("wrote cache");

                                                CacheWriteResult {
                                                    body: Ok(body_value),
                                                    fingerprint: Some(fingerprint),
                                                    status: status,
                                                    headers: headers,
                                                }
                                            }
                                            Err(forward) => {
                                                warn!(
                                                    "could not write cache because: {:?}",
                                                    forward.0
                                                );

                                                CacheWriteResult {
                                                    body: Err(Some(body_value)),
                                                    fingerprint: Some(forward.1),
                                                    status: status,
                                                    headers: headers,
                                                }
                                            }
                                        })
                                    }),
                            )
                        } else {
                            debug!("key: {} not cacheable, ignoring", &key);

                            // Not cacheable, ignore
                            Self::result_cache_write_error(
                                Some(body_value),
                                Some(value),
                                status,
                                headers,
                            )
                        }
                    } else {
                        error!("failed unwrapping body value for key: {}, ignoring", &key);

                        Self::result_cache_write_error(None, None, status, headers)
                    }
                }),
        )
    }

    fn generate_chain_banner(version: &HttpVersion, status: &StatusCode) -> String {
        format!("{} {}", version, status)
    }

    fn generate_chain_headers(headers: &Headers) -> String {
        headers
            .iter()
            .filter(|header_view| {
                HeaderJanitor::is_contextual(&header_view) == false
            })
            .map(|header_view| {
                format!("{}: {}\n", header_view.name(), header_view.value_string())
            })
            .collect()
    }

    fn process_body_fingerprint(body_string: &str) -> String {
        format!("{:x}", farmhash::fingerprint64(body_string.as_bytes()))
    }

    fn result_cache_write_error(
        body: Option<String>,
        value: Option<String>,
        status: StatusCode,
        headers: Headers,
    ) -> CacheWriteResultFuture {
        Box::new(future::ok(CacheWriteResult {
            body: Err(body),
            fingerprint: value,
            status: status,
            headers: headers,
        }))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[should_panic]
    fn it_fails_saving_cache() {
        assert!(
            CacheWrite::save(
                "bloom:0:c:90d52bc6:f773d6f1".to_string(),
                "90d52bc6:f773d6f1".to_string(),
                "90d52bc6".to_string(),
                0,
                Method::Get,
                HttpVersion::Http11,
                StatusCode::Ok,
                Headers::new(),
                Body::empty(),
            ).poll()
                .is_err()
        );
    }
}
<commit_msg>Fix<commit_after>// Bloom
//
// HTTP REST API caching middleware
// Copyright: 2017, Valerian Saliou <valerian@valeriansaliou.name>
// License: Mozilla Public License v2.0 (MPL v2.0)

use hyper::{Error, Method, HttpVersion, StatusCode, Headers, Body};
use futures::{future, Future, Stream};
use farmhash;

use super::route::CacheRoute;
use super::check::CacheCheck;
use APP_CONF;
use APP_CACHE_STORE;
use header::janitor::HeaderJanitor;
use header::response_buckets::HeaderResponseBloomResponseBuckets;
use header::response_ttl::HeaderResponseBloomResponseTTL;

pub struct CacheWrite;

pub struct CacheWriteResult {
    pub body: Result<String, Option<String>>,
    pub fingerprint: Option<String>,
    pub status: StatusCode,
    pub headers: Headers,
}

pub type CacheWriteResultFuture = Box<Future<Item = CacheWriteResult, Error = Error>>;

impl CacheWrite {
    pub fn save(
        key: String,
        key_mask: String,
        auth_hash: String,
        shard: u8,
        method: Method,
        version: HttpVersion,
        status: StatusCode,
        mut headers: Headers,
        body: Body,
    ) -> CacheWriteResultFuture {
        Box::new(
            body.concat2()
                .map(|raw_data| String::from_utf8(raw_data.to_vec()))
                .and_then(move |body_result| {
                    if let Ok(body_value) = body_result {
                        // Clean headers before they get stored
                        HeaderJanitor::clean(&mut headers);

                        // Generate storable value
                        let value = format!(
                            "{}\n{}\n{}",
                            CacheWrite::generate_chain_banner(&version, &status),
                            CacheWrite::generate_chain_headers(&headers),
                            body_value
                        );

                        debug!("checking whether to write cache for key: {}", &key);

                        if APP_CONF.cache.disable_write == false &&
                            CacheCheck::from_response(&method, &status, &headers) == true
                        {
                            debug!("key: {} cacheable, writing cache", &key);

                            // Acquire bucket from response, or fallback to no bucket
                            let mut key_tags =
                                match headers.get::<HeaderResponseBloomResponseBuckets>() {
                                    None => Vec::new(),
                                    Some(value) => {
                                        value
                                            .0
                                            .iter()
                                            .map(|value| {
                                                CacheRoute::gen_key_bucket_from_hash(
                                                    shard,
                                                    &CacheRoute::hash(value),
                                                )
                                            })
                                            .collect::<Vec<String>>()
                                    }
                                };

                            key_tags.push(CacheRoute::gen_key_auth_from_hash(shard, &auth_hash));

                            // Acquire TTL from response, or fallback to default TTL
                            let ttl = match headers.get::<HeaderResponseBloomResponseTTL>() {
                                None => APP_CONF.cache.ttl_default,
                                Some(value) => value.0,
                            };

                            // Process value fingerprint
                            let fingerprint = Self::process_body_fingerprint(&value);

                            // Write to cache
                            Box::new(
                                APP_CACHE_STORE
                                    .set(key, key_mask, value, fingerprint, ttl, key_tags)
                                    .or_else(|_| Err(Error::Incomplete))
                                    .and_then(move |result| {
                                        future::ok(match result {
                                            Ok(fingerprint) => {
                                                debug!("wrote cache");

                                                CacheWriteResult {
                                                    body: Ok(body_value),
                                                    fingerprint: Some(fingerprint),
                                                    status: status,
                                                    headers: headers,
                                                }
                                            }
                                            Err(forward) => {
                                                warn!(
                                                    "could not write cache because: {:?}",
                                                    forward.0
                                                );

                                                CacheWriteResult {
                                                    body: Err(Some(body_value)),
                                                    fingerprint: Some(forward.1),
                                                    status: status,
                                                    headers: headers,
                                                }
                                            }
                                        })
                                    }),
                            )
                        } else {
                            debug!("key: {} not cacheable, ignoring", &key);

                            // Not cacheable, ignore
                            Self::result_cache_write_error(
                                Some(body_value),
                                status,
                                headers,
                            )
                        }
                    } else {
                        error!("failed unwrapping body value for key: {}, ignoring", &key);

                        Self::result_cache_write_error(None, status, headers)
                    }
                }),
        )
    }

    fn generate_chain_banner(version: &HttpVersion, status: &StatusCode) -> String {
        format!("{} {}", version, status)
    }

    fn generate_chain_headers(headers: &Headers) -> String {
        headers
            .iter()
            .filter(|header_view| {
                HeaderJanitor::is_contextual(&header_view) == false
            })
            .map(|header_view| {
                format!("{}: {}\n", header_view.name(), header_view.value_string())
            })
            .collect()
    }

    fn process_body_fingerprint(body_string: &str) -> String {
        format!("{:x}", farmhash::fingerprint64(body_string.as_bytes()))
    }

    fn result_cache_write_error(
        body: Option<String>,
        status: StatusCode,
        headers: Headers,
    ) -> CacheWriteResultFuture {
        Box::new(future::ok(CacheWriteResult {
            body: Err(body),
            fingerprint: None,
            status: status,
            headers: headers,
        }))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[should_panic]
    fn it_fails_saving_cache() {
        assert!(
            CacheWrite::save(
                "bloom:0:c:90d52bc6:f773d6f1".to_string(),
                "90d52bc6:f773d6f1".to_string(),
                "90d52bc6".to_string(),
                0,
                Method::Get,
                HttpVersion::Http11,
                StatusCode::Ok,
                Headers::new(),
                Body::empty(),
            ).poll()
                .is_err()
        );
    }
}
<|endoftext|>


<commit_before>// cargo.rs - Rust package manager

import rustc::syntax::{ast, codemap, visit};
import rustc::syntax::parse::parser;

import std::fs;
import std::generic_os;
import std::io;
import std::json;
import option;
import option::{none, some};
import std::os;
import std::run;
import str;
import std::tempfile;
import vec;

type cargo = {
    root: str,
    bindir: str,
    libdir: str,
    workdir: str,
    fetchdir: str
};

type pkg = {
    name: str,
    vers: str,
    uuid: str,
    desc: option::t<str>,
    sigs: option::t<str>,
    crate_type: option::t<str>
};

fn load_link(mis: [@ast::meta_item]) -> (option::t<str>,
                                         option::t<str>,
                                         option::t<str>) {
    let name = none;
    let vers = none;
    let uuid = none;
    for a: @ast::meta_item in mis {
        alt a.node {
            ast::meta_name_value(v, {node: ast::lit_str(s), span: _}) {
                alt v {
                    "name" { name = some(s); }
                    "vers" { vers = some(s); }
                    "uuid" { uuid = some(s); }
                    _ { }
                }
            }
        }
    }
    (name, vers, uuid)
}

fn load_pkg(filename: str) -> option::t<pkg> {
    let sess = @{cm: codemap::new_codemap(), mutable next_id: 0};
    let c = parser::parse_crate_from_crate_file(filename, [], sess);

    let name = none;
    let vers = none;
    let uuid = none;
    let desc = none;
    let sigs = none;
    let crate_type = none;

    for a in c.node.attrs {
        alt a.node.value.node {
            ast::meta_name_value(v, {node: ast::lit_str(s), span: _}) {
                alt v {
                    "desc" { desc = some(v); }
                    "sigs" { sigs = some(v); }
                    "crate_type" { crate_type = some(v); }
                    _ { }
                }
            }
            ast::meta_list(v, mis) {
                if v == "link" {
                    let (n, v, u) = load_link(mis);
                    name = n;
                    vers = v;
                    uuid = u;
                }
            }
        }
    }

    alt (name, vers, uuid) {
        (some(name0), some(vers0), some(uuid0)) {
            some({
                name: name0,
                vers: vers0,
                uuid: uuid0,
                desc: desc,
                sigs: sigs,
                crate_type: crate_type})
        }
        _ { ret none; }
    }
}

fn print(s: str) {
    io::stdout().write_line(s);
}

fn rest(s: str, start: uint) -> str {
    if (start >= str::char_len(s)) {
        ""
    } else {
        str::char_slice(s, start, str::char_len(s))
    }
}

fn need_dir(s: str) {
    if fs::file_is_dir(s) { ret; }
    if !fs::make_dir(s, 0x1c0i32) {
        fail #fmt["can't make_dir %s", s];
    }
}

fn configure() -> cargo {
    let p = alt generic_os::getenv("CARGO_ROOT") {
        some(_p) { _p }
        none. {
            alt generic_os::getenv("HOME") {
                some(_q) { fs::connect(_q, ".cargo") }
                none. { fail "no CARGO_ROOT or HOME"; }
            }
        }
    };

    log #fmt["p: %s", p];

    let c = {
        root: p,
        bindir: fs::connect(p, "bin"),
        libdir: fs::connect(p, "lib"),
        workdir: fs::connect(p, "work"),
        fetchdir: fs::connect(p, "fetch")
    };

    need_dir(c.root);
    need_dir(c.fetchdir);
    need_dir(c.workdir);
    need_dir(c.libdir);
    need_dir(c.bindir);

    c
}

fn install_one_crate(c: cargo, _path: str, cf: str, _p: pkg) {
    let name = fs::basename(cf);
    let ri = str::index(name, '.' as u8);
    if ri != -1 {
        name = str::slice(name, 0u, ri as uint);
    }
    log #fmt["Installing: %s", name];
    let old = vec::map({|x| str::slice(x, 2u, str::byte_len(x))}, fs::list_dir("."));
    run::run_program("rustc", [name + ".rc"]);
    let new = vec::map({|x| str::slice(x, 2u, str::byte_len(x))}, fs::list_dir("."));
    let created = vec::filter::<str>({ |n| !vec::member::<str>(n, old) }, new);
    for ct: str in created {
        if (os::exec_suffix() != "" && str::ends_with(ct, os::exec_suffix())) ||
           (os::exec_suffix() == "" && !str::starts_with(ct, "lib")) {
            log #fmt["  bin: %s", ct];
            // FIXME: need libstd fs::copy or something
            run::run_program("cp", [ct, c.bindir]);
        } else {
            log #fmt["  lib: %s", ct];
            run::run_program("cp", [ct, c.libdir]);
        }
    }
}

fn install_source(c: cargo, path: str) {
    log #fmt["source: %s", path];
    fs::change_dir(path);
    let contents = fs::list_dir(".");

    log #fmt["contents: %s", str::connect(contents, ", ")];

    let cratefiles = vec::filter::<str>({ |n| str::ends_with(n, ".rc") }, contents);

    if vec::is_empty(cratefiles) {
        fail "This doesn't look like a rust package (no .rc files).";
    }

    for cf: str in cratefiles {
        let p = load_pkg(cf);
        alt p {
            none. { cont; }
            some(_p) {
                install_one_crate(c, path, cf, _p);
            }
        }
    }
}

fn install_git(c: cargo, wd: str, _path: str) {
    run::run_program("git", ["clone", _path, wd]);
    install_source(c, wd);
}

fn install_file(c: cargo, wd: str, _path: str) {
    run::run_program("tar", ["-x", "--strip-components=1",
                             "-C", wd, "-f", _path]);
    install_source(c, wd);
}

fn install_resolved(c: cargo, wd: str, key: str) {
    fs::remove_dir(wd);
    let u = "https://rust-package-index.appspot.com/pkg/" + key;
    let p = run::program_output("curl", [u]);
    if p.status != 0 {
        fail #fmt["Fetch of %s failed: %s", u, p.err];
    }
    let j = json::from_str(p.out);
    alt j {
        some (json::dict(_j)) {
            alt _j.find("install") {
                some (json::string(g)) {
                    log #fmt["Resolved: %s -> %s", key, g];
                    cmd_install(c, ["cargo", "install", g]);
                }
                _ { fail #fmt["Bogus install: '%s'", p.out]; }
            }
        }
        _ { fail #fmt["Bad json: '%s'", p.out]; }
    }
}

fn install_uuid(c: cargo, wd: str, uuid: str) {
    install_resolved(c, wd, uuid);
}

fn install_named(c: cargo, wd: str, name: str) {
    install_resolved(c, wd, name);
}

fn cmd_install(c: cargo, argv: [str]) {
    // cargo install <pkg>
    if vec::len(argv) < 3u {
        cmd_usage();
        ret;
    }

    let wd = alt tempfile::mkdtemp(c.workdir + fs::path_sep(), "") {
        some(_wd) { _wd }
        none. { fail "needed temp dir"; }
    };

    if str::starts_with(argv[2], "git:") {
        install_git(c, wd, argv[2]);
    } else if str::starts_with(argv[2], "github:") {
        let path = rest(argv[2], 7u);
        install_git(c, wd, "git://github.com/" + path);
    } else if str::starts_with(argv[2], "file:") {
        let path = rest(argv[2], 5u);
        install_file(c, wd, path);
    } else if str::starts_with(argv[2], "uuid:") {
        let uuid = rest(argv[2], 5u);
        install_uuid(c, wd, uuid);
    } else {
        install_named(c, wd, argv[2]);
    }
}

fn cmd_usage() {
    print("Usage: cargo <verb> [args...]");
}

fn main(argv: [str]) {
    if vec::len(argv) < 2u {
        cmd_usage();
        ret;
    }
    let c = configure();
    alt argv[1] {
        "install" { cmd_install(c, argv); }
        "usage" { cmd_usage(); }
        _ { cmd_usage(); }
    }
}
<commit_msg>cargo: update to new rust-pkg-index API.<commit_after>// cargo.rs - Rust package manager

import rustc::syntax::{ast, codemap, visit};
import rustc::syntax::parse::parser;

import std::fs;
import std::generic_os;
import std::io;
import std::json;
import option;
import option::{none, some};
import std::os;
import std::run;
import str;
import std::tempfile;
import vec;

type cargo = {
    root: str,
    bindir: str,
    libdir: str,
    workdir: str,
    fetchdir: str
};

type pkg = {
    name: str,
    vers: str,
    uuid: str,
    desc: option::t<str>,
    sigs: option::t<str>,
    crate_type: option::t<str>
};

fn load_link(mis: [@ast::meta_item]) -> (option::t<str>,
                                         option::t<str>,
                                         option::t<str>) {
    let name = none;
    let vers = none;
    let uuid = none;
    for a: @ast::meta_item in mis {
        alt a.node {
            ast::meta_name_value(v, {node: ast::lit_str(s), span: _}) {
                alt v {
                    "name" { name = some(s); }
                    "vers" { vers = some(s); }
                    "uuid" { uuid = some(s); }
                    _ { }
                }
            }
        }
    }
    (name, vers, uuid)
}

fn load_pkg(filename: str) -> option::t<pkg> {
    let sess = @{cm: codemap::new_codemap(), mutable next_id: 0};
    let c = parser::parse_crate_from_crate_file(filename, [], sess);

    let name = none;
    let vers = none;
    let uuid = none;
    let desc = none;
    let sigs = none;
    let crate_type = none;

    for a in c.node.attrs {
        alt a.node.value.node {
            ast::meta_name_value(v, {node: ast::lit_str(s), span: _}) {
                alt v {
                    "desc" { desc = some(v); }
                    "sigs" { sigs = some(v); }
                    "crate_type" { crate_type = some(v); }
                    _ { }
                }
            }
            ast::meta_list(v, mis) {
                if v == "link" {
                    let (n, v, u) = load_link(mis);
                    name = n;
                    vers = v;
                    uuid = u;
                }
            }
        }
    }

    alt (name, vers, uuid) {
        (some(name0), some(vers0), some(uuid0)) {
            some({
                name: name0,
                vers: vers0,
                uuid: uuid0,
                desc: desc,
                sigs: sigs,
                crate_type: crate_type})
        }
        _ { ret none; }
    }
}

fn print(s: str) {
    io::stdout().write_line(s);
}

fn rest(s: str, start: uint) -> str {
    if (start >= str::char_len(s)) {
        ""
    } else {
        str::char_slice(s, start, str::char_len(s))
    }
}

fn need_dir(s: str) {
    if fs::file_is_dir(s) { ret; }
    if !fs::make_dir(s, 0x1c0i32) {
        fail #fmt["can't make_dir %s", s];
    }
}

fn configure() -> cargo {
    let p = alt generic_os::getenv("CARGO_ROOT") {
        some(_p) { _p }
        none. {
            alt generic_os::getenv("HOME") {
                some(_q) { fs::connect(_q, ".cargo") }
                none. { fail "no CARGO_ROOT or HOME"; }
            }
        }
    };

    log #fmt["p: %s", p];

    let c = {
        root: p,
        bindir: fs::connect(p, "bin"),
        libdir: fs::connect(p, "lib"),
        workdir: fs::connect(p, "work"),
        fetchdir: fs::connect(p, "fetch")
    };

    need_dir(c.root);
    need_dir(c.fetchdir);
    need_dir(c.workdir);
    need_dir(c.libdir);
    need_dir(c.bindir);

    c
}

fn install_one_crate(c: cargo, _path: str, cf: str, _p: pkg) {
    let name = fs::basename(cf);
    let ri = str::index(name, '.' as u8);
    if ri != -1 {
        name = str::slice(name, 0u, ri as uint);
    }
    log #fmt["Installing: %s", name];
    let old = vec::map({|x| str::slice(x, 2u, str::byte_len(x))}, fs::list_dir("."));
    run::run_program("rustc", [name + ".rc"]);
    let new = vec::map({|x| str::slice(x, 2u, str::byte_len(x))}, fs::list_dir("."));
    let created = vec::filter::<str>({ |n| !vec::member::<str>(n, old) }, new);
    for ct: str in created {
        if (os::exec_suffix() != "" && str::ends_with(ct, os::exec_suffix())) ||
           (os::exec_suffix() == "" && !str::starts_with(ct, "lib")) {
            log #fmt["  bin: %s", ct];
            // FIXME: need libstd fs::copy or something
            run::run_program("cp", [ct, c.bindir]);
        } else {
            log #fmt["  lib: %s", ct];
            run::run_program("cp", [ct, c.libdir]);
        }
    }
}

fn install_source(c: cargo, path: str) {
    log #fmt["source: %s", path];
    fs::change_dir(path);
    let contents = fs::list_dir(".");

    log #fmt["contents: %s", str::connect(contents, ", ")];

    let cratefiles = vec::filter::<str>({ |n| str::ends_with(n, ".rc") }, contents);

    if vec::is_empty(cratefiles) {
        fail "This doesn't look like a rust package (no .rc files).";
    }

    for cf: str in cratefiles {
        let p = load_pkg(cf);
        alt p {
            none. { cont; }
            some(_p) {
                install_one_crate(c, path, cf, _p);
            }
        }
    }
}

fn install_git(c: cargo, wd: str, _path: str) {
    run::run_program("git", ["clone", _path, wd]);
    install_source(c, wd);
}

fn install_file(c: cargo, wd: str, _path: str) {
    run::run_program("tar", ["-x", "--strip-components=1",
                             "-C", wd, "-f", _path]);
    install_source(c, wd);
}

fn install_resolved(c: cargo, wd: str, key: str) {
    fs::remove_dir(wd);
    let u = "https://rust-package-index.appspot.com/pkg/" + key;
    let p = run::program_output("curl", [u]);
    if p.status != 0 {
        fail #fmt["Fetch of %s failed: %s", u, p.err];
    }
    let j = json::from_str(p.out);
    alt j {
        some (json::dict(_j)) {
            alt _j.find("install") {
                some (json::string(g)) {
                    log #fmt["Resolved: %s -> %s", key, g];
                    cmd_install(c, ["cargo", "install", g]);
                }
                _ { fail #fmt["Bogus install: '%s'", p.out]; }
            }
        }
        _ { fail #fmt["Bad json: '%s'", p.out]; }
    }
}

fn install_uuid(c: cargo, wd: str, uuid: str) {
    install_resolved(c, wd, "by-uuid/" + uuid);
}

fn install_named(c: cargo, wd: str, name: str) {
    install_resolved(c, wd, "by-name/" + name);
}

fn cmd_install(c: cargo, argv: [str]) {
    // cargo install <pkg>
    if vec::len(argv) < 3u {
        cmd_usage();
        ret;
    }

    let wd = alt tempfile::mkdtemp(c.workdir + fs::path_sep(), "") {
        some(_wd) { _wd }
        none. { fail "needed temp dir"; }
    };

    if str::starts_with(argv[2], "git:") {
        install_git(c, wd, argv[2]);
    } else if str::starts_with(argv[2], "github:") {
        let path = rest(argv[2], 7u);
        install_git(c, wd, "git://github.com/" + path);
    } else if str::starts_with(argv[2], "file:") {
        let path = rest(argv[2], 5u);
        install_file(c, wd, path);
    } else if str::starts_with(argv[2], "uuid:") {
        let uuid = rest(argv[2], 5u);
        install_uuid(c, wd, uuid);
    } else {
        install_named(c, wd, argv[2]);
    }
}

fn cmd_usage() {
    print("Usage: cargo <verb> [args...]");
}

fn main(argv: [str]) {
    if vec::len(argv) < 2u {
        cmd_usage();
        ret;
    }
    let c = configure();
    alt argv[1] {
        "install" { cmd_install(c, argv); }
        "usage" { cmd_usage(); }
        _ { cmd_usage(); }
    }
}
<|endoftext|>


<commit_before>extern crate allergies;

use allergies::*;

#[test]
fn test_no_allergies_means_not_allergic() {
    let allergies = Allergies::new(0);
    assert_eq!(false, allergies.is_allergic_to(&Allergen::Peanuts));
    assert_eq!(false, allergies.is_allergic_to(&Allergen::Cats));
    assert_eq!(false, allergies.is_allergic_to(&Allergen::Strawberries));
}

#[test]
#[ignore]
fn test_is_allergic_to_eggs() {
    assert_eq!(true, Allergies::new(1).is_allergic_to(&Allergen::Eggs));
}

#[test]
#[ignore]
fn test_has_the_right_allergies() {
    let allergies = Allergies::new(5);
    assert_eq!(true, allergies.is_allergic_to(&Allergen::Eggs));
    assert_eq!(true, allergies.is_allergic_to(&Allergen::Shellfish));
    assert_eq!(false, allergies.is_allergic_to(&Allergen::Strawberries));
}

#[test]
#[ignore]
fn test_no_allergies_at_all() {
    assert_eq!(Vec::<Allergen>::new(), Allergies::new(0).allergies());
}

#[test]
#[ignore]
fn test_just_to_peanuts() {
    assert_eq!(vec![Allergen::Peanuts], Allergies::new(2).allergies());
}

#[test]
#[ignore]
fn test_allergic_to_everything() {
    assert_eq!(vec![Allergen::Eggs, Allergen::Peanuts, Allergen::Shellfish,
                    Allergen::Strawberries, Allergen::Tomatoes, Allergen::Chocolate,
                    Allergen::Pollen, Allergen::Cats],
        Allergies::new(255).allergies());
}

#[test]
#[ignore]
fn test_ignore_non_allergen_score_parts() {
    assert_eq!(vec![Allergen::Eggs], Allergies::new(257).allergies());
}
<commit_msg>Check that result contains expected allergens<commit_after>extern crate allergies;

use allergies::*;

#[test]
fn test_no_allergies_means_not_allergic() {
    let allergies = Allergies::new(0);
    assert_eq!(false, allergies.is_allergic_to(&Allergen::Peanuts));
    assert_eq!(false, allergies.is_allergic_to(&Allergen::Cats));
    assert_eq!(false, allergies.is_allergic_to(&Allergen::Strawberries));
}

#[test]
#[ignore]
fn test_is_allergic_to_eggs() {
    assert_eq!(true, Allergies::new(1).is_allergic_to(&Allergen::Eggs));
}

#[test]
#[ignore]
fn test_has_the_right_allergies() {
    let allergies = Allergies::new(5);
    assert_eq!(true, allergies.is_allergic_to(&Allergen::Eggs));
    assert_eq!(true, allergies.is_allergic_to(&Allergen::Shellfish));
    assert_eq!(false, allergies.is_allergic_to(&Allergen::Strawberries));
}

#[test]
#[ignore]
fn test_no_allergies_at_all() {
    assert_eq!(Vec::<Allergen>::new(), Allergies::new(0).allergies());
}

#[test]
#[ignore]
fn test_just_to_peanuts() {
    assert_eq!(vec![Allergen::Peanuts], Allergies::new(2).allergies());
}

#[test]
#[ignore]
fn test_allergic_to_everything() {
    let all = vec![Allergen::Eggs, Allergen::Peanuts, Allergen::Shellfish,
                   Allergen::Strawberries, Allergen::Tomatoes,
                   Allergen::Chocolate, Allergen::Pollen, Allergen::Cats];
    let allergies = Allergies::new(255).allergies();

    for allergy in &all {
        assert!(allergies.contains(&allergy));
    }
}

#[test]
#[ignore]
fn test_ignore_non_allergen_score_parts() {
    assert_eq!(vec![Allergen::Eggs], Allergies::new(257).allergies());
}
<|endoftext|>


<commit_before>use std::convert;
use std::ops::{Add, Sub};
use internals::Node;

/// The class of monoids
///
/// The class of monoids (types with an sumiative binary operation that has
/// an identity).
///
/// An instance _M_ should satisfy the following laws:
///
///  + _x_`.sum(`_M_`::identity())` = _x_
///  + _M_`::identity().sum(`_x_`)` = _x_
///  + _x_`.sum(`_y_`.sum(`_z_`))` = _z_`.sum(`_x_`.sum(`_y_`))`
///  + _M_`::concat(`_a_`)` = _a_`.fold(`_M_`::identity,`_M_`::sum)`
///
pub trait Monoid: Sized {
    /// The identity operation of the monoid
    //  TODO: can this be represented by std::default::Default instead?
    fn identity() -> Self;

    /// The sum operation of the monoid
    //  TODO: can this be represented by std::ops::Add instead?
    fn sum(self, other: Self) -> Self;

    #[inline]
    fn concat<F>(xs: F) -> Self
    where F: Iterator<Item=Self>
        , Self: Sized {
        xs.fold(Self::identity(), Self::sum)
    }
}

/// A monoid that can be applied to a `Node` as a measurement
pub trait Metric: Monoid + Eq + Add<usize, Output=Self>
                              + Sub<usize, Output=Self>
                              + Sized {
    /// Apply this `Metric` to the given `Node`
    fn measure(node: &Node) -> Self;

    /// Convert the `Metric` into a byte index into the given `Node`
    ///
    /// # Returns
    /// - `Some` with the byte index of the beginning of the `n`th  element
    ///    in `node` measured by this `Metric`, if there is an `n`th element
    /// - `None` if there is no `n`th element in `node`
    fn to_byte_index(&self, node: &Node) -> Option<usize>;

    /// Returns the byte index of the next element of this metric in `Node`
    #[inline] fn next(self, node: &Node) -> Option<usize> {
        (self + 1).to_byte_index(node)
    }

    /// Returns the byte index of the previous element of this metric in `Node`
    #[inline] fn back(self, node: &Node) -> Option<usize> {
        (self - 1).to_byte_index(node)
    }

    /// Returns true if index `i` in `node` is a boundary along this `Metric`
    // TODO: should this be a method on `Node`s instead?
    fn is_boundary(node: &Node, i: usize) -> bool;
}

#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq)]
pub struct Grapheme(usize);
impl Monoid for Grapheme {
    #[inline] fn identity() -> Self { Grapheme(0) }
    #[inline] fn sum(self, other: Self) -> Self { Grapheme(self.0 + other.0) }
}

impl convert::From<usize> for Grapheme {
    #[inline] fn from(u: usize) -> Self { Grapheme(u) }
}

impl Add<usize> for Grapheme {
    type Output = Self;
    #[inline] fn add(self, rhs: usize) -> Self { Grapheme(self.0 + rhs) }
}
impl Sub<usize> for Grapheme {
    type Output = Self;
    #[inline] fn sub(self, rhs: usize) -> Self { Grapheme(self.0 - rhs) }
}

#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq)]
pub struct Line(usize);
impl Monoid for Line {
    #[inline] fn identity() -> Self { Line(0) }
    #[inline] fn sum(self, other: Self) -> Self { Line(self.0 + other.0) }
}

impl convert::From<usize> for Line {
    #[inline] fn from(u: usize) -> Self { Line(u) }
}
impl Add<usize> for Line {
    type Output = Self;
    #[inline] fn add(self, rhs: usize) -> Self { Line(self.0 + rhs) }
}
impl Sub<usize> for Line {
    type Output = Self;
    #[inline] fn sub(self, rhs: usize) -> Self { Line(self.0 - rhs) }
}
<commit_msg>feat(rope): monoid uses `default()` for identity<commit_after>use std::convert;
use std::ops::{Add, Sub};
use std::default::Default;
use internals::Node;

pub trait Semigroup {
    /// The sum operation of the semigroup
    fn sum(self, other: Self) -> Self;
}

/// The class of monoids
///
/// The class of monoids (types with an accumulative binary operation that has
/// an default).
///
/// An instance _M_ should satisfy the following laws:
///
///  + _x_`.sum(`_M_`::default())` = _x_
///  + _M_`::default().sum(`_x_`)` = _x_
///  + _x_`.sum(`_y_`.sum(`_z_`))` = _z_`.sum(`_x_`.sum(`_y_`))`
///  + _M_`::accumulate(`_a_`)` = _a_`.fold(`_M_`::default,`_M_`::sum)`
///
pub trait Monoid: Semigroup + Default + Sized {

    #[inline]
    fn accumulate<F>(xs: F) -> Self
    where F: Iterator<Item=Self>
        , Self: Sized {
        xs.fold(Self::default(), Self::sum)
    }
}

/// A monoid that can be applied to a `Node` as a measurement
pub trait Metric: Monoid + Eq + Add<usize, Output=Self>
                              + Sub<usize, Output=Self>
                              + Sized {
    /// Apply this `Metric` to the given `Node`
    fn measure(node: &Node) -> Self;

    /// Convert the `Metric` into a byte index into the given `Node`
    ///
    /// # Returns
    /// - `Some` with the byte index of the beginning of the `n`th  element
    ///    in `node` measured by this `Metric`, if there is an `n`th element
    /// - `None` if there is no `n`th element in `node`
    fn to_byte_index(&self, node: &Node) -> Option<usize>;

    /// Returns the byte index of the next element of this metric in `Node`
    #[inline] fn next(self, node: &Node) -> Option<usize> {
        (self + 1).to_byte_index(node)
    }

    /// Returns the byte index of the previous element of this metric in `Node`
    #[inline] fn back(self, node: &Node) -> Option<usize> {
        (self - 1).to_byte_index(node)
    }

    /// Returns true if index `i` in `node` is a boundary along this `Metric`
    // TODO: should this be a method on `Node`s instead?
    fn is_boundary(node: &Node, i: usize) -> bool;
}

#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq)]
pub struct Grapheme(usize);
impl Default for Grapheme {
    #[inline] fn default() -> Self { Grapheme(0) }
}
impl Semigroup for Grapheme {
    #[inline] fn sum(self, other: Self) -> Self { Grapheme(self.0 + other.0) }
}
impl convert::From<usize> for Grapheme {
    #[inline] fn from(u: usize) -> Self { Grapheme(u) }
}
impl Add<usize> for Grapheme {
    type Output = Self;
    #[inline] fn add(self, rhs: usize) -> Self { Grapheme(self.0 + rhs) }
}
impl Sub<usize> for Grapheme {
    type Output = Self;
    #[inline] fn sub(self, rhs: usize) -> Self { Grapheme(self.0 - rhs) }
}

#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq)]
pub struct Line(usize);
impl Default for Line {
    #[inline] fn default() -> Self { Line(0) }
}
impl Semigroup for Line {
    #[inline] fn sum(self, other: Self) -> Self { Line(self.0 + other.0) }
}

impl convert::From<usize> for Line {
    #[inline] fn from(u: usize) -> Self { Line(u) }
}
impl Add<usize> for Line {
    type Output = Self;
    #[inline] fn add(self, rhs: usize) -> Self { Line(self.0 + rhs) }
}
impl Sub<usize> for Line {
    type Output = Self;
    #[inline] fn sub(self, rhs: usize) -> Self { Line(self.0 - rhs) }
}
<|endoftext|>


<commit_before>use hir::{MacroDef, ModuleDef, Name, PathResolution, ScopeDef, SemanticsScope};
use ra_ide_db::{
    defs::{classify_name_ref, Definition, NameRefClass},
    RootDatabase,
};
use ra_syntax::{ast, match_ast, AstNode, SyntaxNode, SyntaxToken, T};

use crate::{
    assist_context::{AssistBuilder, AssistContext, Assists},
    AssistId, AssistKind,
};

// Assist: expand_glob_import
//
// Expands glob imports.
//
// ```
// mod foo {
//     pub struct Bar;
//     pub struct Baz;
// }
//
// use foo::*<|>;
//
// fn qux(bar: Bar, baz: Baz) {}
// ```
// ->
// ```
// mod foo {
//     pub struct Bar;
//     pub struct Baz;
// }
//
// use foo::{Bar, Baz};
//
// fn qux(bar: Bar, baz: Baz) {}
// ```
pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
    let star = ctx.find_token_at_offset(T![*])?;
    let mod_path = find_mod_path(&star)?;

    let source_file = ctx.source_file();
    let scope = ctx.sema.scope_at_offset(source_file.syntax(), ctx.offset());

    let defs_in_mod = find_defs_in_mod(ctx, scope, &mod_path)?;
    let name_refs_in_source_file =
        source_file.syntax().descendants().filter_map(ast::NameRef::cast).collect();
    let used_names = find_used_names(ctx, defs_in_mod, name_refs_in_source_file);

    let parent = star.parent().parent()?;
    acc.add(
        AssistId("expand_glob_import", AssistKind::RefactorRewrite),
        "Expand glob import",
        parent.text_range(),
        |builder| {
            replace_ast(builder, &parent, mod_path, used_names);
        },
    )
}

fn find_mod_path(star: &SyntaxToken) -> Option<ast::Path> {
    let mut node = star.parent();

    loop {
        match_ast! {
            match node {
                ast::UseTree(use_tree) => {
                    if let Some(path) = use_tree.path() {
                        return Some(path);
                    }
                },
                ast::UseTreeList(_use_tree_list) => {},
                _ => return None,
            }
        }

        node = match node.parent() {
            Some(node) => node,
            None => return None,
        }
    }
}

#[derive(PartialEq)]
enum Def {
    ModuleDef(ModuleDef),
    MacroDef(MacroDef),
}

impl Def {
    fn name(&self, db: &RootDatabase) -> Option<Name> {
        match self {
            Def::ModuleDef(def) => def.name(db),
            Def::MacroDef(def) => def.name(db),
        }
    }
}

fn find_defs_in_mod(
    ctx: &AssistContext,
    from: SemanticsScope<'_>,
    path: &ast::Path,
) -> Option<Vec<Def>> {
    let hir_path = ctx.sema.lower_path(&path)?;
    let module = if let Some(PathResolution::Def(ModuleDef::Module(module))) =
        from.resolve_hir_path_qualifier(&hir_path)
    {
        module
    } else {
        return None;
    };

    let module_scope = module.scope(ctx.db(), from.module());

    let mut defs = vec![];
    for (_, def) in module_scope {
        match def {
            ScopeDef::ModuleDef(def) => defs.push(Def::ModuleDef(def)),
            ScopeDef::MacroDef(def) => defs.push(Def::MacroDef(def)),
            _ => continue,
        }
    }

    Some(defs)
}

fn find_used_names(
    ctx: &AssistContext,
    defs_in_mod: Vec<Def>,
    name_refs_in_source_file: Vec<ast::NameRef>,
) -> Vec<Name> {
    let defs_in_source_file = name_refs_in_source_file
        .iter()
        .filter_map(|r| classify_name_ref(&ctx.sema, r))
        .filter_map(|rc| match rc {
            NameRefClass::Definition(Definition::ModuleDef(def)) => Some(Def::ModuleDef(def)),
            NameRefClass::Definition(Definition::Macro(def)) => Some(Def::MacroDef(def)),
            _ => None,
        })
        .collect::<Vec<Def>>();

    defs_in_mod
        .iter()
        .filter(|d| defs_in_source_file.contains(d))
        .filter_map(|d| d.name(ctx.db()))
        .collect()
}

fn replace_ast(
    builder: &mut AssistBuilder,
    node: &SyntaxNode,
    path: ast::Path,
    used_names: Vec<Name>,
) {
    let new_use_tree_list = ast::make::use_tree_list(used_names.iter().map(|n| {
        ast::make::use_tree(ast::make::path_from_text(&n.to_string()), None, None, false)
    }));

    match_ast! {
        match node {
            ast::UseTree(use_tree) => {
                builder.replace_ast(use_tree, make_use_tree(path, new_use_tree_list));
            },
            ast::UseTreeList(use_tree_list) => {
                builder.replace_ast(use_tree_list, new_use_tree_list);
            },
            ast::UseItem(use_item) => {
                builder.replace_ast(use_item, ast::make::use_item(make_use_tree(path, new_use_tree_list)));
            },
            _ => {},
        }
    }

    fn make_use_tree(path: ast::Path, use_tree_list: ast::UseTreeList) -> ast::UseTree {
        ast::make::use_tree(path, Some(use_tree_list), None, false)
    }
}

#[cfg(test)]
mod tests {
    use crate::tests::{check_assist, check_assist_not_applicable};

    use super::*;

    #[test]
    fn expanding_glob_import() {
        check_assist(
            expand_glob_import,
            r"
mod foo {
    pub struct Bar;
    pub struct Baz;
    pub struct Qux;

    pub fn f() {}
}

use foo::*<|>;

fn qux(bar: Bar, baz: Baz) {
    f();
}
",
            r"
mod foo {
    pub struct Bar;
    pub struct Baz;
    pub struct Qux;

    pub fn f() {}
}

use foo::{Baz, Bar, f};

fn qux(bar: Bar, baz: Baz) {
    f();
}
",
        )
    }

    #[test]
    fn expanding_glob_import_with_existing_explicit_names() {
        check_assist(
            expand_glob_import,
            r"
mod foo {
    pub struct Bar;
    pub struct Baz;
    pub struct Qux;

    pub fn f() {}
}

use foo::{*<|>, f};

fn qux(bar: Bar, baz: Baz) {
    f();
}
",
            r"
mod foo {
    pub struct Bar;
    pub struct Baz;
    pub struct Qux;

    pub fn f() {}
}

use foo::{Baz, Bar, f};

fn qux(bar: Bar, baz: Baz) {
    f();
}
",
        )
    }

    #[test]
    fn expanding_nested_glob_import() {
        check_assist(
            expand_glob_import,
            r"
mod foo {
    mod bar {
        pub struct Bar;
        pub struct Baz;
        pub struct Qux;

        pub fn f() {}
    }

    mod baz {
        pub fn g() {}
    }
}

use foo::{bar::{*<|>, f}, baz::*};

fn qux(bar: Bar, baz: Baz) {
    f();
    g();
}
",
            r"
mod foo {
    mod bar {
        pub struct Bar;
        pub struct Baz;
        pub struct Qux;

        pub fn f() {}
    }

    mod baz {
        pub fn g() {}
    }
}

use foo::{bar::{Baz, Bar, f}, baz::*};

fn qux(bar: Bar, baz: Baz) {
    f();
    g();
}
",
        )
    }

    #[test]
    fn expanding_glob_import_with_macro_defs() {
        check_assist(
            expand_glob_import,
            r"
//- /lib.rs crate:foo
#[macro_export]
macro_rules! bar {
    () => ()
}

pub fn baz() {}

//- /main.rs crate:main deps:foo
use foo::*<|>;

fn main() {
    bar!();
    baz();
}
",
            r"
use foo::{bar, baz};

fn main() {
    bar!();
    baz();
}
",
        )
    }

    #[test]
    fn expanding_is_not_applicable_if_cursor_is_not_in_star_token() {
        check_assist_not_applicable(
            expand_glob_import,
            r"
    mod foo {
        pub struct Bar;
        pub struct Baz;
        pub struct Qux;
    }

    use foo::Bar<|>;

    fn qux(bar: Bar, baz: Baz) {}
    ",
        )
    }
}
<commit_msg>Rename ast::UseItem to ast::Use<commit_after>use hir::{MacroDef, ModuleDef, Name, PathResolution, ScopeDef, SemanticsScope};
use ra_ide_db::{
    defs::{classify_name_ref, Definition, NameRefClass},
    RootDatabase,
};
use ra_syntax::{ast, match_ast, AstNode, SyntaxNode, SyntaxToken, T};

use crate::{
    assist_context::{AssistBuilder, AssistContext, Assists},
    AssistId, AssistKind,
};

// Assist: expand_glob_import
//
// Expands glob imports.
//
// ```
// mod foo {
//     pub struct Bar;
//     pub struct Baz;
// }
//
// use foo::*<|>;
//
// fn qux(bar: Bar, baz: Baz) {}
// ```
// ->
// ```
// mod foo {
//     pub struct Bar;
//     pub struct Baz;
// }
//
// use foo::{Bar, Baz};
//
// fn qux(bar: Bar, baz: Baz) {}
// ```
pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
    let star = ctx.find_token_at_offset(T![*])?;
    let mod_path = find_mod_path(&star)?;

    let source_file = ctx.source_file();
    let scope = ctx.sema.scope_at_offset(source_file.syntax(), ctx.offset());

    let defs_in_mod = find_defs_in_mod(ctx, scope, &mod_path)?;
    let name_refs_in_source_file =
        source_file.syntax().descendants().filter_map(ast::NameRef::cast).collect();
    let used_names = find_used_names(ctx, defs_in_mod, name_refs_in_source_file);

    let parent = star.parent().parent()?;
    acc.add(
        AssistId("expand_glob_import", AssistKind::RefactorRewrite),
        "Expand glob import",
        parent.text_range(),
        |builder| {
            replace_ast(builder, &parent, mod_path, used_names);
        },
    )
}

fn find_mod_path(star: &SyntaxToken) -> Option<ast::Path> {
    let mut node = star.parent();

    loop {
        match_ast! {
            match node {
                ast::UseTree(use_tree) => {
                    if let Some(path) = use_tree.path() {
                        return Some(path);
                    }
                },
                ast::UseTreeList(_use_tree_list) => {},
                _ => return None,
            }
        }

        node = match node.parent() {
            Some(node) => node,
            None => return None,
        }
    }
}

#[derive(PartialEq)]
enum Def {
    ModuleDef(ModuleDef),
    MacroDef(MacroDef),
}

impl Def {
    fn name(&self, db: &RootDatabase) -> Option<Name> {
        match self {
            Def::ModuleDef(def) => def.name(db),
            Def::MacroDef(def) => def.name(db),
        }
    }
}

fn find_defs_in_mod(
    ctx: &AssistContext,
    from: SemanticsScope<'_>,
    path: &ast::Path,
) -> Option<Vec<Def>> {
    let hir_path = ctx.sema.lower_path(&path)?;
    let module = if let Some(PathResolution::Def(ModuleDef::Module(module))) =
        from.resolve_hir_path_qualifier(&hir_path)
    {
        module
    } else {
        return None;
    };

    let module_scope = module.scope(ctx.db(), from.module());

    let mut defs = vec![];
    for (_, def) in module_scope {
        match def {
            ScopeDef::ModuleDef(def) => defs.push(Def::ModuleDef(def)),
            ScopeDef::MacroDef(def) => defs.push(Def::MacroDef(def)),
            _ => continue,
        }
    }

    Some(defs)
}

fn find_used_names(
    ctx: &AssistContext,
    defs_in_mod: Vec<Def>,
    name_refs_in_source_file: Vec<ast::NameRef>,
) -> Vec<Name> {
    let defs_in_source_file = name_refs_in_source_file
        .iter()
        .filter_map(|r| classify_name_ref(&ctx.sema, r))
        .filter_map(|rc| match rc {
            NameRefClass::Definition(Definition::ModuleDef(def)) => Some(Def::ModuleDef(def)),
            NameRefClass::Definition(Definition::Macro(def)) => Some(Def::MacroDef(def)),
            _ => None,
        })
        .collect::<Vec<Def>>();

    defs_in_mod
        .iter()
        .filter(|d| defs_in_source_file.contains(d))
        .filter_map(|d| d.name(ctx.db()))
        .collect()
}

fn replace_ast(
    builder: &mut AssistBuilder,
    node: &SyntaxNode,
    path: ast::Path,
    used_names: Vec<Name>,
) {
    let new_use_tree_list = ast::make::use_tree_list(used_names.iter().map(|n| {
        ast::make::use_tree(ast::make::path_from_text(&n.to_string()), None, None, false)
    }));

    match_ast! {
        match node {
            ast::UseTree(use_tree) => {
                builder.replace_ast(use_tree, make_use_tree(path, new_use_tree_list));
            },
            ast::UseTreeList(use_tree_list) => {
                builder.replace_ast(use_tree_list, new_use_tree_list);
            },
            ast::Use(use_item) => {
                builder.replace_ast(use_item, ast::make::use_item(make_use_tree(path, new_use_tree_list)));
            },
            _ => {},
        }
    }

    fn make_use_tree(path: ast::Path, use_tree_list: ast::UseTreeList) -> ast::UseTree {
        ast::make::use_tree(path, Some(use_tree_list), None, false)
    }
}

#[cfg(test)]
mod tests {
    use crate::tests::{check_assist, check_assist_not_applicable};

    use super::*;

    #[test]
    fn expanding_glob_import() {
        check_assist(
            expand_glob_import,
            r"
mod foo {
    pub struct Bar;
    pub struct Baz;
    pub struct Qux;

    pub fn f() {}
}

use foo::*<|>;

fn qux(bar: Bar, baz: Baz) {
    f();
}
",
            r"
mod foo {
    pub struct Bar;
    pub struct Baz;
    pub struct Qux;

    pub fn f() {}
}

use foo::{Baz, Bar, f};

fn qux(bar: Bar, baz: Baz) {
    f();
}
",
        )
    }

    #[test]
    fn expanding_glob_import_with_existing_explicit_names() {
        check_assist(
            expand_glob_import,
            r"
mod foo {
    pub struct Bar;
    pub struct Baz;
    pub struct Qux;

    pub fn f() {}
}

use foo::{*<|>, f};

fn qux(bar: Bar, baz: Baz) {
    f();
}
",
            r"
mod foo {
    pub struct Bar;
    pub struct Baz;
    pub struct Qux;

    pub fn f() {}
}

use foo::{Baz, Bar, f};

fn qux(bar: Bar, baz: Baz) {
    f();
}
",
        )
    }

    #[test]
    fn expanding_nested_glob_import() {
        check_assist(
            expand_glob_import,
            r"
mod foo {
    mod bar {
        pub struct Bar;
        pub struct Baz;
        pub struct Qux;

        pub fn f() {}
    }

    mod baz {
        pub fn g() {}
    }
}

use foo::{bar::{*<|>, f}, baz::*};

fn qux(bar: Bar, baz: Baz) {
    f();
    g();
}
",
            r"
mod foo {
    mod bar {
        pub struct Bar;
        pub struct Baz;
        pub struct Qux;

        pub fn f() {}
    }

    mod baz {
        pub fn g() {}
    }
}

use foo::{bar::{Baz, Bar, f}, baz::*};

fn qux(bar: Bar, baz: Baz) {
    f();
    g();
}
",
        )
    }

    #[test]
    fn expanding_glob_import_with_macro_defs() {
        check_assist(
            expand_glob_import,
            r"
//- /lib.rs crate:foo
#[macro_export]
macro_rules! bar {
    () => ()
}

pub fn baz() {}

//- /main.rs crate:main deps:foo
use foo::*<|>;

fn main() {
    bar!();
    baz();
}
",
            r"
use foo::{bar, baz};

fn main() {
    bar!();
    baz();
}
",
        )
    }

    #[test]
    fn expanding_is_not_applicable_if_cursor_is_not_in_star_token() {
        check_assist_not_applicable(
            expand_glob_import,
            r"
    mod foo {
        pub struct Bar;
        pub struct Baz;
        pub struct Qux;
    }

    use foo::Bar<|>;

    fn qux(bar: Bar, baz: Baz) {}
    ",
        )
    }
}
<|endoftext|>