<commit_before>//! This module contains types for all kinds of errors that can occur when
//! converting between http requests / responses and ruma's representation of
//! matrix API requests / responses.

use std::fmt::{self, Display, Formatter};

// FIXME when `!` becomes stable use it
/// Default `ResponseError` for `ruma_api!` macro
#[derive(Clone, Copy, Debug)]
pub struct Void;

impl crate::EndpointError for Void {
    fn try_from_response(
        response: http::Response<Vec<u8>>,
    ) -> Result<Self, ResponseDeserializationError> {
        Err(ResponseDeserializationError::from_response(response))
    }
}

impl Display for Void {
    fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {}
    }
}

impl std::error::Error for Void {}

/// An error when converting one of ruma's endpoint-specific request or response
/// types to the corresponding http type.
#[derive(Debug)]
pub struct IntoHttpError(InnerIntoHttpError);

impl IntoHttpError {
    // For usage in macros
    #[doc(hidden)]
    pub fn needs_authentication() -> Self {
        Self(InnerIntoHttpError::NeedsAuthentication)
    }
}

#[doc(hidden)]
impl From<serde_json::Error> for IntoHttpError {
    fn from(err: serde_json::Error) -> Self {
        Self(InnerIntoHttpError::Json(err))
    }
}

#[doc(hidden)]
impl From<ruma_serde::urlencoded::ser::Error> for IntoHttpError {
    fn from(err: ruma_serde::urlencoded::ser::Error) -> Self {
        Self(InnerIntoHttpError::Query(err))
    }
}

#[doc(hidden)]
impl From<http::header::InvalidHeaderValue> for IntoHttpError {
    fn from(err: http::header::InvalidHeaderValue) -> Self {
        Self(InnerIntoHttpError::Header(err))
    }
}

#[doc(hidden)]
impl From<http::Error> for IntoHttpError {
    fn from(err: http::Error) -> Self {
        Self(InnerIntoHttpError::Http(err))
    }
}

impl Display for IntoHttpError {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match &self.0 {
            InnerIntoHttpError::NeedsAuthentication => write!(
                f,
                "This endpoint has to be converted to http::Request using \
                try_into_authenticated_http_request"
            ),
            InnerIntoHttpError::Json(err) => write!(f, "JSON serialization failed: {}", err),
            InnerIntoHttpError::Query(err) => {
                write!(f, "Query parameter serialization failed: {}", err)
            }
            InnerIntoHttpError::Header(err) => write!(f, "Header serialization failed: {}", err),
            InnerIntoHttpError::Http(err) => write!(f, "HTTP request construction failed: {}", err),
        }
    }
}

impl std::error::Error for IntoHttpError {}

/// An error when converting a http request to one of ruma's endpoint-specific
/// request types.
#[derive(Debug)]
#[non_exhaustive]
pub enum FromHttpRequestError {
    /// Deserialization failed
    Deserialization(RequestDeserializationError),
}

impl Display for FromHttpRequestError {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            Self::Deserialization(err) => write!(f, "deserialization failed: {}", err),
        }
    }
}

impl From<RequestDeserializationError> for FromHttpRequestError {
    fn from(err: RequestDeserializationError) -> Self {
        Self::Deserialization(err)
    }
}

impl std::error::Error for FromHttpRequestError {}

/// An error that occurred when trying to deserialize a request.
#[derive(Debug)]
pub struct RequestDeserializationError {
    inner: DeserializationError,
    http_request: http::Request<Vec<u8>>,
}

impl RequestDeserializationError {
    /// This method is public so it is accessible from `ruma_api!` generated
    /// code. It is not considered part of ruma-api's public API.
    #[doc(hidden)]
    pub fn new(
        inner: impl Into<DeserializationError>,
        http_request: http::Request<Vec<u8>>,
    ) -> Self {
        Self { inner: inner.into(), http_request }
    }
}

impl Display for RequestDeserializationError {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        Display::fmt(&self.inner, f)
    }
}

impl std::error::Error for RequestDeserializationError {}

/// An error when converting a http response to one of ruma's endpoint-specific
/// response types.
#[derive(Debug)]
#[non_exhaustive]
pub enum FromHttpResponseError<E> {
    /// Deserialization failed
    Deserialization(ResponseDeserializationError),
    /// The server returned a non-success status
    Http(ServerError<E>),
}

impl<E: Display> Display for FromHttpResponseError<E> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            Self::Deserialization(err) => write!(f, "deserialization failed: {}", err),
            Self::Http(err) => write!(f, "the server returned an error: {}", err),
        }
    }
}

impl<E> From<ServerError<E>> for FromHttpResponseError<E> {
    fn from(err: ServerError<E>) -> Self {
        Self::Http(err)
    }
}

impl<E> From<ResponseDeserializationError> for FromHttpResponseError<E> {
    fn from(err: ResponseDeserializationError) -> Self {
        Self::Deserialization(err)
    }
}

impl<E: std::error::Error> std::error::Error for FromHttpResponseError<E> {}

/// An error that occurred when trying to deserialize a response.
#[derive(Debug)]
pub struct ResponseDeserializationError {
    inner: Option<DeserializationError>,
    http_response: http::Response<Vec<u8>>,
}

impl ResponseDeserializationError {
    /// This method is public so it is accessible from `ruma_api!` generated
    /// code. It is not considered part of ruma-api's public API.
    #[doc(hidden)]
    pub fn new(
        inner: impl Into<DeserializationError>,
        http_response: http::Response<Vec<u8>>,
    ) -> Self {
        Self { inner: Some(inner.into()), http_response }
    }

    /// This method is public so it is accessible from `ruma_api!` generated
    /// code. It is not considered part of ruma-api's public API.
    /// Creates an Error from a `http::Response`.
    #[doc(hidden)]
    pub fn from_response(http_response: http::Response<Vec<u8>>) -> Self {
        Self { http_response, inner: None }
    }
}

impl Display for ResponseDeserializationError {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        if let Some(ref inner) = self.inner {
            Display::fmt(inner, f)
        } else {
            Display::fmt("deserialization error, no error specified", f)
        }
    }
}

impl std::error::Error for ResponseDeserializationError {}

/// An error was reported by the server (HTTP status code 4xx or 5xx)
#[derive(Debug)]
pub enum ServerError<E> {
    /// An error that is expected to happen under certain circumstances and
    /// that has a well-defined structure
    Known(E),
    /// An error of unexpected type of structure
    Unknown(ResponseDeserializationError),
}

impl<E: Display> Display for ServerError<E> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            ServerError::Known(e) => Display::fmt(e, f),
            ServerError::Unknown(res_err) => Display::fmt(res_err, f),
        }
    }
}

impl<E: std::error::Error> std::error::Error for ServerError<E> {}

#[derive(Debug)]
enum InnerIntoHttpError {
    NeedsAuthentication,
    Json(serde_json::Error),
    Query(ruma_serde::urlencoded::ser::Error),
    Header(http::header::InvalidHeaderValue),
    Http(http::Error),
}

/// This type is public so it is accessible from `ruma_api!` generated code.
/// It is not considered part of ruma-api's public API.
#[doc(hidden)]
#[derive(Debug)]
pub enum DeserializationError {
    Utf8(std::str::Utf8Error),
    Json(serde_json::Error),
    Query(ruma_serde::urlencoded::de::Error),
    Ident(ruma_identifiers::Error),
    // String <> Enum conversion failed. This can currently only happen in path
    // segment deserialization
    Strum(strum::ParseError),
    Header(http::header::ToStrError),
}

impl Display for DeserializationError {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            DeserializationError::Utf8(err) => Display::fmt(err, f),
            DeserializationError::Json(err) => Display::fmt(err, f),
            DeserializationError::Query(err) => Display::fmt(err, f),
            DeserializationError::Ident(err) => Display::fmt(err, f),
            DeserializationError::Strum(err) => Display::fmt(err, f),
            DeserializationError::Header(err) => Display::fmt(err, f),
        }
    }
}

#[doc(hidden)]
impl From<http::header::ToStrError> for DeserializationError {
    fn from(err: http::header::ToStrError) -> Self {
        Self::Header(err)
    }
}

#[doc(hidden)]
impl From<std::str::Utf8Error> for DeserializationError {
    fn from(err: std::str::Utf8Error) -> Self {
        Self::Utf8(err)
    }
}

#[doc(hidden)]
impl From<serde_json::Error> for DeserializationError {
    fn from(err: serde_json::Error) -> Self {
        Self::Json(err)
    }
}

#[doc(hidden)]
impl From<ruma_serde::urlencoded::de::Error> for DeserializationError {
    fn from(err: ruma_serde::urlencoded::de::Error) -> Self {
        Self::Query(err)
    }
}

#[doc(hidden)]
impl From<ruma_identifiers::Error> for DeserializationError {
    fn from(err: ruma_identifiers::Error) -> Self {
        Self::Ident(err)
    }
}

#[doc(hidden)]
impl From<strum::ParseError> for DeserializationError {
    fn from(err: strum::ParseError) -> Self {
        Self::Strum(err)
    }
}

#[doc(hidden)]
impl From<std::convert::Infallible> for DeserializationError {
    fn from(err: std::convert::Infallible) -> Self {
        match err {}
    }
}
<commit_msg>Fix non-empty Void<commit_after>//! This module contains types for all kinds of errors that can occur when
//! converting between http requests / responses and ruma's representation of
//! matrix API requests / responses.

use std::fmt::{self, Display, Formatter};

// FIXME when `!` becomes stable use it
/// Default `ResponseError` for `ruma_api!` macro
#[derive(Clone, Copy, Debug)]
pub enum Void {}

impl crate::EndpointError for Void {
    fn try_from_response(
        response: http::Response<Vec<u8>>,
    ) -> Result<Self, ResponseDeserializationError> {
        Err(ResponseDeserializationError::from_response(response))
    }
}

impl Display for Void {
    fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {}
    }
}

impl std::error::Error for Void {}

/// An error when converting one of ruma's endpoint-specific request or response
/// types to the corresponding http type.
#[derive(Debug)]
pub struct IntoHttpError(InnerIntoHttpError);

impl IntoHttpError {
    // For usage in macros
    #[doc(hidden)]
    pub fn needs_authentication() -> Self {
        Self(InnerIntoHttpError::NeedsAuthentication)
    }
}

#[doc(hidden)]
impl From<serde_json::Error> for IntoHttpError {
    fn from(err: serde_json::Error) -> Self {
        Self(InnerIntoHttpError::Json(err))
    }
}

#[doc(hidden)]
impl From<ruma_serde::urlencoded::ser::Error> for IntoHttpError {
    fn from(err: ruma_serde::urlencoded::ser::Error) -> Self {
        Self(InnerIntoHttpError::Query(err))
    }
}

#[doc(hidden)]
impl From<http::header::InvalidHeaderValue> for IntoHttpError {
    fn from(err: http::header::InvalidHeaderValue) -> Self {
        Self(InnerIntoHttpError::Header(err))
    }
}

#[doc(hidden)]
impl From<http::Error> for IntoHttpError {
    fn from(err: http::Error) -> Self {
        Self(InnerIntoHttpError::Http(err))
    }
}

impl Display for IntoHttpError {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match &self.0 {
            InnerIntoHttpError::NeedsAuthentication => write!(
                f,
                "This endpoint has to be converted to http::Request using \
                try_into_authenticated_http_request"
            ),
            InnerIntoHttpError::Json(err) => write!(f, "JSON serialization failed: {}", err),
            InnerIntoHttpError::Query(err) => {
                write!(f, "Query parameter serialization failed: {}", err)
            }
            InnerIntoHttpError::Header(err) => write!(f, "Header serialization failed: {}", err),
            InnerIntoHttpError::Http(err) => write!(f, "HTTP request construction failed: {}", err),
        }
    }
}

impl std::error::Error for IntoHttpError {}

/// An error when converting a http request to one of ruma's endpoint-specific
/// request types.
#[derive(Debug)]
#[non_exhaustive]
pub enum FromHttpRequestError {
    /// Deserialization failed
    Deserialization(RequestDeserializationError),
}

impl Display for FromHttpRequestError {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            Self::Deserialization(err) => write!(f, "deserialization failed: {}", err),
        }
    }
}

impl From<RequestDeserializationError> for FromHttpRequestError {
    fn from(err: RequestDeserializationError) -> Self {
        Self::Deserialization(err)
    }
}

impl std::error::Error for FromHttpRequestError {}

/// An error that occurred when trying to deserialize a request.
#[derive(Debug)]
pub struct RequestDeserializationError {
    inner: DeserializationError,
    http_request: http::Request<Vec<u8>>,
}

impl RequestDeserializationError {
    /// This method is public so it is accessible from `ruma_api!` generated
    /// code. It is not considered part of ruma-api's public API.
    #[doc(hidden)]
    pub fn new(
        inner: impl Into<DeserializationError>,
        http_request: http::Request<Vec<u8>>,
    ) -> Self {
        Self { inner: inner.into(), http_request }
    }
}

impl Display for RequestDeserializationError {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        Display::fmt(&self.inner, f)
    }
}

impl std::error::Error for RequestDeserializationError {}

/// An error when converting a http response to one of ruma's endpoint-specific
/// response types.
#[derive(Debug)]
#[non_exhaustive]
pub enum FromHttpResponseError<E> {
    /// Deserialization failed
    Deserialization(ResponseDeserializationError),
    /// The server returned a non-success status
    Http(ServerError<E>),
}

impl<E: Display> Display for FromHttpResponseError<E> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            Self::Deserialization(err) => write!(f, "deserialization failed: {}", err),
            Self::Http(err) => write!(f, "the server returned an error: {}", err),
        }
    }
}

impl<E> From<ServerError<E>> for FromHttpResponseError<E> {
    fn from(err: ServerError<E>) -> Self {
        Self::Http(err)
    }
}

impl<E> From<ResponseDeserializationError> for FromHttpResponseError<E> {
    fn from(err: ResponseDeserializationError) -> Self {
        Self::Deserialization(err)
    }
}

impl<E: std::error::Error> std::error::Error for FromHttpResponseError<E> {}

/// An error that occurred when trying to deserialize a response.
#[derive(Debug)]
pub struct ResponseDeserializationError {
    inner: Option<DeserializationError>,
    http_response: http::Response<Vec<u8>>,
}

impl ResponseDeserializationError {
    /// This method is public so it is accessible from `ruma_api!` generated
    /// code. It is not considered part of ruma-api's public API.
    #[doc(hidden)]
    pub fn new(
        inner: impl Into<DeserializationError>,
        http_response: http::Response<Vec<u8>>,
    ) -> Self {
        Self { inner: Some(inner.into()), http_response }
    }

    /// This method is public so it is accessible from `ruma_api!` generated
    /// code. It is not considered part of ruma-api's public API.
    /// Creates an Error from a `http::Response`.
    #[doc(hidden)]
    pub fn from_response(http_response: http::Response<Vec<u8>>) -> Self {
        Self { http_response, inner: None }
    }
}

impl Display for ResponseDeserializationError {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        if let Some(ref inner) = self.inner {
            Display::fmt(inner, f)
        } else {
            Display::fmt("deserialization error, no error specified", f)
        }
    }
}

impl std::error::Error for ResponseDeserializationError {}

/// An error was reported by the server (HTTP status code 4xx or 5xx)
#[derive(Debug)]
pub enum ServerError<E> {
    /// An error that is expected to happen under certain circumstances and
    /// that has a well-defined structure
    Known(E),
    /// An error of unexpected type of structure
    Unknown(ResponseDeserializationError),
}

impl<E: Display> Display for ServerError<E> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            ServerError::Known(e) => Display::fmt(e, f),
            ServerError::Unknown(res_err) => Display::fmt(res_err, f),
        }
    }
}

impl<E: std::error::Error> std::error::Error for ServerError<E> {}

#[derive(Debug)]
enum InnerIntoHttpError {
    NeedsAuthentication,
    Json(serde_json::Error),
    Query(ruma_serde::urlencoded::ser::Error),
    Header(http::header::InvalidHeaderValue),
    Http(http::Error),
}

/// This type is public so it is accessible from `ruma_api!` generated code.
/// It is not considered part of ruma-api's public API.
#[doc(hidden)]
#[derive(Debug)]
pub enum DeserializationError {
    Utf8(std::str::Utf8Error),
    Json(serde_json::Error),
    Query(ruma_serde::urlencoded::de::Error),
    Ident(ruma_identifiers::Error),
    // String <> Enum conversion failed. This can currently only happen in path
    // segment deserialization
    Strum(strum::ParseError),
    Header(http::header::ToStrError),
}

impl Display for DeserializationError {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            DeserializationError::Utf8(err) => Display::fmt(err, f),
            DeserializationError::Json(err) => Display::fmt(err, f),
            DeserializationError::Query(err) => Display::fmt(err, f),
            DeserializationError::Ident(err) => Display::fmt(err, f),
            DeserializationError::Strum(err) => Display::fmt(err, f),
            DeserializationError::Header(err) => Display::fmt(err, f),
        }
    }
}

#[doc(hidden)]
impl From<http::header::ToStrError> for DeserializationError {
    fn from(err: http::header::ToStrError) -> Self {
        Self::Header(err)
    }
}

#[doc(hidden)]
impl From<std::str::Utf8Error> for DeserializationError {
    fn from(err: std::str::Utf8Error) -> Self {
        Self::Utf8(err)
    }
}

#[doc(hidden)]
impl From<serde_json::Error> for DeserializationError {
    fn from(err: serde_json::Error) -> Self {
        Self::Json(err)
    }
}

#[doc(hidden)]
impl From<ruma_serde::urlencoded::de::Error> for DeserializationError {
    fn from(err: ruma_serde::urlencoded::de::Error) -> Self {
        Self::Query(err)
    }
}

#[doc(hidden)]
impl From<ruma_identifiers::Error> for DeserializationError {
    fn from(err: ruma_identifiers::Error) -> Self {
        Self::Ident(err)
    }
}

#[doc(hidden)]
impl From<strum::ParseError> for DeserializationError {
    fn from(err: strum::ParseError) -> Self {
        Self::Strum(err)
    }
}

#[doc(hidden)]
impl From<std::convert::Infallible> for DeserializationError {
    fn from(err: std::convert::Infallible) -> Self {
        match err {}
    }
}
<|endoftext|>


<commit_before>use hir::db::HirDatabase;
use ra_syntax::{ast::AstNode, ast::Literal, TextRange, TextUnit};

use crate::{Assist, AssistCtx, AssistId};

pub(crate) fn make_raw_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
    let literal = ctx.node_at_offset::<Literal>()?;
    if literal.token().kind() != ra_syntax::SyntaxKind::STRING {
        return None;
    }
    ctx.add_action(AssistId("make_raw_string"), "make raw string", |edit| {
        edit.target(literal.syntax().text_range());
        edit.insert(literal.syntax().text_range().start(), "r");
    });
    ctx.build()
}

fn find_usual_string_range(s: &str) -> Option<TextRange> {
    Some(TextRange::from_to(
        TextUnit::from(s.find('"')? as u32),
        TextUnit::from(s.rfind('"')? as u32),
    ))
}

pub(crate) fn make_usual_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
    let literal = ctx.node_at_offset::<Literal>()?;
    if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING {
        return None;
    }
    let token = literal.token();
    let text = token.text().as_str();
    let usual_string_range = find_usual_string_range(text)?;
    ctx.add_action(AssistId("make_usual_string"), "make usual string", |edit| {
        edit.target(literal.syntax().text_range());
        // parse inside string to escape `"`
        let start_of_inside = usual_string_range.start().to_usize() + 1;
        let end_of_inside = usual_string_range.end().to_usize();
        let inside_str = &text[start_of_inside..end_of_inside];
        let escaped = inside_str.escape_default().to_string();
        edit.replace(literal.syntax().text_range(), format!("\"{}\"", escaped));
    });
    ctx.build()
}

pub(crate) fn add_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
    let literal = ctx.node_at_offset::<Literal>()?;
    if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING {
        return None;
    }
    ctx.add_action(AssistId("add_hash"), "add hash to raw string", |edit| {
        edit.target(literal.syntax().text_range());
        edit.insert(literal.syntax().text_range().start() + TextUnit::of_char('r'), "#");
        edit.insert(literal.syntax().text_range().end(), "#");
    });
    ctx.build()
}

pub(crate) fn remove_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
    let literal = ctx.node_at_offset::<Literal>()?;
    if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING {
        return None;
    }
    let token = literal.token();
    let text = token.text().as_str();
    if text.starts_with("r\"") {
        // no hash to remove
        return None;
    }
    ctx.add_action(AssistId("remove_hash"), "remove hash from raw string", |edit| {
        edit.target(literal.syntax().text_range());
        let result = &text[2..text.len() - 1];
        let result = if result.starts_with("\"") {
            // no more hash, escape
            let internal_str = &result[1..result.len() - 1];
            format!("\"{}\"", internal_str.escape_default().to_string())
        } else {
            result.to_owned()
        };
        edit.replace(literal.syntax().text_range(), format!("r{}", result));
    });
    ctx.build()
}

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

    #[test]
    fn make_raw_string_target() {
        check_assist_target(
            make_raw_string,
            r#"
            fn f() {
                let s = <|>"random string";
            }
            "#,
            r#""random string""#,
        );
    }

    #[test]
    fn make_raw_string_works() {
        check_assist(
            make_raw_string,
            r#"
            fn f() {
                let s = <|>"random string";
            }
            "#,
            r#"
            fn f() {
                let s = <|>r"random string";
            }
            "#,
        )
    }

    #[test]
    fn make_raw_string_with_escaped_works() {
        check_assist(
            make_raw_string,
            r#"
            fn f() {
                let s = <|>"random\nstring";
            }
            "#,
            r#"
            fn f() {
                let s = <|>r"random\nstring";
            }
            "#,
        )
    }

    #[test]
    fn make_raw_string_not_works() {
        check_assist_not_applicable(
            make_raw_string,
            r#"
            fn f() {
                let s = <|>r"random string";
            }
            "#,
        );
    }

    #[test]
    fn add_hash_target() {
        check_assist_target(
            add_hash,
            r#"
            fn f() {
                let s = <|>r"random string";
            }
            "#,
            r#"r"random string""#,
        );
    }

    #[test]
    fn add_hash_works() {
        check_assist(
            add_hash,
            r#"
            fn f() {
                let s = <|>r"random string";
            }
            "#,
            r##"
            fn f() {
                let s = <|>r#"random string"#;
            }
            "##,
        )
    }

    #[test]
    fn add_more_hash_works() {
        check_assist(
            add_hash,
            r##"
            fn f() {
                let s = <|>r#"random"string"#;
            }
            "##,
            r###"
            fn f() {
                let s = <|>r##"random"string"##;
            }
            "###,
        )
    }

    #[test]
    fn add_hash_not_works() {
        check_assist_not_applicable(
            add_hash,
            r#"
            fn f() {
                let s = <|>"random string";
            }
            "#,
        );
    }

    #[test]
    fn remove_hash_target() {
        check_assist_target(
            remove_hash,
            r##"
            fn f() {
                let s = <|>r#"random string"#;
            }
            "##,
            r##"r#"random string"#"##,
        );
    }

    #[test]
    fn remove_hash_works() {
        check_assist(
            remove_hash,
            r##"
            fn f() {
                let s = <|>r#"random string"#;
            }
            "##,
            r#"
            fn f() {
                let s = <|>r"random string";
            }
            "#,
        )
    }

    #[test]
    fn remove_hash_with_quote_works() {
        check_assist(
            remove_hash,
            r##"
            fn f() {
                let s = <|>r#"random"str"ing"#;
            }
            "##,
            r#"
            fn f() {
                let s = <|>r"random\"str\"ing";
            }
            "#,
        )
    }

    #[test]
    fn remove_more_hash_works() {
        check_assist(
            remove_hash,
            r###"
            fn f() {
                let s = <|>r##"random string"##;
            }
            "###,
            r##"
            fn f() {
                let s = <|>r#"random string"#;
            }
            "##,
        )
    }

    #[test]
    fn remove_hash_not_works() {
        check_assist_not_applicable(
            remove_hash,
            r#"
            fn f() {
                let s = <|>"random string";
            }
            "#,
        );
    }

    #[test]
    fn remove_hash_no_hash_not_works() {
        check_assist_not_applicable(
            remove_hash,
            r#"
            fn f() {
                let s = <|>r"random string";
            }
            "#,
        );
    }

    #[test]
    fn make_usual_string_target() {
        check_assist_target(
            make_usual_string,
            r##"
            fn f() {
                let s = <|>r#"random string"#;
            }
            "##,
            r##"r#"random string"#"##,
        );
    }

    #[test]
    fn make_usual_string_works() {
        check_assist(
            make_usual_string,
            r##"
            fn f() {
                let s = <|>r#"random string"#;
            }
            "##,
            r#"
            fn f() {
                let s = <|>"random string";
            }
            "#,
        )
    }

    #[test]
    fn make_usual_string_with_quote_works() {
        check_assist(
            make_usual_string,
            r##"
            fn f() {
                let s = <|>r#"random"str"ing"#;
            }
            "##,
            r#"
            fn f() {
                let s = <|>"random\"str\"ing";
            }
            "#,
        )
    }

    #[test]
    fn make_usual_string_more_hash_works() {
        check_assist(
            make_usual_string,
            r###"
            fn f() {
                let s = <|>r##"random string"##;
            }
            "###,
            r##"
            fn f() {
                let s = <|>"random string";
            }
            "##,
        )
    }

    #[test]
    fn make_usual_string_not_works() {
        check_assist_not_applicable(
            make_usual_string,
            r#"
            fn f() {
                let s = <|>"random string";
            }
            "#,
        );
    }
}
<commit_msg>Suspicious line endings<commit_after>use hir::db::HirDatabase;
use ra_syntax::{ast::AstNode, ast::Literal, TextRange, TextUnit};

use crate::{Assist, AssistCtx, AssistId};

pub(crate) fn make_raw_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
    let literal = ctx.node_at_offset::<Literal>()?;
    if literal.token().kind() != ra_syntax::SyntaxKind::STRING {
        return None;
    }
    ctx.add_action(AssistId("make_raw_string"), "make raw string", |edit| {
        edit.target(literal.syntax().text_range());
        edit.insert(literal.syntax().text_range().start(), "r");
    });
    ctx.build()
}

fn find_usual_string_range(s: &str) -> Option<TextRange> {
    Some(TextRange::from_to(
        TextUnit::from(s.find('"')? as u32),
        TextUnit::from(s.rfind('"')? as u32),
    ))
}

pub(crate) fn make_usual_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
    let literal = ctx.node_at_offset::<Literal>()?;
    if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING {
        return None;
    }
    let token = literal.token();
    let text = token.text().as_str();
    let usual_string_range = find_usual_string_range(text)?;
    ctx.add_action(AssistId("make_usual_string"), "make usual string", |edit| {
        edit.target(literal.syntax().text_range());
        // parse inside string to escape `"`
        let start_of_inside = usual_string_range.start().to_usize() + 1;
        let end_of_inside = usual_string_range.end().to_usize();
        let inside_str = &text[start_of_inside..end_of_inside];
        let escaped = inside_str.escape_default().to_string();
        edit.replace(literal.syntax().text_range(), format!("\"{}\"", escaped));
    });
    ctx.build()
}

pub(crate) fn add_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
    let literal = ctx.node_at_offset::<Literal>()?;
    if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING {
        return None;
    }
    ctx.add_action(AssistId("add_hash"), "add hash to raw string", |edit| {
        edit.target(literal.syntax().text_range());
        edit.insert(literal.syntax().text_range().start() + TextUnit::of_char('r'), "#");
        edit.insert(literal.syntax().text_range().end(), "#");
    });
    ctx.build()
}

pub(crate) fn remove_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
    let literal = ctx.node_at_offset::<Literal>()?;
    if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING {
        return None;
    }
    let token = literal.token();
    let text = token.text().as_str();
    if text.starts_with("r\"") {
        // no hash to remove
        return None;
    }
    ctx.add_action(AssistId("remove_hash"), "remove hash from raw string", |edit| {
        edit.target(literal.syntax().text_range());
        let result = &text[2..text.len() - 1];
        let result = if result.starts_with("\"") {
            // no more hash, escape
            let internal_str = &result[1..result.len() - 1];
            format!("\"{}\"", internal_str.escape_default().to_string())
        } else {
            result.to_owned()
        };
        edit.replace(literal.syntax().text_range(), format!("r{}", result));
    });
    ctx.build()
}

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

    #[test]
    fn make_raw_string_target() {
        check_assist_target(
            make_raw_string,
            r#"
            fn f() {
                let s = <|>"random string";
            }
            "#,
            r#""random string""#,
        );
    }

    #[test]
    fn make_raw_string_works() {
        check_assist(
            make_raw_string,
            r#"
            fn f() {
                let s = <|>"random string";
            }
            "#,
            r#"
            fn f() {
                let s = <|>r"random string";
            }
            "#,
        )
    }

    #[test]
    fn make_raw_string_with_escaped_works() {
        check_assist(
            make_raw_string,
            r#"
            fn f() {
                let s = <|>"random\nstring";
            }
            "#,
            r#"
            fn f() {
                let s = <|>r"random\nstring";
            }
            "#,
        )
    }

    #[test]
    fn make_raw_string_not_works() {
        check_assist_not_applicable(
            make_raw_string,
            r#"
            fn f() {
                let s = <|>r"random string";
            }
            "#,
        );
    }

    #[test]
    fn add_hash_target() {
        check_assist_target(
            add_hash,
            r#"
            fn f() {
                let s = <|>r"random string";
            }
            "#,
            r#"r"random string""#,
        );
    }

    #[test]
    fn add_hash_works() {
        check_assist(
            add_hash,
            r#"
            fn f() {
                let s = <|>r"random string";
            }
            "#,
            r##"
            fn f() {
                let s = <|>r#"random string"#;
            }
            "##,
        )
    }

    #[test]
    fn add_more_hash_works() {
        check_assist(
            add_hash,
            r##"
            fn f() {
                let s = <|>r#"random"string"#;
            }
            "##,
            r###"
            fn f() {
                let s = <|>r##"random"string"##;
            }
            "###,
        )
    }

    #[test]
    fn add_hash_not_works() {
        check_assist_not_applicable(
            add_hash,
            r#"
            fn f() {
                let s = <|>"random string";
            }
            "#,
        );
    }

    #[test]
    fn remove_hash_target() {
        check_assist_target(
            remove_hash,
            r##"
            fn f() {
                let s = <|>r#"random string"#;
            }
            "##,
            r##"r#"random string"#"##,
        );
    }

    #[test]
    fn remove_hash_works() {
        check_assist(
            remove_hash,
            r##"
            fn f() {
                let s = <|>r#"random string"#;
            }
            "##,
            r#"
            fn f() {
                let s = <|>r"random string";
            }
            "#,
        )
    }

    #[test]
    fn remove_hash_with_quote_works() {
        check_assist(
            remove_hash,
            r##"
            fn f() {
                let s = <|>r#"random"str"ing"#;
            }
            "##,
            r#"
            fn f() {
                let s = <|>r"random\"str\"ing";
            }
            "#,
        )
    }

    #[test]
    fn remove_more_hash_works() {
        check_assist(
            remove_hash,
            r###"
            fn f() {
                let s = <|>r##"random string"##;
            }
            "###,
            r##"
            fn f() {
                let s = <|>r#"random string"#;
            }
            "##,
        )
    }

    #[test]
    fn remove_hash_not_works() {
        check_assist_not_applicable(
            remove_hash,
            r#"
            fn f() {
                let s = <|>"random string";
            }
            "#,
        );
    }

    #[test]
    fn remove_hash_no_hash_not_works() {
        check_assist_not_applicable(
            remove_hash,
            r#"
            fn f() {
                let s = <|>r"random string";
            }
            "#,
        );
    }

    #[test]
    fn make_usual_string_target() {
        check_assist_target(
            make_usual_string,
            r##"
            fn f() {
                let s = <|>r#"random string"#;
            }
            "##,
            r##"r#"random string"#"##,
        );
    }

    #[test]
    fn make_usual_string_works() {
        check_assist(
            make_usual_string,
            r##"
            fn f() {
                let s = <|>r#"random string"#;
            }
            "##,
            r#"
            fn f() {
                let s = <|>"random string";
            }
            "#,
        )
    }

    #[test]
    fn make_usual_string_with_quote_works() {
        check_assist(
            make_usual_string,
            r##"
            fn f() {
                let s = <|>r#"random"str"ing"#;
            }
            "##,
            r#"
            fn f() {
                let s = <|>"random\"str\"ing";
            }
            "#,
        )
    }

    #[test]
    fn make_usual_string_more_hash_works() {
        check_assist(
            make_usual_string,
            r###"
            fn f() {
                let s = <|>r##"random string"##;
            }
            "###,
            r##"
            fn f() {
                let s = <|>"random string";
            }
            "##,
        )
    }

    #[test]
    fn make_usual_string_not_works() {
        check_assist_not_applicable(
            make_usual_string,
            r#"
            fn f() {
                let s = <|>"random string";
            }
            "#,
        );
    }
}
<|endoftext|>


<commit_before>//! FIXME: write short doc here

use std::any::Any;

use hir_expand::{db::AstDatabase, name::Name, HirFileId, InFile};
use ra_syntax::{ast, AstNode, AstPtr, SyntaxNodePtr};
use stdx::format_to;

pub use hir_def::{diagnostics::UnresolvedModule, expr::MatchArm, path::Path};
pub use hir_expand::diagnostics::{AstDiagnostic, Diagnostic, DiagnosticSink};

#[derive(Debug)]
pub struct NoSuchField {
    pub file: HirFileId,
    pub field: AstPtr<ast::RecordField>,
}

impl Diagnostic for NoSuchField {
    fn message(&self) -> String {
        "no such field".to_string()
    }

    fn source(&self) -> InFile<SyntaxNodePtr> {
        InFile::new(self.file, self.field.clone().into())
    }

    fn as_any(&self) -> &(dyn Any + Send + 'static) {
        self
    }
}

impl AstDiagnostic for NoSuchField {
    type AST = ast::RecordField;

    fn ast(&self, db: &dyn AstDatabase) -> Self::AST {
        let root = db.parse_or_expand(self.source().file_id).unwrap();
        let node = self.source().value.to_node(&root);
        ast::RecordField::cast(node).unwrap()
    }
}

#[derive(Debug)]
pub struct MissingFields {
    pub file: HirFileId,
    pub field_list: AstPtr<ast::RecordFieldList>,
    pub missed_fields: Vec<Name>,
}

impl Diagnostic for MissingFields {
    fn message(&self) -> String {
        let mut buf = String::from("Missing structure fields:\n");
        for field in &self.missed_fields {
            format_to!(buf, "- {}\n", field);
        }
        buf
    }
    fn source(&self) -> InFile<SyntaxNodePtr> {
        InFile { file_id: self.file, value: self.field_list.clone().into() }
    }
    fn as_any(&self) -> &(dyn Any + Send + 'static) {
        self
    }
}

impl AstDiagnostic for MissingFields {
    type AST = ast::RecordFieldList;

    fn ast(&self, db: &dyn AstDatabase) -> Self::AST {
        let root = db.parse_or_expand(self.source().file_id).unwrap();
        let node = self.source().value.to_node(&root);
        ast::RecordFieldList::cast(node).unwrap()
    }
}

#[derive(Debug)]
pub struct MissingPatFields {
    pub file: HirFileId,
    pub field_list: AstPtr<ast::RecordFieldPatList>,
    pub missed_fields: Vec<Name>,
}

impl Diagnostic for MissingPatFields {
    fn message(&self) -> String {
        let mut buf = String::from("Missing structure fields:\n");
        for field in &self.missed_fields {
            format_to!(buf, "- {}\n", field);
        }
        buf
    }
    fn source(&self) -> InFile<SyntaxNodePtr> {
        InFile { file_id: self.file, value: self.field_list.clone().into() }
    }
    fn as_any(&self) -> &(dyn Any + Send + 'static) {
        self
    }
}

#[derive(Debug)]
pub struct MissingMatchArms {
    pub file: HirFileId,
    pub match_expr: AstPtr<ast::Expr>,
    pub arms: AstPtr<ast::MatchArmList>,
}

impl Diagnostic for MissingMatchArms {
    fn message(&self) -> String {
        String::from("Missing match arm")
    }
    fn source(&self) -> InFile<SyntaxNodePtr> {
        InFile { file_id: self.file, value: self.match_expr.clone().into() }
    }
    fn as_any(&self) -> &(dyn Any + Send + 'static) {
        self
    }
}

#[derive(Debug)]
pub struct MissingOkInTailExpr {
    pub file: HirFileId,
    pub expr: AstPtr<ast::Expr>,
}

impl Diagnostic for MissingOkInTailExpr {
    fn message(&self) -> String {
        "wrap return expression in Ok".to_string()
    }
    fn source(&self) -> InFile<SyntaxNodePtr> {
        InFile { file_id: self.file, value: self.expr.clone().into() }
    }
    fn as_any(&self) -> &(dyn Any + Send + 'static) {
        self
    }
}

impl AstDiagnostic for MissingOkInTailExpr {
    type AST = ast::Expr;

    fn ast(&self, db: &dyn AstDatabase) -> Self::AST {
        let root = db.parse_or_expand(self.file).unwrap();
        let node = self.source().value.to_node(&root);
        ast::Expr::cast(node).unwrap()
    }
}

#[derive(Debug)]
pub struct BreakOutsideOfLoop {
    pub file: HirFileId,
    pub expr: AstPtr<ast::Expr>,
}

impl Diagnostic for BreakOutsideOfLoop {
    fn message(&self) -> String {
        "break outside of loop".to_string()
    }
    fn source(&self) -> InFile<SyntaxNodePtr> {
        InFile { file_id: self.file, value: self.expr.clone().into() }
    }
    fn as_any(&self) -> &(dyn Any + Send + 'static) {
        self
    }
}

impl AstDiagnostic for BreakOutsideOfLoop {
    type AST = ast::Expr;

    fn ast(&self, db: &dyn AstDatabase) -> Self::AST {
        let root = db.parse_or_expand(self.file).unwrap();
        let node = self.source().value.to_node(&root);
        ast::Expr::cast(node).unwrap()
    }
}

#[derive(Debug)]
pub struct MissingUnsafe {
    pub file: HirFileId,
    pub expr: AstPtr<ast::Expr>,
}

impl Diagnostic for MissingUnsafe {
    fn message(&self) -> String {
        format!("This operation is unsafe and requires an unsafe function or block")
    }
    fn source(&self) -> InFile<SyntaxNodePtr> {
        InFile { file_id: self.file, value: self.expr.clone().into() }
    }
    fn as_any(&self) -> &(dyn Any + Send + 'static) {
        self
    }
}

impl AstDiagnostic for MissingUnsafe {
    type AST = ast::Expr;

    fn ast(&self, db: &dyn AstDatabase) -> Self::AST {
        let root = db.parse_or_expand(self.source().file_id).unwrap();
        let node = self.source().value.to_node(&root);
        ast::Expr::cast(node).unwrap()
    }
}

#[derive(Debug)]
pub struct MismatchedArgCount {
    pub file: HirFileId,
    pub call_expr: AstPtr<ast::Expr>,
    pub expected: usize,
    pub found: usize,
}

impl Diagnostic for MismatchedArgCount {
    fn message(&self) -> String {
        format!("Expected {} arguments, found {}", self.expected, self.found)
    }
    fn source(&self) -> InFile<SyntaxNodePtr> {
        InFile { file_id: self.file, value: self.call_expr.clone().into() }
    }
    fn as_any(&self) -> &(dyn Any + Send + 'static) {
        self
    }
}

impl AstDiagnostic for MismatchedArgCount {
    type AST = ast::CallExpr;
    fn ast(&self, db: &dyn AstDatabase) -> Self::AST {
        let root = db.parse_or_expand(self.source().file_id).unwrap();
        let node = self.source().value.to_node(&root);
        ast::CallExpr::cast(node).unwrap()
    }
}
<commit_msg>Correctly pluralize message<commit_after>//! FIXME: write short doc here

use std::any::Any;

use hir_expand::{db::AstDatabase, name::Name, HirFileId, InFile};
use ra_syntax::{ast, AstNode, AstPtr, SyntaxNodePtr};
use stdx::format_to;

pub use hir_def::{diagnostics::UnresolvedModule, expr::MatchArm, path::Path};
pub use hir_expand::diagnostics::{AstDiagnostic, Diagnostic, DiagnosticSink};

#[derive(Debug)]
pub struct NoSuchField {
    pub file: HirFileId,
    pub field: AstPtr<ast::RecordField>,
}

impl Diagnostic for NoSuchField {
    fn message(&self) -> String {
        "no such field".to_string()
    }

    fn source(&self) -> InFile<SyntaxNodePtr> {
        InFile::new(self.file, self.field.clone().into())
    }

    fn as_any(&self) -> &(dyn Any + Send + 'static) {
        self
    }
}

impl AstDiagnostic for NoSuchField {
    type AST = ast::RecordField;

    fn ast(&self, db: &dyn AstDatabase) -> Self::AST {
        let root = db.parse_or_expand(self.source().file_id).unwrap();
        let node = self.source().value.to_node(&root);
        ast::RecordField::cast(node).unwrap()
    }
}

#[derive(Debug)]
pub struct MissingFields {
    pub file: HirFileId,
    pub field_list: AstPtr<ast::RecordFieldList>,
    pub missed_fields: Vec<Name>,
}

impl Diagnostic for MissingFields {
    fn message(&self) -> String {
        let mut buf = String::from("Missing structure fields:\n");
        for field in &self.missed_fields {
            format_to!(buf, "- {}\n", field);
        }
        buf
    }
    fn source(&self) -> InFile<SyntaxNodePtr> {
        InFile { file_id: self.file, value: self.field_list.clone().into() }
    }
    fn as_any(&self) -> &(dyn Any + Send + 'static) {
        self
    }
}

impl AstDiagnostic for MissingFields {
    type AST = ast::RecordFieldList;

    fn ast(&self, db: &dyn AstDatabase) -> Self::AST {
        let root = db.parse_or_expand(self.source().file_id).unwrap();
        let node = self.source().value.to_node(&root);
        ast::RecordFieldList::cast(node).unwrap()
    }
}

#[derive(Debug)]
pub struct MissingPatFields {
    pub file: HirFileId,
    pub field_list: AstPtr<ast::RecordFieldPatList>,
    pub missed_fields: Vec<Name>,
}

impl Diagnostic for MissingPatFields {
    fn message(&self) -> String {
        let mut buf = String::from("Missing structure fields:\n");
        for field in &self.missed_fields {
            format_to!(buf, "- {}\n", field);
        }
        buf
    }
    fn source(&self) -> InFile<SyntaxNodePtr> {
        InFile { file_id: self.file, value: self.field_list.clone().into() }
    }
    fn as_any(&self) -> &(dyn Any + Send + 'static) {
        self
    }
}

#[derive(Debug)]
pub struct MissingMatchArms {
    pub file: HirFileId,
    pub match_expr: AstPtr<ast::Expr>,
    pub arms: AstPtr<ast::MatchArmList>,
}

impl Diagnostic for MissingMatchArms {
    fn message(&self) -> String {
        String::from("Missing match arm")
    }
    fn source(&self) -> InFile<SyntaxNodePtr> {
        InFile { file_id: self.file, value: self.match_expr.clone().into() }
    }
    fn as_any(&self) -> &(dyn Any + Send + 'static) {
        self
    }
}

#[derive(Debug)]
pub struct MissingOkInTailExpr {
    pub file: HirFileId,
    pub expr: AstPtr<ast::Expr>,
}

impl Diagnostic for MissingOkInTailExpr {
    fn message(&self) -> String {
        "wrap return expression in Ok".to_string()
    }
    fn source(&self) -> InFile<SyntaxNodePtr> {
        InFile { file_id: self.file, value: self.expr.clone().into() }
    }
    fn as_any(&self) -> &(dyn Any + Send + 'static) {
        self
    }
}

impl AstDiagnostic for MissingOkInTailExpr {
    type AST = ast::Expr;

    fn ast(&self, db: &dyn AstDatabase) -> Self::AST {
        let root = db.parse_or_expand(self.file).unwrap();
        let node = self.source().value.to_node(&root);
        ast::Expr::cast(node).unwrap()
    }
}

#[derive(Debug)]
pub struct BreakOutsideOfLoop {
    pub file: HirFileId,
    pub expr: AstPtr<ast::Expr>,
}

impl Diagnostic for BreakOutsideOfLoop {
    fn message(&self) -> String {
        "break outside of loop".to_string()
    }
    fn source(&self) -> InFile<SyntaxNodePtr> {
        InFile { file_id: self.file, value: self.expr.clone().into() }
    }
    fn as_any(&self) -> &(dyn Any + Send + 'static) {
        self
    }
}

impl AstDiagnostic for BreakOutsideOfLoop {
    type AST = ast::Expr;

    fn ast(&self, db: &dyn AstDatabase) -> Self::AST {
        let root = db.parse_or_expand(self.file).unwrap();
        let node = self.source().value.to_node(&root);
        ast::Expr::cast(node).unwrap()
    }
}

#[derive(Debug)]
pub struct MissingUnsafe {
    pub file: HirFileId,
    pub expr: AstPtr<ast::Expr>,
}

impl Diagnostic for MissingUnsafe {
    fn message(&self) -> String {
        format!("This operation is unsafe and requires an unsafe function or block")
    }
    fn source(&self) -> InFile<SyntaxNodePtr> {
        InFile { file_id: self.file, value: self.expr.clone().into() }
    }
    fn as_any(&self) -> &(dyn Any + Send + 'static) {
        self
    }
}

impl AstDiagnostic for MissingUnsafe {
    type AST = ast::Expr;

    fn ast(&self, db: &dyn AstDatabase) -> Self::AST {
        let root = db.parse_or_expand(self.source().file_id).unwrap();
        let node = self.source().value.to_node(&root);
        ast::Expr::cast(node).unwrap()
    }
}

#[derive(Debug)]
pub struct MismatchedArgCount {
    pub file: HirFileId,
    pub call_expr: AstPtr<ast::Expr>,
    pub expected: usize,
    pub found: usize,
}

impl Diagnostic for MismatchedArgCount {
    fn message(&self) -> String {
        let s = if self.expected == 1 { "" } else { "s" };
        format!("Expected {} argument{}, found {}", self.expected, s, self.found)
    }
    fn source(&self) -> InFile<SyntaxNodePtr> {
        InFile { file_id: self.file, value: self.call_expr.clone().into() }
    }
    fn as_any(&self) -> &(dyn Any + Send + 'static) {
        self
    }
}

impl AstDiagnostic for MismatchedArgCount {
    type AST = ast::CallExpr;
    fn ast(&self, db: &dyn AstDatabase) -> Self::AST {
        let root = db.parse_or_expand(self.source().file_id).unwrap();
        let node = self.source().value.to_node(&root);
        ast::CallExpr::cast(node).unwrap()
    }
}
<|endoftext|>


<commit_before>use std::collections::HashMap;
use std::cmp::min;

use super::crystallized_state::CrystallizedState;
use super::crosslink_record::CrosslinkRecord;
use super::partial_crosslink_record::PartialCrosslinkRecord;
use super::config::Config;

// Returns the maximum possible shards for a given validator_count
// and configuration.
pub fn get_crosslink_shards_count(
    active_validator_count: &usize,
    config: &Config)
    -> u16
{
    let system_shard_count: u16 = config.shard_count;
    let notaries_per_crosslink: u16 = config.notaries_per_crosslink;

    assert!(notaries_per_crosslink > 0, "Shards must require > 0 notaries.");

    let notarisable_shard_count =
        *active_validator_count as u64 / notaries_per_crosslink as u64;

    min(notarisable_shard_count, system_shard_count as u64) as u16
}

pub fn get_crosslink_shards(
    cry_state: &CrystallizedState,
    config: &Config)
    -> Vec<u16>
{
    let max_shard_count: u16 = config.shard_count;
    let first_shard: u16 = cry_state.next_shard;
    assert!(first_shard < max_shard_count, "CrystallizedState.next_shard \
    must be less than Config.shard_count.");

    let shard_count = get_crosslink_shards_count(
        &cry_state.num_active_validators(),
        &config);

    let unwrapped_shards: u16 = min(first_shard + shard_count, max_shard_count);
    let wrapped_shards: u16 = (first_shard + shard_count) % max_shard_count;
    let mut crosslink_shards: Vec<u16> = (first_shard..unwrapped_shards).collect();
    crosslink_shards.append(&mut (0_u16..wrapped_shards).collect());
    crosslink_shards
}

pub fn get_crosslink_notaries(
    cry_state: &CrystallizedState,
    shard_id: &u16,
    crosslink_shards: &Vec<u16>)
    -> Vec<usize>
{
    let shard_crosslink_index = crosslink_shards.iter().
        position(|&s| s == *shard_id);

    match shard_crosslink_index {
        None => panic!("shard_id not in crosslink_shards."),
        Some(i) => {
            let crosslink_shards_count = crosslink_shards.len();
            assert!(crosslink_shards_count > 0,
                    "crosslink_shards_count must be > 0");
            let active_validators = cry_state.num_active_validators();
            assert!(active_validators > 0,
                    "active_validators must be > 0");

            let start = active_validators * i / crosslink_shards_count;
            let end = active_validators * (i + 1) / crosslink_shards_count;

            assert!(cry_state.current_shuffling.len() == active_validators,
                    "Crystallized state shuffling does not match active \
                    validator count");

            cry_state.current_shuffling[start..end].to_vec()
        }
    }
}

pub fn process_crosslinks(
    cry_state: &CrystallizedState,
    partial_crosslinks: &Vec<PartialCrosslinkRecord>,
    config: &Config)
    -> (Vec<i64>, Vec<CrosslinkRecord>)
{
    assert!(partial_crosslinks.len() > 0, "No crosslinks present.");

    let mut shard_pc_map:
        HashMap<u16, (&PartialCrosslinkRecord, u64)> = HashMap::new();

    for pc in partial_crosslinks {
        let vote_count = pc.voter_bitfield.num_true_bits();
        let mut competiting_vote_count = 0;
        match shard_pc_map.get(&pc.shard_id) {
            Some(&competitor) => competiting_vote_count = competitor.1,
            None => {}
        }
        // Here we implicitly avoid adding crosslinks with 0 votes
        // to our shard_pc_map.
        if vote_count > competiting_vote_count {
            shard_pc_map.insert(pc.shard_id, (pc, vote_count));
        }
    }

    let mut new_partial_crosslinks: Vec<&PartialCrosslinkRecord> = Vec::new();
    shard_pc_map.iter_mut()
        .for_each(|(_, v)| new_partial_crosslinks.push(v.0));

    let crosslink_shards = get_crosslink_shards(&cry_state, &config);

    let mut deltas = vec![0_i64; cry_state.num_active_validators()];

    let mut new_crosslink_records: Vec<CrosslinkRecord>
        = Vec::new();

    for shard_id in &crosslink_shards {
        let notaries_indicies = get_crosslink_notaries(
            &cry_state,
            &shard_id,
            &crosslink_shards);
        let new_partial_crosslink = shard_pc_map.get(&shard_id);
        let previous_crosslink_epoch =
            match cry_state.crosslink_records.get(*shard_id as usize) {
                None => panic!("shard_id not known by \
                               crystallized state."),
                Some(c) => c.epoch
            };
        let current_epoch = cry_state.current_epoch;
        assert!(current_epoch >= previous_crosslink_epoch, "Previous crosslink \
        epoch cannot be > current epoch.");
        let crosslink_distance = cry_state.current_epoch- previous_crosslink_epoch;
        let online_reward: i64 = if crosslink_distance <= 2 { 3 } else { 0 };
        let offline_penalty: i64 = (crosslink_distance as i64).saturating_mul(2);
        for notary in &notaries_indicies {
            let voted = match new_partial_crosslink {
                None => false,
                Some(pc) => pc.0.voter_bitfield.get_bit(&notary)
            };
            match voted {
                true => deltas[*notary] += online_reward,
                false => deltas[*notary] -= offline_penalty
            };
        }
        match new_partial_crosslink {
            None => {
                new_crosslink_records[*shard_id as usize] =
                    cry_state.crosslink_records[*shard_id as usize].clone()
            },
            Some(pc) => {
                let votes = pc.1;
                if ((votes as usize) * 3) >= (notaries_indicies.len() * 2) {
                    new_crosslink_records[*shard_id as usize] =
                        CrosslinkRecord {
                            epoch: current_epoch,
                            hash: pc.0.shard_block_hash
                        };
                }

            }
        }
    }
    (deltas, new_crosslink_records)
}

#[cfg(test)]
mod tests {
    use super::*;
    use super::super::shuffling::get_shuffling;
    use super::super::super::validator_record::ValidatorRecord;
    use super::super::super::super::utils::types::Sha256Digest;

    #[test]
    fn test_crosslink_shard_count_with_varying_active_vals() {
        let mut config = Config::standard();

        config.shard_count = 10;
        config.notaries_per_crosslink = 10;

        let mut c = get_crosslink_shards_count(
            &100,
            &config);
        assert_eq!(c, 10);

        c = get_crosslink_shards_count(
            &101,
            &config);
        assert_eq!(c, 10);

        c = get_crosslink_shards_count(
            &99,
            &config);
        assert_eq!(c, 9);

        c = get_crosslink_shards_count(
            &0,
            &config);
        assert_eq!(c, 0);
    }

    #[test]
    #[should_panic(expected = "must require > 0 notaries.")]
    fn test_crosslink_shard_count_with_zero_notaries_per_crosslink() {
        let mut config = Config::standard();

        config.shard_count = 10;
        config.notaries_per_crosslink = 0;

        let validators: u16 = 10;

        let _ = get_crosslink_shards_count(
            &(validators as usize),
            &config);
    }

    #[test]
    fn test_crosslink_shard_getter_with_5_shards() {
        let mut cry_state = CrystallizedState::zero();
        let mut config = Config::standard();

        config.shard_count = 5;
        config.notaries_per_crosslink = 2;

        (0..10).for_each(
            |_| cry_state.active_validators.push(
                ValidatorRecord::zero_with_thread_rand_pub_key()));

        cry_state.next_shard = 0;
        let c = get_crosslink_shards(
            &cry_state,
            &config);
        assert_eq!(c, [0, 1, 2, 3, 4]);

        cry_state.next_shard = 4;
        let c = get_crosslink_shards(
            &cry_state,
            &config);
        assert_eq!(c, [4, 0, 1, 2, 3]);

        cry_state.next_shard = 1;
        let c = get_crosslink_shards(
            &cry_state,
            &config);
        assert_eq!(c, [1, 2, 3, 4, 0]);

        cry_state.next_shard = 3;
        let c = get_crosslink_shards(
            &cry_state,
            &config);
        assert_eq!(c, [3, 4, 0, 1, 2]);
    }

    #[test]
    #[should_panic(expected = "next_shard must be less than Config.shard_count")]
    fn test_crosslink_shard_getter_with_too_large_next_shard() {
        let mut cry_state = CrystallizedState::zero();
        let mut config = Config::standard();

        config.shard_count = 1;
        config.notaries_per_crosslink = 2;

        (0..2).for_each(
            |_| cry_state.active_validators.push(
                ValidatorRecord::zero_with_thread_rand_pub_key()));

        cry_state.next_shard = 6;
        let _ = get_crosslink_shards(
            &cry_state,
            &config);
    }

    #[test]
    fn test_crosslink_notaries_allocation() {
        let mut cry_state = CrystallizedState::zero();
        let mut config = Config::standard();
config.shard_count = 5;
        config.notaries_per_crosslink = 2;

        (0..10).for_each(
            |_| cry_state.active_validators.push(
                ValidatorRecord::zero_with_thread_rand_pub_key()));

        cry_state.next_shard = 0;
        let crosslink_shards = get_crosslink_shards(
            &cry_state,
            &config);

        let s = get_shuffling(
            &Sha256Digest::zero(),
            &cry_state.num_active_validators(),
            &config);
        assert_eq!(s, [0, 9, 7, 6, 4, 1, 8, 5, 2, 3]);
        cry_state.current_shuffling = s.clone();

        let mut n = get_crosslink_notaries(
            &cry_state,
            &0,
            &crosslink_shards);
        assert_eq!(n, [0, 9]);

        n = get_crosslink_notaries(
            &cry_state,
            &1,
            &crosslink_shards);
        assert_eq!(n, [7, 6]);

        n = get_crosslink_notaries(
            &cry_state,
            &2,
            &crosslink_shards);
        assert_eq!(n, [4, 1]);

        n = get_crosslink_notaries(
            &cry_state,
            &3,
            &crosslink_shards);
        assert_eq!(n, [8, 5]);

        n = get_crosslink_notaries(
            &cry_state,
            &4,
            &crosslink_shards);
        assert_eq!(n, [2, 3]);
    }

    #[test]
    #[should_panic(expected = "shard_id not in crosslink_shards")]
    fn test_crosslink_notaries_allocation_with_invalid_shard() {
        let mut cry_state = CrystallizedState::zero();
        let mut config = Config::standard();

        config.shard_count = 5;
        config.notaries_per_crosslink = 2;

        (0..10).for_each(
            |_| cry_state.active_validators.push(
                ValidatorRecord::zero_with_thread_rand_pub_key()));

        cry_state.next_shard = 0;
        let crosslink_shards = get_crosslink_shards(
            &cry_state,
            &config);

        cry_state.current_shuffling = get_shuffling(
            &Sha256Digest::zero(),
            &cry_state.num_active_validators(),
            &config);

        let _ = get_crosslink_notaries(
            &cry_state,
            &5,
            &crosslink_shards);
    }
}
<commit_msg>Add comments, test for crosslink_processing()<commit_after>use std::collections::HashMap;
use std::cmp::min;

use super::crystallized_state::CrystallizedState;
use super::crosslink_record::CrosslinkRecord;
use super::partial_crosslink_record::PartialCrosslinkRecord;
use super::config::Config;

// Returns the maximum possible shards for a given validator_count
// and configuration.
pub fn get_crosslink_shards_count(
    active_validator_count: &usize,
    config: &Config)
    -> u16
{
    let system_shard_count: u16 = config.shard_count;
    let notaries_per_crosslink: u16 = config.notaries_per_crosslink;

    assert!(notaries_per_crosslink > 0, "Shards must require > 0 notaries.");

    let notarisable_shard_count =
        *active_validator_count as u64 / notaries_per_crosslink as u64;

    min(notarisable_shard_count, system_shard_count as u64) as u16
}

pub fn get_crosslink_shards(
    cry_state: &CrystallizedState,
    config: &Config)
    -> Vec<u16>
{
    let max_shard_count: u16 = config.shard_count;
    let first_shard: u16 = cry_state.next_shard;
    assert!(first_shard < max_shard_count, "CrystallizedState.next_shard \
    must be less than Config.shard_count.");

    let shard_count = get_crosslink_shards_count(
        &cry_state.num_active_validators(),
        &config);

    let unwrapped_shards: u16 = min(first_shard + shard_count, max_shard_count);
    let wrapped_shards: u16 = (first_shard + shard_count) % max_shard_count;
    let mut crosslink_shards: Vec<u16> = (first_shard..unwrapped_shards).collect();
    crosslink_shards.append(&mut (0_u16..wrapped_shards).collect());
    crosslink_shards
}

pub fn get_crosslink_notaries(
    cry_state: &CrystallizedState,
    shard_id: &u16,
    crosslink_shards: &Vec<u16>)
    -> Vec<usize>
{
    let shard_crosslink_index = crosslink_shards.iter().
        position(|&s| s == *shard_id);

    match shard_crosslink_index {
        None => panic!("shard_id not in crosslink_shards."),
        Some(i) => {
            let crosslink_shards_count = crosslink_shards.len();
            assert!(crosslink_shards_count > 0,
                    "crosslink_shards_count must be > 0");
            let active_validators = cry_state.num_active_validators();
            assert!(active_validators > 0,
                    "active_validators must be > 0");

            let start = active_validators * i / crosslink_shards_count;
            let end = active_validators * (i + 1) / crosslink_shards_count;

            assert!(cry_state.current_shuffling.len() == active_validators,
                    "Crystallized state shuffling does not match active \
                    validator count");

            cry_state.current_shuffling[start..end].to_vec()
        }
    }
}

pub fn process_crosslinks(
    cry_state: &CrystallizedState,
    partial_crosslinks: &Vec<PartialCrosslinkRecord>,
    config: &Config)
    -> (Vec<i64>, Vec<CrosslinkRecord>)
{
    assert!(partial_crosslinks.len() > 0, "No crosslinks present.");

    /*
     * Create a map of shard_id -> (partial_crosslink, vote_count)
     * to store the partial crosslink with the most votes for
     * each shard.
     */
    let mut shard_pc_map:
        HashMap<u16, (&PartialCrosslinkRecord, u64)> = HashMap::new();
    for pc in partial_crosslinks {
        let vote_count = pc.voter_bitfield.num_true_bits();
        let mut competiting_vote_count = 0;
        match shard_pc_map.get(&pc.shard_id) {
            Some(&competitor) => competiting_vote_count = competitor.1,
            None => {}
        }
        // Here we implicitly avoid adding crosslinks with 0 votes
        // to our shard_pc_map.
        if vote_count > competiting_vote_count {
            shard_pc_map.insert(pc.shard_id, (pc, vote_count));
        }
    }

    // All shards which may are to be included in the next state.
    let crosslink_shards = get_crosslink_shards(&cry_state, &config);
    // A list of balance deltas for each validator.
    let mut deltas = vec![0_i64; cry_state.num_active_validators()];
    // A cloned list of validator records from crystallized state.
    let mut new_crosslink_records: Vec<CrosslinkRecord>
        = cry_state.crosslink_records.to_vec();

    /*
     * Loop through all shards up for inclusion in the next crystallized
     * state and replace the existing CrosslinkRecord if we have a new
     * PartialCrosslinkRecord with a quorum.
     */
    for shard_id in &crosslink_shards {
        // Set of validator indicies for a given shard.
        let notaries_indicies = get_crosslink_notaries(
            &cry_state,
            &shard_id,
            &crosslink_shards);
        // Attempt to retrieve a partial crosslink for the current shard_id.
        let new_partial_crosslink = shard_pc_map.get(&shard_id);
        // Retrieve present enshrined crosslink record for this shard.
        let previous_crosslink_epoch =
            match cry_state.crosslink_records.get(*shard_id as usize) {
                None => panic!("shard_id not known by \
                               crystallized state."),
                Some(c) => c.epoch
            };
        // Determine rewards
        let current_epoch = cry_state.current_epoch;
        assert!(current_epoch >= previous_crosslink_epoch, "Previous crosslink \
        epoch cannot be > current epoch.");
        let crosslink_distance = cry_state.current_epoch- previous_crosslink_epoch;
        let online_reward: i64 = if crosslink_distance <= 2 { 3 } else { 0 };
        let offline_penalty: i64 = (crosslink_distance as i64).saturating_mul(2);
        // Loop through each notary for this shard and penalise/reward depending
        // on if they voted or not.
        for notary in &notaries_indicies {
            let voted = match new_partial_crosslink {
                None => false,
                Some(pc) => pc.0.voter_bitfield.get_bit(&notary)
            };
            match voted {
                true => deltas[*notary] += online_reward,
                false => deltas[*notary] -= offline_penalty
            };
        }
        /*
         * If there is a PartialCrosslinkRecord with a quorum of votes for
         * this shard, create a new CrosslinkRecord. By default, if there
         * is not a new partial record, the old CrosslinkRecord will be
         * maintained.
         */
        match new_partial_crosslink {
            None => {},
            Some(pc) => {
                let votes = pc.1;
                // If there are 2/3 or more votes from the notaries for this
                // partial crosslink record, create a new CrosslinkRecord.
                if ((votes as usize) * 3) >= (notaries_indicies.len() * 2) {
                    new_crosslink_records[*shard_id as usize] =
                        CrosslinkRecord {
                            epoch: current_epoch,
                            hash: pc.0.shard_block_hash
                        };
                }

            }
        }
    }
    (deltas, new_crosslink_records)
}

#[cfg(test)]
mod tests {
    use super::*;
    use super::super::shuffling::get_shuffling;
    use super::super::super::validator_record::ValidatorRecord;
    use super::super::super::super::utils::types::{ Sha256Digest, Bitfield };

    #[test]
    fn test_crosslink_shard_count_with_varying_active_vals() {
        let mut config = Config::standard();

        config.shard_count = 10;
        config.notaries_per_crosslink = 10;

        let mut c = get_crosslink_shards_count(
            &100,
            &config);
        assert_eq!(c, 10);

        c = get_crosslink_shards_count(
            &101,
            &config);
        assert_eq!(c, 10);

        c = get_crosslink_shards_count(
            &99,
            &config);
        assert_eq!(c, 9);

        c = get_crosslink_shards_count(
            &0,
            &config);
        assert_eq!(c, 0);
    }

    #[test]
    #[should_panic(expected = "must require > 0 notaries.")]
    fn test_crosslink_shard_count_with_zero_notaries_per_crosslink() {
        let mut config = Config::standard();

        config.shard_count = 10;
        config.notaries_per_crosslink = 0;

        let validators: u16 = 10;

        let _ = get_crosslink_shards_count(
            &(validators as usize),
            &config);
    }

    #[test]
    fn test_crosslink_shard_getter_with_5_shards() {
        let mut cry_state = CrystallizedState::zero();
        let mut config = Config::standard();

        config.shard_count = 5;
        config.notaries_per_crosslink = 2;

        (0..10).for_each(
            |_| cry_state.active_validators.push(
                ValidatorRecord::zero_with_thread_rand_pub_key()));

        cry_state.next_shard = 0;
        let c = get_crosslink_shards(
            &cry_state,
            &config);
        assert_eq!(c, [0, 1, 2, 3, 4]);

        cry_state.next_shard = 4;
        let c = get_crosslink_shards(
            &cry_state,
            &config);
        assert_eq!(c, [4, 0, 1, 2, 3]);

        cry_state.next_shard = 1;
        let c = get_crosslink_shards(
            &cry_state,
            &config);
        assert_eq!(c, [1, 2, 3, 4, 0]);

        cry_state.next_shard = 3;
        let c = get_crosslink_shards(
            &cry_state,
            &config);
        assert_eq!(c, [3, 4, 0, 1, 2]);
    }

    #[test]
    #[should_panic(expected = "next_shard must be less than Config.shard_count")]
    fn test_crosslink_shard_getter_with_too_large_next_shard() {
        let mut cry_state = CrystallizedState::zero();
        let mut config = Config::standard();

        config.shard_count = 1;
        config.notaries_per_crosslink = 2;

        (0..2).for_each(
            |_| cry_state.active_validators.push(
                ValidatorRecord::zero_with_thread_rand_pub_key()));

        cry_state.next_shard = 6;
        let _ = get_crosslink_shards(
            &cry_state,
            &config);
    }

    #[test]
    fn test_crosslink_notaries_allocation() {
        let mut cry_state = CrystallizedState::zero();
        let mut config = Config::standard();
        config.shard_count = 5;
        config.notaries_per_crosslink = 2;

        (0..10).for_each(
            |_| cry_state.active_validators.push(
                ValidatorRecord::zero_with_thread_rand_pub_key()));

        cry_state.next_shard = 0;
        let crosslink_shards = get_crosslink_shards(
            &cry_state,
            &config);

        let s = get_shuffling(
            &Sha256Digest::zero(),
            &cry_state.num_active_validators(),
            &config);
        assert_eq!(s, [0, 9, 7, 6, 4, 1, 8, 5, 2, 3]);
        cry_state.current_shuffling = s.clone();

        let mut n = get_crosslink_notaries(
            &cry_state,
            &0,
            &crosslink_shards);
        assert_eq!(n, [0, 9]);

        n = get_crosslink_notaries(
            &cry_state,
            &1,
            &crosslink_shards);
        assert_eq!(n, [7, 6]);

        n = get_crosslink_notaries(
            &cry_state,
            &2,
            &crosslink_shards);
        assert_eq!(n, [4, 1]);

        n = get_crosslink_notaries(
            &cry_state,
            &3,
            &crosslink_shards);
        assert_eq!(n, [8, 5]);

        n = get_crosslink_notaries(
            &cry_state,
            &4,
            &crosslink_shards);
        assert_eq!(n, [2, 3]);
    }

    #[test]
    #[should_panic(expected = "shard_id not in crosslink_shards")]
    fn test_crosslink_notaries_allocation_with_invalid_shard() {
        let mut cry_state = CrystallizedState::zero();
        let mut config = Config::standard();

        config.shard_count = 5;
        config.notaries_per_crosslink = 2;

        (0..10).for_each(
            |_| cry_state.active_validators.push(
                ValidatorRecord::zero_with_thread_rand_pub_key()));

        cry_state.next_shard = 0;
        let crosslink_shards = get_crosslink_shards(
            &cry_state,
            &config);

        cry_state.current_shuffling = get_shuffling(
            &Sha256Digest::zero(),
            &cry_state.num_active_validators(),
            &config);

        let _ = get_crosslink_notaries(
            &cry_state,
            &5,
            &crosslink_shards);
    }

    #[test]
    fn test_crosslink_processing() {
        let mut cry_state = CrystallizedState::zero();
        let mut config = Config::standard();
        let validator_count: usize = 10;

        config.shard_count = 5;
        config.notaries_per_crosslink = 2;

        (0..validator_count).for_each(
            |_| cry_state.active_validators.push(
                ValidatorRecord::zero_with_thread_rand_pub_key()));

        let s = get_shuffling(
            &Sha256Digest::zero(),
            &cry_state.num_active_validators(),
            &config);
        assert_eq!(s, [0, 9, 7, 6, 4, 1, 8, 5, 2, 3]);
        cry_state.current_shuffling = s.clone();

        cry_state.current_epoch = 100;

        let mut partial_crosslinks: Vec<PartialCrosslinkRecord> = vec![];
        for shard_id in 0..config.shard_count {
            // Setup a recent crosslink record for each shard
            cry_state.crosslink_records.push(CrosslinkRecord {
                epoch: cry_state.current_epoch - 1,
                hash: Sha256Digest::zero()
            });
            // Create a new partial crosslink record
            let mut voter_bitfield = Bitfield::new();
            (0..validator_count).for_each(|i| voter_bitfield.set_bit(&i, &true));
            partial_crosslinks.push(PartialCrosslinkRecord {
                shard_id,
                shard_block_hash: Sha256Digest::from(shard_id as u64),
                voter_bitfield
            });
        }

        let (deltas, new_crosslinks) = process_crosslinks(
            &cry_state,
            &partial_crosslinks,
            &config);

        assert_eq!(deltas, vec![3; validator_count]);
        for shard_id in 0..config.shard_count {
            let c = new_crosslinks[shard_id as usize];
            assert_eq!(c.epoch, cry_state.current_epoch);
            assert_eq!(c.hash.low_u64(), shard_id as u64);
        }
    }
}
<|endoftext|>


<commit_before>use super::*;
use crate::client::Context;
use crate::model::channel::Message;
use uwl::Stream;

pub mod map;

use map::{CommandMap, GroupMap, ParseMap};

use std::borrow::Cow;

#[inline]
fn to_lowercase<'a>(config: &Configuration, s: &'a str) -> Cow<'a, str> {
    if config.case_insensitive {
        Cow::Owned(s.to_lowercase())
    } else {
        Cow::Borrowed(s)
    }
}

/// Parse a mention in the message that is of either the direct (`<@id>`) or nickname (`<@!id>`) syntax,
/// and compare the encoded `id` with the id from [`Configuration::on_mention`] for a match.
/// Returns `Some(<id>)` on success, `None` otherwise.
///
/// [`Configuration::on_mention`]: ../struct.Configuration.html#method.on_mention
pub fn mention<'a>(stream: &mut Stream<'a>, config: &Configuration) -> Option<&'a str> {
    let on_mention = config.on_mention.as_ref().map(String::as_str)?;

    let start = stream.offset();

    if !stream.eat("<@") {
        return None;
    }

    // Optional.
    stream.eat("!");

    let id = stream.take_while(|b| b.is_ascii_digit());

    if !stream.eat(">") {
        // Backtrack to where we were.
        stream.set(start);

        return None;
    }

    if id == on_mention {
        Some(id)
    } else {
        stream.set(start);

        None
    }
}

fn find_prefix<'a>(
    ctx: &Context,
    msg: &Message,
    config: &Configuration,
    stream: &Stream<'a>,
) -> Option<Cow<'a, str>> {
    let try_match = |prefix: &str| {
        let peeked = stream.peek_for_char(prefix.chars().count());
        let peeked = to_lowercase(config, peeked);

        if prefix == &peeked {
            Some(peeked)
        } else {
            None
        }
    };

    for f in &config.dynamic_prefixes {
        if let Some(p) = f(ctx, msg) {
            let p = to_lowercase(config, &p);
            if let Some(p) = try_match(&p) {
                return Some(p);
            }
        }
    }

    config.prefixes.iter().find_map(|p| try_match(&p))
}

/// Parse a prefix in the message.
///
/// The "prefix" may be one of the following:
/// - A mention (`<@id>`/`<@!id>`)
/// - A dynamically constructed prefix ([`Configuration::dynamic_prefix`])
/// - A static prefix ([`Configuration::prefix`])
/// - Nothing
///
/// In all cases, whitespace after the prefix is cleared.
///
/// [`Configuration::dynamic_prefix`]: ../struct.Configuration.html#method.dynamic_prefix
/// [`Configuration::prefix`]: ../struct.Configuration.html#method.prefix
pub fn prefix<'a>(
    ctx: &Context,
    msg: &Message,
    stream: &mut Stream<'a>,
    config: &Configuration,
) -> Option<Cow<'a, str>> {
    if let Some(id) = mention(stream, config) {
        stream.take_while_char(|c| c.is_whitespace());

        return Some(Cow::Borrowed(id));
    }

    let prefix = find_prefix(ctx, msg, config, stream);

    if let Some(prefix) = &prefix {
        stream.increment(prefix.len());
    }

    if config.with_whitespace.prefixes {
        stream.take_while_char(|c| c.is_whitespace());
    }

    prefix
}

/// Checked per valid group or command in the message.
fn check_discrepancy(
    ctx: &Context,
    msg: &Message,
    config: &Configuration,
    options: &impl CommonOptions,
) -> Result<(), DispatchError> {
    if options.owners_only() && !config.owners.contains(&msg.author.id) {
        return Err(DispatchError::OnlyForOwners);
    }

    if options.only_in() == OnlyIn::Dm && !msg.is_private() {
        return Err(DispatchError::OnlyForDM);
    }

    if (!config.allow_dm || options.only_in() == OnlyIn::Guild) && msg.is_private() {
        return Err(DispatchError::OnlyForGuilds);
    }

    #[cfg(feature = "cache")]
    {
        if let Some(guild_id) = msg.guild_id {
            let guild = match guild_id.to_guild_cached(&ctx) {
                Some(g) => g,
                None => return Ok(()),
            };

            let guild = guild.read();

            let perms = guild.user_permissions_in(msg.channel_id, msg.author.id);

            if !perms.contains(*options.required_permissions())
                && !(options.owner_privilege() && config.owners.contains(&msg.author.id))
            {
                return Err(DispatchError::LackingPermissions(
                    *options.required_permissions(),
                ));
            }

            if let Some(member) = guild.members.get(&msg.author.id) {
                if !perms.administrator() && !has_correct_roles(options, &guild, &member) {
                    return Err(DispatchError::LackingRole);
                }
            }
        }
    }

    Ok(())
}

fn try_parse<M: ParseMap>(
    stream: &mut Stream<'_>,
    map: &M,
    by_space: bool,
    f: impl Fn(&str) -> String,
) -> (String, Option<M::Storage>) {
    if by_space {
        let n = f(stream.peek_until_char(|c| c.is_whitespace()));

        let o = map.get(&n);

        (n, o)
    } else {
        let mut n = f(stream.peek_for_char(map.max_length()));
        let mut o = None;

        for _ in 0..(map.max_length() - map.min_length()) {
            o = map.get(&n);

            if o.is_some() {
                break;
            }

            n.pop();
        }

        (n, o)
    }
}

fn parse_cmd(
    stream: &mut Stream<'_>,
    ctx: &Context,
    msg: &Message,
    config: &Configuration,
    map: &CommandMap,
) -> Result<&'static Command, ParseError> {
    let (n, r) = try_parse(stream, map, config.by_space, |s| {
        to_lowercase(config, s).into_owned()
    });

    if config.disabled_commands.contains(&n) {
        return Err(ParseError::Dispatch(DispatchError::CommandDisabled(n)));
    }

    if let Some((cmd, map)) = r {
        stream.increment(n.len());

        if config.with_whitespace.commands {
            stream.take_while_char(|c| c.is_whitespace());
        }

        check_discrepancy(ctx, msg, config, &cmd.options)?;

        if map.is_empty() {
            return Ok(cmd);
        }

        return match parse_cmd(stream, ctx, msg, config, &map) {
            Err(ParseError::UnrecognisedCommand(Some(_))) => Ok(cmd),
            res => res,
        };
    }

    Err(ParseError::UnrecognisedCommand(Some(n.to_string())))
}

fn parse_group(
    stream: &mut Stream<'_>,
    ctx: &Context,
    msg: &Message,
    config: &Configuration,
    map: &GroupMap,
) -> Result<(&'static CommandGroup, Arc<CommandMap>), ParseError> {
    let (n, o) = try_parse(stream, map, config.by_space, ToString::to_string);

    if let Some((group, map, commands)) = o {
        stream.increment(n.len());

        if config.with_whitespace.groups {
            stream.take_while_char(|c| c.is_whitespace());
        }

        check_discrepancy(ctx, msg, config, &group.options)?;

        if map.is_empty() {
            return Ok((group, commands));
        }

        return match parse_group(stream, ctx, msg, config, &map) {
            Err(ParseError::UnrecognisedCommand(None)) => Ok((group, commands)),
            res => res,
        };
    }

    Err(ParseError::UnrecognisedCommand(None))
}

#[inline]
fn handle_command(
    stream: &mut Stream<'_>,
    ctx: &Context,
    msg: &Message,
    config: &Configuration,
    map: &CommandMap,
    group: &'static CommandGroup,
) -> Result<Invoke, ParseError> {
    match parse_cmd(stream, ctx, msg, config, map) {
        Ok(command) => Ok(Invoke::Command { group, command }),
        Err(err) => match group.options.default_command {
            Some(command) => Ok(Invoke::Command { group, command }),
            None => Err(err),
        },
    }
}

#[inline]
fn handle_group(
    stream: &mut Stream<'_>,
    ctx: &Context,
    msg: &Message,
    config: &Configuration,
    map: &GroupMap,
) -> Result<Invoke, ParseError> {
    parse_group(stream, ctx, msg, config, map)
        .and_then(|(group, map)| handle_command(stream, ctx, msg, config, &map, group))
}

#[derive(Debug)]
pub enum ParseError {
    UnrecognisedCommand(Option<String>),
    Dispatch(DispatchError),
}

impl From<DispatchError> for ParseError {
    #[inline]
    fn from(err: DispatchError) -> Self {
        ParseError::Dispatch(err)
    }
}

/// Parse a command from the message.
///
/// The "command" may be:
/// 1. A *help command* that provides a friendly browsing interface of all groups and commands,
/// explaining what each of them are, how they are layed out and how to invoke them.
/// There can only one help command registered, but might have many names defined for invocation of itself.
///
/// 2. A command defined under another command or a group, which may also belong to another group and so on.
/// To invoke this command, all names and prefixes of its parent commands and groups must be specified before it.
pub fn command(
    ctx: &Context,
    msg: &Message,
    stream: &mut Stream<'_>,
    groups: &[(&'static CommandGroup, Map)],
    config: &Configuration,
    help_was_set: Option<&[&'static str]>,
) -> Result<Invoke, ParseError> {
    // Precedence is taken over commands named as one of the help names.
    if let Some(names) = help_was_set {
        for name in names {
            let n = to_lowercase(config, stream.peek_for_char(name.chars().count()));

            if name == &n {
                stream.increment(n.len());

                stream.take_while_char(|c| c.is_whitespace());

                return Ok(Invoke::Help(name));
            }
        }
    }

    let mut last = Err(ParseError::UnrecognisedCommand(None));
    let mut is_prefixless = false;

    for (group, map) in groups {
        match map {
            // Includes [group] itself.
            Map::WithPrefixes(map) => {
                let res = handle_group(stream, ctx, msg, config, map);

                if res.is_ok() {
                    return res;
                }

                if !is_prefixless {
                    last = res;
                }
            }
            Map::Prefixless(subgroups, commands) => {
                let res = handle_group(stream, ctx, msg, config, subgroups);

                if res.is_ok() {
                    check_discrepancy(ctx, msg, config, &group.options)?;

                    return res;
                }

                let res = handle_command(stream, ctx, msg, config, commands, group);

                if res.is_ok() {
                    check_discrepancy(ctx, msg, config, &group.options)?;

                    is_prefixless = true;
                }

                last = res;
            }
        }
    }

    last
}

#[derive(Debug)]
pub enum Invoke {
    Command {
        group: &'static CommandGroup,
        command: &'static Command,
    },
    Help(&'static str),
}
<commit_msg>Fixed order of execution caused by removal of a return (#907)<commit_after>use super::*;
use crate::client::Context;
use crate::model::channel::Message;
use uwl::Stream;

pub mod map;

use map::{CommandMap, GroupMap, ParseMap};

use std::borrow::Cow;

#[inline]
fn to_lowercase<'a>(config: &Configuration, s: &'a str) -> Cow<'a, str> {
    if config.case_insensitive {
        Cow::Owned(s.to_lowercase())
    } else {
        Cow::Borrowed(s)
    }
}

/// Parse a mention in the message that is of either the direct (`<@id>`) or nickname (`<@!id>`) syntax,
/// and compare the encoded `id` with the id from [`Configuration::on_mention`] for a match.
/// Returns `Some(<id>)` on success, `None` otherwise.
///
/// [`Configuration::on_mention`]: ../struct.Configuration.html#method.on_mention
pub fn mention<'a>(stream: &mut Stream<'a>, config: &Configuration) -> Option<&'a str> {
    let on_mention = config.on_mention.as_ref().map(String::as_str)?;

    let start = stream.offset();

    if !stream.eat("<@") {
        return None;
    }

    // Optional.
    stream.eat("!");

    let id = stream.take_while(|b| b.is_ascii_digit());

    if !stream.eat(">") {
        // Backtrack to where we were.
        stream.set(start);

        return None;
    }

    if id == on_mention {
        Some(id)
    } else {
        stream.set(start);

        None
    }
}

fn find_prefix<'a>(
    ctx: &Context,
    msg: &Message,
    config: &Configuration,
    stream: &Stream<'a>,
) -> Option<Cow<'a, str>> {
    let try_match = |prefix: &str| {
        let peeked = stream.peek_for_char(prefix.chars().count());
        let peeked = to_lowercase(config, peeked);

        if prefix == &peeked {
            Some(peeked)
        } else {
            None
        }
    };

    for f in &config.dynamic_prefixes {
        if let Some(p) = f(ctx, msg) {
            let p = to_lowercase(config, &p);
            if let Some(p) = try_match(&p) {
                return Some(p);
            }
        }
    }

    config.prefixes.iter().find_map(|p| try_match(&p))
}

/// Parse a prefix in the message.
///
/// The "prefix" may be one of the following:
/// - A mention (`<@id>`/`<@!id>`)
/// - A dynamically constructed prefix ([`Configuration::dynamic_prefix`])
/// - A static prefix ([`Configuration::prefix`])
/// - Nothing
///
/// In all cases, whitespace after the prefix is cleared.
///
/// [`Configuration::dynamic_prefix`]: ../struct.Configuration.html#method.dynamic_prefix
/// [`Configuration::prefix`]: ../struct.Configuration.html#method.prefix
pub fn prefix<'a>(
    ctx: &Context,
    msg: &Message,
    stream: &mut Stream<'a>,
    config: &Configuration,
) -> Option<Cow<'a, str>> {
    if let Some(id) = mention(stream, config) {
        stream.take_while_char(|c| c.is_whitespace());

        return Some(Cow::Borrowed(id));
    }

    let prefix = find_prefix(ctx, msg, config, stream);

    if let Some(prefix) = &prefix {
        stream.increment(prefix.len());
    }

    if config.with_whitespace.prefixes {
        stream.take_while_char(|c| c.is_whitespace());
    }

    prefix
}

/// Checked per valid group or command in the message.
fn check_discrepancy(
    ctx: &Context,
    msg: &Message,
    config: &Configuration,
    options: &impl CommonOptions,
) -> Result<(), DispatchError> {
    if options.owners_only() && !config.owners.contains(&msg.author.id) {
        return Err(DispatchError::OnlyForOwners);
    }

    if options.only_in() == OnlyIn::Dm && !msg.is_private() {
        return Err(DispatchError::OnlyForDM);
    }

    if (!config.allow_dm || options.only_in() == OnlyIn::Guild) && msg.is_private() {
        return Err(DispatchError::OnlyForGuilds);
    }

    #[cfg(feature = "cache")]
    {
        if let Some(guild_id) = msg.guild_id {
            let guild = match guild_id.to_guild_cached(&ctx) {
                Some(g) => g,
                None => return Ok(()),
            };

            let guild = guild.read();

            let perms = guild.user_permissions_in(msg.channel_id, msg.author.id);

            if !perms.contains(*options.required_permissions())
                && !(options.owner_privilege() && config.owners.contains(&msg.author.id))
            {
                return Err(DispatchError::LackingPermissions(
                    *options.required_permissions(),
                ));
            }

            if let Some(member) = guild.members.get(&msg.author.id) {
                if !perms.administrator() && !has_correct_roles(options, &guild, &member) {
                    return Err(DispatchError::LackingRole);
                }
            }
        }
    }

    Ok(())
}

fn try_parse<M: ParseMap>(
    stream: &mut Stream<'_>,
    map: &M,
    by_space: bool,
    f: impl Fn(&str) -> String,
) -> (String, Option<M::Storage>) {
    if by_space {
        let n = f(stream.peek_until_char(|c| c.is_whitespace()));

        let o = map.get(&n);

        (n, o)
    } else {
        let mut n = f(stream.peek_for_char(map.max_length()));
        let mut o = None;

        for _ in 0..(map.max_length() - map.min_length()) {
            o = map.get(&n);

            if o.is_some() {
                break;
            }

            n.pop();
        }

        (n, o)
    }
}

fn parse_cmd(
    stream: &mut Stream<'_>,
    ctx: &Context,
    msg: &Message,
    config: &Configuration,
    map: &CommandMap,
) -> Result<&'static Command, ParseError> {
    let (n, r) = try_parse(stream, map, config.by_space, |s| {
        to_lowercase(config, s).into_owned()
    });

    if config.disabled_commands.contains(&n) {
        return Err(ParseError::Dispatch(DispatchError::CommandDisabled(n)));
    }

    if let Some((cmd, map)) = r {
        stream.increment(n.len());

        if config.with_whitespace.commands {
            stream.take_while_char(|c| c.is_whitespace());
        }

        check_discrepancy(ctx, msg, config, &cmd.options)?;

        if map.is_empty() {
            return Ok(cmd);
        }

        return match parse_cmd(stream, ctx, msg, config, &map) {
            Err(ParseError::UnrecognisedCommand(Some(_))) => Ok(cmd),
            res => res,
        };
    }

    Err(ParseError::UnrecognisedCommand(Some(n.to_string())))
}

fn parse_group(
    stream: &mut Stream<'_>,
    ctx: &Context,
    msg: &Message,
    config: &Configuration,
    map: &GroupMap,
) -> Result<(&'static CommandGroup, Arc<CommandMap>), ParseError> {
    let (n, o) = try_parse(stream, map, config.by_space, ToString::to_string);

    if let Some((group, map, commands)) = o {
        stream.increment(n.len());

        if config.with_whitespace.groups {
            stream.take_while_char(|c| c.is_whitespace());
        }

        check_discrepancy(ctx, msg, config, &group.options)?;

        if map.is_empty() {
            return Ok((group, commands));
        }

        return match parse_group(stream, ctx, msg, config, &map) {
            Err(ParseError::UnrecognisedCommand(None)) => Ok((group, commands)),
            res => res,
        };
    }

    Err(ParseError::UnrecognisedCommand(None))
}

#[inline]
fn handle_command(
    stream: &mut Stream<'_>,
    ctx: &Context,
    msg: &Message,
    config: &Configuration,
    map: &CommandMap,
    group: &'static CommandGroup,
) -> Result<Invoke, ParseError> {
    match parse_cmd(stream, ctx, msg, config, map) {
        Ok(command) => Ok(Invoke::Command { group, command }),
        Err(err) => match group.options.default_command {
            Some(command) => Ok(Invoke::Command { group, command }),
            None => Err(err),
        },
    }
}

#[inline]
fn handle_group(
    stream: &mut Stream<'_>,
    ctx: &Context,
    msg: &Message,
    config: &Configuration,
    map: &GroupMap,
) -> Result<Invoke, ParseError> {
    parse_group(stream, ctx, msg, config, map)
        .and_then(|(group, map)| handle_command(stream, ctx, msg, config, &map, group))
}

#[derive(Debug)]
pub enum ParseError {
    UnrecognisedCommand(Option<String>),
    Dispatch(DispatchError),
}

impl From<DispatchError> for ParseError {
    #[inline]
    fn from(err: DispatchError) -> Self {
        ParseError::Dispatch(err)
    }
}

/// Parse a command from the message.
///
/// The "command" may be:
/// 1. A *help command* that provides a friendly browsing interface of all groups and commands,
/// explaining what each of them are, how they are layed out and how to invoke them.
/// There can only one help command registered, but might have many names defined for invocation of itself.
///
/// 2. A command defined under another command or a group, which may also belong to another group and so on.
/// To invoke this command, all names and prefixes of its parent commands and groups must be specified before it.
pub fn command(
    ctx: &Context,
    msg: &Message,
    stream: &mut Stream<'_>,
    groups: &[(&'static CommandGroup, Map)],
    config: &Configuration,
    help_was_set: Option<&[&'static str]>,
) -> Result<Invoke, ParseError> {
    // Precedence is taken over commands named as one of the help names.
    if let Some(names) = help_was_set {
        for name in names {
            let n = to_lowercase(config, stream.peek_for_char(name.chars().count()));

            if name == &n {
                stream.increment(n.len());

                stream.take_while_char(|c| c.is_whitespace());

                return Ok(Invoke::Help(name));
            }
        }
    }

    let mut last = Err(ParseError::UnrecognisedCommand(None));
    let mut is_prefixless = false;

    for (group, map) in groups {
        match map {
            // Includes [group] itself.
            Map::WithPrefixes(map) => {
                let res = handle_group(stream, ctx, msg, config, map);

                if res.is_ok() {
                    return res;
                }

                if !is_prefixless {
                    last = res;
                }
            }
            Map::Prefixless(subgroups, commands) => {
                is_prefixless = true;

                let res = handle_group(stream, ctx, msg, config, subgroups);

                if res.is_ok() {
                    check_discrepancy(ctx, msg, config, &group.options)?;

                    return res;
                }

                let res = handle_command(stream, ctx, msg, config, commands, group);

                if res.is_ok() {
                    check_discrepancy(ctx, msg, config, &group.options)?;

                    return res;
                }

                last = res;
            }
        }
    }

    last
}

#[derive(Debug)]
pub enum Invoke {
    Command {
        group: &'static CommandGroup,
        command: &'static Command,
    },
    Help(&'static str),
}
<|endoftext|>