<commit_before>/*
 * Copyright 2015-2016 Ben Ashford
 *
 * 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.
 */

//! Implementation of the ElasticSearch Query DSL.
//!
//! ElasticSearch offers a
//! [rich DSL for searches](https://www.elastic.co/guide/en/elasticsearch/reference/1.x/query-dsl.html).
//! It is JSON based, and therefore very easy to use and composable if using from a
//! dynamic language (e.g.
//! [Ruby](https://github.com/elastic/elasticsearch-ruby/tree/master/elasticsearch-dsl#features-overview));
//! but Rust, being a staticly-typed language, things are different.  The `rs_es::query`
//! module defines a set of builder objects which can be similarly composed to the same
//! ends.
//!
//! For example:
//!
//! ```rust
//! use rs_es::query::Query;
//!
//! let query = Query::build_bool()
//!     .with_must(vec![Query::build_term("field_a",
//!                                       "value").build(),
//!                     Query::build_range("field_b")
//!                           .with_gte(5)
//!                           .with_lt(10)
//!                           .build()])
//!     .build();
//! ```

use std::collections::BTreeMap;

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

use serde::{Serialize, Serializer};

use ::json::ShouldSkip;
use ::util::StrJoin;

#[macro_use]
mod common;

pub mod compound;
pub mod full_text;
pub mod functions;
pub mod geo;
pub mod joining;
pub mod specialized;
pub mod term;

// Miscellaneous types required by queries go here

// Enums

/// Minimum should match - used in numerous queries
/// TODO: should go somewhere specific
#[derive(Debug)]
pub struct CombinationMinimumShouldMatch {
    first: MinimumShouldMatch,
    second: MinimumShouldMatch
}

impl CombinationMinimumShouldMatch {
    pub fn new(first: A, second: B) -> CombinationMinimumShouldMatch
        where A: Into<MinimumShouldMatch>,
              B: Into<MinimumShouldMatch>
    {
        CombinationMinimumShouldMatch {
            first:  first.into(),
            second: second.into()
        }
    }
}

impl ToString for CombinationMinimumShouldMatch {
    fn to_string(&self) -> String {
        format!("{}<{}", self.first.to_string(), self.second.to_string())
    }
}

impl ToJson for CombinationMinimumShouldMatch {
    fn to_json(&self) -> Json {
        Json::String(self.to_string())
    }
}

impl Serialize for CombinationMinimumShouldMatch {
    fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
        where S: Serializer {
        self.to_string().serialize(serializer)
    }
}

#[derive(Debug)]
pub enum MinimumShouldMatch {
    Integer(i64),
    Percentage(f64),
    Combination(Box<CombinationMinimumShouldMatch>),
    MultipleCombination(Vec<CombinationMinimumShouldMatch>),
    LowHigh(i64, i64)
}

from!(i64, MinimumShouldMatch, Integer);
from!(f64, MinimumShouldMatch, Percentage);
from_exp!(CombinationMinimumShouldMatch,
          MinimumShouldMatch,
          from,
          MinimumShouldMatch::Combination(Box::new(from)));
from!(Vec<CombinationMinimumShouldMatch>, MinimumShouldMatch, MultipleCombination);
from_exp!((i64, i64),
          MinimumShouldMatch,
          from,
          MinimumShouldMatch::LowHigh(from.0, from.1));

impl ToString for MinimumShouldMatch {
    fn to_string(&self) -> String {
        match self {
            &MinimumShouldMatch::Integer(val) => val.to_string(),
            &MinimumShouldMatch::Percentage(val) => {
                format!("{}%", val)
            },
            _ => panic!("Can't convert {:?} to String", self)
        }
    }
}

impl ToJson for MinimumShouldMatch {
    fn to_json(&self) -> Json {
        match self {
            &MinimumShouldMatch::Integer(val) => val.to_json(),
            &MinimumShouldMatch::Percentage(_) => {
                self.to_string().to_json()
            },
            &MinimumShouldMatch::Combination(ref comb) => {
                comb.to_json()
            },
            &MinimumShouldMatch::MultipleCombination(ref combs) => {
                Json::String(combs.iter().map(|c| c.to_string()).join(" "))
            }
            &MinimumShouldMatch::LowHigh(low, high) => {
                let mut d = BTreeMap::new();
                d.insert("low_freq".to_owned(), low.to_json());
                d.insert("high_freq".to_owned(), high.to_json());
                Json::Object(d)
            }
        }
    }
}

impl Serialize for MinimumShouldMatch {
    fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
        where S: Serializer {
        match self {
            &MinimumShouldMatch::Integer(val) => val.serialize(serializer),
            &MinimumShouldMatch::Percentage(_) => self.to_string().serialize(serializer),
            &MinimumShouldMatch::Combination(ref comb) => comb.serialize(serializer),
            &MinimumShouldMatch::MultipleCombination(ref combs) => {
                combs.iter().map(|c| c.to_string()).join(" ").serialize(serializer)
            },
            &MinimumShouldMatch::LowHigh(low, high) => {
                let mut d = BTreeMap::new();
                d.insert("low_freq", low);
                d.insert("high_freq", high);
                d.serialize(serializer)
            }
        }
    }
}

/// Fuzziness
#[derive(Debug)]
pub enum Fuzziness {
    Auto,
    LevenshteinDistance(i64),
    Proportionate(f64)
}

from!(i64, Fuzziness, LevenshteinDistance);
from!(f64, Fuzziness, Proportionate);

impl ToJson for Fuzziness {
    fn to_json(&self) -> Json {
        use self::Fuzziness::{Auto, LevenshteinDistance, Proportionate};
        match self {
            &Auto                      => "auto".to_json(),
            &LevenshteinDistance(dist) => dist.to_json(),
            &Proportionate(prop)       => prop.to_json()
        }
    }
}

// Flags

/// Flags - multiple operations can take a set of flags, each set is dependent
/// on the operation in question, but they're all formatted to a similar looking
/// String
#[derive(Debug)]
pub struct Flags(Vec)
    where A: AsRef<str>;

impl ToJson for Flags
where A: AsRef<str> {
    fn to_json(&self) -> Json {
        Json::String(self.0.iter().join("|"))
    }
}

impl From<Vec> for Flags
where A: AsRef<str> {
    fn from(from: Vec) -> Self {
        Flags(from)
    }
}

/// ScoreMode
#[derive(Debug)]
pub enum ScoreMode {
    Multiply,
    Sum,
    Avg,
    First,
    Max,
    Min
}

impl ToJson for ScoreMode {
    fn to_json(&self) -> Json {
        match self {
            &ScoreMode::Multiply => "multiply".to_json(),
            &ScoreMode::Sum => "sum".to_json(),
            &ScoreMode::Avg => "avg".to_json(),
            &ScoreMode::First => "first".to_json(),
            &ScoreMode::Max => "max".to_json(),
            &ScoreMode::Min => "min".to_json()
        }
    }
}

/// Query represents all available queries
///
/// Each value is boxed as Queries can be recursive, they also vary
/// significantly in size

// TODO: Filters and Queries are merged, ensure all filters are included in this enum
#[derive(Debug, Serialize)]
pub enum Query {
    // TODO - uncomment one-level to re-enable after Serdeification
    #[serde(rename="match_all")]
    MatchAll(Box<MatchAllQuery>),

    // // Full-text queries
    // Match(Box<full_text::MatchQuery>),
    // MultiMatch(Box<full_text::MultiMatchQuery>),
    // Common(Box<full_text::CommonQuery>),
    // QueryString(Box<full_text::QueryStringQuery>),
    // SimpleQueryString(Box<full_text::SimpleQueryStringQuery>),

    // // Term level queries
    #[serde(rename="term")]
    Term(Box<term::TermQuery>),
    #[serde(rename="terms")]
    Terms(Box<term::TermsQuery>),
    #[serde(rename="range")]
    Range(Box<term::RangeQuery>),
    // Exists(Box<term::ExistsQuery>),
    // // Not implementing the Missing query, as it's deprecated, use `must_not` and `Exists`
    // // instead
    // Prefix(Box<term::PrefixQuery>),
    // Wildcard(Box<term::WildcardQuery>),
    // Regexp(Box<term::RegexpQuery>),
    // Fuzzy(Box<term::FuzzyQuery>),
    // Type(Box<term::TypeQuery>),
    // Ids(Box<term::IdsQuery>),

    // // Compound queries
    // ConstantScore(Box<compound::ConstantScoreQuery>),
    Bool(Box<compound::BoolQuery>),
    // DisMax(Box<compound::DisMaxQuery>),
    // FunctionScore(Box<compound::FunctionScoreQuery>),
    // Boosting(Box<compound::BoostingQuery>),
    // Indices(Box<compound::IndicesQuery>),
    // // Not implementing the And query, as it's deprecated, use `bool` instead.
    // // Not implementing the Not query, as it's deprecated
    // // Not implementing the Or query, as it's deprecated, use `bool` instead.
    // // Not implementing the Filtered query, as it's deprecated.
    // // Not implementing the Limit query, as it's deprecated.

    // // Joining queries
    // Nested(Box<joining::NestedQuery>),
    // HasChild(Box<joining::HasChildQuery>),
    // HasParent(Box<joining::HasParentQuery>),

    // // Geo queries
    // GeoShape(Box<geo::GeoShapeQuery>),
    // GeoBoundingBox(Box<geo::GeoBoundingBoxQuery>),
    // GeoDistance(Box<geo::GeoDistanceQuery>),
    // // TODO: implement me - pending changes to range query
    // //GeoDistanceRange(Box<geo::GeoDistanceRangeQuery>)
    // GeoPolygon(Box<geo::GeoPolygonQuery>),
    // GeohashCell(Box<geo::GeohashCellQuery>),

    // // Specialized queries
    // MoreLikeThis(Box<specialized::MoreLikeThisQuery>),
    // // TODO: template queries
    // // TODO: Search by script

    // // Span queries
    // // TODO: SpanTerm(Box<term::TermQuery>),
    // // TODO: Span multi term query
    // // TODO: Span first query
    // // TODO: Span near query
    // // TODO: Span or query
    // // TODO: Span not query
    // // TODO: Span containing query
    // // TODO: Span within query
}

impl Default for Query {
    fn default() -> Query {
        // TODO - put this back
        //Query::MatchAll(Default::default())
        Query::build_terms("test_idx").build()
    }
}

/// Convert a Query to Json
// TODO - remove
impl ToJson for Query {
    fn to_json(&self) -> Json {
        // TODO - remove this entirely
        unimplemented!()
    }
}

// Specific query types go here

/// Match all query

#[derive(Debug, Default, Serialize)]
pub struct MatchAllQuery {
    #[serde(skip_serializing_if="ShouldSkip::should_skip")]
    boost: Option<f64>,
}

impl Query {
    pub fn build_match_all() -> MatchAllQuery {
        MatchAllQuery::default()
    }
}

impl MatchAllQuery {
    add_option!(with_boost, boost, f64);

    build!(MatchAll);
}

impl ToJson for MatchAllQuery {
    fn to_json(&self) -> Json {
        let mut d = BTreeMap::new();
        optional_add!(self, d, boost);
        Json::Object(d)
    }
}

#[cfg(test)]
mod tests {
    // TODO: replace with Serde
    extern crate rustc_serialize;

    extern crate serde_json;

    use rustc_serialize::json::ToJson;

    use super::{Flags, Query};
    use super::full_text::SimpleQueryStringFlags;
    use super::functions::Function;
    use super::term::TermsQueryLookup;

    #[test]
    fn test_simple_query_string_flags() {
        let opts = vec![SimpleQueryStringFlags::And, SimpleQueryStringFlags::Not];
        let flags:Flags<SimpleQueryStringFlags> = opts.into();
        let json = flags.to_json();
        assert_eq!("AND|NOT", json.as_string().unwrap());
    }

    #[test]
    fn test_terms_query() {
        let terms_query = Query::build_terms("field_name")
            .with_values(vec!["a", "b", "c"])
            .build();
        assert_eq!("{\"terms\":{\"field_name\":[\"a\",\"b\",\"c\"]}}",
                   serde_json::to_string(&terms_query).unwrap());
        assert_eq!("{\"terms\":{\"field_name\":[\"a\",\"b\",\"c\"]}}",
                   terms_query.to_json().to_string());

        let terms_query_2 = Query::build_terms("field_name")
            .with_values(["a", "b", "c"].as_ref())
            .build();
        assert_eq!("{\"terms\":{\"field_name\":[\"a\",\"b\",\"c\"]}}",
                   serde_json::to_string(&terms_query_2).unwrap());
        assert_eq!("{\"terms\":{\"field_name\":[\"a\",\"b\",\"c\"]}}",
                   terms_query_2.to_json().to_string());

        let terms_query_3 = Query::build_terms("field_name")
            .with_values(TermsQueryLookup::new(123, "blah.de.blah").with_index("other"))
            .build();
        assert_eq!("{\"terms\":{\"field_name\":{\"id\":123,\"index\":\"other\",\"path\":\"blah.de.blah\"}}}",
                   serde_json::to_string(&terms_query_3).unwrap());
        assert_eq!("{\"terms\":{\"field_name\":{\"id\":123,\"index\":\"other\",\"path\":\"blah.de.blah\"}}}",
                   terms_query_3.to_json().to_string());
    }

    // TODO - re-enable
    // #[test]
    // fn test_function_score_query() {
    //     let function_score_query = Query::build_function_score()
    //         .with_function(Function::build_script_score("this_is_a_script")
    //                        .with_lang("made_up")
    //                        .add_param("A", 12)
    //                        .build())
    //         .build();
    //     assert_eq!("{\"function_score\":{\"functions\":[{\"script_score\":{\"inline\":\"this_is_a_script\",\"lang\":\"made_up\",\"params\":{\"A\":12}}}]}}",
    //                function_score_query.to_json().to_string());
    // }
}
<commit_msg>Stop testing the old way of generating JSON<commit_after>/*
 * Copyright 2015-2016 Ben Ashford
 *
 * 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.
 */

//! Implementation of the ElasticSearch Query DSL.
//!
//! ElasticSearch offers a
//! [rich DSL for searches](https://www.elastic.co/guide/en/elasticsearch/reference/1.x/query-dsl.html).
//! It is JSON based, and therefore very easy to use and composable if using from a
//! dynamic language (e.g.
//! [Ruby](https://github.com/elastic/elasticsearch-ruby/tree/master/elasticsearch-dsl#features-overview));
//! but Rust, being a staticly-typed language, things are different.  The `rs_es::query`
//! module defines a set of builder objects which can be similarly composed to the same
//! ends.
//!
//! For example:
//!
//! ```rust
//! use rs_es::query::Query;
//!
//! let query = Query::build_bool()
//!     .with_must(vec![Query::build_term("field_a",
//!                                       "value").build(),
//!                     Query::build_range("field_b")
//!                           .with_gte(5)
//!                           .with_lt(10)
//!                           .build()])
//!     .build();
//! ```

use std::collections::BTreeMap;

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

use serde::{Serialize, Serializer};

use ::json::ShouldSkip;
use ::util::StrJoin;

#[macro_use]
mod common;

pub mod compound;
pub mod full_text;
pub mod functions;
pub mod geo;
pub mod joining;
pub mod specialized;
pub mod term;

// Miscellaneous types required by queries go here

// Enums

/// Minimum should match - used in numerous queries
/// TODO: should go somewhere specific
#[derive(Debug)]
pub struct CombinationMinimumShouldMatch {
    first: MinimumShouldMatch,
    second: MinimumShouldMatch
}

impl CombinationMinimumShouldMatch {
    pub fn new(first: A, second: B) -> CombinationMinimumShouldMatch
        where A: Into<MinimumShouldMatch>,
              B: Into<MinimumShouldMatch>
    {
        CombinationMinimumShouldMatch {
            first:  first.into(),
            second: second.into()
        }
    }
}

impl ToString for CombinationMinimumShouldMatch {
    fn to_string(&self) -> String {
        format!("{}<{}", self.first.to_string(), self.second.to_string())
    }
}

impl ToJson for CombinationMinimumShouldMatch {
    fn to_json(&self) -> Json {
        Json::String(self.to_string())
    }
}

impl Serialize for CombinationMinimumShouldMatch {
    fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
        where S: Serializer {
        self.to_string().serialize(serializer)
    }
}

#[derive(Debug)]
pub enum MinimumShouldMatch {
    Integer(i64),
    Percentage(f64),
    Combination(Box<CombinationMinimumShouldMatch>),
    MultipleCombination(Vec<CombinationMinimumShouldMatch>),
    LowHigh(i64, i64)
}

from!(i64, MinimumShouldMatch, Integer);
from!(f64, MinimumShouldMatch, Percentage);
from_exp!(CombinationMinimumShouldMatch,
          MinimumShouldMatch,
          from,
          MinimumShouldMatch::Combination(Box::new(from)));
from!(Vec<CombinationMinimumShouldMatch>, MinimumShouldMatch, MultipleCombination);
from_exp!((i64, i64),
          MinimumShouldMatch,
          from,
          MinimumShouldMatch::LowHigh(from.0, from.1));

impl ToString for MinimumShouldMatch {
    fn to_string(&self) -> String {
        match self {
            &MinimumShouldMatch::Integer(val) => val.to_string(),
            &MinimumShouldMatch::Percentage(val) => {
                format!("{}%", val)
            },
            _ => panic!("Can't convert {:?} to String", self)
        }
    }
}

impl ToJson for MinimumShouldMatch {
    fn to_json(&self) -> Json {
        match self {
            &MinimumShouldMatch::Integer(val) => val.to_json(),
            &MinimumShouldMatch::Percentage(_) => {
                self.to_string().to_json()
            },
            &MinimumShouldMatch::Combination(ref comb) => {
                comb.to_json()
            },
            &MinimumShouldMatch::MultipleCombination(ref combs) => {
                Json::String(combs.iter().map(|c| c.to_string()).join(" "))
            }
            &MinimumShouldMatch::LowHigh(low, high) => {
                let mut d = BTreeMap::new();
                d.insert("low_freq".to_owned(), low.to_json());
                d.insert("high_freq".to_owned(), high.to_json());
                Json::Object(d)
            }
        }
    }
}

impl Serialize for MinimumShouldMatch {
    fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
        where S: Serializer {
        match self {
            &MinimumShouldMatch::Integer(val) => val.serialize(serializer),
            &MinimumShouldMatch::Percentage(_) => self.to_string().serialize(serializer),
            &MinimumShouldMatch::Combination(ref comb) => comb.serialize(serializer),
            &MinimumShouldMatch::MultipleCombination(ref combs) => {
                combs.iter().map(|c| c.to_string()).join(" ").serialize(serializer)
            },
            &MinimumShouldMatch::LowHigh(low, high) => {
                let mut d = BTreeMap::new();
                d.insert("low_freq", low);
                d.insert("high_freq", high);
                d.serialize(serializer)
            }
        }
    }
}

/// Fuzziness
#[derive(Debug)]
pub enum Fuzziness {
    Auto,
    LevenshteinDistance(i64),
    Proportionate(f64)
}

from!(i64, Fuzziness, LevenshteinDistance);
from!(f64, Fuzziness, Proportionate);

impl ToJson for Fuzziness {
    fn to_json(&self) -> Json {
        use self::Fuzziness::{Auto, LevenshteinDistance, Proportionate};
        match self {
            &Auto                      => "auto".to_json(),
            &LevenshteinDistance(dist) => dist.to_json(),
            &Proportionate(prop)       => prop.to_json()
        }
    }
}

// Flags

/// Flags - multiple operations can take a set of flags, each set is dependent
/// on the operation in question, but they're all formatted to a similar looking
/// String
#[derive(Debug)]
pub struct Flags(Vec)
    where A: AsRef<str>;

impl ToJson for Flags
where A: AsRef<str> {
    fn to_json(&self) -> Json {
        Json::String(self.0.iter().join("|"))
    }
}

impl From<Vec> for Flags
where A: AsRef<str> {
    fn from(from: Vec) -> Self {
        Flags(from)
    }
}

/// ScoreMode
#[derive(Debug)]
pub enum ScoreMode {
    Multiply,
    Sum,
    Avg,
    First,
    Max,
    Min
}

impl ToJson for ScoreMode {
    fn to_json(&self) -> Json {
        match self {
            &ScoreMode::Multiply => "multiply".to_json(),
            &ScoreMode::Sum => "sum".to_json(),
            &ScoreMode::Avg => "avg".to_json(),
            &ScoreMode::First => "first".to_json(),
            &ScoreMode::Max => "max".to_json(),
            &ScoreMode::Min => "min".to_json()
        }
    }
}

/// Query represents all available queries
///
/// Each value is boxed as Queries can be recursive, they also vary
/// significantly in size

// TODO: Filters and Queries are merged, ensure all filters are included in this enum
#[derive(Debug, Serialize)]
pub enum Query {
    // TODO - uncomment one-level to re-enable after Serdeification
    #[serde(rename="match_all")]
    MatchAll(Box<MatchAllQuery>),

    // // Full-text queries
    // Match(Box<full_text::MatchQuery>),
    // MultiMatch(Box<full_text::MultiMatchQuery>),
    // Common(Box<full_text::CommonQuery>),
    // QueryString(Box<full_text::QueryStringQuery>),
    // SimpleQueryString(Box<full_text::SimpleQueryStringQuery>),

    // // Term level queries
    #[serde(rename="term")]
    Term(Box<term::TermQuery>),
    #[serde(rename="terms")]
    Terms(Box<term::TermsQuery>),
    #[serde(rename="range")]
    Range(Box<term::RangeQuery>),
    // Exists(Box<term::ExistsQuery>),
    // // Not implementing the Missing query, as it's deprecated, use `must_not` and `Exists`
    // // instead
    // Prefix(Box<term::PrefixQuery>),
    // Wildcard(Box<term::WildcardQuery>),
    // Regexp(Box<term::RegexpQuery>),
    // Fuzzy(Box<term::FuzzyQuery>),
    // Type(Box<term::TypeQuery>),
    // Ids(Box<term::IdsQuery>),

    // // Compound queries
    // ConstantScore(Box<compound::ConstantScoreQuery>),
    Bool(Box<compound::BoolQuery>),
    // DisMax(Box<compound::DisMaxQuery>),
    // FunctionScore(Box<compound::FunctionScoreQuery>),
    // Boosting(Box<compound::BoostingQuery>),
    // Indices(Box<compound::IndicesQuery>),
    // // Not implementing the And query, as it's deprecated, use `bool` instead.
    // // Not implementing the Not query, as it's deprecated
    // // Not implementing the Or query, as it's deprecated, use `bool` instead.
    // // Not implementing the Filtered query, as it's deprecated.
    // // Not implementing the Limit query, as it's deprecated.

    // // Joining queries
    // Nested(Box<joining::NestedQuery>),
    // HasChild(Box<joining::HasChildQuery>),
    // HasParent(Box<joining::HasParentQuery>),

    // // Geo queries
    // GeoShape(Box<geo::GeoShapeQuery>),
    // GeoBoundingBox(Box<geo::GeoBoundingBoxQuery>),
    // GeoDistance(Box<geo::GeoDistanceQuery>),
    // // TODO: implement me - pending changes to range query
    // //GeoDistanceRange(Box<geo::GeoDistanceRangeQuery>)
    // GeoPolygon(Box<geo::GeoPolygonQuery>),
    // GeohashCell(Box<geo::GeohashCellQuery>),

    // // Specialized queries
    // MoreLikeThis(Box<specialized::MoreLikeThisQuery>),
    // // TODO: template queries
    // // TODO: Search by script

    // // Span queries
    // // TODO: SpanTerm(Box<term::TermQuery>),
    // // TODO: Span multi term query
    // // TODO: Span first query
    // // TODO: Span near query
    // // TODO: Span or query
    // // TODO: Span not query
    // // TODO: Span containing query
    // // TODO: Span within query
}

impl Default for Query {
    fn default() -> Query {
        // TODO - put this back
        //Query::MatchAll(Default::default())
        Query::build_terms("test_idx").build()
    }
}

/// Convert a Query to Json
// TODO - remove
impl ToJson for Query {
    fn to_json(&self) -> Json {
        // TODO - remove this entirely
        unimplemented!()
    }
}

// Specific query types go here

/// Match all query

#[derive(Debug, Default, Serialize)]
pub struct MatchAllQuery {
    #[serde(skip_serializing_if="ShouldSkip::should_skip")]
    boost: Option<f64>,
}

impl Query {
    pub fn build_match_all() -> MatchAllQuery {
        MatchAllQuery::default()
    }
}

impl MatchAllQuery {
    add_option!(with_boost, boost, f64);

    build!(MatchAll);
}

impl ToJson for MatchAllQuery {
    fn to_json(&self) -> Json {
        let mut d = BTreeMap::new();
        optional_add!(self, d, boost);
        Json::Object(d)
    }
}

#[cfg(test)]
mod tests {
    // TODO: replace with Serde
    extern crate rustc_serialize;

    extern crate serde_json;

    use rustc_serialize::json::ToJson;

    use super::{Flags, Query};
    use super::full_text::SimpleQueryStringFlags;
    use super::functions::Function;
    use super::term::TermsQueryLookup;

    #[test]
    fn test_simple_query_string_flags() {
        let opts = vec![SimpleQueryStringFlags::And, SimpleQueryStringFlags::Not];
        let flags:Flags<SimpleQueryStringFlags> = opts.into();
        let json = flags.to_json();
        assert_eq!("AND|NOT", json.as_string().unwrap());
    }

    #[test]
    fn test_terms_query() {
        let terms_query = Query::build_terms("field_name")
            .with_values(vec!["a", "b", "c"])
            .build();
        assert_eq!("{\"terms\":{\"field_name\":[\"a\",\"b\",\"c\"]}}",
                   serde_json::to_string(&terms_query).unwrap());

        let terms_query_2 = Query::build_terms("field_name")
            .with_values(["a", "b", "c"].as_ref())
            .build();
        assert_eq!("{\"terms\":{\"field_name\":[\"a\",\"b\",\"c\"]}}",
                   serde_json::to_string(&terms_query_2).unwrap());

        let terms_query_3 = Query::build_terms("field_name")
            .with_values(TermsQueryLookup::new(123, "blah.de.blah").with_index("other"))
            .build();
        assert_eq!("{\"terms\":{\"field_name\":{\"id\":123,\"index\":\"other\",\"path\":\"blah.de.blah\"}}}",
                   serde_json::to_string(&terms_query_3).unwrap());
    }

    // TODO - re-enable
    // #[test]
    // fn test_function_score_query() {
    //     let function_score_query = Query::build_function_score()
    //         .with_function(Function::build_script_score("this_is_a_script")
    //                        .with_lang("made_up")
    //                        .add_param("A", 12)
    //                        .build())
    //         .build();
    //     assert_eq!("{\"function_score\":{\"functions\":[{\"script_score\":{\"inline\":\"this_is_a_script\",\"lang\":\"made_up\",\"params\":{\"A\":12}}}]}}",
    //                function_score_query.to_json().to_string());
    // }
}
<|endoftext|>


<commit_before>/*
 * Copyright 2018-2019 Ben Ashford
 *
 * Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
 * http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
 * <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
 * option. This file may not be copied, modified, or distributed
 * except according to those terms.
 */

use std::{
    fmt,
    future::Future,
    sync::{Mutex, RwLock},
    time::Duration,
};

use tokio::time::timeout;

use crate::error::{self, ConnectionReason};

type WorkFn<T, A> = dyn Fn(&T, A) -> Result<(), error::Error>;
type ConnFn<T> = dyn Fn() -> Box<dyn Future<Output = Result<T, error::Error>> + Unpin>;

pub(crate) struct Reconnect<A, T> {
    state: RwLock<ReconnectState<T>>,
    work_fn: Box<WorkFn<T, A>>,
    conn_fn: Box<ConnFn<T>>,
}

impl<A, T> fmt::Debug for Reconnect<A, T>
where
    T: fmt::Debug,
{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("Reconnect")
            .field("state", &self.state)
            .field("work_fn", &String::from("REDACTED"))
            .field("conn_fn", &String::from("REDACTED"))
            .finish()
    }
}

pub(crate) async fn reconnect<A, T, W, C>(w: W, c: C) -> Result<Reconnect<A, T>, error::Error>
where
    A: Send + 'static,
    W: Fn(&T, A) -> Result<(), error::Error> + 'static,
    C: Fn() -> Box<dyn Future<Output = Result<T, error::Error>> + Unpin> + 'static,
    T: Clone + Send + Sync + 'static,
{
    let r = Reconnect {
        state: RwLock::new(ReconnectState::NotConnected),

        work_fn: Box::new(w),
        conn_fn: Box::new(c),
    };
    r.reconnect().await?;
    Ok(r)
}

enum ReconnectState<T> {
    NotConnected,
    Connected(T),
    ConnectionFailed(Mutex<Option<error::Error>>),
    Connecting,
}

impl<T> fmt::Debug for ReconnectState<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "ReconnectState::")?;
        match self {
            NotConnected => write!(f, "NotConnected"),
            Connected(_) => write!(f, "Connected"),
            ConnectionFailed(_) => write!(f, "ConnectionFailed"),
            Connecting => write!(f, "Connecting"),
        }
    }
}

use self::ReconnectState::*;

const CONNECTION_TIMEOUT_SECONDS: u64 = 10;
const CONNECTION_TIMEOUT: Duration = Duration::from_secs(CONNECTION_TIMEOUT_SECONDS);

impl<A, T> Reconnect<A, T>
where
    A: Send + 'static,
    T: Clone + Send + Sync + 'static,
{
    fn call_work(&self, t: &T, a: A) -> Result<bool, error::Error> {
        if let Err(e) = (self.work_fn)(t, a) {
            match e {
                error::Error::IO(_) | error::Error::Unexpected(_) => {
                    log::error!("Error in work_fn will force connection closed, next command will attempt to re-establish connection: {}", e);
                    return Ok(false);
                }
                _ => (),
            }
            Err(e)
        } else {
            Ok(true)
        }
    }

    // Called when a bad situation has been discovered, force the connections to re-connect.
    fn disconnect(&self) {
        let mut state = self.state.write().expect("Cannot obtain a write lock");
        *state = NotConnected;
    }

    pub(crate) fn do_work(&self, a: A) -> Result<(), error::Error> {
        let rv = {
            let state = self.state.read().expect("Cannot obtain read lock");
            match *state {
                NotConnected => Err(error::Error::Connection(ConnectionReason::NotConnected)),
                Connected(ref t) => {
                    let success = self.call_work(t, a)?;
                    if !success {
                        drop(state);
                        self.disconnect();
                        self.reconnect_spawn();
                    }
                    return Ok(());
                }
                ConnectionFailed(ref e) => {
                    let mut lock = e.lock().expect("Poisioned lock");
                    let e = match lock.take() {
                        Some(e) => e,
                        None => error::Error::Connection(ConnectionReason::NotConnected),
                    };
                    Err(e)
                }
                Connecting => {
                    return Err(error::Error::Connection(ConnectionReason::Connecting));
                }
            }
        };
        self.reconnect_spawn();
        rv
    }

    /// Returns a future that completes when the connection is established or failed to establish
    /// used only for timing.
    async fn reconnect(&self) -> Result<(), error::Error> {
        {
            let mut state = self.state.write().expect("Cannot obtain write lock");

            log::info!("Attempting to reconnect, current state: {:?}", *state);

            match *state {
                Connected(_) => {
                    return Err(error::Error::Connection(ConnectionReason::Connected));
                }
                Connecting => {
                    return Err(error::Error::Connection(ConnectionReason::Connecting));
                }
                NotConnected | ConnectionFailed(_) => (),
            }
            *state = ReconnectState::Connecting;
        }

        let connection = match timeout(CONNECTION_TIMEOUT, (self.conn_fn)()).await {
            Ok(con_r) => con_r,
            Err(_) => {
                return Err(error::internal(format!(
                    "Connection timed-out after {} seconds",
                    CONNECTION_TIMEOUT_SECONDS
                )))
            }
        };

        let mut state = self.state.write().expect("Cannot obtain write lock");

        match *state {
            NotConnected | Connecting => match connection {
                Ok(t) => {
                    log::info!("Connection established");
                    *state = Connected(t);
                    Ok(())
                }
                Err(e) => {
                    log::error!("Connection cannot be established: {}", e);
                    *state = ConnectionFailed(Mutex::new(Some(e)));
                    Err(error::Error::Connection(ConnectionReason::ConnectionFailed))
                }
            },
            ConnectionFailed(_) => panic!("The connection state wasn't reset before connecting"),
            Connected(_) => panic!("A connected state shouldn't be attempting to reconnect"),
        }
    }

    fn reconnect_spawn(&self) {
        unimplemented!()
        // let reconnect_f = self
        //     .reconnect()
        //     .map_err(|e| log::error!("Error asynchronously reconnecting: {}", e));

        // let mut executor = DefaultExecutor::current();
        // executor
        //     .spawn(Box::new(reconnect_f))
        //     .expect("Cannot spawn asynchronous reconnection");
    }
}
<commit_msg>Ensuring reconnection<commit_after>/*
 * Copyright 2018-2019 Ben Ashford
 *
 * Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
 * http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
 * <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
 * option. This file may not be copied, modified, or distributed
 * except according to those terms.
 */

use std::{
    fmt,
    future::Future,
    sync::{Arc, Mutex, RwLock},
    time::Duration,
};

use futures_util::{
    future::{self, Either},
    TryFutureExt,
};

use tokio::time::timeout;

use crate::error::{self, ConnectionReason};

type WorkFn<T, A> = dyn Fn(&T, A) -> Result<(), error::Error> + Send + Sync;
type ConnFn<T> = dyn Fn() -> Box<dyn Future<Output = Result<T, error::Error>> + Unpin + Send + Sync>
    + Send
    + Sync;

struct ReconnectInner<A, T> {
    state: RwLock<ReconnectState<T>>,
    work_fn: Box<WorkFn<T, A>>,
    conn_fn: Box<ConnFn<T>>,
}

pub(crate) struct Reconnect<A, T>(Arc<ReconnectInner<A, T>>);

impl<A, T> Clone for Reconnect<A, T> {
    fn clone(&self) -> Self {
        Reconnect(self.0.clone())
    }
}

impl<A, T> fmt::Debug for Reconnect<A, T>
where
    T: fmt::Debug,
{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("Reconnect")
            .field("state", &self.0.state)
            .field("work_fn", &String::from("REDACTED"))
            .field("conn_fn", &String::from("REDACTED"))
            .finish()
    }
}

pub(crate) async fn reconnect<A, T, W, C>(w: W, c: C) -> Result<Reconnect<A, T>, error::Error>
where
    A: Send + 'static,
    W: Fn(&T, A) -> Result<(), error::Error> + Send + Sync + 'static,
    C: Fn() -> Box<dyn Future<Output = Result<T, error::Error>> + Unpin + Send + Sync>
        + Send
        + Sync
        + 'static,
    T: Clone + Send + Sync + 'static,
{
    let r = Reconnect(Arc::new(ReconnectInner {
        state: RwLock::new(ReconnectState::NotConnected),

        work_fn: Box::new(w),
        conn_fn: Box::new(c),
    }));
    r.reconnect().await?;
    Ok(r)
}

enum ReconnectState<T> {
    NotConnected,
    Connected(T),
    ConnectionFailed(Mutex<Option<error::Error>>),
    Connecting,
}

impl<T> fmt::Debug for ReconnectState<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "ReconnectState::")?;
        match self {
            NotConnected => write!(f, "NotConnected"),
            Connected(_) => write!(f, "Connected"),
            ConnectionFailed(_) => write!(f, "ConnectionFailed"),
            Connecting => write!(f, "Connecting"),
        }
    }
}

use self::ReconnectState::*;

const CONNECTION_TIMEOUT_SECONDS: u64 = 10;
const CONNECTION_TIMEOUT: Duration = Duration::from_secs(CONNECTION_TIMEOUT_SECONDS);

impl<A, T> Reconnect<A, T>
where
    A: Send + 'static,
    T: Clone + Send + Sync + 'static,
{
    fn call_work(&self, t: &T, a: A) -> Result<bool, error::Error> {
        if let Err(e) = (self.0.work_fn)(t, a) {
            match e {
                error::Error::IO(_) | error::Error::Unexpected(_) => {
                    log::error!("Error in work_fn will force connection closed, next command will attempt to re-establish connection: {}", e);
                    return Ok(false);
                }
                _ => (),
            }
            Err(e)
        } else {
            Ok(true)
        }
    }

    // Called when a bad situation has been discovered, force the connections to re-connect.
    fn disconnect(&self) {
        let mut state = self.0.state.write().expect("Cannot obtain a write lock");
        *state = NotConnected;
    }

    pub(crate) fn do_work(&self, a: A) -> Result<(), error::Error> {
        let rv = {
            let state = self.0.state.read().expect("Cannot obtain read lock");
            match *state {
                NotConnected => Err(error::Error::Connection(ConnectionReason::NotConnected)),
                Connected(ref t) => {
                    let success = self.call_work(t, a)?;
                    if !success {
                        drop(state);
                        self.disconnect();
                        self.reconnect_spawn();
                    }
                    return Ok(());
                }
                ConnectionFailed(ref e) => {
                    let mut lock = e.lock().expect("Poisioned lock");
                    let e = match lock.take() {
                        Some(e) => e,
                        None => error::Error::Connection(ConnectionReason::NotConnected),
                    };
                    Err(e)
                }
                Connecting => {
                    return Err(error::Error::Connection(ConnectionReason::Connecting));
                }
            }
        };
        self.reconnect_spawn();
        rv
    }

    /// Returns a future that completes when the connection is established or failed to establish
    /// used only for timing.
    fn reconnect(&self) -> impl Future<Output = Result<(), error::Error>> + Send {
        {
            let mut state = self.0.state.write().expect("Cannot obtain write lock");

            log::info!("Attempting to reconnect, current state: {:?}", *state);

            match *state {
                Connected(_) => {
                    return Either::Right(future::err(error::Error::Connection(
                        ConnectionReason::Connected,
                    )));
                }
                Connecting => {
                    return Either::Right(future::err(error::Error::Connection(
                        ConnectionReason::Connecting,
                    )));
                }
                NotConnected | ConnectionFailed(_) => (),
            }
            *state = ReconnectState::Connecting;
        }

        let reconnect = self.clone();

        let connection_f = async move {
            let connection = match timeout(CONNECTION_TIMEOUT, (reconnect.0.conn_fn)()).await {
                Ok(con_r) => con_r,
                Err(_) => {
                    return Err(error::internal(format!(
                        "Connection timed-out after {} seconds",
                        CONNECTION_TIMEOUT_SECONDS
                    )))
                }
            };

            let mut state = reconnect.0.state.write().expect("Cannot obtain write lock");

            match *state {
                NotConnected | Connecting => match connection {
                    Ok(t) => {
                        log::info!("Connection established");
                        *state = Connected(t);
                        Ok(())
                    }
                    Err(e) => {
                        log::error!("Connection cannot be established: {}", e);
                        *state = ConnectionFailed(Mutex::new(Some(e)));
                        Err(error::Error::Connection(ConnectionReason::ConnectionFailed))
                    }
                },
                ConnectionFailed(_) => {
                    panic!("The connection state wasn't reset before connecting")
                }
                Connected(_) => panic!("A connected state shouldn't be attempting to reconnect"),
            }
        };

        Either::Left(connection_f)
    }

    fn reconnect_spawn(&self) {
        let reconnect_f = self
            .reconnect()
            .map_err(|e| log::error!("Error asynchronously reconnecting: {}", e));

        tokio::spawn(reconnect_f);

        // let mut executor = DefaultExecutor::current();
        // executor
        //     .spawn(Box::new(reconnect_f))
        //     .expect("Cannot spawn asynchronous reconnection");
    }
}
<|endoftext|>


<commit_before>//! redis-rs is a rust implementation of a Redis client library.  It exposes
//! a general purpose interface to Redis and also provides specific helpers for
//! commonly used functionality.
//!
//! # Basic Operation
//!
//! To open a connection you need to create a client and then to fetch a
//! connection from it.  In the future there will be a connection pool for
//! those, currently each connection is separate and not pooled.
//!
//! Queries are then performed through the `execute` and `query` methods.
//!
//! ```rust,no_run
//! extern crate redis;
//!
//! fn main() {
//!     let client = redis::Client::open("redis://127.0.0.1/").unwrap();
//!     let con = client.get_connection().unwrap();
//!     redis::cmd("SET").arg("my_key").arg(42i).execute(&con);
//!     assert_eq!(redis::cmd("GET").arg("my_key").query(&con), Ok(42i));
//! }
//! ```
//!
//! ## Type Conversions
//!
//! Because redis inherently is mostly type-less and the protocol is not
//! exactly friendly to developers, this library provides flexible support
//! for casting values to the intended results.  This is driven through the
//! `FromRedisValue` and `ToRedisArgs` traits.
//!
//! The `arg` method of the command will accept a wide range of types through
//! the `ToRedisArgs` trait and the `query` method of a command can convert the
//! value to what you expect the function to return through the `FromRedisValue`
//! trait.  This is quite flexible and allows vectors, tuples, hashsets, hashmaps
//! as well as optional values:
//!
//! ```rust,no_run
//! # use std::collections::{HashMap, HashSet};
//! # let client = redis::Client::open("redis://127.0.0.1/").unwrap();
//! # let con = client.get_connection().unwrap();
//! let count : i32 = redis::cmd("GET").arg("my_counter").query(&con).unwrap();
//! let count = redis::cmd("GET").arg("my_counter").query(&con).unwrap_or(0i32);
//! let k : Option<String> = redis::cmd("GET").arg("missing_key").query(&con).unwrap();
//! let name : String = redis::cmd("GET").arg("my_name").query(&con).unwrap();
//! let bin : Vec<u8> = redis::cmd("GET").arg("my_binary").query(&con).unwrap();
//! let map : HashMap<String, i32> = redis::cmd("HGETALL").arg("my_hash").query(&con).unwrap();
//! let keys : Vec<String> = redis::cmd("KEYS").query(&con).unwrap();
//! let mems : HashSet<i32> = redis::cmd("SMEMBERS").arg("s").query(&con).unwrap();
//! let (k1, k2) : (String, String) = redis::cmd("MGET").arg("k1").arg("k2").query(&con).unwrap();
//! ```
//!
//! ## Iteration Protocol
//!
//! In addition to sending a single query you iterators are also supported.  When
//! used with regular bulk responses they don't give you much over querying and
//! converting into a vector (both use a vector internally) but they can also
//! be used with `SCAN` like commands in which case iteration will send more
//! queries until the cursor is exhausted:
//!
//! ```rust,no_run
//! # let client = redis::Client::open("redis://127.0.0.1/").unwrap();
//! # let con = client.get_connection().unwrap();
//! let mut cmd = redis::cmd("SSCAN");
//! let mut iter : redis::Iter<int> = cmd.arg("my_set").cursor_arg(0).iter(&con).unwrap();
//! for x in iter {
//!     // do something with the item
//! }
//! ```
//!
//! As you can see the cursor argument needs to be defined with `cursor_arg` instead
//! of `arg` so that the library knows which argument needs updating as the
//! query is run for more items.
//!
//! ## Pipelining
//!
//! In addition to simple queries you can also send command pipelines.  This
//! is provided through the `pipe` function.  It works very similar to sending
//! individual commands but you can send more than one in one go.  This also
//! allows you to ignore individual results so that matching on the end result
//! is easier:
//!
//! ```rust,no_run
//! # let client = redis::Client::open("redis://127.0.0.1/").unwrap();
//! # let con = client.get_connection().unwrap();
//! let (k1, k2) : (i32, i32) = redis::pipe()
//!     .cmd("SET").arg("key_1").arg(42i).ignore()
//!     .cmd("SET").arg("key_2").arg(43i).ignore()
//!     .cmd("GET").arg("key_1")
//!     .cmd("GET").arg("key_2").query(&con).unwrap();
//! ```
//!
//! If you want the pipeline to be wrapped in a `MULTI`/`EXEC` block you can
//! easily do that by switching the pipeline into `atomic` mode.  From the
//! caller's point of view nothing changes, the pipeline itself will take
//! care of the rest for you:
//!
//! ```rust,no_run
//! # let client = redis::Client::open("redis://127.0.0.1/").unwrap();
//! # let con = client.get_connection().unwrap();
//! let (k1, k2) : (i32, i32) = redis::pipe()
//!     .atomic()
//!     .cmd("SET").arg("key_1").arg(42i).ignore()
//!     .cmd("SET").arg("key_2").arg(43i).ignore()
//!     .cmd("GET").arg("key_1")
//!     .cmd("GET").arg("key_2").query(&con).unwrap();
//! ```
//!
//! ## PubSub
//!
//! Pubsub is currently work in progress but provided through the `PubSub`
//! connection object.  Due to the fact that Rust does not have support
//! for async IO in libnative yet, the API does not provide a way to
//! read messages with any form of timeout yet.
//!
//! Example usage:
//!
//! ```rust,no_run
//! let client = redis::Client::open("redis://127.0.0.1/").unwrap();
//! let mut pubsub = client.get_pubsub().unwrap();
//! pubsub.subscribe("channel_1").unwrap();
//! pubsub.subscribe("channel_2").unwrap();
//!
//! loop {
//!     let msg = pubsub.get_message().unwrap();
//!     let payload : String = msg.get_payload().unwrap();
//!     println!("channel '{}': {}", msg.get_channel_name(), payload);
//! }
//! ```

#![crate_name = "redis"]
#![crate_type = "lib"]
#![license = "BSD"]
#![comment = "Bindings and wrapper functions for redis."]

#![deny(non_camel_case_types)]

#![feature(macro_rules)]
#![feature(default_type_params)]

#![experimental]

extern crate url;
extern crate serialize;

/* public api */
pub use parser::{parse_redis_value, Parser};
pub use client::Client;
pub use connection::{Connection, PubSub, Msg};
pub use cmd::{cmd, Cmd, pipe, Pipeline, Iter, pack_command};

#[doc(hidden)]
pub use types::{
    /* low level values */
    Value,
        Nil,
        Int,
        Data,
        Bulk,
        Okay,
        Status,

    /* error and result types */
    Error,
    RedisResult,

    /* error kinds */
    ErrorKind,
        ResponseError,
        TypeError,
        ExecAbortError,
        BusyLoadingError,
        NoScriptError,
        ExtensionError,
        InternalIoError,

    /* utility types */
    InfoDict,

    /* conversion traits */
    FromRedisValue,
    ToRedisArgs,

    /* utility functions */
    from_redis_value,
};

pub mod macros;

mod parser;
mod client;
mod connection;
mod types;
mod cmd;
<commit_msg>Added transaction example to overview page.<commit_after>//! redis-rs is a rust implementation of a Redis client library.  It exposes
//! a general purpose interface to Redis and also provides specific helpers for
//! commonly used functionality.
//!
//! # Basic Operation
//!
//! To open a connection you need to create a client and then to fetch a
//! connection from it.  In the future there will be a connection pool for
//! those, currently each connection is separate and not pooled.
//!
//! Queries are then performed through the `execute` and `query` methods.
//!
//! ```rust,no_run
//! extern crate redis;
//!
//! fn main() {
//!     let client = redis::Client::open("redis://127.0.0.1/").unwrap();
//!     let con = client.get_connection().unwrap();
//!     redis::cmd("SET").arg("my_key").arg(42i).execute(&con);
//!     assert_eq!(redis::cmd("GET").arg("my_key").query(&con), Ok(42i));
//! }
//! ```
//!
//! ## Type Conversions
//!
//! Because redis inherently is mostly type-less and the protocol is not
//! exactly friendly to developers, this library provides flexible support
//! for casting values to the intended results.  This is driven through the
//! `FromRedisValue` and `ToRedisArgs` traits.
//!
//! The `arg` method of the command will accept a wide range of types through
//! the `ToRedisArgs` trait and the `query` method of a command can convert the
//! value to what you expect the function to return through the `FromRedisValue`
//! trait.  This is quite flexible and allows vectors, tuples, hashsets, hashmaps
//! as well as optional values:
//!
//! ```rust,no_run
//! # use std::collections::{HashMap, HashSet};
//! # let client = redis::Client::open("redis://127.0.0.1/").unwrap();
//! # let con = client.get_connection().unwrap();
//! let count : i32 = redis::cmd("GET").arg("my_counter").query(&con).unwrap();
//! let count = redis::cmd("GET").arg("my_counter").query(&con).unwrap_or(0i32);
//! let k : Option<String> = redis::cmd("GET").arg("missing_key").query(&con).unwrap();
//! let name : String = redis::cmd("GET").arg("my_name").query(&con).unwrap();
//! let bin : Vec<u8> = redis::cmd("GET").arg("my_binary").query(&con).unwrap();
//! let map : HashMap<String, i32> = redis::cmd("HGETALL").arg("my_hash").query(&con).unwrap();
//! let keys : Vec<String> = redis::cmd("KEYS").query(&con).unwrap();
//! let mems : HashSet<i32> = redis::cmd("SMEMBERS").arg("s").query(&con).unwrap();
//! let (k1, k2) : (String, String) = redis::cmd("MGET").arg("k1").arg("k2").query(&con).unwrap();
//! ```
//!
//! ## Iteration Protocol
//!
//! In addition to sending a single query you iterators are also supported.  When
//! used with regular bulk responses they don't give you much over querying and
//! converting into a vector (both use a vector internally) but they can also
//! be used with `SCAN` like commands in which case iteration will send more
//! queries until the cursor is exhausted:
//!
//! ```rust,no_run
//! # let client = redis::Client::open("redis://127.0.0.1/").unwrap();
//! # let con = client.get_connection().unwrap();
//! let mut cmd = redis::cmd("SSCAN");
//! let mut iter : redis::Iter<int> = cmd.arg("my_set").cursor_arg(0).iter(&con).unwrap();
//! for x in iter {
//!     // do something with the item
//! }
//! ```
//!
//! As you can see the cursor argument needs to be defined with `cursor_arg` instead
//! of `arg` so that the library knows which argument needs updating as the
//! query is run for more items.
//!
//! ## Pipelining
//!
//! In addition to simple queries you can also send command pipelines.  This
//! is provided through the `pipe` function.  It works very similar to sending
//! individual commands but you can send more than one in one go.  This also
//! allows you to ignore individual results so that matching on the end result
//! is easier:
//!
//! ```rust,no_run
//! # let client = redis::Client::open("redis://127.0.0.1/").unwrap();
//! # let con = client.get_connection().unwrap();
//! let (k1, k2) : (i32, i32) = redis::pipe()
//!     .cmd("SET").arg("key_1").arg(42i).ignore()
//!     .cmd("SET").arg("key_2").arg(43i).ignore()
//!     .cmd("GET").arg("key_1")
//!     .cmd("GET").arg("key_2").query(&con).unwrap();
//! ```
//!
//! If you want the pipeline to be wrapped in a `MULTI`/`EXEC` block you can
//! easily do that by switching the pipeline into `atomic` mode.  From the
//! caller's point of view nothing changes, the pipeline itself will take
//! care of the rest for you:
//!
//! ```rust,no_run
//! # let client = redis::Client::open("redis://127.0.0.1/").unwrap();
//! # let con = client.get_connection().unwrap();
//! let (k1, k2) : (i32, i32) = redis::pipe()
//!     .atomic()
//!     .cmd("SET").arg("key_1").arg(42i).ignore()
//!     .cmd("SET").arg("key_2").arg(43i).ignore()
//!     .cmd("GET").arg("key_1")
//!     .cmd("GET").arg("key_2").query(&con).unwrap();
//! ```
//!
//! ## Transactions
//!
//! Transactions are available through atomic pipelines.  In order to use
//! them in a more simple way you can use the `transaction` function of a
//! connection:
//! ```rust,no_run
//! # let client = redis::Client::open("redis://127.0.0.1/").unwrap();
//! # let con = client.get_connection().unwrap();
//! let key = "the_key";
//! let (new_val,) : (int,) = con.transaction([key][], |pipe| {
//!     let old_val : int = try!(redis::cmd("GET").arg(key).query(&con));
//!     pipe
//!         .cmd("SET").arg(key).arg(old_val + 1).ignore()
//!         .cmd("GET").arg(key).query(&con)
//! }).unwrap();
//! println!("The incremented number is: {}", new_val);
//! ```
//!
//! For more information see the `transaction` function.
//!
//! ## PubSub
//!
//! Pubsub is currently work in progress but provided through the `PubSub`
//! connection object.  Due to the fact that Rust does not have support
//! for async IO in libnative yet, the API does not provide a way to
//! read messages with any form of timeout yet.
//!
//! Example usage:
//!
//! ```rust,no_run
//! let client = redis::Client::open("redis://127.0.0.1/").unwrap();
//! let mut pubsub = client.get_pubsub().unwrap();
//! pubsub.subscribe("channel_1").unwrap();
//! pubsub.subscribe("channel_2").unwrap();
//!
//! loop {
//!     let msg = pubsub.get_message().unwrap();
//!     let payload : String = msg.get_payload().unwrap();
//!     println!("channel '{}': {}", msg.get_channel_name(), payload);
//! }
//! ```

#![crate_name = "redis"]
#![crate_type = "lib"]
#![license = "BSD"]
#![comment = "Bindings and wrapper functions for redis."]

#![deny(non_camel_case_types)]

#![feature(macro_rules)]
#![feature(default_type_params)]

#![experimental]

extern crate url;
extern crate serialize;

/* public api */
pub use parser::{parse_redis_value, Parser};
pub use client::Client;
pub use connection::{Connection, PubSub, Msg};
pub use cmd::{cmd, Cmd, pipe, Pipeline, Iter, pack_command};

#[doc(hidden)]
pub use types::{
    /* low level values */
    Value,
        Nil,
        Int,
        Data,
        Bulk,
        Okay,
        Status,

    /* error and result types */
    Error,
    RedisResult,

    /* error kinds */
    ErrorKind,
        ResponseError,
        TypeError,
        ExecAbortError,
        BusyLoadingError,
        NoScriptError,
        ExtensionError,
        InternalIoError,

    /* utility types */
    InfoDict,

    /* conversion traits */
    FromRedisValue,
    ToRedisArgs,

    /* utility functions */
    from_redis_value,
};

pub mod macros;

mod parser;
mod client;
mod connection;
mod types;
mod cmd;
<|endoftext|>


<commit_before>//! Fractal Global Relations module
//!
//! This module holds the Fractal Global Relationships, and there associated byte value.

/// Defined relationships
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Relationship {
    /// A stranger to the user
    Stranger,
    /// An Acquaintance to the uesr
    Acquaintance,
    /// A CoWorker to the user
    CoWorker,
    /// A friend to the uesr
    Friend,
    /// A Family member to the user
    Family,
}

/// Translates relationsip enum
pub fn get_relationship_id(rel: Relationship) -> u8 {
    match rel {
        Relationship::Stranger => 0,
        Relationship::Acquaintance => 1,
        Relationship::CoWorker => 2,
        Relationship::Friend => 3,
        Relationship::Family => 4,
    }
}

/// Grabs the relationship from the given id
pub fn get_relationship(id: u8) -> Relationship {
    match id {
        0 => Relationship::Stranger,
        1 => Relationship::Acquaintance,
        2 => Relationship::CoWorker,
        3 => Relationship::Friend,
        4 => Relationship::Family,
        _ => unreachable!(),
    }
}
<commit_msg>made relationship decodable and encodable<commit_after>//! Fractal Global Relations module
//!
//! This module holds the Fractal Global Relationships, and there associated byte value.

/// Defined relationships
#[derive(Clone, Copy, Debug, PartialEq, RustcEncodable, RustcDecodable)]
pub enum Relationship {
    /// A stranger to the user
    Stranger,
    /// An Acquaintance to the uesr
    Acquaintance,
    /// A CoWorker to the user
    CoWorker,
    /// A friend to the uesr
    Friend,
    /// A Family member to the user
    Family,
}

/// Translates relationsip enum
pub fn get_relationship_id(rel: Relationship) -> u8 {
    match rel {
        Relationship::Stranger => 0,
        Relationship::Acquaintance => 1,
        Relationship::CoWorker => 2,
        Relationship::Friend => 3,
        Relationship::Family => 4,
    }
}

/// Grabs the relationship from the given id
pub fn get_relationship(id: u8) -> Relationship {
    match id {
        0 => Relationship::Stranger,
        1 => Relationship::Acquaintance,
        2 => Relationship::CoWorker,
        3 => Relationship::Friend,
        4 => Relationship::Family,
        _ => unreachable!(),
    }
}
<|endoftext|>


<commit_before>use piston_window;

pub struct Events {
    up: bool,
    down: bool,
    left: bool,
    right: bool,
}

impl Events {
    pub fn new() -> Events {
        Events { up: false, down: false, left: false, right: false }
    }

    pub fn is_up(&self) -> bool {
        self.up
    }

    pub fn is_down(&self) -> bool {
        self.down
    }

    pub fn is_left(&self) -> bool {
        self.left
    }

    pub fn is_right(&self) -> bool {
        self.right
    }

    pub fn process_input(&mut self, input: &piston_window::Input) {
        use piston_window::{Input, Button, Key};

        match input {
            &Input::Press(button) => {
                match button {
                    Button::Keyboard(Key::Up) => { self.up = true; }
                    Button::Keyboard(Key::Down) => { self.down = true; }
                    Button::Keyboard(Key::Left) => { self.left = true; }
                    Button::Keyboard(Key::Right) => { self.right = true; }
                    _ => {}
                }
            }
            &Input::Release(button) => {
                match button {
                    Button::Keyboard(Key::Up) => { self.up = false; }
                    Button::Keyboard(Key::Down) => { self.down = false; }
                    Button::Keyboard(Key::Left) => { self.left = false; }
                    Button::Keyboard(Key::Right) => { self.right = false; }
                    _ => {}
                }
            }
            _ => {}
        }
    }
}

#[test]
fn events_new() {
    let events = Events::new();
    assert_eq!(false, events.up);
    assert_eq!(false, events.down);
    assert_eq!(false, events.left);
    assert_eq!(false, events.right);
}
<commit_msg>Events module has been deleted<commit_after><|endoftext|>


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

use log::warn;

pub struct Entry {
    pub path: PathBuf,
    pub metadata: Metadata,
}

pub fn walk_dir<P: AsRef<Path>>(root: P) -> std::io::Result<WalkDir> {
    let metadata = std::fs::symlink_metadata(&root)?;
    let pending = vec!(list_dir(&root)?);

    Ok(WalkDir {
        root: Some(Entry {
            path: root.as_ref().to_path_buf(),
            metadata: metadata,
        }),
        pending: pending,
    })
}

fn list_dir<P: AsRef<Path>>(path: P) -> std::io::Result<ListDir> {
    let iter = std::fs::read_dir(path)?;

    Ok(ListDir {
        iter: iter,
    })
}

pub struct WalkDir {
    // TODO: Add support for stopping at device boundaries.
    root: Option<Entry>,
    pending: Vec<ListDir>,
}

impl WalkDir {

    fn push(&mut self, entry: &Entry) {
        match list_dir(&entry.path) {
            Ok(iter) => {
                self.pending.push(iter);
            },
            Err(error) => {
                warn!("failed to read '{}': {}", entry.path.display(), error);
            },
        }
    }

    fn pop(&mut self) -> Option<Entry> {
        while let Some(iter) = self.pending.last_mut() {
            for entry in iter {
                return Some(entry);
            }

            self.pending.pop();
        }

        None
    }
}

impl std::iter::Iterator for WalkDir {

    type Item = Entry;

    fn next(&mut self) -> Option<Entry> {
        if self.root.is_some() {
            return self.root.take();
        }

        let entry = self.pop()?;

        if entry.metadata.is_dir() {
            self.push(&entry);
        }

        Some(entry)
    }
}

/// Iterator over the entries in a directory.
///
/// This iterator is very similar to the standard `ReadDir` iterator, except
/// that it is forgetful and only yields entries that did not cause any errors.
///
/// Unlike the `ReadDir` iterator entries, `ListDir` entries are guaranteed to
/// have valid metadata objects attached.
struct ListDir {
    iter: std::fs::ReadDir,
}

impl std::iter::Iterator for ListDir {

    type Item = Entry;

    fn next(&mut self) -> Option<Entry> {
        for entry in &mut self.iter {
            let entry = match entry {
                Ok(entry) => entry,
                Err(error) => {
                    warn!("directory iteration error: {}", error);
                    continue
                },
            };

            let path = entry.path();
            let metadata = match entry.metadata() {
                Ok(metadata) => metadata,
                Err(error) => {
                    warn!("failed to stat '{}': {}", path.display(), error);
                    continue
                },
            };

            return Some(Entry {
                path: path,
                metadata: metadata,
            });
        }

        None
    }
}

#[cfg(test)]
mod tests {

    use std::fs::File;

    use super::*;

    // TODO: Add test case for non-existing directory.

    #[test]
    fn test_list_dir_empty() {
        let tempdir = tempfile::tempdir().unwrap();

        let mut iter = list_dir(&tempdir).unwrap();

        assert!(iter.next().is_none());
    }

    #[test]
    fn test_list_dir_with_files() {
        let tempdir = tempfile::tempdir().unwrap();
        File::create(tempdir.path().join("abc")).unwrap();
        File::create(tempdir.path().join("def")).unwrap();
        File::create(tempdir.path().join("ghi")).unwrap();

        let mut results = list_dir(&tempdir).unwrap().collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 3);

        assert_eq!(results[0].path, tempdir.path().join("abc"));
        assert!(results[0].metadata.is_file());

        assert_eq!(results[1].path, tempdir.path().join("def"));
        assert!(results[1].metadata.is_file());

        assert_eq!(results[2].path, tempdir.path().join("ghi"));
        assert!(results[2].metadata.is_file());
    }

    #[test]
    fn test_list_dir_with_dirs() {
        let tempdir = tempfile::tempdir().unwrap();
        std::fs::create_dir(tempdir.path().join("abc")).unwrap();
        std::fs::create_dir(tempdir.path().join("def")).unwrap();

        let mut results = list_dir(&tempdir).unwrap().collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 2);

        assert_eq!(results[0].path, tempdir.path().join("abc"));
        assert!(results[0].metadata.is_dir());

        assert_eq!(results[1].path, tempdir.path().join("def"));
        assert!(results[1].metadata.is_dir());
    }

    #[cfg(target_family = "unix")]
    #[test]
    fn test_list_dir_with_links() {
        let tempdir = tempfile::tempdir().unwrap();
        let source = tempdir.path().join("abc");
        let target = tempdir.path().join("def");

        File::create(&source).unwrap();
        std::os::unix::fs::symlink(&source, &target).unwrap();

        let mut results = list_dir(&tempdir).unwrap().collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 2);

        assert_eq!(results[0].path, source);
        assert!(results[0].metadata.file_type().is_file());

        assert_eq!(results[1].path, target);
        assert!(results[1].metadata.file_type().is_symlink());
    }

    #[test]
    fn test_walk_dir_empty() {
        let tempdir = tempfile::tempdir().unwrap();

        let mut iter = walk_dir(&tempdir).unwrap();

        let entry = iter.next().unwrap();
        assert_eq!(entry.path, tempdir.path());
        assert!(entry.metadata.is_dir());

        assert!(iter.next().is_none());
    }

    #[test]
    fn test_walk_dir_with_flat_files() {
        let tempdir = tempfile::tempdir().unwrap();
        File::create(tempdir.path().join("abc")).unwrap();
        File::create(tempdir.path().join("def")).unwrap();
        File::create(tempdir.path().join("ghi")).unwrap();

        let mut results = walk_dir(&tempdir).unwrap().collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 4);

        assert_eq!(results[0].path, tempdir.path());
        assert!(results[0].metadata.is_dir());

        assert_eq!(results[1].path, tempdir.path().join("abc"));
        assert!(results[1].metadata.is_file());

        assert_eq!(results[2].path, tempdir.path().join("def"));
        assert!(results[2].metadata.is_file());

        assert_eq!(results[3].path, tempdir.path().join("ghi"));
        assert!(results[3].metadata.is_file());
    }

    #[test]
    fn test_walk_dir_with_flat_dirs() {
        let tempdir = tempfile::tempdir().unwrap();
        std::fs::create_dir(tempdir.path().join("abc")).unwrap();
        std::fs::create_dir(tempdir.path().join("def")).unwrap();

        let mut results = walk_dir(&tempdir).unwrap().collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 3);

        assert_eq!(results[0].path, tempdir.path());
        assert!(results[0].metadata.is_dir());

        assert_eq!(results[1].path, tempdir.path().join("abc"));
        assert!(results[1].metadata.is_dir());

        assert_eq!(results[2].path, tempdir.path().join("def"));
        assert!(results[2].metadata.is_dir());
    }

    #[test]
    fn test_walk_dir_with_nested_dirs() {
        let tempdir = tempfile::tempdir().unwrap();
        std::fs::create_dir(tempdir.path().join("abc")).unwrap();
        std::fs::create_dir(tempdir.path().join("abc").join("def")).unwrap();

        let mut results = walk_dir(&tempdir).unwrap().collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 3);

        assert_eq!(results[0].path, tempdir.path());
        assert!(results[0].metadata.is_dir());

        assert_eq!(results[1].path, tempdir.path().join("abc"));
        assert!(results[1].metadata.is_dir());

        assert_eq!(results[2].path, tempdir.path().join("abc").join("def"));
        assert!(results[2].metadata.is_dir());
    }

    #[test]
    fn test_walk_dir_with_deeply_nested_dirs() {
        let tempdir = tempfile::tempdir().unwrap();

        let mut dir = tempdir.path().to_path_buf();
        for _ in 0..512 {
            dir.push("foo");
            std::fs::create_dir(&dir).unwrap();
        }

        let mut results = walk_dir(&tempdir).unwrap().collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 513);

        for entry in &results[1..] {
            assert!(entry.path.starts_with(&tempdir));
            assert!(entry.path.ends_with("foo"));
            assert!(entry.metadata.is_dir());
        }
    }

    #[test]
    fn test_walk_dir_with_files_inside_dir() {
        let tempdir = tempfile::tempdir().unwrap();
        std::fs::create_dir(tempdir.path().join("foo")).unwrap();
        File::create(tempdir.path().join("foo").join("abc")).unwrap();
        File::create(tempdir.path().join("foo").join("def")).unwrap();

        let mut results = walk_dir(&tempdir).unwrap().collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 4);

        assert_eq!(results[0].path, tempdir.path());
        assert!(results[0].metadata.is_dir());

        assert_eq!(results[1].path, tempdir.path().join("foo"));
        assert!(results[1].metadata.is_dir());

        assert_eq!(results[2].path, tempdir.path().join("foo").join("abc"));
        assert!(results[2].metadata.is_file());

        assert_eq!(results[3].path, tempdir.path().join("foo").join("def"));
        assert!(results[3].metadata.is_file());
    }

    #[cfg(target_family = "unix")]
    #[test]
    fn test_walk_dir_with_dir_symlinks() {
        let tempdir = tempfile::tempdir().unwrap();
        let dir = tempdir.path().join("abc");
        let file = dir.join("def");
        let symlink = tempdir.path().join("ghi");

        std::fs::create_dir(&dir).unwrap();
        File::create(&file).unwrap();
        std::os::unix::fs::symlink(&dir, &symlink).unwrap();

        let mut results = walk_dir(&tempdir).unwrap().collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 4);

        assert_eq!(results[0].path, tempdir.path());
        assert!(results[0].metadata.file_type().is_dir());

        assert_eq!(results[1].path, dir);
        assert!(results[1].metadata.file_type().is_dir());

        assert_eq!(results[2].path, file);
        assert!(results[2].metadata.file_type().is_file());

        assert_eq!(results[3].path, symlink);
        assert!(results[3].metadata.file_type().is_symlink());
    }

    #[cfg(target_family = "unix")]
    #[test]
    fn test_walk_dir_with_circular_symlinks() {
        let tempdir = tempfile::tempdir().unwrap();
        let dir = tempdir.path().join("foo");
        let symlink = tempdir.path().join("foo").join("bar");

        std::fs::create_dir(&dir).unwrap();
        std::os::unix::fs::symlink(&dir, &symlink).unwrap();

        let mut results = walk_dir(&tempdir).unwrap().collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 3);

        assert_eq!(results[0].path, tempdir.path());
        assert!(results[0].metadata.file_type().is_dir());

        assert_eq!(results[1].path, dir);
        assert!(results[1].metadata.file_type().is_dir());

        assert_eq!(results[2].path, symlink);
        assert!(results[2].metadata.file_type().is_symlink());
    }

    #[test]
    fn test_walk_dir_with_unicode_names() {
        let tempdir = tempfile::tempdir().unwrap();
        File::create(tempdir.path().join("zażółć gęślą jaźń")).unwrap();

        let mut results = walk_dir(&tempdir).unwrap().collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 2);
        assert_eq!(results[0].path, tempdir.path());
        assert_eq!(results[1].path, tempdir.path().join("zażółć gęślą jaźń"));
    }

    #[test]
    fn test_walk_dir_metadata_size() {
        let tempdir = tempfile::tempdir().unwrap();
        std::fs::write(tempdir.path().join("foo"), b"123456789").unwrap();

        let mut results = walk_dir(&tempdir).unwrap().collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 2);
        assert_eq!(results[1].metadata.len(), 9);
    }
}
<commit_msg>Add error test cases for filesystem iteration.<commit_after>use std::fs::Metadata;
use std::path::{Path, PathBuf};

use log::warn;

pub struct Entry {
    pub path: PathBuf,
    pub metadata: Metadata,
}

pub fn walk_dir<P: AsRef<Path>>(root: P) -> std::io::Result<WalkDir> {
    let metadata = std::fs::symlink_metadata(&root)?;
    let pending = vec!(list_dir(&root)?);

    Ok(WalkDir {
        root: Some(Entry {
            path: root.as_ref().to_path_buf(),
            metadata: metadata,
        }),
        pending: pending,
    })
}

fn list_dir<P: AsRef<Path>>(path: P) -> std::io::Result<ListDir> {
    let iter = std::fs::read_dir(path)?;

    Ok(ListDir {
        iter: iter,
    })
}

pub struct WalkDir {
    // TODO: Add support for stopping at device boundaries.
    root: Option<Entry>,
    pending: Vec<ListDir>,
}

impl WalkDir {

    fn push(&mut self, entry: &Entry) {
        match list_dir(&entry.path) {
            Ok(iter) => {
                self.pending.push(iter);
            },
            Err(error) => {
                warn!("failed to read '{}': {}", entry.path.display(), error);
            },
        }
    }

    fn pop(&mut self) -> Option<Entry> {
        while let Some(iter) = self.pending.last_mut() {
            for entry in iter {
                return Some(entry);
            }

            self.pending.pop();
        }

        None
    }
}

impl std::iter::Iterator for WalkDir {

    type Item = Entry;

    fn next(&mut self) -> Option<Entry> {
        if self.root.is_some() {
            return self.root.take();
        }

        let entry = self.pop()?;

        if entry.metadata.is_dir() {
            self.push(&entry);
        }

        Some(entry)
    }
}

/// Iterator over the entries in a directory.
///
/// This iterator is very similar to the standard `ReadDir` iterator, except
/// that it is forgetful and only yields entries that did not cause any errors.
///
/// Unlike the `ReadDir` iterator entries, `ListDir` entries are guaranteed to
/// have valid metadata objects attached.
struct ListDir {
    iter: std::fs::ReadDir,
}

impl std::iter::Iterator for ListDir {

    type Item = Entry;

    fn next(&mut self) -> Option<Entry> {
        for entry in &mut self.iter {
            let entry = match entry {
                Ok(entry) => entry,
                Err(error) => {
                    warn!("directory iteration error: {}", error);
                    continue
                },
            };

            let path = entry.path();
            let metadata = match entry.metadata() {
                Ok(metadata) => metadata,
                Err(error) => {
                    warn!("failed to stat '{}': {}", path.display(), error);
                    continue
                },
            };

            return Some(Entry {
                path: path,
                metadata: metadata,
            });
        }

        None
    }
}

#[cfg(test)]
mod tests {

    use std::fs::File;

    use super::*;

    #[test]
    fn test_list_dir_non_existing() {
        let tempdir = tempfile::tempdir().unwrap();

        let iter = list_dir(tempdir.path().join("foo"));
        assert!(iter.is_err());
    }

    #[test]
    fn test_list_dir_empty() {
        let tempdir = tempfile::tempdir().unwrap();

        let mut iter = list_dir(&tempdir).unwrap();

        assert!(iter.next().is_none());
    }

    #[test]
    fn test_list_dir_with_files() {
        let tempdir = tempfile::tempdir().unwrap();
        File::create(tempdir.path().join("abc")).unwrap();
        File::create(tempdir.path().join("def")).unwrap();
        File::create(tempdir.path().join("ghi")).unwrap();

        let mut results = list_dir(&tempdir).unwrap().collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 3);

        assert_eq!(results[0].path, tempdir.path().join("abc"));
        assert!(results[0].metadata.is_file());

        assert_eq!(results[1].path, tempdir.path().join("def"));
        assert!(results[1].metadata.is_file());

        assert_eq!(results[2].path, tempdir.path().join("ghi"));
        assert!(results[2].metadata.is_file());
    }

    #[test]
    fn test_list_dir_with_dirs() {
        let tempdir = tempfile::tempdir().unwrap();
        std::fs::create_dir(tempdir.path().join("abc")).unwrap();
        std::fs::create_dir(tempdir.path().join("def")).unwrap();

        let mut results = list_dir(&tempdir).unwrap().collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 2);

        assert_eq!(results[0].path, tempdir.path().join("abc"));
        assert!(results[0].metadata.is_dir());

        assert_eq!(results[1].path, tempdir.path().join("def"));
        assert!(results[1].metadata.is_dir());
    }

    #[cfg(target_family = "unix")]
    #[test]
    fn test_list_dir_with_links() {
        let tempdir = tempfile::tempdir().unwrap();
        let source = tempdir.path().join("abc");
        let target = tempdir.path().join("def");

        File::create(&source).unwrap();
        std::os::unix::fs::symlink(&source, &target).unwrap();

        let mut results = list_dir(&tempdir).unwrap().collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 2);

        assert_eq!(results[0].path, source);
        assert!(results[0].metadata.file_type().is_file());

        assert_eq!(results[1].path, target);
        assert!(results[1].metadata.file_type().is_symlink());
    }

    #[test]
    fn test_walk_dir_non_existing() {
        let tempdir = tempfile::tempdir().unwrap();

        let iter = walk_dir(tempdir.path().join("foo"));
        assert!(iter.is_err());
    }

    #[test]
    fn test_walk_dir_empty() {
        let tempdir = tempfile::tempdir().unwrap();

        let mut iter = walk_dir(&tempdir).unwrap();

        let entry = iter.next().unwrap();
        assert_eq!(entry.path, tempdir.path());
        assert!(entry.metadata.is_dir());

        assert!(iter.next().is_none());
    }

    #[test]
    fn test_walk_dir_with_flat_files() {
        let tempdir = tempfile::tempdir().unwrap();
        File::create(tempdir.path().join("abc")).unwrap();
        File::create(tempdir.path().join("def")).unwrap();
        File::create(tempdir.path().join("ghi")).unwrap();

        let mut results = walk_dir(&tempdir).unwrap().collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 4);

        assert_eq!(results[0].path, tempdir.path());
        assert!(results[0].metadata.is_dir());

        assert_eq!(results[1].path, tempdir.path().join("abc"));
        assert!(results[1].metadata.is_file());

        assert_eq!(results[2].path, tempdir.path().join("def"));
        assert!(results[2].metadata.is_file());

        assert_eq!(results[3].path, tempdir.path().join("ghi"));
        assert!(results[3].metadata.is_file());
    }

    #[test]
    fn test_walk_dir_with_flat_dirs() {
        let tempdir = tempfile::tempdir().unwrap();
        std::fs::create_dir(tempdir.path().join("abc")).unwrap();
        std::fs::create_dir(tempdir.path().join("def")).unwrap();

        let mut results = walk_dir(&tempdir).unwrap().collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 3);

        assert_eq!(results[0].path, tempdir.path());
        assert!(results[0].metadata.is_dir());

        assert_eq!(results[1].path, tempdir.path().join("abc"));
        assert!(results[1].metadata.is_dir());

        assert_eq!(results[2].path, tempdir.path().join("def"));
        assert!(results[2].metadata.is_dir());
    }

    #[test]
    fn test_walk_dir_with_nested_dirs() {
        let tempdir = tempfile::tempdir().unwrap();
        std::fs::create_dir(tempdir.path().join("abc")).unwrap();
        std::fs::create_dir(tempdir.path().join("abc").join("def")).unwrap();

        let mut results = walk_dir(&tempdir).unwrap().collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 3);

        assert_eq!(results[0].path, tempdir.path());
        assert!(results[0].metadata.is_dir());

        assert_eq!(results[1].path, tempdir.path().join("abc"));
        assert!(results[1].metadata.is_dir());

        assert_eq!(results[2].path, tempdir.path().join("abc").join("def"));
        assert!(results[2].metadata.is_dir());
    }

    #[test]
    fn test_walk_dir_with_deeply_nested_dirs() {
        let tempdir = tempfile::tempdir().unwrap();

        let mut dir = tempdir.path().to_path_buf();
        for _ in 0..512 {
            dir.push("foo");
            std::fs::create_dir(&dir).unwrap();
        }

        let mut results = walk_dir(&tempdir).unwrap().collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 513);

        for entry in &results[1..] {
            assert!(entry.path.starts_with(&tempdir));
            assert!(entry.path.ends_with("foo"));
            assert!(entry.metadata.is_dir());
        }
    }

    #[test]
    fn test_walk_dir_with_files_inside_dir() {
        let tempdir = tempfile::tempdir().unwrap();
        std::fs::create_dir(tempdir.path().join("foo")).unwrap();
        File::create(tempdir.path().join("foo").join("abc")).unwrap();
        File::create(tempdir.path().join("foo").join("def")).unwrap();

        let mut results = walk_dir(&tempdir).unwrap().collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 4);

        assert_eq!(results[0].path, tempdir.path());
        assert!(results[0].metadata.is_dir());

        assert_eq!(results[1].path, tempdir.path().join("foo"));
        assert!(results[1].metadata.is_dir());

        assert_eq!(results[2].path, tempdir.path().join("foo").join("abc"));
        assert!(results[2].metadata.is_file());

        assert_eq!(results[3].path, tempdir.path().join("foo").join("def"));
        assert!(results[3].metadata.is_file());
    }

    #[cfg(target_family = "unix")]
    #[test]
    fn test_walk_dir_with_dir_symlinks() {
        let tempdir = tempfile::tempdir().unwrap();
        let dir = tempdir.path().join("abc");
        let file = dir.join("def");
        let symlink = tempdir.path().join("ghi");

        std::fs::create_dir(&dir).unwrap();
        File::create(&file).unwrap();
        std::os::unix::fs::symlink(&dir, &symlink).unwrap();

        let mut results = walk_dir(&tempdir).unwrap().collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 4);

        assert_eq!(results[0].path, tempdir.path());
        assert!(results[0].metadata.file_type().is_dir());

        assert_eq!(results[1].path, dir);
        assert!(results[1].metadata.file_type().is_dir());

        assert_eq!(results[2].path, file);
        assert!(results[2].metadata.file_type().is_file());

        assert_eq!(results[3].path, symlink);
        assert!(results[3].metadata.file_type().is_symlink());
    }

    #[cfg(target_family = "unix")]
    #[test]
    fn test_walk_dir_with_circular_symlinks() {
        let tempdir = tempfile::tempdir().unwrap();
        let dir = tempdir.path().join("foo");
        let symlink = tempdir.path().join("foo").join("bar");

        std::fs::create_dir(&dir).unwrap();
        std::os::unix::fs::symlink(&dir, &symlink).unwrap();

        let mut results = walk_dir(&tempdir).unwrap().collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 3);

        assert_eq!(results[0].path, tempdir.path());
        assert!(results[0].metadata.file_type().is_dir());

        assert_eq!(results[1].path, dir);
        assert!(results[1].metadata.file_type().is_dir());

        assert_eq!(results[2].path, symlink);
        assert!(results[2].metadata.file_type().is_symlink());
    }

    #[test]
    fn test_walk_dir_with_unicode_names() {
        let tempdir = tempfile::tempdir().unwrap();
        File::create(tempdir.path().join("zażółć gęślą jaźń")).unwrap();

        let mut results = walk_dir(&tempdir).unwrap().collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 2);
        assert_eq!(results[0].path, tempdir.path());
        assert_eq!(results[1].path, tempdir.path().join("zażółć gęślą jaźń"));
    }

    #[test]
    fn test_walk_dir_metadata_size() {
        let tempdir = tempfile::tempdir().unwrap();
        std::fs::write(tempdir.path().join("foo"), b"123456789").unwrap();

        let mut results = walk_dir(&tempdir).unwrap().collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 2);
        assert_eq!(results[1].metadata.len(), 9);
    }
}
<|endoftext|>