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

<%namespace name="helpers" file="/helpers.mako.rs" />

<% data.new_style_struct("List", inherited=True) %>

${helpers.single_keyword("list-style-position", "outside inside", animation_value_type="discrete",
                         spec="https://drafts.csswg.org/css-lists/#propdef-list-style-position")}

// TODO(pcwalton): Implement the full set of counter styles per CSS-COUNTER-STYLES [1] 6.1:
//
//     decimal-leading-zero, armenian, upper-armenian, lower-armenian, georgian, lower-roman,
//     upper-roman
//
// TODO(bholley): Missing quite a few gecko properties here as well.
//
// In gecko, {upper,lower}-{roman,alpha} are implemented as @counter-styles in the
// UA, however they can also be set from pres attrs. When @counter-style is supported
// we may need to look into this and handle these differently.
//
// [1]: http://dev.w3.org/csswg/css-counter-styles/
% if product == "servo":
    ${helpers.single_keyword("list-style-type", """
        disc none circle square decimal disclosure-open disclosure-closed lower-alpha upper-alpha
        arabic-indic bengali cambodian cjk-decimal devanagari gujarati gurmukhi kannada khmer lao
        malayalam mongolian myanmar oriya persian telugu thai tibetan cjk-earthly-branch
        cjk-heavenly-stem lower-greek hiragana hiragana-iroha katakana katakana-iroha""",
        animation_value_type="discrete",
        spec="https://drafts.csswg.org/css-lists/#propdef-list-style-type")}
% else:
    <%helpers:longhand name="list-style-type" animation_value_type="discrete" boxed="True"
                       spec="https://drafts.csswg.org/css-lists/#propdef-list-style-type">
        use values::CustomIdent;
        use values::generics::CounterStyleOrNone;

        pub use self::computed_value::T as SpecifiedValue;

        pub mod computed_value {
            use values::generics::CounterStyleOrNone;

            /// <counter-style> | <string> | none
            #[derive(Clone, Debug, Eq, PartialEq, ToComputedValue, ToCss)]
            pub enum T {
                CounterStyle(CounterStyleOrNone),
                String(String),
            }
        }

        #[cfg(feature = "gecko")]
        impl SpecifiedValue {
            /// Convert from gecko keyword to list-style-type.
            ///
            /// This should only be used for mapping type attribute to
            /// list-style-type, and thus only values possible in that
            /// attribute is considered here.
            pub fn from_gecko_keyword(value: u32) -> Self {
                use gecko_bindings::structs;
                SpecifiedValue::CounterStyle(if value == structs::NS_STYLE_LIST_STYLE_NONE {
                    CounterStyleOrNone::None
                } else {
                    <%
                        values = """disc circle square decimal lower-roman
                                    upper-roman lower-alpha upper-alpha""".split()
                    %>
                    CounterStyleOrNone::Name(CustomIdent(match value {
                        % for style in values:
                        structs::NS_STYLE_LIST_STYLE_${style.replace('-', '_').upper()} => atom!("${style}"),
                        % endfor
                        _ => unreachable!("Unknown counter style keyword value"),
                    }))
                })
            }
        }

        #[inline]
        pub fn get_initial_value() -> computed_value::T {
            computed_value::T::CounterStyle(CounterStyleOrNone::disc())
        }

        #[inline]
        pub fn get_initial_specified_value() -> SpecifiedValue {
            SpecifiedValue::CounterStyle(CounterStyleOrNone::disc())
        }

        pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
                             -> Result<SpecifiedValue, ParseError<'i>> {
            Ok(if let Ok(style) = input.try(|i| CounterStyleOrNone::parse(context, i)) {
                SpecifiedValue::CounterStyle(style)
            } else {
                SpecifiedValue::String(input.expect_string()?.as_ref().to_owned())
            })
        }
    </%helpers:longhand>
% endif

<%helpers:longhand name="list-style-image" animation_value_type="discrete"
                   boxed="${product == 'gecko'}"
                   spec="https://drafts.csswg.org/css-lists/#propdef-list-style-image">
    use values::computed::ComputedValueAsSpecified;
    use values::specified::UrlOrNone;
    pub use self::computed_value::T as SpecifiedValue;

    pub mod computed_value {
        use values::specified::UrlOrNone;

        #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
        #[derive(Clone, Debug, PartialEq, ToCss)]
        pub struct T(pub UrlOrNone);
    }

    impl ComputedValueAsSpecified for SpecifiedValue {}

    #[inline]
    pub fn get_initial_value() -> computed_value::T {
        computed_value::T(Either::Second(None_))
    }
    #[inline]
    pub fn get_initial_specified_value() -> SpecifiedValue {
        SpecifiedValue(Either::Second(None_))
    }
    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
                         -> Result<SpecifiedValue,ParseError<'i>> {
        % if product == "gecko":
        let mut value = input.try(|input| UrlOrNone::parse(context, input))?;
        if let Either::First(ref mut url) = value {
            url.build_image_value();
        }
        % else :
        let value = input.try(|input| UrlOrNone::parse(context, input))?;
        % endif

        return Ok(SpecifiedValue(value));
    }
</%helpers:longhand>

<%helpers:longhand name="quotes" animation_value_type="discrete"
                   spec="https://drafts.csswg.org/css-content/#propdef-quotes">
    use cssparser::serialize_string;
    use std::fmt;
    use style_traits::ToCss;

    pub use self::computed_value::T as SpecifiedValue;

    pub mod computed_value {
        #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
        #[derive(Clone, Debug, PartialEq, ToComputedValue)]
        pub struct T(pub Vec<(String, String)>);
    }

    impl ToCss for SpecifiedValue {
        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
            if self.0.is_empty() {
                return dest.write_str("none")
            }

            let mut first = true;
            for pair in &self.0 {
                if !first {
                    dest.write_str(" ")?;
                }
                first = false;
                serialize_string(&*pair.0, dest)?;
                dest.write_str(" ")?;
                serialize_string(&*pair.1, dest)?;
            }
            Ok(())
        }
    }

    #[inline]
    pub fn get_initial_value() -> computed_value::T {
        computed_value::T(vec![
            ("\u{201c}".to_owned(), "\u{201d}".to_owned()),
            ("\u{2018}".to_owned(), "\u{2019}".to_owned()),
        ])
    }

    pub fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>)
                         -> Result<SpecifiedValue,ParseError<'i>> {
        if input.try(|input| input.expect_ident_matching("none")).is_ok() {
            return Ok(SpecifiedValue(Vec::new()))
        }

        let mut quotes = Vec::new();
        loop {
            let first = match input.next() {
                Ok(&Token::QuotedString(ref value)) => value.as_ref().to_owned(),
                Ok(t) => return Err(BasicParseError::UnexpectedToken(t.clone()).into()),
                Err(_) => break,
            };
            let second = match input.next() {
                Ok(&Token::QuotedString(ref value)) => value.as_ref().to_owned(),
                Ok(t) => return Err(BasicParseError::UnexpectedToken(t.clone()).into()),
                Err(e) => return Err(e.into()),
            };
            quotes.push((first, second))
        }
        if !quotes.is_empty() {
            Ok(SpecifiedValue(quotes))
        } else {
            Err(StyleParseError::UnspecifiedError.into())
        }
    }
</%helpers:longhand>

${helpers.predefined_type("-moz-image-region",
                          "ClipRectOrAuto",
                          "computed::ClipRectOrAuto::auto()",
                          animation_value_type="ComputedValue",
                          products="gecko",
                          boxed="True",
                          spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-image-region)")}
<commit_msg>Do not use CVAS for the list-style-image property<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

<%namespace name="helpers" file="/helpers.mako.rs" />

<% data.new_style_struct("List", inherited=True) %>

${helpers.single_keyword("list-style-position", "outside inside", animation_value_type="discrete",
                         spec="https://drafts.csswg.org/css-lists/#propdef-list-style-position")}

// TODO(pcwalton): Implement the full set of counter styles per CSS-COUNTER-STYLES [1] 6.1:
//
//     decimal-leading-zero, armenian, upper-armenian, lower-armenian, georgian, lower-roman,
//     upper-roman
//
// TODO(bholley): Missing quite a few gecko properties here as well.
//
// In gecko, {upper,lower}-{roman,alpha} are implemented as @counter-styles in the
// UA, however they can also be set from pres attrs. When @counter-style is supported
// we may need to look into this and handle these differently.
//
// [1]: http://dev.w3.org/csswg/css-counter-styles/
% if product == "servo":
    ${helpers.single_keyword("list-style-type", """
        disc none circle square decimal disclosure-open disclosure-closed lower-alpha upper-alpha
        arabic-indic bengali cambodian cjk-decimal devanagari gujarati gurmukhi kannada khmer lao
        malayalam mongolian myanmar oriya persian telugu thai tibetan cjk-earthly-branch
        cjk-heavenly-stem lower-greek hiragana hiragana-iroha katakana katakana-iroha""",
        animation_value_type="discrete",
        spec="https://drafts.csswg.org/css-lists/#propdef-list-style-type")}
% else:
    <%helpers:longhand name="list-style-type" animation_value_type="discrete" boxed="True"
                       spec="https://drafts.csswg.org/css-lists/#propdef-list-style-type">
        use values::CustomIdent;
        use values::generics::CounterStyleOrNone;

        pub use self::computed_value::T as SpecifiedValue;

        pub mod computed_value {
            use values::generics::CounterStyleOrNone;

            /// <counter-style> | <string> | none
            #[derive(Clone, Debug, Eq, PartialEq, ToComputedValue, ToCss)]
            pub enum T {
                CounterStyle(CounterStyleOrNone),
                String(String),
            }
        }

        #[cfg(feature = "gecko")]
        impl SpecifiedValue {
            /// Convert from gecko keyword to list-style-type.
            ///
            /// This should only be used for mapping type attribute to
            /// list-style-type, and thus only values possible in that
            /// attribute is considered here.
            pub fn from_gecko_keyword(value: u32) -> Self {
                use gecko_bindings::structs;
                SpecifiedValue::CounterStyle(if value == structs::NS_STYLE_LIST_STYLE_NONE {
                    CounterStyleOrNone::None
                } else {
                    <%
                        values = """disc circle square decimal lower-roman
                                    upper-roman lower-alpha upper-alpha""".split()
                    %>
                    CounterStyleOrNone::Name(CustomIdent(match value {
                        % for style in values:
                        structs::NS_STYLE_LIST_STYLE_${style.replace('-', '_').upper()} => atom!("${style}"),
                        % endfor
                        _ => unreachable!("Unknown counter style keyword value"),
                    }))
                })
            }
        }

        #[inline]
        pub fn get_initial_value() -> computed_value::T {
            computed_value::T::CounterStyle(CounterStyleOrNone::disc())
        }

        #[inline]
        pub fn get_initial_specified_value() -> SpecifiedValue {
            SpecifiedValue::CounterStyle(CounterStyleOrNone::disc())
        }

        pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
                             -> Result<SpecifiedValue, ParseError<'i>> {
            Ok(if let Ok(style) = input.try(|i| CounterStyleOrNone::parse(context, i)) {
                SpecifiedValue::CounterStyle(style)
            } else {
                SpecifiedValue::String(input.expect_string()?.as_ref().to_owned())
            })
        }
    </%helpers:longhand>
% endif

<%helpers:longhand name="list-style-image" animation_value_type="discrete"
                   boxed="${product == 'gecko'}"
                   spec="https://drafts.csswg.org/css-lists/#propdef-list-style-image">
    use values::specified::UrlOrNone;
    pub use self::computed_value::T as SpecifiedValue;

    pub mod computed_value {
        use values::specified::UrlOrNone;

        #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
        #[derive(Clone, Debug, PartialEq, ToCss)]
        pub struct T(pub UrlOrNone);

        // FIXME(nox): This is wrong, there are different types for specified
        // and computed URLs in Servo.
        trivial_to_computed_value!(T);
    }

    #[inline]
    pub fn get_initial_value() -> computed_value::T {
        computed_value::T(Either::Second(None_))
    }
    #[inline]
    pub fn get_initial_specified_value() -> SpecifiedValue {
        SpecifiedValue(Either::Second(None_))
    }
    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
                         -> Result<SpecifiedValue,ParseError<'i>> {
        % if product == "gecko":
        let mut value = input.try(|input| UrlOrNone::parse(context, input))?;
        if let Either::First(ref mut url) = value {
            url.build_image_value();
        }
        % else :
        let value = input.try(|input| UrlOrNone::parse(context, input))?;
        % endif

        return Ok(SpecifiedValue(value));
    }
</%helpers:longhand>

<%helpers:longhand name="quotes" animation_value_type="discrete"
                   spec="https://drafts.csswg.org/css-content/#propdef-quotes">
    use cssparser::serialize_string;
    use std::fmt;
    use style_traits::ToCss;

    pub use self::computed_value::T as SpecifiedValue;

    pub mod computed_value {
        #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
        #[derive(Clone, Debug, PartialEq, ToComputedValue)]
        pub struct T(pub Vec<(String, String)>);
    }

    impl ToCss for SpecifiedValue {
        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
            if self.0.is_empty() {
                return dest.write_str("none")
            }

            let mut first = true;
            for pair in &self.0 {
                if !first {
                    dest.write_str(" ")?;
                }
                first = false;
                serialize_string(&*pair.0, dest)?;
                dest.write_str(" ")?;
                serialize_string(&*pair.1, dest)?;
            }
            Ok(())
        }
    }

    #[inline]
    pub fn get_initial_value() -> computed_value::T {
        computed_value::T(vec![
            ("\u{201c}".to_owned(), "\u{201d}".to_owned()),
            ("\u{2018}".to_owned(), "\u{2019}".to_owned()),
        ])
    }

    pub fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>)
                         -> Result<SpecifiedValue,ParseError<'i>> {
        if input.try(|input| input.expect_ident_matching("none")).is_ok() {
            return Ok(SpecifiedValue(Vec::new()))
        }

        let mut quotes = Vec::new();
        loop {
            let first = match input.next() {
                Ok(&Token::QuotedString(ref value)) => value.as_ref().to_owned(),
                Ok(t) => return Err(BasicParseError::UnexpectedToken(t.clone()).into()),
                Err(_) => break,
            };
            let second = match input.next() {
                Ok(&Token::QuotedString(ref value)) => value.as_ref().to_owned(),
                Ok(t) => return Err(BasicParseError::UnexpectedToken(t.clone()).into()),
                Err(e) => return Err(e.into()),
            };
            quotes.push((first, second))
        }
        if !quotes.is_empty() {
            Ok(SpecifiedValue(quotes))
        } else {
            Err(StyleParseError::UnspecifiedError.into())
        }
    }
</%helpers:longhand>

${helpers.predefined_type("-moz-image-region",
                          "ClipRectOrAuto",
                          "computed::ClipRectOrAuto::auto()",
                          animation_value_type="ComputedValue",
                          products="gecko",
                          boxed="True",
                          spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-image-region)")}
<|endoftext|>


<commit_before>use syntax::ast::{self, AstNode};

use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists};

// Assist: apply_demorgan
//
// Apply https://en.wikipedia.org/wiki/De_Morgan%27s_laws[De Morgan's law].
// This transforms expressions of the form `!l || !r` into `!(l && r)`.
// This also works with `&&`. This assist can only be applied with the cursor
// on either `||` or `&&`.
//
// ```
// fn main() {
//     if x != 4 ||$0 y < 3.14 {}
// }
// ```
// ->
// ```
// fn main() {
//     if !(x == 4 && !(y < 3.14)) {}
// }
// ```
pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
    let expr = ctx.find_node_at_offset::<ast::BinExpr>()?;
    let op = expr.op_kind()?;
    let op_range = expr.op_token()?.text_range();
    let opposite_op = opposite_logic_op(op)?;
    let cursor_in_range = op_range.contains_range(ctx.frange.range);
    if !cursor_in_range {
        return None;
    }

    let lhs = expr.lhs()?;
    let lhs_range = lhs.syntax().text_range();
    let not_lhs = invert_boolean_expression(&ctx.sema, lhs);

    let rhs = expr.rhs()?;
    let rhs_range = rhs.syntax().text_range();
    let not_rhs = invert_boolean_expression(&ctx.sema, rhs);

    acc.add(
        AssistId("apply_demorgan", AssistKind::RefactorRewrite),
        "Apply De Morgan's law",
        op_range,
        |edit| {
            edit.replace(op_range, opposite_op);
            edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text()));
            edit.replace(rhs_range, format!("{})", not_rhs.syntax().text()));
        },
    )
}

// Return the opposite text for a given logical operator, if it makes sense
fn opposite_logic_op(kind: ast::BinOp) -> Option<&'static str> {
    match kind {
        ast::BinOp::BooleanOr => Some("&&"),
        ast::BinOp::BooleanAnd => Some("||"),
        _ => None,
    }
}

#[cfg(test)]
mod tests {
    use ide_db::helpers::FamousDefs;

    use super::*;

    use crate::tests::{check_assist, check_assist_not_applicable};

    const ORDABLE_FIXTURE: &'static str = r"
//- /lib.rs deps:core crate:ordable
struct NonOrderable;
struct Orderable;
impl core::cmp::Ord for Orderable {}
";

    fn check(ra_fixture_before: &str, ra_fixture_after: &str) {
        let before = &format!(
            "//- /main.rs crate:main deps:core,ordable\n{}\n{}{}",
            ra_fixture_before,
            FamousDefs::FIXTURE,
            ORDABLE_FIXTURE
        );
        check_assist(apply_demorgan, before, &format!("{}\n", ra_fixture_after));
    }

    #[test]
    fn demorgan_handles_leq() {
        check(
            r"use ordable::Orderable;
fn f() {
    Orderable < Orderable &&$0 Orderable <= Orderable
}",
            r"use ordable::Orderable;
fn f() {
    !(Orderable >= Orderable || Orderable > Orderable)
}",
        );
        check(
            r"use ordable::NonOrderable;
fn f() {
    NonOrderable < NonOrderable &&$0 NonOrderable <= NonOrderable
}",
            r"use ordable::NonOrderable;
fn f() {
    !(!(NonOrderable < NonOrderable) || !(NonOrderable <= NonOrderable))
}",
        );
    }

    #[test]
    fn demorgan_handles_geq() {
        check(
            r"use ordable::Orderable;
fn f() {
    Orderable > Orderable &&$0 Orderable >= Orderable
}",
            r"use ordable::Orderable;
fn f() {
    !(Orderable <= Orderable || Orderable < Orderable)
}",
        );
        check(
            r"use ordable::NonOrderable;
fn f() {
    Orderable > Orderable &&$0 Orderable >= Orderable
}",
            r"use ordable::NonOrderable;
fn f() {
    !(!(Orderable > Orderable) || !(Orderable >= Orderable))
}",
        );
    }

    #[test]
    fn demorgan_turns_and_into_or() {
        check_assist(apply_demorgan, "fn f() { !x &&$0 !x }", "fn f() { !(x || x) }")
    }

    #[test]
    fn demorgan_turns_or_into_and() {
        check_assist(apply_demorgan, "fn f() { !x ||$0 !x }", "fn f() { !(x && x) }")
    }

    #[test]
    fn demorgan_removes_inequality() {
        check_assist(apply_demorgan, "fn f() { x != x ||$0 !x }", "fn f() { !(x == x && x) }")
    }

    #[test]
    fn demorgan_general_case() {
        check_assist(apply_demorgan, "fn f() { x ||$0 x }", "fn f() { !(!x && !x) }")
    }

    #[test]
    fn demorgan_doesnt_apply_with_cursor_not_on_op() {
        check_assist_not_applicable(apply_demorgan, "fn f() { $0 !x || !x }")
    }
}
<commit_msg>Special-case parenthesized and negated expressions in demorgan assist<commit_after>use syntax::ast::{self, AstNode};
use test_utils::mark;

use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists};

// Assist: apply_demorgan
//
// Apply https://en.wikipedia.org/wiki/De_Morgan%27s_laws[De Morgan's law].
// This transforms expressions of the form `!l || !r` into `!(l && r)`.
// This also works with `&&`. This assist can only be applied with the cursor
// on either `||` or `&&`.
//
// ```
// fn main() {
//     if x != 4 ||$0 y < 3.14 {}
// }
// ```
// ->
// ```
// fn main() {
//     if !(x == 4 && !(y < 3.14)) {}
// }
// ```
pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
    let expr = ctx.find_node_at_offset::<ast::BinExpr>()?;
    let op = expr.op_kind()?;
    let op_range = expr.op_token()?.text_range();
    let opposite_op = opposite_logic_op(op)?;
    let cursor_in_range = op_range.contains_range(ctx.frange.range);
    if !cursor_in_range {
        return None;
    }

    let lhs = expr.lhs()?;
    let lhs_range = lhs.syntax().text_range();
    let not_lhs = invert_boolean_expression(&ctx.sema, lhs);

    let rhs = expr.rhs()?;
    let rhs_range = rhs.syntax().text_range();
    let not_rhs = invert_boolean_expression(&ctx.sema, rhs);

    acc.add(
        AssistId("apply_demorgan", AssistKind::RefactorRewrite),
        "Apply De Morgan's law",
        op_range,
        |edit| {
            let paren_expr = expr.syntax().parent().and_then(|parent| ast::ParenExpr::cast(parent));

            let neg_expr = paren_expr
                .clone()
                .and_then(|paren_expr| paren_expr.syntax().parent())
                .and_then(|parent| ast::PrefixExpr::cast(parent))
                .and_then(|prefix_expr| {
                    if prefix_expr.op_kind().unwrap() == ast::PrefixOp::Not {
                        Some(prefix_expr)
                    } else {
                        None
                    }
                });

            edit.replace(op_range, opposite_op);

            if let Some(paren_expr) = paren_expr {
                edit.replace(lhs_range, not_lhs.syntax().text());
                edit.replace(rhs_range, not_rhs.syntax().text());
                if let Some(neg_expr) = neg_expr {
                    mark::hit!(demorgan_double_negation);
                    edit.replace(neg_expr.op_token().unwrap().text_range(), "");
                } else {
                    mark::hit!(demorgan_double_parens);
                    edit.replace(paren_expr.l_paren_token().unwrap().text_range(), "!(");
                }
            } else {
                edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text()));
                edit.replace(rhs_range, format!("{})", not_rhs.syntax().text()));
            }
        },
    )
}

// Return the opposite text for a given logical operator, if it makes sense
fn opposite_logic_op(kind: ast::BinOp) -> Option<&'static str> {
    match kind {
        ast::BinOp::BooleanOr => Some("&&"),
        ast::BinOp::BooleanAnd => Some("||"),
        _ => None,
    }
}

#[cfg(test)]
mod tests {
    use ide_db::helpers::FamousDefs;
    use test_utils::mark;

    use super::*;

    use crate::tests::{check_assist, check_assist_not_applicable};

    const ORDABLE_FIXTURE: &'static str = r"
//- /lib.rs deps:core crate:ordable
struct NonOrderable;
struct Orderable;
impl core::cmp::Ord for Orderable {}
";

    fn check(ra_fixture_before: &str, ra_fixture_after: &str) {
        let before = &format!(
            "//- /main.rs crate:main deps:core,ordable\n{}\n{}{}",
            ra_fixture_before,
            FamousDefs::FIXTURE,
            ORDABLE_FIXTURE
        );
        check_assist(apply_demorgan, before, &format!("{}\n", ra_fixture_after));
    }

    #[test]
    fn demorgan_handles_leq() {
        check(
            r"use ordable::Orderable;
fn f() {
    Orderable < Orderable &&$0 Orderable <= Orderable
}",
            r"use ordable::Orderable;
fn f() {
    !(Orderable >= Orderable || Orderable > Orderable)
}",
        );
        check(
            r"use ordable::NonOrderable;
fn f() {
    NonOrderable < NonOrderable &&$0 NonOrderable <= NonOrderable
}",
            r"use ordable::NonOrderable;
fn f() {
    !(!(NonOrderable < NonOrderable) || !(NonOrderable <= NonOrderable))
}",
        );
    }

    #[test]
    fn demorgan_handles_geq() {
        check(
            r"use ordable::Orderable;
fn f() {
    Orderable > Orderable &&$0 Orderable >= Orderable
}",
            r"use ordable::Orderable;
fn f() {
    !(Orderable <= Orderable || Orderable < Orderable)
}",
        );
        check(
            r"use ordable::NonOrderable;
fn f() {
    Orderable > Orderable &&$0 Orderable >= Orderable
}",
            r"use ordable::NonOrderable;
fn f() {
    !(!(Orderable > Orderable) || !(Orderable >= Orderable))
}",
        );
    }

    #[test]
    fn demorgan_turns_and_into_or() {
        check_assist(apply_demorgan, "fn f() { !x &&$0 !x }", "fn f() { !(x || x) }")
    }

    #[test]
    fn demorgan_turns_or_into_and() {
        check_assist(apply_demorgan, "fn f() { !x ||$0 !x }", "fn f() { !(x && x) }")
    }

    #[test]
    fn demorgan_removes_inequality() {
        check_assist(apply_demorgan, "fn f() { x != x ||$0 !x }", "fn f() { !(x == x && x) }")
    }

    #[test]
    fn demorgan_general_case() {
        check_assist(apply_demorgan, "fn f() { x ||$0 x }", "fn f() { !(!x && !x) }")
    }

    #[test]
    fn demorgan_doesnt_apply_with_cursor_not_on_op() {
        check_assist_not_applicable(apply_demorgan, "fn f() { $0 !x || !x }")
    }

    #[test]
    fn demorgan_doesnt_double_negation() {
        mark::check!(demorgan_double_negation);
        check_assist(apply_demorgan, "fn f() { !(x ||$0 x) }", "fn f() { (!x && !x) }")
    }

    #[test]
    fn demorgan_doesnt_double_parens() {
        mark::check!(demorgan_double_parens);
        check_assist(apply_demorgan, "fn f() { (x ||$0 x) }", "fn f() { !(!x && !x) }")
    }
}
<|endoftext|>


<commit_before>use crate::{Assist, AssistId, AssistCtx};

use hir::Resolver;
use hir::db::HirDatabase;
use ra_syntax::{SmolStr, SyntaxKind, TextRange, TextUnit, TreeArc};
use ra_syntax::ast::{self, AstNode, FnDef, ImplItem, ImplItemKind, NameOwner};
use ra_db::FilePosition;
use ra_fmt::{leading_indent, reindent};

use itertools::Itertools;

/// Given an `ast::ImplBlock`, resolves the target trait (the one being
/// implemented) to a `ast::TraitDef`.
pub(crate) fn resolve_target_trait_def(
    db: &impl HirDatabase,
    resolver: &Resolver,
    impl_block: &ast::ImplBlock,
) -> Option<TreeArc<ast::TraitDef>> {
    let ast_path = impl_block.target_trait().map(AstNode::syntax).and_then(ast::PathType::cast)?;
    let hir_path = ast_path.path().and_then(hir::Path::from_ast)?;

    match resolver.resolve_path(db, &hir_path).take_types() {
        Some(hir::Resolution::Def(hir::ModuleDef::Trait(def))) => Some(def.source(db).1),
        _ => None,
    }
}

pub(crate) fn build_func_body(def: &ast::FnDef) -> String {
    let mut buf = String::new();

    for child in def.syntax().children() {
        if child.kind() == SyntaxKind::SEMI {
            buf.push_str(" { unimplemented!() }")
        } else {
            child.text().push_to(&mut buf);
        }
    }

    buf.trim_end().to_string()
}

pub(crate) fn add_missing_impl_members(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
    let node = ctx.covering_node();
    let impl_node = node.ancestors().find_map(ast::ImplBlock::cast)?;
    let impl_item_list = impl_node.item_list()?;
    // Don't offer the assist when cursor is at the end, outside the block itself.
    let cursor_range = TextRange::from_to(node.range().end(), node.range().end());
    if node.range().end() == impl_node.syntax().range().end() {
        return None;
    }

    let trait_def = {
        let position = FilePosition { file_id: ctx.frange.file_id, offset: cursor_range.end() };
        let resolver = hir::source_binder::resolver_for_position(ctx.db, position);

        resolve_target_trait_def(ctx.db, &resolver, impl_node)?
    };

    let missing_fns: Vec<_> = {
        let fn_def_opt = |kind| if let ImplItemKind::FnDef(def) = kind { Some(def) } else { None };
        let def_name = |def| -> Option<&SmolStr> { FnDef::name(def).map(ast::Name::text) };

        let trait_items =
            trait_def.syntax().descendants().find_map(ast::ItemList::cast)?.impl_items();
        let impl_items = impl_item_list.impl_items();

        let trait_fns = trait_items.map(ImplItem::kind).filter_map(fn_def_opt).collect::<Vec<_>>();
        let impl_fns = impl_items.map(ImplItem::kind).filter_map(fn_def_opt).collect::<Vec<_>>();

        trait_fns
            .into_iter()
            .filter(|t| def_name(t).is_some())
            .filter(|t| impl_fns.iter().all(|i| def_name(i) != def_name(t)))
            .collect()
    };
    if missing_fns.is_empty() {
        return None;
    }

    ctx.add_action(AssistId("add_impl_missing_members"), "add missing impl members", |edit| {
        let (parent_indent, indent) = {
            // FIXME: Find a way to get the indent already used in the file.
            // Now, we copy the indent of first item or indent with 4 spaces relative to impl block
            const DEFAULT_INDENT: &str = "    ";
            let first_item = impl_item_list.impl_items().next();
            let first_item_indent =
                first_item.and_then(|i| leading_indent(i.syntax())).map(ToOwned::to_owned);
            let impl_block_indent = leading_indent(impl_node.syntax()).unwrap_or_default();

            (
                impl_block_indent.to_owned(),
                first_item_indent.unwrap_or_else(|| impl_block_indent.to_owned() + DEFAULT_INDENT),
            )
        };

        let changed_range = {
            let last_whitespace = impl_item_list.syntax().children();
            let last_whitespace = last_whitespace.filter_map(ast::Whitespace::cast).last();

            last_whitespace.map(|w| w.syntax().range()).unwrap_or(cursor_range)
        };

        let func_bodies = format!("\n{}", missing_fns.into_iter().map(build_func_body).join("\n"));
        let trailing_whitespace = format!("\n{}", parent_indent);
        let func_bodies = reindent(&func_bodies, &indent) + &trailing_whitespace;

        let replaced_text_range = TextUnit::of_str(&func_bodies);

        edit.replace(changed_range, func_bodies);
        edit.set_cursor(
            changed_range.start() + replaced_text_range - TextUnit::of_str(&trailing_whitespace),
        );
    });

    ctx.build()
}

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

    #[test]
    fn test_add_missing_impl_members() {
        check_assist(
            add_missing_impl_members,
            "
trait Foo {
    fn foo(&self);
    fn bar(&self);
    fn baz(&self);
}

struct S;

impl Foo for S {
    fn bar(&self) {}
    <|>
}",
            "
trait Foo {
    fn foo(&self);
    fn bar(&self);
    fn baz(&self);
}

struct S;

impl Foo for S {
    fn bar(&self) {}
    fn foo(&self) { unimplemented!() }
    fn baz(&self) { unimplemented!() }<|>
}",
        );
    }

    #[test]
    fn test_copied_overriden_members() {
        check_assist(
            add_missing_impl_members,
            "
trait Foo {
    fn foo(&self);
    fn bar(&self) -> bool { true }
    fn baz(&self) -> u32 { 42 }
}

struct S;

impl Foo for S {
    fn bar(&self) {}
    <|>
}",
            "
trait Foo {
    fn foo(&self);
    fn bar(&self) -> bool { true }
    fn baz(&self) -> u32 { 42 }
}

struct S;

impl Foo for S {
    fn bar(&self) {}
    fn foo(&self) { unimplemented!() }
    fn baz(&self) -> u32 { 42 }<|>
}",
        );
    }

    #[test]
    fn test_empty_impl_block() {
        check_assist(
            add_missing_impl_members,
            "
trait Foo { fn foo(&self); }
struct S;
impl Foo for S {<|>}",
            "
trait Foo { fn foo(&self); }
struct S;
impl Foo for S {
    fn foo(&self) { unimplemented!() }<|>
}",
        );
    }

    #[test]
    fn test_cursor_after_empty_impl_block() {
        check_assist_not_applicable(
            add_missing_impl_members,
            "
trait Foo { fn foo(&self); }
struct S;
impl Foo for S {}<|>",
        )
    }

    #[test]
    fn test_empty_trait() {
        check_assist_not_applicable(
            add_missing_impl_members,
            "
trait Foo;
struct S;
impl Foo for S { <|> }",
        )
    }

    #[test]
    fn test_ignore_unnamed_trait_members() {
        check_assist(
            add_missing_impl_members,
            "
trait Foo {
    fn (arg: u32);
    fn valid(some: u32) -> bool { false }
}
struct S;
impl Foo for S { <|> }",
            "
trait Foo {
    fn (arg: u32);
    fn valid(some: u32) -> bool { false }
}
struct S;
impl Foo for S {
    fn valid(some: u32) -> bool { false }<|>
}",
        )
    }

    #[test]
    fn test_indented_impl_block() {
        check_assist(
            add_missing_impl_members,
            "
trait Foo {
    fn valid(some: u32) -> bool { false }
}
struct S;

mod my_mod {
    impl crate::Foo for S { <|> }
}",
            "
trait Foo {
    fn valid(some: u32) -> bool { false }
}
struct S;

mod my_mod {
    impl crate::Foo for S {
        fn valid(some: u32) -> bool { false }<|>
    }
}",
        )
    }
}
<commit_msg>Provide assist when cursor is immediately outside impl item block<commit_after>use crate::{Assist, AssistId, AssistCtx};

use hir::Resolver;
use hir::db::HirDatabase;
use ra_syntax::{SmolStr, SyntaxKind, TextRange, TextUnit, TreeArc};
use ra_syntax::ast::{self, AstNode, FnDef, ImplItem, ImplItemKind, NameOwner};
use ra_db::FilePosition;
use ra_fmt::{leading_indent, reindent};

use itertools::Itertools;

/// Given an `ast::ImplBlock`, resolves the target trait (the one being
/// implemented) to a `ast::TraitDef`.
fn resolve_target_trait_def(
    db: &impl HirDatabase,
    resolver: &Resolver,
    impl_block: &ast::ImplBlock,
) -> Option<TreeArc<ast::TraitDef>> {
    let ast_path = impl_block.target_trait().map(AstNode::syntax).and_then(ast::PathType::cast)?;
    let hir_path = ast_path.path().and_then(hir::Path::from_ast)?;

    match resolver.resolve_path(db, &hir_path).take_types() {
        Some(hir::Resolution::Def(hir::ModuleDef::Trait(def))) => Some(def.source(db).1),
        _ => None,
    }
}

fn build_func_body(def: &ast::FnDef) -> String {
    let mut buf = String::new();

    for child in def.syntax().children() {
        if child.kind() == SyntaxKind::SEMI {
            buf.push_str(" { unimplemented!() }")
        } else {
            child.text().push_to(&mut buf);
        }
    }

    buf.trim_end().to_string()
}

pub(crate) fn add_missing_impl_members(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
    let impl_node = ctx.node_at_offset::<ast::ImplBlock>()?;
    let impl_item_list = impl_node.item_list()?;

    let trait_def = {
        let file_id = ctx.frange.file_id;
        let position = FilePosition { file_id, offset: impl_node.syntax().range().start() };
        let resolver = hir::source_binder::resolver_for_position(ctx.db, position);

        resolve_target_trait_def(ctx.db, &resolver, impl_node)?
    };

    let missing_fns: Vec<_> = {
        let fn_def_opt = |kind| if let ImplItemKind::FnDef(def) = kind { Some(def) } else { None };
        let def_name = |def| -> Option<&SmolStr> { FnDef::name(def).map(ast::Name::text) };

        let trait_items =
            trait_def.syntax().descendants().find_map(ast::ItemList::cast)?.impl_items();
        let impl_items = impl_item_list.impl_items();

        let trait_fns = trait_items.map(ImplItem::kind).filter_map(fn_def_opt).collect::<Vec<_>>();
        let impl_fns = impl_items.map(ImplItem::kind).filter_map(fn_def_opt).collect::<Vec<_>>();

        trait_fns
            .into_iter()
            .filter(|t| def_name(t).is_some())
            .filter(|t| impl_fns.iter().all(|i| def_name(i) != def_name(t)))
            .collect()
    };
    if missing_fns.is_empty() {
        return None;
    }

    ctx.add_action(AssistId("add_impl_missing_members"), "add missing impl members", |edit| {
        let (parent_indent, indent) = {
            // FIXME: Find a way to get the indent already used in the file.
            // Now, we copy the indent of first item or indent with 4 spaces relative to impl block
            const DEFAULT_INDENT: &str = "    ";
            let first_item = impl_item_list.impl_items().next();
            let first_item_indent =
                first_item.and_then(|i| leading_indent(i.syntax())).map(ToOwned::to_owned);
            let impl_block_indent = leading_indent(impl_node.syntax()).unwrap_or_default();

            (
                impl_block_indent.to_owned(),
                first_item_indent.unwrap_or_else(|| impl_block_indent.to_owned() + DEFAULT_INDENT),
            )
        };

        let changed_range = {
            let children = impl_item_list.syntax().children();
            let last_whitespace = children.filter_map(ast::Whitespace::cast).last();

            last_whitespace.map(|w| w.syntax().range()).unwrap_or_else(|| {
                let in_brackets = impl_item_list.syntax().range().end() - TextUnit::of_str("}");
                TextRange::from_to(in_brackets, in_brackets)
            })
        };

        let func_bodies = format!("\n{}", missing_fns.into_iter().map(build_func_body).join("\n"));
        let trailing_whitespace = format!("\n{}", parent_indent);
        let func_bodies = reindent(&func_bodies, &indent) + &trailing_whitespace;

        let replaced_text_range = TextUnit::of_str(&func_bodies);

        edit.replace(changed_range, func_bodies);
        edit.set_cursor(
            changed_range.start() + replaced_text_range - TextUnit::of_str(&trailing_whitespace),
        );
    });

    ctx.build()
}

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

    #[test]
    fn test_add_missing_impl_members() {
        check_assist(
            add_missing_impl_members,
            "
trait Foo {
    fn foo(&self);
    fn bar(&self);
    fn baz(&self);
}

struct S;

impl Foo for S {
    fn bar(&self) {}
    <|>
}",
            "
trait Foo {
    fn foo(&self);
    fn bar(&self);
    fn baz(&self);
}

struct S;

impl Foo for S {
    fn bar(&self) {}
    fn foo(&self) { unimplemented!() }
    fn baz(&self) { unimplemented!() }<|>
}",
        );
    }

    #[test]
    fn test_copied_overriden_members() {
        check_assist(
            add_missing_impl_members,
            "
trait Foo {
    fn foo(&self);
    fn bar(&self) -> bool { true }
    fn baz(&self) -> u32 { 42 }
}

struct S;

impl Foo for S {
    fn bar(&self) {}
    <|>
}",
            "
trait Foo {
    fn foo(&self);
    fn bar(&self) -> bool { true }
    fn baz(&self) -> u32 { 42 }
}

struct S;

impl Foo for S {
    fn bar(&self) {}
    fn foo(&self) { unimplemented!() }
    fn baz(&self) -> u32 { 42 }<|>
}",
        );
    }

    #[test]
    fn test_empty_impl_block() {
        check_assist(
            add_missing_impl_members,
            "
trait Foo { fn foo(&self); }
struct S;
impl Foo for S {<|>}",
            "
trait Foo { fn foo(&self); }
struct S;
impl Foo for S {
    fn foo(&self) { unimplemented!() }<|>
}",
        );
    }

    #[test]
    fn test_cursor_after_empty_impl_block() {
        check_assist(
            add_missing_impl_members,
            "
trait Foo { fn foo(&self); }
struct S;
impl Foo for S {}<|>",
            "
trait Foo { fn foo(&self); }
struct S;
impl Foo for S {
    fn foo(&self) { unimplemented!() }<|>
}",
        )
    }

    #[test]
    fn test_empty_trait() {
        check_assist_not_applicable(
            add_missing_impl_members,
            "
trait Foo;
struct S;
impl Foo for S { <|> }",
        )
    }

    #[test]
    fn test_ignore_unnamed_trait_members() {
        check_assist(
            add_missing_impl_members,
            "
trait Foo {
    fn (arg: u32);
    fn valid(some: u32) -> bool { false }
}
struct S;
impl Foo for S { <|> }",
            "
trait Foo {
    fn (arg: u32);
    fn valid(some: u32) -> bool { false }
}
struct S;
impl Foo for S {
    fn valid(some: u32) -> bool { false }<|>
}",
        )
    }

    #[test]
    fn test_indented_impl_block() {
        check_assist(
            add_missing_impl_members,
            "
trait Foo {
    fn valid(some: u32) -> bool { false }
}
struct S;

mod my_mod {
    impl crate::Foo for S { <|> }
}",
            "
trait Foo {
    fn valid(some: u32) -> bool { false }
}
struct S;

mod my_mod {
    impl crate::Foo for S {
        fn valid(some: u32) -> bool { false }<|>
    }
}",
        )
    }
}
<|endoftext|>


<commit_before>use std::collections::HashMap;
use std::fs::File;
use std::path::Path;

use log::warn;
use regex::Regex;
use serde::{Deserialize, Serialize};

use gerritbot_spark as spark;

use super::BotError;

#[derive(Debug, PartialEq)]
pub enum AddFilterResult {
    UserNotFound,
    UserDisabled,
    InvalidFilter,
    FilterNotConfigured,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Filter {
    pub regex: String,
    pub enabled: bool,
}

impl Filter {
    pub fn new<A: Into<String>>(regex: A) -> Self {
        Self {
            regex: regex.into(),
            enabled: true,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
    // Legacy attribute.  Keep so we don't drop it on deserialize, serialize.
    // Should be removed later.
    spark_person_id: Option<String>,
    /// email of the user; assumed to be the same in Spark and Gerrit
    pub email: spark::Email,
    pub enabled: bool,
    pub filter: Option<Filter>,
}

impl User {
    fn new(email: spark::Email) -> Self {
        Self {
            spark_person_id: None,
            email: email,
            filter: None,
            enabled: true,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct State {
    users: Vec<User>,
    #[serde(skip_serializing, skip_deserializing)]
    email_index: HashMap<spark::Email, usize>,
}

impl State {
    pub fn new() -> Self {
        Default::default()
    }

    pub fn load<P>(filename: P) -> Result<Self, BotError>
    where
        P: AsRef<Path>,
    {
        let f = File::open(filename)?;

        serde_json::from_reader(f)
            .map(|mut state: Self| {
                state.index_users();
                state
            })
            .map_err(BotError::from)
    }

    fn index_users(&mut self) {
        for (user_pos, user) in self.users.iter().enumerate() {
            self.email_index.insert(user.email.clone(), user_pos);
        }
    }

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

    // Note: This method is not idempotent, and in particular, when adding the same user twice,
    // it will completely mess up the indexes.
    pub fn add_user<'a>(&'a mut self, email: &spark::EmailRef) -> &'a mut User {
        let user_pos = self.users.len();
        self.users.push(User::new(email.to_owned()));
        self.email_index.insert(email.to_owned(), user_pos);
        self.users.last_mut().unwrap()
    }

    fn find_or_add_user_by_email<'a>(&'a mut self, email: &spark::EmailRef) -> &'a mut User {
        let pos = self.users.iter().position(|u| u.email == email);
        let user: &'a mut User = match pos {
            Some(pos) => &mut self.users[pos],
            None => self.add_user(email),
        };
        user
    }

    fn find_user_mut<'a, P: ?Sized>(&'a mut self, email: &P) -> Option<&'a mut User>
    where
        spark::Email: std::borrow::Borrow<P>,
        P: std::hash::Hash + Eq,
    {
        self.email_index
            .get(email)
            .cloned()
            .map(move |pos| &mut self.users[pos])
    }

    pub fn find_user<'a, P: ?Sized>(&'a self, email: &P) -> Option<&'a User>
    where
        spark::Email: std::borrow::Borrow<P>,
        P: std::hash::Hash + Eq,
    {
        self.email_index
            .get(email)
            .cloned()
            .map(|pos| &self.users[pos])
    }

    pub fn find_user_by_email<'a, E: ?Sized>(&self, email: &E) -> Option<&User>
    where
        spark::Email: std::borrow::Borrow<E>,
        E: std::hash::Hash + Eq,
    {
        self.email_index.get(email).map(|pos| &self.users[*pos])
    }

    pub fn enable<'a>(&'a mut self, email: &spark::EmailRef, enabled: bool) -> &'a User {
        let user: &'a mut User = self.find_or_add_user_by_email(email);
        user.enabled = enabled;
        user
    }

    pub fn add_filter<A>(
        &mut self,
        email: &spark::EmailRef,
        filter: A,
    ) -> Result<(), AddFilterResult>
    where
        A: Into<String>,
    {
        let user = self.find_user_mut(email);
        match user {
            Some(user) => {
                if !user.enabled {
                    Err(AddFilterResult::UserDisabled)
                } else {
                    let filter: String = filter.into();
                    if Regex::new(&filter).is_err() {
                        return Err(AddFilterResult::InvalidFilter);
                    }
                    user.filter = Some(Filter::new(filter));
                    Ok(())
                }
            }
            None => Err(AddFilterResult::UserNotFound),
        }
    }

    pub fn get_filter<'a>(
        &'a self,
        email: &spark::EmailRef,
    ) -> Result<Option<&'a Filter>, AddFilterResult> {
        let user = self.find_user(email);
        match user {
            Some(user) => Ok(user.filter.as_ref()),
            None => Err(AddFilterResult::UserNotFound),
        }
    }

    pub fn enable_filter(
        &mut self,
        email: &spark::EmailRef,
        enabled: bool,
    ) -> Result<String /* filter */, AddFilterResult> {
        let user = self.find_user_mut(email);
        match user {
            Some(user) => {
                if !user.enabled {
                    Err(AddFilterResult::UserDisabled)
                } else {
                    match user.filter.as_mut() {
                        Some(filter) => {
                            if Regex::new(&filter.regex).is_err() {
                                return Err(AddFilterResult::InvalidFilter);
                            }
                            filter.enabled = enabled;
                            Ok(filter.regex.clone())
                        }
                        None => Err(AddFilterResult::FilterNotConfigured),
                    }
                }
            }
            None => Err(AddFilterResult::UserNotFound),
        }
    }

    pub fn users(&self) -> impl Iterator<Item = &User> + Clone {
        self.users.iter()
    }

    pub fn is_filtered(&self, user: &User, msg: &str) -> bool {
        if let Some(filter) = user.filter.as_ref() {
            if filter.enabled {
                if let Ok(re) = Regex::new(&filter.regex) {
                    return re.is_match(msg);
                } else {
                    warn!(
                        "User {} has configured invalid filter regex: {}",
                        user.email, filter.regex
                    );
                }
            }
        }
        false
    }
}

#[cfg(test)]
mod test {
    use spark::EmailRef;

    use super::*;

    #[test]
    fn test_add_user() {
        let mut state = State::new();
        state.add_user(EmailRef::new("some@example.com"));
        assert_eq!(state.users.len(), 1);
        assert_eq!(state.email_index.len(), 1);
        assert_eq!(state.users[0].email, EmailRef::new("some@example.com"));
        assert_eq!(
            state.email_index.get(EmailRef::new("some@example.com")),
            Some(&0)
        );
        assert_eq!(
            state.email_index.get(EmailRef::new("some@example.com")),
            Some(&0)
        );

        state.add_user(EmailRef::new("some_2@example.com"));
        assert_eq!(state.users.len(), 2);
        assert_eq!(state.email_index.len(), 2);
        assert_eq!(state.users[1].email, EmailRef::new("some_2@example.com"));
        assert_eq!(
            state.email_index.get(EmailRef::new("some_2@example.com")),
            Some(&1)
        );
        assert_eq!(
            state.email_index.get(EmailRef::new("some_2@example.com")),
            Some(&1)
        );

        let user = state.find_user(EmailRef::new("some@example.com"));
        assert!(user.is_some());
        assert_eq!(user.unwrap().email, EmailRef::new("some@example.com"));

        let user = state.find_user(EmailRef::new("some_2@example.com"));
        assert!(user.is_some());
        assert_eq!(user.unwrap().email, EmailRef::new("some_2@example.com"));
    }

    #[test]
    fn add_invalid_filter_for_existing_user() {
        let mut state = State::new();
        state.add_user(EmailRef::new("some@example.com"));
        let res = state.add_filter(EmailRef::new("some@example.com"), ".some_weard_regex/[");
        assert_eq!(res, Err(AddFilterResult::InvalidFilter));
        assert!(state
            .users
            .iter()
            .position(|u| u.email == EmailRef::new("some@example.com") && u.filter == None)
            .is_some());

        let res = state.enable_filter(EmailRef::new("some@example.com"), true);
        assert_eq!(res, Err(AddFilterResult::FilterNotConfigured));
        let res = state.enable_filter(EmailRef::new("some@example.com"), false);
        assert_eq!(res, Err(AddFilterResult::FilterNotConfigured));
    }

    #[test]
    fn add_valid_filter_for_existing_user() {
        let mut state = State::new();
        state.add_user(EmailRef::new("some@example.com"));

        let res = state.add_filter(EmailRef::new("some@example.com"), ".*some_word.*");
        assert!(res.is_ok());
        assert!(state
            .users
            .iter()
            .position(|u| u.email == EmailRef::new("some@example.com")
                && u.filter == Some(Filter::new(".*some_word.*")))
            .is_some());

        {
            let filter = state.get_filter(EmailRef::new("some@example.com"));
            assert_eq!(filter, Ok(Some(&Filter::new(".*some_word.*"))));
        }
        let res = state.enable_filter(EmailRef::new("some@example.com"), false);
        assert_eq!(res, Ok(String::from(".*some_word.*")));
        assert!(state
            .users
            .iter()
            .position(|u| u.email == EmailRef::new("some@example.com")
                && u.filter.as_ref().map(|f| f.enabled) == Some(false))
            .is_some());
        {
            let filter = state
                .get_filter(EmailRef::new("some@example.com"))
                .unwrap()
                .unwrap();
            assert_eq!(filter.regex, ".*some_word.*");
            assert_eq!(filter.enabled, false);
        }
        let res = state.enable_filter(EmailRef::new("some@example.com"), true);
        assert_eq!(res, Ok(String::from(".*some_word.*")));
        assert!(state
            .users
            .iter()
            .position(|u| u.email == EmailRef::new("some@example.com")
                && u.filter.as_ref().map(|f| f.enabled) == Some(true))
            .is_some());
        {
            let filter = state.get_filter(EmailRef::new("some@example.com"));
            assert_eq!(filter, Ok(Some(&Filter::new(".*some_word.*"))));
        }
    }

    #[test]
    fn add_valid_filter_for_non_existing_user() {
        let mut state = State::new();
        let res = state.add_filter(EmailRef::new("some@example.com"), ".*some_word.*");
        assert_eq!(res, Err(AddFilterResult::UserNotFound));
        let res = state.enable_filter(EmailRef::new("some@example.com"), true);
        assert_eq!(res, Err(AddFilterResult::UserNotFound));
        let res = state.enable_filter(EmailRef::new("some@example.com"), false);
        assert_eq!(res, Err(AddFilterResult::UserNotFound));
    }

    #[test]
    fn add_valid_filter_for_disabled_user() {
        let mut state = State::new();
        state.add_user(EmailRef::new("some@example.com"));
        state.users[0].enabled = false;

        let res = state.add_filter(EmailRef::new("some@example.com"), ".*some_word.*");
        assert_eq!(res, Err(AddFilterResult::UserDisabled));
        let res = state.enable_filter(EmailRef::new("some@example.com"), true);
        assert_eq!(res, Err(AddFilterResult::UserDisabled));
        let res = state.enable_filter(EmailRef::new("some@example.com"), false);
        assert_eq!(res, Err(AddFilterResult::UserDisabled));
    }

    #[test]
    fn enable_non_configured_filter_for_existing_user() {
        let mut state = State::new();
        state.add_user(EmailRef::new("some@example.com"));

        let res = state.enable_filter(EmailRef::new("some@example.com"), true);
        assert_eq!(res, Err(AddFilterResult::FilterNotConfigured));
        let res = state.enable_filter(EmailRef::new("some@example.com"), false);
        assert_eq!(res, Err(AddFilterResult::FilterNotConfigured));
    }

    #[test]
    fn enable_invalid_filter_for_existing_user() {
        let mut state = State::new();
        state.add_user(EmailRef::new("some@example.com"));
        state.users[0].filter = Some(Filter::new("invlide_filter_set_from_outside["));

        let res = state.enable_filter(EmailRef::new("some@example.com"), true);
        assert_eq!(res, Err(AddFilterResult::InvalidFilter));
        let res = state.enable_filter(EmailRef::new("some@example.com"), false);
        assert_eq!(res, Err(AddFilterResult::InvalidFilter));
    }

}
<commit_msg>skip serializiation of null values in users<commit_after>use std::collections::HashMap;
use std::fs::File;
use std::path::Path;

use log::warn;
use regex::Regex;
use serde::{Deserialize, Serialize};

use gerritbot_spark as spark;

use super::BotError;

#[derive(Debug, PartialEq)]
pub enum AddFilterResult {
    UserNotFound,
    UserDisabled,
    InvalidFilter,
    FilterNotConfigured,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Filter {
    pub regex: String,
    pub enabled: bool,
}

impl Filter {
    pub fn new<A: Into<String>>(regex: A) -> Self {
        Self {
            regex: regex.into(),
            enabled: true,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
    // Legacy attribute.  Keep so we don't drop it on deserialize, serialize.
    // Should be removed later.
    #[serde(skip_serializing_if = "Option::is_none")]
    spark_person_id: Option<String>,
    /// email of the user; assumed to be the same in Spark and Gerrit
    pub email: spark::Email,
    pub enabled: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub filter: Option<Filter>,
}

impl User {
    fn new(email: spark::Email) -> Self {
        Self {
            spark_person_id: None,
            email: email,
            filter: None,
            enabled: true,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct State {
    users: Vec<User>,
    #[serde(skip_serializing, skip_deserializing)]
    email_index: HashMap<spark::Email, usize>,
}

impl State {
    pub fn new() -> Self {
        Default::default()
    }

    pub fn load<P>(filename: P) -> Result<Self, BotError>
    where
        P: AsRef<Path>,
    {
        let f = File::open(filename)?;

        serde_json::from_reader(f)
            .map(|mut state: Self| {
                state.index_users();
                state
            })
            .map_err(BotError::from)
    }

    fn index_users(&mut self) {
        for (user_pos, user) in self.users.iter().enumerate() {
            self.email_index.insert(user.email.clone(), user_pos);
        }
    }

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

    // Note: This method is not idempotent, and in particular, when adding the same user twice,
    // it will completely mess up the indexes.
    pub fn add_user<'a>(&'a mut self, email: &spark::EmailRef) -> &'a mut User {
        let user_pos = self.users.len();
        self.users.push(User::new(email.to_owned()));
        self.email_index.insert(email.to_owned(), user_pos);
        self.users.last_mut().unwrap()
    }

    fn find_or_add_user_by_email<'a>(&'a mut self, email: &spark::EmailRef) -> &'a mut User {
        let pos = self.users.iter().position(|u| u.email == email);
        let user: &'a mut User = match pos {
            Some(pos) => &mut self.users[pos],
            None => self.add_user(email),
        };
        user
    }

    fn find_user_mut<'a, P: ?Sized>(&'a mut self, email: &P) -> Option<&'a mut User>
    where
        spark::Email: std::borrow::Borrow<P>,
        P: std::hash::Hash + Eq,
    {
        self.email_index
            .get(email)
            .cloned()
            .map(move |pos| &mut self.users[pos])
    }

    pub fn find_user<'a, P: ?Sized>(&'a self, email: &P) -> Option<&'a User>
    where
        spark::Email: std::borrow::Borrow<P>,
        P: std::hash::Hash + Eq,
    {
        self.email_index
            .get(email)
            .cloned()
            .map(|pos| &self.users[pos])
    }

    pub fn find_user_by_email<'a, E: ?Sized>(&self, email: &E) -> Option<&User>
    where
        spark::Email: std::borrow::Borrow<E>,
        E: std::hash::Hash + Eq,
    {
        self.email_index.get(email).map(|pos| &self.users[*pos])
    }

    pub fn enable<'a>(&'a mut self, email: &spark::EmailRef, enabled: bool) -> &'a User {
        let user: &'a mut User = self.find_or_add_user_by_email(email);
        user.enabled = enabled;
        user
    }

    pub fn add_filter<A>(
        &mut self,
        email: &spark::EmailRef,
        filter: A,
    ) -> Result<(), AddFilterResult>
    where
        A: Into<String>,
    {
        let user = self.find_user_mut(email);
        match user {
            Some(user) => {
                if !user.enabled {
                    Err(AddFilterResult::UserDisabled)
                } else {
                    let filter: String = filter.into();
                    if Regex::new(&filter).is_err() {
                        return Err(AddFilterResult::InvalidFilter);
                    }
                    user.filter = Some(Filter::new(filter));
                    Ok(())
                }
            }
            None => Err(AddFilterResult::UserNotFound),
        }
    }

    pub fn get_filter<'a>(
        &'a self,
        email: &spark::EmailRef,
    ) -> Result<Option<&'a Filter>, AddFilterResult> {
        let user = self.find_user(email);
        match user {
            Some(user) => Ok(user.filter.as_ref()),
            None => Err(AddFilterResult::UserNotFound),
        }
    }

    pub fn enable_filter(
        &mut self,
        email: &spark::EmailRef,
        enabled: bool,
    ) -> Result<String /* filter */, AddFilterResult> {
        let user = self.find_user_mut(email);
        match user {
            Some(user) => {
                if !user.enabled {
                    Err(AddFilterResult::UserDisabled)
                } else {
                    match user.filter.as_mut() {
                        Some(filter) => {
                            if Regex::new(&filter.regex).is_err() {
                                return Err(AddFilterResult::InvalidFilter);
                            }
                            filter.enabled = enabled;
                            Ok(filter.regex.clone())
                        }
                        None => Err(AddFilterResult::FilterNotConfigured),
                    }
                }
            }
            None => Err(AddFilterResult::UserNotFound),
        }
    }

    pub fn users(&self) -> impl Iterator<Item = &User> + Clone {
        self.users.iter()
    }

    pub fn is_filtered(&self, user: &User, msg: &str) -> bool {
        if let Some(filter) = user.filter.as_ref() {
            if filter.enabled {
                if let Ok(re) = Regex::new(&filter.regex) {
                    return re.is_match(msg);
                } else {
                    warn!(
                        "User {} has configured invalid filter regex: {}",
                        user.email, filter.regex
                    );
                }
            }
        }
        false
    }
}

#[cfg(test)]
mod test {
    use spark::EmailRef;

    use super::*;

    #[test]
    fn test_add_user() {
        let mut state = State::new();
        state.add_user(EmailRef::new("some@example.com"));
        assert_eq!(state.users.len(), 1);
        assert_eq!(state.email_index.len(), 1);
        assert_eq!(state.users[0].email, EmailRef::new("some@example.com"));
        assert_eq!(
            state.email_index.get(EmailRef::new("some@example.com")),
            Some(&0)
        );
        assert_eq!(
            state.email_index.get(EmailRef::new("some@example.com")),
            Some(&0)
        );

        state.add_user(EmailRef::new("some_2@example.com"));
        assert_eq!(state.users.len(), 2);
        assert_eq!(state.email_index.len(), 2);
        assert_eq!(state.users[1].email, EmailRef::new("some_2@example.com"));
        assert_eq!(
            state.email_index.get(EmailRef::new("some_2@example.com")),
            Some(&1)
        );
        assert_eq!(
            state.email_index.get(EmailRef::new("some_2@example.com")),
            Some(&1)
        );

        let user = state.find_user(EmailRef::new("some@example.com"));
        assert!(user.is_some());
        assert_eq!(user.unwrap().email, EmailRef::new("some@example.com"));

        let user = state.find_user(EmailRef::new("some_2@example.com"));
        assert!(user.is_some());
        assert_eq!(user.unwrap().email, EmailRef::new("some_2@example.com"));
    }

    #[test]
    fn add_invalid_filter_for_existing_user() {
        let mut state = State::new();
        state.add_user(EmailRef::new("some@example.com"));
        let res = state.add_filter(EmailRef::new("some@example.com"), ".some_weard_regex/[");
        assert_eq!(res, Err(AddFilterResult::InvalidFilter));
        assert!(state
            .users
            .iter()
            .position(|u| u.email == EmailRef::new("some@example.com") && u.filter == None)
            .is_some());

        let res = state.enable_filter(EmailRef::new("some@example.com"), true);
        assert_eq!(res, Err(AddFilterResult::FilterNotConfigured));
        let res = state.enable_filter(EmailRef::new("some@example.com"), false);
        assert_eq!(res, Err(AddFilterResult::FilterNotConfigured));
    }

    #[test]
    fn add_valid_filter_for_existing_user() {
        let mut state = State::new();
        state.add_user(EmailRef::new("some@example.com"));

        let res = state.add_filter(EmailRef::new("some@example.com"), ".*some_word.*");
        assert!(res.is_ok());
        assert!(state
            .users
            .iter()
            .position(|u| u.email == EmailRef::new("some@example.com")
                && u.filter == Some(Filter::new(".*some_word.*")))
            .is_some());

        {
            let filter = state.get_filter(EmailRef::new("some@example.com"));
            assert_eq!(filter, Ok(Some(&Filter::new(".*some_word.*"))));
        }
        let res = state.enable_filter(EmailRef::new("some@example.com"), false);
        assert_eq!(res, Ok(String::from(".*some_word.*")));
        assert!(state
            .users
            .iter()
            .position(|u| u.email == EmailRef::new("some@example.com")
                && u.filter.as_ref().map(|f| f.enabled) == Some(false))
            .is_some());
        {
            let filter = state
                .get_filter(EmailRef::new("some@example.com"))
                .unwrap()
                .unwrap();
            assert_eq!(filter.regex, ".*some_word.*");
            assert_eq!(filter.enabled, false);
        }
        let res = state.enable_filter(EmailRef::new("some@example.com"), true);
        assert_eq!(res, Ok(String::from(".*some_word.*")));
        assert!(state
            .users
            .iter()
            .position(|u| u.email == EmailRef::new("some@example.com")
                && u.filter.as_ref().map(|f| f.enabled) == Some(true))
            .is_some());
        {
            let filter = state.get_filter(EmailRef::new("some@example.com"));
            assert_eq!(filter, Ok(Some(&Filter::new(".*some_word.*"))));
        }
    }

    #[test]
    fn add_valid_filter_for_non_existing_user() {
        let mut state = State::new();
        let res = state.add_filter(EmailRef::new("some@example.com"), ".*some_word.*");
        assert_eq!(res, Err(AddFilterResult::UserNotFound));
        let res = state.enable_filter(EmailRef::new("some@example.com"), true);
        assert_eq!(res, Err(AddFilterResult::UserNotFound));
        let res = state.enable_filter(EmailRef::new("some@example.com"), false);
        assert_eq!(res, Err(AddFilterResult::UserNotFound));
    }

    #[test]
    fn add_valid_filter_for_disabled_user() {
        let mut state = State::new();
        state.add_user(EmailRef::new("some@example.com"));
        state.users[0].enabled = false;

        let res = state.add_filter(EmailRef::new("some@example.com"), ".*some_word.*");
        assert_eq!(res, Err(AddFilterResult::UserDisabled));
        let res = state.enable_filter(EmailRef::new("some@example.com"), true);
        assert_eq!(res, Err(AddFilterResult::UserDisabled));
        let res = state.enable_filter(EmailRef::new("some@example.com"), false);
        assert_eq!(res, Err(AddFilterResult::UserDisabled));
    }

    #[test]
    fn enable_non_configured_filter_for_existing_user() {
        let mut state = State::new();
        state.add_user(EmailRef::new("some@example.com"));

        let res = state.enable_filter(EmailRef::new("some@example.com"), true);
        assert_eq!(res, Err(AddFilterResult::FilterNotConfigured));
        let res = state.enable_filter(EmailRef::new("some@example.com"), false);
        assert_eq!(res, Err(AddFilterResult::FilterNotConfigured));
    }

    #[test]
    fn enable_invalid_filter_for_existing_user() {
        let mut state = State::new();
        state.add_user(EmailRef::new("some@example.com"));
        state.users[0].filter = Some(Filter::new("invlide_filter_set_from_outside["));

        let res = state.enable_filter(EmailRef::new("some@example.com"), true);
        assert_eq!(res, Err(AddFilterResult::InvalidFilter));
        let res = state.enable_filter(EmailRef::new("some@example.com"), false);
        assert_eq!(res, Err(AddFilterResult::InvalidFilter));
    }

}
<|endoftext|>


<commit_before>// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0

//! Aptos Rosetta CLI
//!
//! Why have an Aptos version of the Rosetta CLI?
//!
//! The Rosetta CLI doesn't build on my Mac easily and I just wanted something simple to test out
//! the POST requests
//!
//! Why have a separate CLI?
//!
//! We want users to use the Aptos CLI over the Rosetta CLI because of the added complexity of a
//! proxy server.  So, we split it out so general users aren't confused.
//!
//! TODO: Make Aptos CLI framework common among multiple CLIs

#![forbid(unsafe_code)]

mod account;
mod block;
mod common;
mod construction;
mod network;

use crate::common::{ErrorWrapper, RosettaCliArgs};
use aptos_logger::Level;
use clap::Parser;

#[tokio::main]
async fn main() {
    let mut logger = aptos_logger::Logger::new();
    logger
        .channel_size(1000)
        .is_async(false)
        .level(Level::Warn)
        .read_env();
    logger.build();

    let args: RosettaCliArgs = RosettaCliArgs::parse();

    let result = args.execute().await;

    match result {
        Ok(value) => println!("{}", value),
        Err(error) => {
            let error = ErrorWrapper {
                error: error.to_string(),
            };
            println!("{}", serde_json::to_string_pretty(&error).unwrap())
        }
    }
}
<commit_msg>[aptos-rosetta-cli] Add error code to failures<commit_after>// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0

//! Aptos Rosetta CLI
//!
//! Why have an Aptos version of the Rosetta CLI?
//!
//! The Rosetta CLI doesn't build on my Mac easily and I just wanted something simple to test out
//! the POST requests
//!
//! Why have a separate CLI?
//!
//! We want users to use the Aptos CLI over the Rosetta CLI because of the added complexity of a
//! proxy server.  So, we split it out so general users aren't confused.
//!
//! TODO: Make Aptos CLI framework common among multiple CLIs

#![forbid(unsafe_code)]

mod account;
mod block;
mod common;
mod construction;
mod network;

use crate::common::{ErrorWrapper, RosettaCliArgs};
use aptos_logger::Level;
use clap::Parser;
use std::process::exit;

#[tokio::main]
async fn main() {
    let mut logger = aptos_logger::Logger::new();
    logger
        .channel_size(1000)
        .is_async(false)
        .level(Level::Warn)
        .read_env();
    logger.build();

    let args: RosettaCliArgs = RosettaCliArgs::parse();

    let result = args.execute().await;

    match result {
        Ok(value) => println!("{}", value),
        Err(error) => {
            let error = ErrorWrapper {
                error: error.to_string(),
            };
            println!("{}", serde_json::to_string_pretty(&error).unwrap());
            exit(-1)
        }
    }
}
<|endoftext|>


<commit_before>use std::error::Error;
use std::io::Write;
use std::io::stderr;

use ansi_term::Colour::Red;

/// Print an Error type and its cause recursively
///
/// The error is printed with "Error NNNN :" as prefix, where "NNNN" is a number which increases
/// which each recursion into the errors cause. The error description is used to visualize what
/// failed and if there is a cause "-- caused by:" is appended, and the cause is printed on the next
/// line.
///
/// Example output:
///
/// ```ignore
/// Error    1 : Some error -- caused by:
/// Error    2 : Some other error -- caused by:
/// Error    3 : Yet another Error -- caused by:
/// ...
///
/// Error <NNNN> : <Error description>
/// ```
pub fn trace_error(e: &Error) {
    print_trace_maxdepth(count_error_causes(e), e, ::std::u64::MAX);
    write!(stderr(), "\n").ok();
}

/// Convenience function: calls `trace_error()` with `e` and afterwards `std::process::exit()`
/// with `code`
pub fn trace_error_exit(e: &Error, code: i32) -> ! {
    use std::process::exit;

    debug!("Tracing error...");
    trace_error(e);
    debug!("Calling exit()");
    exit(code);
}

/// Print an Error type and its cause recursively, but only `max` levels
///
/// Output is the same as for `trace_error()`, though there are only `max` levels printed.
pub fn trace_error_maxdepth(e: &Error, max: u64) {
    let n = count_error_causes(e);
    let msg = Red.blink().paint(format!("{}/{} Levels of errors will be printed\n",
                                        (if max > n { n } else { max }), n));
    write!(stderr(), "{}", msg).ok();
    print_trace_maxdepth(n, e, max);
    write!(stderr(), "").ok();
}

/// Print an Error type and its cause recursively with the debug!() macro
///
/// Output is the same as for `trace_error()`.
pub fn trace_error_dbg(e: &Error) {
    print_trace_dbg(0, e);
}

/// Helper function for `trace_error()` and `trace_error_maxdepth()`.
///
/// Returns the cause of the last processed error in the recursion, so `None` if all errors where
/// processed.
fn print_trace_maxdepth(idx: u64, e: &Error, max: u64) -> Option<&Error> {
    if e.cause().is_some() && idx > 0 {
        e.cause().map(|cause| {
            match print_trace_maxdepth(idx - 1, cause, max) {
                None    => write!(stderr(), "\n").ok(),
                Some(_) => write!(stderr(), " -- caused:\n").ok(),
            };
        });
    } else {
        write!(stderr(), "\n").ok();
    }
    write!(stderr(), "{}: {}", Red.paint(format!("ERROR[{:>4}]", idx)), e.description()).ok();
    e.cause()
}

/// Count errors in `Error::cause()` recursively
fn count_error_causes(e: &Error) -> u64 {
    1 + if e.cause().is_some() { count_error_causes(e.cause().unwrap()) } else { 0 }
}

fn print_trace_dbg(idx: u64, e: &Error) {
    debug!("{}: {}", Red.blink().paint(format!("ERROR[{:>4}]", idx)), e.description());
    if e.cause().is_some() {
        print_trace_dbg(idx + 1, e.cause().unwrap());
    }
}

<commit_msg>Replace unwrap() with map()<commit_after>use std::error::Error;
use std::io::Write;
use std::io::stderr;

use ansi_term::Colour::Red;

/// Print an Error type and its cause recursively
///
/// The error is printed with "Error NNNN :" as prefix, where "NNNN" is a number which increases
/// which each recursion into the errors cause. The error description is used to visualize what
/// failed and if there is a cause "-- caused by:" is appended, and the cause is printed on the next
/// line.
///
/// Example output:
///
/// ```ignore
/// Error    1 : Some error -- caused by:
/// Error    2 : Some other error -- caused by:
/// Error    3 : Yet another Error -- caused by:
/// ...
///
/// Error <NNNN> : <Error description>
/// ```
pub fn trace_error(e: &Error) {
    print_trace_maxdepth(count_error_causes(e), e, ::std::u64::MAX);
    write!(stderr(), "\n").ok();
}

/// Convenience function: calls `trace_error()` with `e` and afterwards `std::process::exit()`
/// with `code`
pub fn trace_error_exit(e: &Error, code: i32) -> ! {
    use std::process::exit;

    debug!("Tracing error...");
    trace_error(e);
    debug!("Calling exit()");
    exit(code);
}

/// Print an Error type and its cause recursively, but only `max` levels
///
/// Output is the same as for `trace_error()`, though there are only `max` levels printed.
pub fn trace_error_maxdepth(e: &Error, max: u64) {
    let n = count_error_causes(e);
    let msg = Red.blink().paint(format!("{}/{} Levels of errors will be printed\n",
                                        (if max > n { n } else { max }), n));
    write!(stderr(), "{}", msg).ok();
    print_trace_maxdepth(n, e, max);
    write!(stderr(), "").ok();
}

/// Print an Error type and its cause recursively with the debug!() macro
///
/// Output is the same as for `trace_error()`.
pub fn trace_error_dbg(e: &Error) {
    print_trace_dbg(0, e);
}

/// Helper function for `trace_error()` and `trace_error_maxdepth()`.
///
/// Returns the cause of the last processed error in the recursion, so `None` if all errors where
/// processed.
fn print_trace_maxdepth(idx: u64, e: &Error, max: u64) -> Option<&Error> {
    if e.cause().is_some() && idx > 0 {
        e.cause().map(|cause| {
            match print_trace_maxdepth(idx - 1, cause, max) {
                None    => write!(stderr(), "\n").ok(),
                Some(_) => write!(stderr(), " -- caused:\n").ok(),
            };
        });
    } else {
        write!(stderr(), "\n").ok();
    }
    write!(stderr(), "{}: {}", Red.paint(format!("ERROR[{:>4}]", idx)), e.description()).ok();
    e.cause()
}

/// Count errors in `Error::cause()` recursively
fn count_error_causes(e: &Error) -> u64 {
    1 + e.cause().map(|c| count_error_causes(c)).unwrap_or(0)
}

fn print_trace_dbg(idx: u64, e: &Error) {
    debug!("{}: {}", Red.blink().paint(format!("ERROR[{:>4}]", idx)), e.description());
    if e.cause().is_some() {
        print_trace_dbg(idx + 1, e.cause().unwrap());
    }
}

<|endoftext|>


<commit_before>pub(crate) mod http_request_to_grpc_frames_typed;
pub(crate) mod http_response_to_grpc_frames;
pub(crate) mod http_response_to_grpc_frames_typed;
pub(crate) mod req_sink;
pub(crate) mod types;

use std::net::SocketAddr;
use std::sync::Arc;

use bytes::Bytes;

use httpbis;
use httpbis::Header;
use httpbis::Headers;
use httpbis::HttpScheme;

use tls_api;

use method::MethodDescriptor;

use error::*;
use result;

use client::http_request_to_grpc_frames_typed::http_req_to_grpc_frames_typed;
use client::http_response_to_grpc_frames_typed::http_response_to_grpc_frames_typed;
use client::req_sink::ClientRequestSink;
use error;
use futures::future;
use futures::Future;
use proto::grpc_frame::write_grpc_frame_to_vec;
use req::*;
use resp::*;

#[derive(Default, Debug, Clone)]
pub struct ClientConf {
    pub http: httpbis::ClientConf,
}

impl ClientConf {
    pub fn new() -> ClientConf {
        Default::default()
    }
}

/// gRPC client implementation.
/// Used by generated code.
pub struct Client {
    client: ::std::sync::Arc<httpbis::Client>,
    host: String,
    http_scheme: HttpScheme,
}

impl Client {
    /// Create a client connected to specified host and port.
    pub fn new_plain(host: &str, port: u16, conf: ClientConf) -> result::Result<Client> {
        let mut conf = conf;
        conf.http.thread_name = Some(
            conf.http
                .thread_name
                .unwrap_or_else(|| "grpc-client-loop".to_owned()),
        );

        httpbis::Client::new_plain(host, port, conf.http)
            .map(|client| Client {
                client: ::std::sync::Arc::new(client),
                host: host.to_owned(),
                http_scheme: HttpScheme::Http,
            }).map_err(Error::from)
    }

    /// Create a client connected to specified Unix domain socket.
    #[cfg(unix)]
    pub fn new_plain_unix(addr: &str, conf: ClientConf) -> result::Result<Client> {
        let mut conf = conf;
        conf.http.thread_name = Some(
            conf.http
                .thread_name
                .unwrap_or_else(|| "grpc-client-loop".to_owned()),
        );

        httpbis::Client::new_plain_unix(addr, conf.http)
            .map(|client| Client {
                client: ::std::sync::Arc::new(client),
                host: addr.to_owned(),
                http_scheme: HttpScheme::Http,
            }).map_err(Error::from)
    }

    /// Create a client connected to specified host and port.
    pub fn new_tls<C: tls_api::TlsConnector>(
        host: &str,
        port: u16,
        conf: ClientConf,
    ) -> result::Result<Client> {
        let mut conf = conf;
        conf.http.thread_name = Some(
            conf.http
                .thread_name
                .unwrap_or_else(|| "grpc-client-loop".to_owned()),
        );

        httpbis::Client::new_tls::<C>(host, port, conf.http)
            .map(|client| Client {
                client: ::std::sync::Arc::new(client),
                host: host.to_owned(),
                http_scheme: HttpScheme::Https,
            }).map_err(Error::from)
    }

    pub fn new_expl<C: tls_api::TlsConnector>(
        addr: &SocketAddr,
        host: &str,
        tls: httpbis::ClientTlsOption<C>,
        conf: ClientConf,
    ) -> result::Result<Client> {
        let mut conf = conf;
        conf.http.thread_name = Some(
            conf.http
                .thread_name
                .unwrap_or_else(|| "grpc-client-loop".to_owned()),
        );

        let http_scheme = tls.http_scheme();

        httpbis::Client::new_expl(addr, tls, conf.http)
            .map(|client| Client {
                client: ::std::sync::Arc::new(client),
                host: host.to_owned(),
                http_scheme: http_scheme,
            }).map_err(Error::from)
    }

    fn call_impl<Req, Resp>(
        &self,
        options: RequestOptions,
        req: Option<Req>,
        method: Arc<MethodDescriptor<Req, Resp>>,
    ) -> Box<
        Future<Item = (ClientRequestSink<Req>, StreamingResponse<Resp>), Error = error::Error>
            + Send,
    >
    where
        Req: Send + 'static,
        Resp: Send + 'static,
    {
        info!("start call {}", method.name);

        let mut headers = Headers::from_vec(vec![
            Header::new(Bytes::from_static(b":method"), Bytes::from_static(b"POST")),
            Header::new(Bytes::from_static(b":path"), method.name.clone()),
            Header::new(Bytes::from_static(b":authority"), self.host.clone()),
            Header::new(
                Bytes::from_static(b":scheme"),
                Bytes::from_static(self.http_scheme.as_bytes()),
            ),
            Header::new(
                Bytes::from_static(b"content-type"),
                Bytes::from_static(b"application/grpc"),
            ),
            Header::new(Bytes::from_static(b"te"), Bytes::from_static(b"trailers")),
        ]);

        headers.extend(options.metadata.into_headers());

        let req_bytes = match req {
            Some(req) => match method.req_marshaller.write(&req) {
                Ok(bytes) => {
                    // TODO: extra allocation
                    Some(Bytes::from(write_grpc_frame_to_vec(&bytes)))
                }
                Err(e) => return Box::new(future::err(e)),
            },
            None => None,
        };

        let end_stream = req_bytes.is_some();

        //        let request_frames = {
        //            let method = method.clone();
        //            req.0
        //                .and_then(move |req| {
        //                    let grpc_frame = method.req_marshaller.write(&req)?;
        //                    Ok(Bytes::from(write_grpc_frame_to_vec(&grpc_frame)))
        //                }).map_err(|_e| httpbis::Error::Other("grpc error")) // TODO: preserve error
        //        };

        let http_future = self
            .client
            .start_request(headers, req_bytes, None, end_stream);

        let http_future = http_future.map_err(error::Error::from);

        let req_marshaller = method.req_marshaller.clone();
        let resp_marshaller = method.resp_marshaller.clone();

        Box::new(http_future.map(move |(req, resp)| {
            let grpc_req = http_req_to_grpc_frames_typed(req, req_marshaller);
            let grpc_resp = http_response_to_grpc_frames_typed(resp, resp_marshaller);
            (grpc_req, grpc_resp)
        }))

        //        Box::new(http_future
        //            .map(|(req, resp)| {
        //                let grpc_req = ClientRequestSink {
        //                    common: SinkCommon {
        //                        marshaller: method.req_marshaller,
        //                        sink: req,
        //                    }
    }

    pub fn call_unary<Req, Resp>(
        &self,
        o: RequestOptions,
        req: Req,
        method: Arc<MethodDescriptor<Req, Resp>>,
    ) -> SingleResponse<Resp>
    where
        Req: Send + 'static,
        Resp: Send + 'static,
    {
        SingleResponse::new(
            self.call_impl(o, Some(req), method)
                .and_then(|(_req, resp)| resp.single()),
        )
    }

    pub fn call_server_streaming<Req, Resp>(
        &self,
        o: RequestOptions,
        req: Req,
        method: Arc<MethodDescriptor<Req, Resp>>,
    ) -> StreamingResponse<Resp>
    where
        Req: Send + 'static,
        Resp: Send + 'static,
    {
        StreamingResponse::new(
            self.call_impl(o, Some(req), method)
                .map(|(_req, resp)| resp.0)
                .flatten(),
        )
    }

    pub fn call_client_streaming<Req, Resp>(
        &self,
        o: RequestOptions,
        method: Arc<MethodDescriptor<Req, Resp>>,
    ) -> impl Future<Item = (ClientRequestSink<Req>, SingleResponse<Resp>), Error = error::Error>
    where
        Req: Send + 'static,
        Resp: Send + 'static,
    {
        self.call_impl(o, None, method)
            .map(|(req, resp)| (req, resp.single()))
    }

    pub fn call_bidi<Req, Resp>(
        &self,
        o: RequestOptions,
        method: Arc<MethodDescriptor<Req, Resp>>,
    ) -> impl Future<Item = (ClientRequestSink<Req>, StreamingResponse<Resp>), Error = error::Error>
    where
        Req: Send + 'static,
        Resp: Send + 'static,
    {
        self.call_impl(o, None, method)
    }
}

fn _assert_types() {
    ::assert_types::assert_send::<Client>();
    ::assert_types::assert_sync::<Client>();
}
<commit_msg>Add port info to Uri.Authority, to fix grpc routing error in Linked2.0 (a Service-Mesh framework)<commit_after>pub(crate) mod http_request_to_grpc_frames_typed;
pub(crate) mod http_response_to_grpc_frames;
pub(crate) mod http_response_to_grpc_frames_typed;
pub(crate) mod req_sink;
pub(crate) mod types;

use std::net::SocketAddr;
use std::sync::Arc;

use bytes::Bytes;

use httpbis;
use httpbis::Header;
use httpbis::Headers;
use httpbis::HttpScheme;

use tls_api;

use method::MethodDescriptor;

use error::*;
use result;

use client::http_request_to_grpc_frames_typed::http_req_to_grpc_frames_typed;
use client::http_response_to_grpc_frames_typed::http_response_to_grpc_frames_typed;
use client::req_sink::ClientRequestSink;
use error;
use futures::future;
use futures::Future;
use proto::grpc_frame::write_grpc_frame_to_vec;
use req::*;
use resp::*;

#[derive(Default, Debug, Clone)]
pub struct ClientConf {
    pub http: httpbis::ClientConf,
}

impl ClientConf {
    pub fn new() -> ClientConf {
        Default::default()
    }
}

/// gRPC client implementation.
/// Used by generated code.
pub struct Client {
    client: ::std::sync::Arc<httpbis::Client>,
    host: String,
    http_scheme: HttpScheme,
    port: Option<u16>,
}

impl Client {
    /// Create a client connected to specified host and port.
    pub fn new_plain(host: &str, port: u16, conf: ClientConf) -> result::Result<Client> {
        let mut conf = conf;
        conf.http.thread_name = Some(
            conf.http
                .thread_name
                .unwrap_or_else(|| "grpc-client-loop".to_owned()),
        );

        httpbis::Client::new_plain(host, port, conf.http)
            .map(|client| Client {
                client: ::std::sync::Arc::new(client),
                host: host.to_owned(),
                http_scheme: HttpScheme::Http,
                port: Some(port.to_owned()),
            }).map_err(Error::from)
    }

    /// Create a client connected to specified Unix domain socket.
    #[cfg(unix)]
    pub fn new_plain_unix(addr: &str, conf: ClientConf) -> result::Result<Client> {
        let mut conf = conf;
        conf.http.thread_name = Some(
            conf.http
                .thread_name
                .unwrap_or_else(|| "grpc-client-loop".to_owned()),
        );

        httpbis::Client::new_plain_unix(addr, conf.http)
            .map(|client| Client {
                client: ::std::sync::Arc::new(client),
                host: addr.to_owned(),
                http_scheme: HttpScheme::Http,
                port: None,
            }).map_err(Error::from)
    }

    /// Create a client connected to specified host and port.
    pub fn new_tls<C: tls_api::TlsConnector>(
        host: &str,
        port: u16,
        conf: ClientConf,
    ) -> result::Result<Client> {
        let mut conf = conf;
        conf.http.thread_name = Some(
            conf.http
                .thread_name
                .unwrap_or_else(|| "grpc-client-loop".to_owned()),
        );

        httpbis::Client::new_tls::<C>(host, port, conf.http)
            .map(|client| Client {
                client: ::std::sync::Arc::new(client),
                host: host.to_owned(),
                http_scheme: HttpScheme::Https,
                port: Some(port.to_owned()),
            }).map_err(Error::from)
    }

    pub fn new_expl<C: tls_api::TlsConnector>(
        addr: &SocketAddr,
        host: &str,
        tls: httpbis::ClientTlsOption<C>,
        conf: ClientConf,
    ) -> result::Result<Client> {
        let mut conf = conf;
        conf.http.thread_name = Some(
            conf.http
                .thread_name
                .unwrap_or_else(|| "grpc-client-loop".to_owned()),
        );

        let http_scheme = tls.http_scheme();

        httpbis::Client::new_expl(addr, tls, conf.http)
            .map(|client| Client {
                client: ::std::sync::Arc::new(client),
                host: host.to_owned(),
                http_scheme: http_scheme,
                port: Some(addr.port()),
            }).map_err(Error::from)
    }

    fn call_impl<Req, Resp>(
        &self,
        options: RequestOptions,
        req: Option<Req>,
        method: Arc<MethodDescriptor<Req, Resp>>,
    ) -> Box<
        Future<Item = (ClientRequestSink<Req>, StreamingResponse<Resp>), Error = error::Error>
            + Send,
    >
    where
        Req: Send + 'static,
        Resp: Send + 'static,
    {
        let mut authority = self.host.clone();
        if let Some(port_value) = self.port {
            authority = format!("{}:{}", authority, port_value);
        }

        info!("start call {}/{}", authority, method.name);

        let mut headers = Headers::from_vec(vec![
            Header::new(Bytes::from_static(b":method"), Bytes::from_static(b"POST")),
            Header::new(Bytes::from_static(b":path"), method.name.clone()),
            Header::new(Bytes::from_static(b":authority"), authority),
            Header::new(
                Bytes::from_static(b":scheme"),
                Bytes::from_static(self.http_scheme.as_bytes()),
            ),
            Header::new(
                Bytes::from_static(b"content-type"),
                Bytes::from_static(b"application/grpc"),
            ),
            Header::new(Bytes::from_static(b"te"), Bytes::from_static(b"trailers")),
        ]);

        headers.extend(options.metadata.into_headers());

        let req_bytes = match req {
            Some(req) => match method.req_marshaller.write(&req) {
                Ok(bytes) => {
                    // TODO: extra allocation
                    Some(Bytes::from(write_grpc_frame_to_vec(&bytes)))
                }
                Err(e) => return Box::new(future::err(e)),
            },
            None => None,
        };

        let end_stream = req_bytes.is_some();

        //        let request_frames = {
        //            let method = method.clone();
        //            req.0
        //                .and_then(move |req| {
        //                    let grpc_frame = method.req_marshaller.write(&req)?;
        //                    Ok(Bytes::from(write_grpc_frame_to_vec(&grpc_frame)))
        //                }).map_err(|_e| httpbis::Error::Other("grpc error")) // TODO: preserve error
        //        };

        let http_future = self
            .client
            .start_request(headers, req_bytes, None, end_stream);

        let http_future = http_future.map_err(error::Error::from);

        let req_marshaller = method.req_marshaller.clone();
        let resp_marshaller = method.resp_marshaller.clone();

        Box::new(http_future.map(move |(req, resp)| {
            let grpc_req = http_req_to_grpc_frames_typed(req, req_marshaller);
            let grpc_resp = http_response_to_grpc_frames_typed(resp, resp_marshaller);
            (grpc_req, grpc_resp)
        }))

        //        Box::new(http_future
        //            .map(|(req, resp)| {
        //                let grpc_req = ClientRequestSink {
        //                    common: SinkCommon {
        //                        marshaller: method.req_marshaller,
        //                        sink: req,
        //                    }
    }

    pub fn call_unary<Req, Resp>(
        &self,
        o: RequestOptions,
        req: Req,
        method: Arc<MethodDescriptor<Req, Resp>>,
    ) -> SingleResponse<Resp>
    where
        Req: Send + 'static,
        Resp: Send + 'static,
    {
        SingleResponse::new(
            self.call_impl(o, Some(req), method)
                .and_then(|(_req, resp)| resp.single()),
        )
    }

    pub fn call_server_streaming<Req, Resp>(
        &self,
        o: RequestOptions,
        req: Req,
        method: Arc<MethodDescriptor<Req, Resp>>,
    ) -> StreamingResponse<Resp>
    where
        Req: Send + 'static,
        Resp: Send + 'static,
    {
        StreamingResponse::new(
            self.call_impl(o, Some(req), method)
                .map(|(_req, resp)| resp.0)
                .flatten(),
        )
    }

    pub fn call_client_streaming<Req, Resp>(
        &self,
        o: RequestOptions,
        method: Arc<MethodDescriptor<Req, Resp>>,
    ) -> impl Future<Item = (ClientRequestSink<Req>, SingleResponse<Resp>), Error = error::Error>
    where
        Req: Send + 'static,
        Resp: Send + 'static,
    {
        self.call_impl(o, None, method)
            .map(|(req, resp)| (req, resp.single()))
    }

    pub fn call_bidi<Req, Resp>(
        &self,
        o: RequestOptions,
        method: Arc<MethodDescriptor<Req, Resp>>,
    ) -> impl Future<Item = (ClientRequestSink<Req>, StreamingResponse<Resp>), Error = error::Error>
    where
        Req: Send + 'static,
        Resp: Send + 'static,
    {
        self.call_impl(o, None, method)
    }
}

fn _assert_types() {
    ::assert_types::assert_send::<Client>();
    ::assert_types::assert_sync::<Client>();
}
<|endoftext|>