<commit_before>use geojson::{GeoJson, Geometry};
use std::convert::Infallible;
use tracing::instrument;
use url::Url;
use warp::{http::StatusCode, path, reject::Reject, Filter, Rejection, Reply};

use crate::adapters::primary::bragi::api::{
    ForwardGeocoderExplainQuery, ForwardGeocoderQuery, JsonParam, ReverseGeocoderQuery, Type,
};
use crate::adapters::primary::common::settings::QuerySettings;
use crate::domain::ports::primary::search_documents::SearchDocuments;

/// This function defines the base path for Bragi's REST API
fn path_prefix() -> impl Filter<Extract = (), Error = Rejection> + Clone {
    path!("api" / "v1" / ..).boxed()
}

/// This is the entry warp filter for the GET autocomplete endpoint
///
/// It validates:
/// * It is a GET HTTP request
/// * The path is <prefix> / autocomplete
/// * It has valid query parameters
///
/// If any of these steps fails, this filter rejects the request
///
/// If all succeed, it returns
/// * a `ForwardGeocoderQuery` structure representing input parameters,
/// * None for the Geometry, since the Geometry can only be obtained from a POST request
#[instrument]
pub fn forward_geocoder_get(
) -> impl Filter<Extract = (ForwardGeocoderQuery, Option<Geometry>), Error = Rejection> + Clone {
    warp::get()
        .and(path_prefix())
        .and(warp::path("autocomplete"))
        .and(warp::path::end())
        .and(forward_geocoder_query()) // We get the query parameters
        .and(warp::any().map(move || None)) // And the shape is None
}

/// This is the entry warp filter for the POST autocomplete endpoint
/// It validates:
/// * It is a POST HTTP request
/// * The path is prefix / autocomplete
/// * It has valid query parameters and the body of the request is a valid shape.
///
/// If any of these steps fails, this filter rejects the request
#[instrument]
pub fn forward_geocoder_post(
) -> impl Filter<Extract = (ForwardGeocoderQuery, Option<Geometry>), Error = Rejection> + Clone {
    warp::post()
        .and(path_prefix())
        .and(warp::path("autocomplete"))
        .and(warp::path::end())
        .and(forward_geocoder_query()) // Query Parameters
        .and(forward_geocoder_body()) // Shape
}

#[instrument]
pub fn forward_geocoder_explain_get(
) -> impl Filter<Extract = (ForwardGeocoderExplainQuery, Option<Geometry>), Error = Rejection> + Clone
{
    warp::get()
        .and(path_prefix())
        .and(warp::path("autocomplete-explain"))
        .and(warp::path::end())
        .and(forward_geocoder_explain_query()) // We get the query parameters
        .and(warp::any().map(move || None)) // And the shape is None
}

#[instrument]
pub fn forward_geocoder_explain_post(
) -> impl Filter<Extract = (ForwardGeocoderExplainQuery, Option<Geometry>), Error = Rejection> + Clone
{
    warp::post()
        .and(path_prefix())
        .and(warp::path("autocomplete-explain"))
        .and(warp::path::end())
        .and(forward_geocoder_explain_query()) // Query Parameters
        .and(forward_geocoder_body()) // Shape
}

/// This function reads the input parameters on a get request, makes a summary validation
/// of the parameters, and returns them.
#[instrument]
pub fn reverse_geocoder(
) -> impl Filter<Extract = (ReverseGeocoderQuery,), Error = Rejection> + Clone {
    warp::get()
        .and(path_prefix())
        .and(warp::path("reverse"))
        .and(reverse_geocoder_query())
}

pub fn with_client<S>(s: S) -> impl Filter<Extract = (S,), Error = std::convert::Infallible> + Clone
where
    S: SearchDocuments + Send + Sync + Clone,
{
    warp::any().map(move || s.clone())
}

pub fn with_settings(
    settings: QuerySettings,
) -> impl Filter<Extract = (QuerySettings,), Error = std::convert::Infallible> + Clone {
    warp::any().map(move || settings.clone())
}

pub fn with_elasticsearch(
    url: &Url, // elasticsearch url
) -> impl Filter<Extract = (String,), Error = std::convert::Infallible> + Clone {
    let url = url.to_string();
    warp::any().map(move || url.clone())
}

#[derive(Debug, PartialEq)]
pub enum InvalidRequestReason {
    CannotDeserialize,
    EmptyQueryString,
    InconsistentPoiRequest,
    InconsistentZoneRequest,
    InconsistentLatLonRequest,
}

#[derive(Debug)]
struct InvalidRequest {
    pub reason: InvalidRequestReason,
}

impl Reject for InvalidRequest {}

#[derive(Debug)]
struct InvalidPostBody;
impl Reject for InvalidPostBody {}

/// Extract and Validate input parameters from the query
#[instrument]
pub fn forward_geocoder_query(
) -> impl Filter<Extract = (ForwardGeocoderQuery,), Error = Rejection> + Copy {
    // warp::query cannot parse array parameters correctly, so we use serde_qs for that:
    warp::filters::query::raw()
        .and_then(|param: String| async move {
            serde_qs::from_str::<ForwardGeocoderQuery>(&param).map_err(|_| {
                warp::reject::custom(InvalidRequest {
                    reason: InvalidRequestReason::CannotDeserialize,
                })
            })
        })
        .and_then(ensure_query_string_not_empty)
        .and_then(ensure_poi_type_consistent)
        .and_then(ensure_zone_type_consistent)
        .and_then(ensure_lat_lon_consistent)
}

/// Extract and Validate input parameters from the query
#[instrument]
pub fn forward_geocoder_explain_query(
) -> impl Filter<Extract = (ForwardGeocoderExplainQuery,), Error = Rejection> + Copy {
    warp::filters::query::raw().and_then(|param: String| async move {
        serde_qs::from_str::<ForwardGeocoderExplainQuery>(&param).map_err(|_| {
            warp::reject::custom(InvalidRequest {
                reason: InvalidRequestReason::CannotDeserialize,
            })
        })
    })
}

pub async fn ensure_query_string_not_empty(
    params: ForwardGeocoderQuery,
) -> Result<ForwardGeocoderQuery, Rejection> {
    if params.q.is_empty() {
        Err(warp::reject::custom(InvalidRequest {
            reason: InvalidRequestReason::EmptyQueryString,
        }))
    } else {
        Ok(params)
    }
}

/// This filter ensures that if the user requests 'poi', then he must specify the list
/// of poi_types.
pub async fn ensure_poi_type_consistent(
    params: ForwardGeocoderQuery,
) -> Result<ForwardGeocoderQuery, Rejection> {
    if params
        .types
        .as_ref()
        .map(|types| types.iter().any(|s| *s == Type::Poi))
        .unwrap_or(false)
        && params
            .poi_types
            .as_ref()
            .map(|poi_types| poi_types.is_empty())
            .unwrap_or(true)
    {
        Err(warp::reject::custom(InvalidRequest {
            reason: InvalidRequestReason::InconsistentPoiRequest,
        }))
    } else {
        Ok(params)
    }
}

/// This filter ensures that if the user specifies lat or lon,
/// then he must specify also lon or lat.
pub async fn ensure_lat_lon_consistent(
    params: ForwardGeocoderQuery,
) -> Result<ForwardGeocoderQuery, Rejection> {
    if params.lat.is_some() ^ params.lon.is_some() {
        Err(warp::reject::custom(InvalidRequest {
            reason: InvalidRequestReason::InconsistentLatLonRequest,
        }))
    } else {
        Ok(params)
    }
}

/// This filter ensures that if the user requests 'zone', then he must specify the list
/// of zone_types.
pub async fn ensure_zone_type_consistent(
    params: ForwardGeocoderQuery,
) -> Result<ForwardGeocoderQuery, Rejection> {
    if params
        .types
        .as_ref()
        .map(|types| types.iter().any(|s| *s == Type::Zone))
        .unwrap_or(false)
        && params
            .zone_types
            .as_ref()
            .map(|zone_types| zone_types.is_empty())
            .unwrap_or(true)
    {
        Err(warp::reject::custom(InvalidRequest {
            reason: InvalidRequestReason::InconsistentZoneRequest,
        }))
    } else {
        Ok(params)
    }
}

// This filter extracts the GeoJson shape from the body of the request
#[instrument]
pub fn forward_geocoder_body(
) -> impl Filter<Extract = (Option<Geometry>,), Error = Rejection> + Copy {
    warp::body::content_length_limit(1024 * 32)
        .and(warp::body::json())
        .and_then(validate_geojson_shape)
}

pub async fn validate_geojson_shape(json: JsonParam) -> Result<Option<Geometry>, Rejection> {
    match json.shape {
        GeoJson::Feature(f) => f
            .geometry
            .ok_or_else(|| warp::reject::custom(InvalidPostBody))
            .map(Some),
        _ => Err(warp::reject::custom(InvalidPostBody)),
    }
}

pub fn reverse_geocoder_query(
) -> impl Filter<Extract = (ReverseGeocoderQuery,), Error = Rejection> + Copy {
    warp::filters::query::query()
}

pub async fn report_invalid(rejection: Rejection) -> Result<impl Reply, Infallible> {
    let reply = warp::reply::reply();

    if rejection.find::<warp::reject::InvalidQuery>().is_some()
        || rejection.find::<InvalidRequest>().is_some()
    {
        Ok(warp::reply::with_status(reply, StatusCode::BAD_REQUEST))
    } else {
        // Do better error handling here
        Ok(warp::reply::with_status(
            reply,
            StatusCode::INTERNAL_SERVER_ERROR,
        ))
    }
}

pub fn status() -> impl Filter<Extract = (), Error = Rejection> + Clone {
    warp::get().and(path_prefix()).and(warp::path("status"))
}

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

    #[tokio::test]
    async fn should_report_invalid_query_with_no_query() {
        let filter = forward_geocoder_get();
        let resp = warp::test::request()
            .path("/api/v1/autocomplete")
            .filter(&filter)
            .await;
        assert!(
            resp.unwrap_err()
                .find::<warp::reject::InvalidQuery>()
                .is_some(),
            "Empty query parameter not allowed"
        );
    }

    #[tokio::test]
    async fn should_report_invalid_request_with_empty_query_string() {
        let filter = forward_geocoder_get();
        let resp = warp::test::request()
            .path("/api/v1/autocomplete?q=")
            .filter(&filter)
            .await;
        assert_eq!(
            resp.unwrap_err().find::<InvalidRequest>().unwrap().reason,
            InvalidRequestReason::EmptyQueryString,
            "Empty query string not allowed"
        );
    }

    #[tokio::test]
    async fn should_report_invalid_request_with_invalid_query() {
        let filter = forward_geocoder_get();
        let resp = warp::test::request()
            .path("/api/v1/autocomplete?place=paris") // place is an unknown key
            .filter(&filter)
            .await;
        assert_eq!(
            resp.unwrap_err().find::<InvalidRequest>().unwrap().reason,
            InvalidRequestReason::CannotDeserialize,
            "Unknown parameter, cannot deserialize"
        );
    }

    #[tokio::test]
    async fn should_report_invalid_request_with_invalid_type() {
        let filter = forward_geocoder_get();
        let resp = warp::test::request()
            .path("/api/v1/autocomplete?place=paris&type[]=country") // place is an unknown key
            .filter(&filter)
            .await;
        assert_eq!(
            resp.unwrap_err().find::<InvalidRequest>().unwrap().reason,
            InvalidRequestReason::CannotDeserialize,
            "Unknown type, cannot deserialize"
        );
    }

    #[tokio::test]
    async fn should_correctly_extract_query_string() {
        let filter = forward_geocoder_get();
        let resp = warp::test::request()
            .path("/api/v1/autocomplete?q=paris")
            .filter(&filter)
            .await;
        assert_eq!(resp.unwrap().0.q, String::from("paris"));
    }

    #[tokio::test]
    async fn should_correctly_extract_types() {
        let filter = forward_geocoder_get();
        let resp = warp::test::request()
            .path("/api/v1/autocomplete?q=paris&type[]=street&type[]=zone&zone_type[]=city")
            .filter(&filter)
            .await;
        assert_eq!(resp.as_ref().unwrap().0.types.as_ref().unwrap().len(), 2);
        assert!(resp
            .unwrap()
            .0
            .types
            .unwrap()
            .iter()
            .zip([Type::Street, Type::Zone].iter())
            .all(|(a, b)| *a == *b));
    }

    // TODO The shape_scope parameter can only be used with a POST request (since that's the only
    // way of specifying the shape). But to write a test for that case, we'd need to have access
    // to both the query parameters (ForwardGeocoderQuery) and the body (Option<Geometry>) which
    // is possible at the handler level...

    #[tokio::test]
    async fn should_correctly_extract_geojson_shape() {
        let filter = forward_geocoder_post();
        let resp = warp::test::request()
            .method("POST")
            .path("/api/v1/autocomplete?q=paris")
            .body(r#"{"shape":{"type":"Feature","properties":{},"geometry":{"type":"Polygon", "coordinates":[[[2.376488, 48.846431],
        [2.376306, 48.846430],[2.376309, 48.846606],[2.376486, 48.846603], [2.376488, 48.846431]]]}}}"#)
            .filter(&filter)
            .await;
        assert!(resp.unwrap().1.is_some());
    }

    #[tokio::test]
    async fn should_report_invalid_shape() {
        let filter = forward_geocoder_post();
        let resp = warp::test::request()
            .method("POST")
            .path("/api/v1/autocomplete?q=paris")
            .body(r#"{"shape":{"type":"Feature","properties":{}}}"#)
            .filter(&filter)
            .await;
        println!("{:?}", resp);
        assert!(
            resp.unwrap_err()
                .find::<warp::filters::body::BodyDeserializeError>()
                .unwrap()
                .to_string()
                .contains("Expected a GeoJSON property for `geometry`"),
            "Invalid GeoJSON shape (missing geometry). cannot deserialize body"
        );
    }
}
<commit_msg>activate no strict mode in arguments params<commit_after>use geojson::{GeoJson, Geometry};
use std::convert::Infallible;
use tracing::instrument;
use url::Url;

use warp::{http::StatusCode, path, reject::Reject, Filter, Rejection, Reply};

use crate::adapters::primary::bragi::api::{
    ForwardGeocoderExplainQuery, ForwardGeocoderQuery, JsonParam, ReverseGeocoderQuery, Type,
};
use crate::adapters::primary::common::settings::QuerySettings;
use crate::domain::ports::primary::search_documents::SearchDocuments;
use serde_qs::Config;

/// This function defines the base path for Bragi's REST API
fn path_prefix() -> impl Filter<Extract = (), Error = Rejection> + Clone {
    path!("api" / "v1" / ..).boxed()
}

/// This is the entry warp filter for the GET autocomplete endpoint
///
/// It validates:
/// * It is a GET HTTP request
/// * The path is <prefix> / autocomplete
/// * It has valid query parameters
///
/// If any of these steps fails, this filter rejects the request
///
/// If all succeed, it returns
/// * a `ForwardGeocoderQuery` structure representing input parameters,
/// * None for the Geometry, since the Geometry can only be obtained from a POST request
#[instrument]
pub fn forward_geocoder_get(
) -> impl Filter<Extract = (ForwardGeocoderQuery, Option<Geometry>), Error = Rejection> + Clone {
    warp::get()
        .and(path_prefix())
        .and(warp::path("autocomplete"))
        .and(warp::path::end())
        .and(forward_geocoder_query()) // We get the query parameters
        .and(warp::any().map(move || None)) // And the shape is None
}

/// This is the entry warp filter for the POST autocomplete endpoint
/// It validates:
/// * It is a POST HTTP request
/// * The path is prefix / autocomplete
/// * It has valid query parameters and the body of the request is a valid shape.
///
/// If any of these steps fails, this filter rejects the request
#[instrument]
pub fn forward_geocoder_post(
) -> impl Filter<Extract = (ForwardGeocoderQuery, Option<Geometry>), Error = Rejection> + Clone {
    warp::post()
        .and(path_prefix())
        .and(warp::path("autocomplete"))
        .and(warp::path::end())
        .and(forward_geocoder_query()) // Query Parameters
        .and(forward_geocoder_body()) // Shape
}

#[instrument]
pub fn forward_geocoder_explain_get(
) -> impl Filter<Extract = (ForwardGeocoderExplainQuery, Option<Geometry>), Error = Rejection> + Clone
{
    warp::get()
        .and(path_prefix())
        .and(warp::path("autocomplete-explain"))
        .and(warp::path::end())
        .and(forward_geocoder_explain_query()) // We get the query parameters
        .and(warp::any().map(move || None)) // And the shape is None
}

#[instrument]
pub fn forward_geocoder_explain_post(
) -> impl Filter<Extract = (ForwardGeocoderExplainQuery, Option<Geometry>), Error = Rejection> + Clone
{
    warp::post()
        .and(path_prefix())
        .and(warp::path("autocomplete-explain"))
        .and(warp::path::end())
        .and(forward_geocoder_explain_query()) // Query Parameters
        .and(forward_geocoder_body()) // Shape
}

/// This function reads the input parameters on a get request, makes a summary validation
/// of the parameters, and returns them.
#[instrument]
pub fn reverse_geocoder(
) -> impl Filter<Extract = (ReverseGeocoderQuery,), Error = Rejection> + Clone {
    warp::get()
        .and(path_prefix())
        .and(warp::path("reverse"))
        .and(reverse_geocoder_query())
}

pub fn with_client<S>(s: S) -> impl Filter<Extract = (S,), Error = std::convert::Infallible> + Clone
where
    S: SearchDocuments + Send + Sync + Clone,
{
    warp::any().map(move || s.clone())
}

pub fn with_settings(
    settings: QuerySettings,
) -> impl Filter<Extract = (QuerySettings,), Error = std::convert::Infallible> + Clone {
    warp::any().map(move || settings.clone())
}

pub fn with_elasticsearch(
    url: &Url, // elasticsearch url
) -> impl Filter<Extract = (String,), Error = std::convert::Infallible> + Clone {
    let url = url.to_string();
    warp::any().map(move || url.clone())
}

#[derive(Debug, PartialEq)]
pub enum InvalidRequestReason {
    CannotDeserialize,
    EmptyQueryString,
    InconsistentPoiRequest,
    InconsistentZoneRequest,
    InconsistentLatLonRequest,
}

#[derive(Debug)]
struct InvalidRequest {
    pub reason: InvalidRequestReason,
}

impl Reject for InvalidRequest {}

#[derive(Debug)]
struct InvalidPostBody;
impl Reject for InvalidPostBody {}

/// Extract and Validate input parameters from the query
#[instrument]
pub fn forward_geocoder_query(
) -> impl Filter<Extract = (ForwardGeocoderQuery,), Error = Rejection> + Copy {
    // warp::query cannot parse array parameters correctly, so we use serde_qs for that:
    warp::filters::query::raw()
        .and_then(|param: String| async move {
            let config = Config::new(3, false);
            config.deserialize_str(&param).map_err(|_| {
                warp::reject::custom(InvalidRequest {
                    reason: InvalidRequestReason::CannotDeserialize,
                })
            })
        })
        .and_then(ensure_query_string_not_empty)
        .and_then(ensure_poi_type_consistent)
        .and_then(ensure_zone_type_consistent)
        .and_then(ensure_lat_lon_consistent)
}

/// Extract and Validate input parameters from the query
#[instrument]
pub fn forward_geocoder_explain_query(
) -> impl Filter<Extract = (ForwardGeocoderExplainQuery,), Error = Rejection> + Copy {
    warp::filters::query::raw().and_then(|param: String| async move {
        let config = Config::new(3, false);
        config.deserialize_str(&param).map_err(|_| {
            warp::reject::custom(InvalidRequest {
                reason: InvalidRequestReason::CannotDeserialize,
            })
        })
    })
}

pub async fn ensure_query_string_not_empty(
    params: ForwardGeocoderQuery,
) -> Result<ForwardGeocoderQuery, Rejection> {
    if params.q.is_empty() {
        Err(warp::reject::custom(InvalidRequest {
            reason: InvalidRequestReason::EmptyQueryString,
        }))
    } else {
        Ok(params)
    }
}

/// This filter ensures that if the user requests 'poi', then he must specify the list
/// of poi_types.
pub async fn ensure_poi_type_consistent(
    params: ForwardGeocoderQuery,
) -> Result<ForwardGeocoderQuery, Rejection> {
    if params
        .types
        .as_ref()
        .map(|types| types.iter().any(|s| *s == Type::Poi))
        .unwrap_or(false)
        && params
            .poi_types
            .as_ref()
            .map(|poi_types| poi_types.is_empty())
            .unwrap_or(true)
    {
        Err(warp::reject::custom(InvalidRequest {
            reason: InvalidRequestReason::InconsistentPoiRequest,
        }))
    } else {
        Ok(params)
    }
}

/// This filter ensures that if the user specifies lat or lon,
/// then he must specify also lon or lat.
pub async fn ensure_lat_lon_consistent(
    params: ForwardGeocoderQuery,
) -> Result<ForwardGeocoderQuery, Rejection> {
    if params.lat.is_some() ^ params.lon.is_some() {
        Err(warp::reject::custom(InvalidRequest {
            reason: InvalidRequestReason::InconsistentLatLonRequest,
        }))
    } else {
        Ok(params)
    }
}

/// This filter ensures that if the user requests 'zone', then he must specify the list
/// of zone_types.
pub async fn ensure_zone_type_consistent(
    params: ForwardGeocoderQuery,
) -> Result<ForwardGeocoderQuery, Rejection> {
    if params
        .types
        .as_ref()
        .map(|types| types.iter().any(|s| *s == Type::Zone))
        .unwrap_or(false)
        && params
            .zone_types
            .as_ref()
            .map(|zone_types| zone_types.is_empty())
            .unwrap_or(true)
    {
        Err(warp::reject::custom(InvalidRequest {
            reason: InvalidRequestReason::InconsistentZoneRequest,
        }))
    } else {
        Ok(params)
    }
}

// This filter extracts the GeoJson shape from the body of the request
#[instrument]
pub fn forward_geocoder_body(
) -> impl Filter<Extract = (Option<Geometry>,), Error = Rejection> + Copy {
    warp::body::content_length_limit(1024 * 32)
        .and(warp::body::json())
        .and_then(validate_geojson_shape)
}

pub async fn validate_geojson_shape(json: JsonParam) -> Result<Option<Geometry>, Rejection> {
    match json.shape {
        GeoJson::Feature(f) => f
            .geometry
            .ok_or_else(|| warp::reject::custom(InvalidPostBody))
            .map(Some),
        _ => Err(warp::reject::custom(InvalidPostBody)),
    }
}

pub fn reverse_geocoder_query(
) -> impl Filter<Extract = (ReverseGeocoderQuery,), Error = Rejection> + Copy {
    warp::filters::query::query()
}

pub async fn report_invalid(rejection: Rejection) -> Result<impl Reply, Infallible> {
    let reply = warp::reply::reply();

    if rejection.find::<warp::reject::InvalidQuery>().is_some()
        || rejection.find::<InvalidRequest>().is_some()
    {
        Ok(warp::reply::with_status(reply, StatusCode::BAD_REQUEST))
    } else {
        // Do better error handling here
        Ok(warp::reply::with_status(
            reply,
            StatusCode::INTERNAL_SERVER_ERROR,
        ))
    }
}

pub fn status() -> impl Filter<Extract = (), Error = Rejection> + Clone {
    warp::get().and(path_prefix()).and(warp::path("status"))
}

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

    #[tokio::test]
    async fn should_report_invalid_query_with_no_query() {
        let filter = forward_geocoder_get();
        let resp = warp::test::request()
            .path("/api/v1/autocomplete")
            .filter(&filter)
            .await;
        assert!(
            resp.unwrap_err()
                .find::<warp::reject::InvalidQuery>()
                .is_some(),
            "Empty query parameter not allowed"
        );
    }

    #[tokio::test]
    async fn should_report_invalid_request_with_empty_query_string() {
        let filter = forward_geocoder_get();
        let resp = warp::test::request()
            .path("/api/v1/autocomplete?q=")
            .filter(&filter)
            .await;
        assert_eq!(
            resp.unwrap_err().find::<InvalidRequest>().unwrap().reason,
            InvalidRequestReason::EmptyQueryString,
            "Empty query string not allowed"
        );
    }

    #[tokio::test]
    async fn should_report_invalid_request_with_invalid_query() {
        let filter = forward_geocoder_get();
        let resp = warp::test::request()
            .path("/api/v1/autocomplete?place=paris") // place is an unknown key
            .filter(&filter)
            .await;
        assert_eq!(
            resp.unwrap_err().find::<InvalidRequest>().unwrap().reason,
            InvalidRequestReason::CannotDeserialize,
            "Unknown parameter, cannot deserialize"
        );
    }

    #[tokio::test]
    async fn should_report_invalid_request_with_invalid_type() {
        let filter = forward_geocoder_get();
        let resp = warp::test::request()
            .path("/api/v1/autocomplete?place=paris&type[]=country") // place is an unknown key
            .filter(&filter)
            .await;
        assert_eq!(
            resp.unwrap_err().find::<InvalidRequest>().unwrap().reason,
            InvalidRequestReason::CannotDeserialize,
            "Unknown type, cannot deserialize"
        );
    }

    #[tokio::test]
    async fn should_correctly_extract_query_string() {
        let filter = forward_geocoder_get();
        let resp = warp::test::request()
            .path("/api/v1/autocomplete?q=paris")
            .filter(&filter)
            .await;
        assert_eq!(resp.unwrap().0.q, String::from("paris"));
    }

    #[tokio::test]
    async fn should_correctly_extract_types() {
        let filter = forward_geocoder_get();
        let resp = warp::test::request()
            .path("/api/v1/autocomplete?q=paris&type[]=street&type[]=zone&zone_type[]=city")
            .filter(&filter)
            .await;
        assert_eq!(resp.as_ref().unwrap().0.types.as_ref().unwrap().len(), 2);
        assert!(resp
            .unwrap()
            .0
            .types
            .unwrap()
            .iter()
            .zip([Type::Street, Type::Zone].iter())
            .all(|(a, b)| *a == *b));
    }

    // TODO The shape_scope parameter can only be used with a POST request (since that's the only
    // way of specifying the shape). But to write a test for that case, we'd need to have access
    // to both the query parameters (ForwardGeocoderQuery) and the body (Option<Geometry>) which
    // is possible at the handler level...

    #[tokio::test]
    async fn should_correctly_extract_geojson_shape() {
        let filter = forward_geocoder_post();
        let resp = warp::test::request()
            .method("POST")
            .path("/api/v1/autocomplete?q=paris")
            .body(r#"{"shape":{"type":"Feature","properties":{},"geometry":{"type":"Polygon", "coordinates":[[[2.376488, 48.846431],
        [2.376306, 48.846430],[2.376309, 48.846606],[2.376486, 48.846603], [2.376488, 48.846431]]]}}}"#)
            .filter(&filter)
            .await;
        assert!(resp.unwrap().1.is_some());
    }

    #[tokio::test]
    async fn should_report_invalid_shape() {
        let filter = forward_geocoder_post();
        let resp = warp::test::request()
            .method("POST")
            .path("/api/v1/autocomplete?q=paris")
            .body(r#"{"shape":{"type":"Feature","properties":{}}}"#)
            .filter(&filter)
            .await;
        println!("{:?}", resp);
        assert!(
            resp.unwrap_err()
                .find::<warp::filters::body::BodyDeserializeError>()
                .unwrap()
                .to_string()
                .contains("Expected a GeoJSON property for `geometry`"),
            "Invalid GeoJSON shape (missing geometry). cannot deserialize body"
        );
    }

    #[tokio::test]
    async fn should_correctly_extract_query_no_strict_mode() {
        let filter = forward_geocoder_get();
        let resp = warp::test::request()
            .path("/api/v1/autocomplete?q=rue+hictor+malot&type%5B%5D=street&type%5B%5D=house")
            .filter(&filter)
            .await;

        assert_eq!(resp.as_ref().unwrap().0.types.as_ref().unwrap().len(), 2);
    }
}
<|endoftext|>


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

//! The reference loader implementation for the `gltf` crate.
//!
//! # Examples
//!
//! ### Importing some `glTF` 2.0
//!
//! ```rust
//! use gltf_importer::import;
//! # #[allow(unused_variables)]
//! let path = "path/to/asset.gltf";
//! # let path = "../examples/Box.gltf";
//! match import(path) {
//!     Ok((gltf, _)) => println!("{:#?}", gltf.as_json()),
//!     Err(err) => println!("error: {:?}", err),
//! }
//! ```

extern crate gltf;
extern crate gltf_utils;

use gltf::json::{self, validation};
use std::{fmt, fs, io};

use gltf::Gltf;
use gltf_utils::Source;
use std::error::Error as StdError;
use std::path::Path;

/// Contains parameters for import configuration.
pub mod config;

pub use self::config::Config;

/// Error encountered when importing a glTF 2.0 asset.
#[derive(Debug)]
pub enum Error {
    /// A loaded glTF buffer is not of the required length.
    BufferLength(json::Path),

    /// A glTF extension required by the asset has not been enabled by the user.
    ExtensionDisabled(String),

    /// A glTF extension required by the asset is not supported by the library.
    ExtensionUnsupported(String),

    /// The glTF version of the asset is incompatible with the importer.
    IncompatibleVersion(String),

    /// Standard I/O error.
    Io(std::io::Error),

    /// `gltf` crate error.
    Gltf(gltf::Error),

    /// Failure when deserializing .gltf or .glb JSON.
    MalformedJson(json::Error),

    /// The .gltf data is invalid.
    Validation(Vec<(json::Path, validation::Error)>),
}

/// Buffer data returned from `import`.
#[derive(Clone, Debug)]
pub struct Buffers(Vec<Vec<u8>>);

impl Source for Buffers {
    fn source_buffer(&self, buffer: &gltf::Buffer) -> &[u8] {
        &self.0[buffer.index()]
    }
}

impl Buffers {
    /// Take the loaded buffer data.
    pub fn take(self) -> Vec<Vec<u8>> {
        self.0
    }
}

fn import_impl(path: &Path, config: Config) -> Result<(Gltf, Buffers), Error> {
    let data = read_to_end(path)?;
    if data.starts_with(b"glTF") {
        import_binary(&data, &config, path)
    } else {
        import_standard(&data, &config, path)
    }
}

/// Imports glTF 2.0
pub fn import<P>(path: P) -> Result<(Gltf, Buffers), Error>
    where P: AsRef<Path>
{
    import_impl(path.as_ref(), Default::default())
}

fn read_to_end_impl(path: &Path) -> Result<Vec<u8>, Error> {
    use io::Read;
    let file = fs::File::open(path)?;
    let mut reader = io::BufReader::new(file);
    let mut buffer = vec![];
    let _ = reader.read_to_end(&mut buffer)?;
    Ok(buffer)
}

fn read_to_end<P: AsRef<Path>>(path: P) -> Result<Vec<u8>, Error> {
    read_to_end_impl(path.as_ref())
}

fn load_external_buffers(
    base_path: &Path,
    gltf: &Gltf,
    has_bin: bool,
) -> Result<Vec<Vec<u8>>, Error> {
    let mut iter = gltf.as_json().buffers.iter().enumerate();
    if has_bin {
        let _ = iter.next();
    }
    iter
        .map(|(index, buffer)| {
            let uri = buffer.uri.as_ref().unwrap();
            let path = base_path.parent().unwrap_or(Path::new("./")).join(uri);
            let data = read_to_end(&path)?;
            if data.len() != buffer.byte_length as usize {
                let path = json::Path::new().field("buffers").index(index);
                return Err(Error::BufferLength(path));
            }
            Ok(data)
        })
        .collect()
}

fn validate_standard(
    unvalidated: gltf::Unvalidated,
    config: &Config,
) -> Result<Gltf, Error> {
    use config::ValidationStrategy;
    Ok(match config.validation_strategy {
        ValidationStrategy::Skip => unsafe { unvalidated.skip_validation() },
        ValidationStrategy::Minimal => unvalidated.validate_minimally()?,
        ValidationStrategy::Complete => unvalidated.validate_completely()?,
    })
}

fn validate_binary(
    unvalidated: gltf::Unvalidated,
    config: &Config,
    has_bin: bool,
) -> Result<Gltf, Error> {
    use config::ValidationStrategy;
    use json::validation::Error as Reason;

    if config.validation_strategy == ValidationStrategy::Skip {
        return Ok(unsafe { unvalidated.skip_validation() });
    }

    let mut errs = vec![];
    {
        let json = unvalidated.as_json();
        for (index, buffer) in json.buffers.iter().enumerate() {
            let path = || {
                json::Path::new()
                    .field("buffers")
                    .index(index)
                    .field("uri")
            };
            match index {
                0 if has_bin => if buffer.uri.is_some() {
                    errs.push((path(), Reason::Missing));
                },
                _ if buffer.uri.is_none() => {
                    errs.push((path(), Reason::Missing));
                },
                _ => {},
            }
        }
    }

    if errs.is_empty() {
        Ok(match config.validation_strategy {
            ValidationStrategy::Minimal => unvalidated.validate_minimally()?,
            ValidationStrategy::Complete => unvalidated.validate_completely()?,
            ValidationStrategy::Skip => unreachable!(),
        })
    } else {
        Err(Error::Validation(errs))
    }
}

fn import_standard<'a>(
    data: &'a [u8],
    config: &Config,
    base_path: &Path,
) -> Result<(Gltf, Buffers), Error> {
    let unvalidated = Gltf::from_slice(data)?;
    let gltf = validate_standard(unvalidated, &config)?;
    let has_bin = false;
    let mut buffers = Buffers(vec![]);
    for buffer in load_external_buffers(base_path, &gltf, has_bin)? {
        buffers.0.push(buffer);
    }
    Ok((gltf, buffers))
}

fn import_binary<'a>(
    data: &'a [u8],
    config: &Config,
    base_path: &Path,
) -> Result<(Gltf, Buffers), Error> {
    let gltf::Glb { header: _, json, bin } = gltf::Glb::from_slice(data)?;
    let unvalidated = Gltf::from_slice(json)?;
    let has_bin = bin.is_some();
    let gltf = validate_binary(unvalidated, &config, has_bin)?;
    let mut buffers = Buffers(vec![]);
    if let Some(buffer) = bin {
        buffers.0.push(buffer.to_vec());
    }
    for buffer in load_external_buffers(base_path, &gltf, has_bin)? {
        buffers.0.push(buffer);
    }
    Ok((gltf, buffers))
}

impl From<json::Error> for Error {
    fn from(err: json::Error) -> Error {
        Error::MalformedJson(err)
    }
}

impl From<std::io::Error> for Error {
    fn from(err: std::io::Error) -> Error {
        Error::Io(err)
    }
}

impl From<Vec<(json::Path, validation::Error)>> for Error {
    fn from(errs: Vec<(json::Path, validation::Error)>) -> Error {
        Error::Validation(errs)
    }
}

impl From<gltf::Error> for Error {
    fn from(err: gltf::Error) -> Error {
        match err {
            gltf::Error::Validation(errs) => Error::Validation(errs),
            _ => Error::Gltf(err),
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use std::error::Error;
        write!(f, "{}", self.description())
    }
}

impl StdError for Error {
    fn description(&self) -> &str {
        use self::Error::*;
        match *self {
            BufferLength(_) => "Loaded buffer does not match required length",
            ExtensionDisabled(_) => "Asset requires a disabled extension",
            ExtensionUnsupported(_) => "Assets requires an unsupported extension",
            IncompatibleVersion(_) => "Asset is not glTF version 2.0",
            Io(_) => "I/O error",
            Gltf(_) => "Error from gltf crate",
            MalformedJson(_) => "Malformed .gltf / .glb JSON",
            Validation(_) => "Asset failed validation tests",
        }
    }

    fn cause(&self) -> Option<&StdError> {
        use self::Error::*;
        match *self {
            MalformedJson(ref err) => Some(err),
            Io(ref err) => Some(err),
            _ => None,
        }
    }
}
<commit_msg>Update gltf-importer<commit_after>
// Copyright 2017 The gltf Library Developers
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! The reference loader implementation for the `gltf` crate.
//!
//! # Examples
//!
//! ### Importing some `glTF` 2.0
//!
//! ```rust
//! use gltf_importer::import;
//! # #[allow(unused_variables)]
//! let path = "path/to/asset.gltf";
//! # let path = "../examples/Box.gltf";
//! match import(path) {
//!     Ok((gltf, _)) => println!("{:#?}", gltf.as_json()),
//!     Err(err) => println!("error: {:?}", err),
//! }
//! ```

extern crate gltf;
extern crate gltf_utils;

use gltf::json::{self, validation};
use std::{fmt, fs, io};

use gltf::Gltf;
use gltf_utils::Source;
use std::error::Error as StdError;
use std::path::Path;

/// Contains parameters for import configuration.
pub mod config;

pub use self::config::Config;

/// Error encountered when importing a glTF 2.0 asset.
#[derive(Debug)]
pub enum Error {
    /// A loaded glTF buffer is not of the required length.
    BufferLength(json::Path),

    /// A glTF extension required by the asset has not been enabled by the user.
    ExtensionDisabled(String),

    /// A glTF extension required by the asset is not supported by the library.
    ExtensionUnsupported(String),

    /// The glTF version of the asset is incompatible with the importer.
    IncompatibleVersion(String),

    /// Standard I/O error.
    Io(std::io::Error),

    /// `gltf` crate error.
    Gltf(gltf::Error),

    /// Failure when deserializing .gltf or .glb JSON.
    MalformedJson(json::Error),

    /// The .gltf data is invalid.
    Validation(Vec<(json::Path, validation::Error)>),
}

/// Buffer data returned from `import`.
#[derive(Clone, Debug)]
pub struct Buffers(Vec<Vec<u8>>);

impl Source for Buffers {
    fn source_buffer(&self, buffer: &gltf::Buffer) -> &[u8] {
        &self.0[buffer.index()]
    }
}

impl Buffers {
    /// Obtain the contents of a loaded buffer.
    pub fn buffer(&self, buffer: &gltf::Buffer) -> Option<&[u8]> {
        self.0.get(buffer.index()).map(Vec::as_slice)
    }

    /// Obtain the contents of a loaded buffer view.
    pub fn view(&self, view: &gltf::buffer::View) -> Option<&[u8]> {
        self.buffer(&view.buffer())
            .map(|data| {
                let begin = view.offset();
                let end = view.length();
                &data[begin..end]
            })
    }

    /// Take the loaded buffer data.
    pub fn take(self) -> Vec<Vec<u8>> {
        self.0
    }
}

fn import_impl(path: &Path, config: Config) -> Result<(Gltf, Buffers), Error> {
    let data = read_to_end(path)?;
    if data.starts_with(b"glTF") {
        import_binary(&data, &config, path)
    } else {
        import_standard(&data, &config, path)
    }
}

/// Imports glTF 2.0
pub fn import<P>(path: P) -> Result<(Gltf, Buffers), Error>
    where P: AsRef<Path>
{
    import_impl(path.as_ref(), Default::default())
}

fn read_to_end_impl(path: &Path) -> Result<Vec<u8>, Error> {
    use io::Read;
    let file = fs::File::open(path)?;
    let mut reader = io::BufReader::new(file);
    let mut buffer = vec![];
    let _ = reader.read_to_end(&mut buffer)?;
    Ok(buffer)
}

fn read_to_end<P: AsRef<Path>>(path: P) -> Result<Vec<u8>, Error> {
    read_to_end_impl(path.as_ref())
}

fn load_external_buffers(
    base_path: &Path,
    gltf: &Gltf,
    has_bin: bool,
) -> Result<Vec<Vec<u8>>, Error> {
    let mut iter = gltf.as_json().buffers.iter().enumerate();
    if has_bin {
        let _ = iter.next();
    }
    iter
        .map(|(index, buffer)| {
            let uri = buffer.uri.as_ref().unwrap();
            let path = base_path.parent().unwrap_or(Path::new("./")).join(uri);
            let data = read_to_end(&path)?;
            if data.len() != buffer.byte_length as usize {
                let path = json::Path::new().field("buffers").index(index);
                return Err(Error::BufferLength(path));
            }
            Ok(data)
        })
        .collect()
}

fn validate_standard(
    unvalidated: gltf::Unvalidated,
    config: &Config,
) -> Result<Gltf, Error> {
    use config::ValidationStrategy;
    Ok(match config.validation_strategy {
        ValidationStrategy::Skip => unsafe { unvalidated.skip_validation() },
        ValidationStrategy::Minimal => unvalidated.validate_minimally()?,
        ValidationStrategy::Complete => unvalidated.validate_completely()?,
    })
}

fn validate_binary(
    unvalidated: gltf::Unvalidated,
    config: &Config,
    has_bin: bool,
) -> Result<Gltf, Error> {
    use config::ValidationStrategy;
    use json::validation::Error as Reason;

    if config.validation_strategy == ValidationStrategy::Skip {
        return Ok(unsafe { unvalidated.skip_validation() });
    }

    let mut errs = vec![];
    {
        let json = unvalidated.as_json();
        for (index, buffer) in json.buffers.iter().enumerate() {
            let path = || {
                json::Path::new()
                    .field("buffers")
                    .index(index)
                    .field("uri")
            };
            match index {
                0 if has_bin => if buffer.uri.is_some() {
                    errs.push((path(), Reason::Missing));
                },
                _ if buffer.uri.is_none() => {
                    errs.push((path(), Reason::Missing));
                },
                _ => {},
            }
        }
    }

    if errs.is_empty() {
        Ok(match config.validation_strategy {
            ValidationStrategy::Minimal => unvalidated.validate_minimally()?,
            ValidationStrategy::Complete => unvalidated.validate_completely()?,
            ValidationStrategy::Skip => unreachable!(),
        })
    } else {
        Err(Error::Validation(errs))
    }
}

fn import_standard<'a>(
    data: &'a [u8],
    config: &Config,
    base_path: &Path,
) -> Result<(Gltf, Buffers), Error> {
    let unvalidated = Gltf::from_slice(data)?;
    let gltf = validate_standard(unvalidated, &config)?;
    let has_bin = false;
    let mut buffers = Buffers(vec![]);
    for buffer in load_external_buffers(base_path, &gltf, has_bin)? {
        buffers.0.push(buffer);
    }
    Ok((gltf, buffers))
}

fn import_binary<'a>(
    data: &'a [u8],
    config: &Config,
    base_path: &Path,
) -> Result<(Gltf, Buffers), Error> {
    let gltf::Glb { header: _, json, bin } = gltf::Glb::from_slice(data)?;
    let unvalidated = Gltf::from_slice(json)?;
    let has_bin = bin.is_some();
    let gltf = validate_binary(unvalidated, &config, has_bin)?;
    let mut buffers = Buffers(vec![]);
    if let Some(buffer) = bin {
        buffers.0.push(buffer.to_vec());
    }
    for buffer in load_external_buffers(base_path, &gltf, has_bin)? {
        buffers.0.push(buffer);
    }
    Ok((gltf, buffers))
}

impl From<json::Error> for Error {
    fn from(err: json::Error) -> Error {
        Error::MalformedJson(err)
    }
}

impl From<std::io::Error> for Error {
    fn from(err: std::io::Error) -> Error {
        Error::Io(err)
    }
}

impl From<Vec<(json::Path, validation::Error)>> for Error {
    fn from(errs: Vec<(json::Path, validation::Error)>) -> Error {
        Error::Validation(errs)
    }
}

impl From<gltf::Error> for Error {
    fn from(err: gltf::Error) -> Error {
        match err {
            gltf::Error::Validation(errs) => Error::Validation(errs),
            _ => Error::Gltf(err),
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use std::error::Error;
        write!(f, "{}", self.description())
    }
}

impl StdError for Error {
    fn description(&self) -> &str {
        use self::Error::*;
        match *self {
            BufferLength(_) => "Loaded buffer does not match required length",
            ExtensionDisabled(_) => "Asset requires a disabled extension",
            ExtensionUnsupported(_) => "Assets requires an unsupported extension",
            IncompatibleVersion(_) => "Asset is not glTF version 2.0",
            Io(_) => "I/O error",
            Gltf(_) => "Error from gltf crate",
            MalformedJson(_) => "Malformed .gltf / .glb JSON",
            Validation(_) => "Asset failed validation tests",
        }
    }

    fn cause(&self) -> Option<&StdError> {
        use self::Error::*;
        match *self {
            MalformedJson(ref err) => Some(err),
            Io(ref err) => Some(err),
            _ => None,
        }
    }
}
<|endoftext|>


<commit_before>extern crate time;

use self::time::now_utc;
use std::rand::{OsRng, Rng};
use std::iter::Iterator;
use std::fmt;

pub mod session;
pub mod builder;

#[derive(Copy, Debug, PartialEq, Eq, Clone)]
#[unstable]
pub enum HTTPMethod {
    GET,
    POST,
    DELETE,
    PUT,
    HEAD
}

impl fmt::Display for HTTPMethod {
    fn fmt(&self, f : &mut fmt::Formatter) -> fmt::Result{
        let out = match *self {
            HTTPMethod::GET     => {"GET"},
            HTTPMethod::POST    => {"POST"},
            HTTPMethod::DELETE  => {"DELETE"},
            HTTPMethod::PUT     => {"PUT"},
            HTTPMethod::HEAD   => {"HEAD"}
        };
        write!(f, "{}", out)
    }
}

pub trait AuthorizationHeader {
    fn get_header(&self) -> String;
}

// TODO: add to crypto library?
// TODO: Should we have a longer nonce than 10?
fn generate_nonce() -> String {
    OsRng::new().unwrap()
                .gen_ascii_chars()
                .take(10)
                .collect()
}

fn generate_timestamp() -> String{
    now_utc().to_timespec().sec.to_string()
}

pub trait BaseString {
    // Returns a base string URI, ecnoded with [RFC3986]. This gets used to
    // generate the `oauth_signature`. It takes a different path dependent
    // on the signature type
    fn get_base_string(&self, method: HTTPMethod, base_url: &str, data: Vec<(&str, &str)>) -> String {
        // format!("{}&{}&{}", method,
        //         utf8_percent_encode(base_url.as_slice(), FORM_URLENCODED_ENCODE_SET),
        //         utf8_percent_encode(self.get_base_parameters(data).as_slice(), FORM_URLENCODED_ENCODE_SET))
        String::new()
    }

    fn get_self_paramaters(&self) -> Vec<String>;

    fn get_base_parameters(&self, mut data: Vec<(&str, &str)>) -> String {
        let to_pair = |&: (key, value) : (&str, &str) | -> String {
            format!("{}={}", key, value)};

        let mut params = self.get_self_paramaters();
        params.append(&mut (data.into_iter().map(to_pair).collect::<Vec<String>>()));
        params.sort();
        concat(params.as_slice(), "&")
    }
}

/// Concatenate all values in `data`, seperated by `sep`
///
/// ```
/// use rust_oauth::oauth1::client::concat;
/// let values = vec!["cat".to_string(), "dog".to_string(), "bird".to_string()];
/// let concatenated = concat(values.as_slice(), " ");
/// assert_eq!(concatenated, "cat dog bird".to_string());
/// ```
pub fn concat(data: &[String], sep : &str) -> String {
    match data {
        []              => String::new(),
        [ref param]         => format!("{}", param),
        [ref param, rest..]  => format!("{}{}{}", param, sep, concat(rest, sep))
    }
}

#[cfg(test)]
mod test {
    use super::{concat, generate_nonce};

    #[test]
    fn concat_test_multiple_items() {
        let data = vec!["alpha".to_string(), "beta".to_string(), "gamma".to_string()];
        assert_eq!(concat(data.as_slice(), "&"), "alpha&beta&gamma".to_string())
    }

    #[test]
    fn concat_test_empty_vec() {
        let data = vec![];
        assert_eq!(concat(data.as_slice(), "&"), String::new())
    }

    #[test]
    fn concat_test_single() {
        let data = vec!["alpha".to_string()];
        assert_eq!(concat(data.as_slice(), "&"), "alpha".to_string())
    }

    #[test]
    fn generate_nonce_unique(){
        let mut nonces = Vec::new();
        for _ in 0..1000 {
            nonces.push(generate_nonce())
        }
        let len = nonces.len();
        nonces.dedup();
        assert_eq!(len, nonces.len());
    }
}<commit_msg>Compiles now<commit_after>extern crate time;

use self::time::now_utc;
use std::rand::{OsRng, Rng};
use std::iter::Iterator;
use std::fmt;

pub mod session;
pub mod builder;

#[derive(Copy, Debug, PartialEq, Eq, Clone)]
#[unstable]
pub enum HTTPMethod {
    GET,
    POST,
    DELETE,
    PUT,
    HEAD
}

impl fmt::Display for HTTPMethod {
    fn fmt(&self, f : &mut fmt::Formatter) -> fmt::Result{
        let out = match *self {
            HTTPMethod::GET     => {"GET"},
            HTTPMethod::POST    => {"POST"},
            HTTPMethod::DELETE  => {"DELETE"},
            HTTPMethod::PUT     => {"PUT"},
            HTTPMethod::HEAD   => {"HEAD"}
        };
        write!(f, "{}", out)
    }
}

pub trait AuthorizationHeader {
    fn get_header(&self) -> String;
}

// TODO: add to crypto library?
// TODO: Should we have a longer nonce than 10?
fn generate_nonce() -> String {
    OsRng::new().unwrap()
                .gen_ascii_chars()
                .take(10)
                .collect()
}

fn generate_timestamp() -> String{
    now_utc().to_timespec().sec.to_string()
}

pub trait BaseString {
    // Returns a base string URI, ecnoded with [RFC3986]. This gets used to
    // generate the `oauth_signature`. It takes a different path dependent
    // on the signature type
    fn get_base_string(&self, method: HTTPMethod, base_url: &str, data: Vec<(&str, &str)>) -> String {
        // format!("{}&{}&{}", method,
        //         utf8_percent_encode(base_url.as_slice(), FORM_URLENCODED_ENCODE_SET),
        //         utf8_percent_encode(self.get_base_parameters(data).as_slice(), FORM_URLENCODED_ENCODE_SET))
        String::new()
    }

    fn get_self_paramaters(&self) -> Vec<String>;

    fn get_base_parameters(&self, data: Vec<(&str, &str)>) -> String {
        let to_pair = |&: (key, value) : (&str, &str) | -> String {
            format!("{}={}", key, value)};

        let mut params = self.get_self_paramaters();
        params.append(&mut (data.into_iter().map(to_pair).collect::<Vec<String>>()));
        params.sort();
        concat(params.as_slice(), "&")
    }
}

/// Concatenate all values in `data`, seperated by `sep`
///
/// ```
/// use rust_oauth::oauth1::client::concat;
/// let values = vec!["cat".to_string(), "dog".to_string(), "bird".to_string()];
/// let concatenated = concat(values.as_slice(), " ");
/// assert_eq!(concatenated, "cat dog bird".to_string());
/// ```
pub fn concat(data: &[String], sep : &str) -> String {
    match data {
        []              => String::new(),
        [ref param]         => format!("{}", param),
        [ref param, rest..]  => format!("{}{}{}", param, sep, concat(rest, sep))
    }
}

#[cfg(test)]
mod test {
    use super::{concat, generate_nonce};

    #[test]
    fn concat_test_multiple_items() {
        let data = vec!["alpha".to_string(), "beta".to_string(), "gamma".to_string()];
        assert_eq!(concat(data.as_slice(), "&"), "alpha&beta&gamma".to_string())
    }

    #[test]
    fn concat_test_empty_vec() {
        let data = vec![];
        assert_eq!(concat(data.as_slice(), "&"), String::new())
    }

    #[test]
    fn concat_test_single() {
        let data = vec!["alpha".to_string()];
        assert_eq!(concat(data.as_slice(), "&"), "alpha".to_string())
    }

    #[test]
    fn generate_nonce_unique(){
        let mut nonces = Vec::new();
        for _ in 0..1000 {
            nonces.push(generate_nonce())
        }
        let len = nonces.len();
        nonces.dedup();
        assert_eq!(len, nonces.len());
    }
}<|endoftext|>


<commit_before>#![recursion_limit = "256"]
// http://stackoverflow.com/questions/25877285/how-to-disable-unused-code-warnings-in-rust
// https://users.rust-lang.org/t/turning-off-compiler-warning-messages/4975/2
#![allow(non_snake_case)]

mod api;
mod files;

use serde_derive::Serialize;

use dotenv::dotenv;
use std::env;

use actix_web::{guard, middleware, web, App, Error as ActixError, HttpResponse, HttpServer};

use typed_html::elements::FlowContent;
use typed_html::types::Metadata;
use typed_html::{dom::DOMTree, html, text, OutputType};

use tinytemplate::TinyTemplate;

use deadpool_postgres::{Manager, ManagerConfig, Pool, RecyclingMethod};
use tokio_postgres::NoTls;

use std::path::Path;

static TEMPLATE: &str = "Hello {name}!";
#[derive(Serialize)]
struct Context {
    name: String,
}

#[derive(Serialize, Debug)]
struct TemplateContext {
    parent: String,
    name: String,
    content: String,
    items: Vec<String>,
}

use std::path;
use std::process::Command;

async fn template(ssr: web::Path<bool>) -> Result<HttpResponse, ActixError> {
    let s2 = getStr();
    let s: String = if *ssr { s2.to_owned() } else { "".to_owned() };
    let context = TemplateContext {
        parent: "index".to_owned(),
        name: "Roman".to_owned(),
        content: s,
        items: vec!["One", "Two", "Three"]
            .iter()
            .map(|s| s.to_string())
            .collect(),
    };

    let doc: DOMTree<String> = doc(
        html!(

"Hello Kitty"

                            "She is not a cat. She is a human girl."

                        { (0..3).map(|_| html!(

                                "Her name is Kitty White."

                        )) }

                            "We still don't know how she eats."

"Hello Kitty"

                        "DYNAMIC_CONTENT"

                ),
        context,
    );
    let mut doc_str = "<!doctype html>".to_owned() + &doc.to_string();

    // https://users.rust-lang.org/t/how-to-create-a-macro-from-dynamic-content/5079
    doc_str = doc_str.replacen("DYNAMIC_CONTENT", &s2, 1);

    Ok(HttpResponse::Ok().content_type("text/html").body(doc_str))
}

fn doc<T: OutputType + 'static + Send>(
    tree: Box<dyn FlowContent<T>>,
    context: TemplateContext,
) -> DOMTree<T> {
    let TemplateContext { name, items, .. } = context;
    html!(

                <title>"Hello Kitty"</title>
                <meta charset="utf-8" />
                <meta name="viewport" content="width=device-width, initial-scale=1.0" />
                <link rel="icon" href="/favicon.ico?v=1" />
                <link href="/styles/normalize.css" rel="stylesheet" type="text/css" media="all" />
                <link href="/styles/style.css" rel="stylesheet" type="text/css" media="all" />
                <base href="/"></base>
                <meta name=Metadata::Author content="Not Sanrio Co., Ltd" />

{text!("{}", name)}

"Here are your items:"

                    {
                     items.iter().map(|item| html!(

{text!("{}", item)}

                     ))
                    }

                 { tree }

"Try going to ""/hello/YourName"

    )
}

use std::fs;
use std::fs::OpenOptions;
use std::io::prelude::*;

fn getStr() -> String {
    fs::copy(
        "../client/src/elm/Main.elm",
        "../client/src/elm/Main.elm.bak",
    )
    .expect("file not copied");
    let stdout: Vec<u8>;
    {
        let mut main_file = OpenOptions::new()
            .append(true)
            .open("../client/src/elm/Main.elm")
            .expect("file not opened");

        let str1 = format!(
            r#"
            view : Html Msg
            view =
                viewWithModel <|
                    Model [] HomeRoute <|
                        Flags "" {time} ""
            "#,
            time = time::OffsetDateTime::unix_epoch().second()
        );

        main_file
            .write_all(str1.as_bytes())
            .expect("file content not saved");

        stdout = Command::new("node")
            .current_dir(&path::Path::new("../client"))
            .arg("./node_modules/elm-static-html/index.js")
            .arg("-f")
            .arg("src/elm/Main.elm")
            .output()
            .expect("elm-static-html command failed to start")
            .stdout;
    }
    fs::rename(
        "../client/src/elm/Main.elm.bak",
        "../client/src/elm/Main.elm",
    )
    .expect("file not renamed");

    String::from_utf8_lossy(&*stdout)
        .lines()
        .skip_while(|x| x.find(r#"id="content""#).is_none())
        .collect()
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Error: NotPresent
    // Chaning to specific file, bc `strace ./target/debug/rustommerce` shown,
    // that dotenv searches up for .env till it reaches /
    // https://galenguyer.com/blog/2020/05/19/docker-rust-notpresent
    let my_path = env::current_dir().and_then(|a| Ok(a.join("../.env")))?;
    dotenv::from_path(my_path.as_path());

    env::set_var("RUST_LOG", "actix_web=info");
    env_logger::init();
    let endpoint = format!("127.0.0.1:{}", env::var("SERVER_PORT")?);

    let connection_string = format!(
        "postgres://{}:{}@{}:{}/{}",
        &env::var("DBUSER")?,
        &env::var("DBPASS")?,
        &env::var("DBHOST")?,
        &env::var("DBPORT")?,
        &env::var("DBNAME")?,
    );
    // Idea for parse from
    // https://github.com/OneSignal/L3-37/blob/2031639d1f3a7aa00a5a741b87b464e19d995d21/l337-postgres/src/lib.rs#L217
    let pg_config: tokio_postgres::Config = connection_string.parse()?;
    let mgr_config = ManagerConfig {
        recycling_method: RecyclingMethod::Fast,
    };
    let mgr = Manager::from_config(pg_config, NoTls, mgr_config);
    let pool = Pool::new(mgr, 16);

    let mut tt = TinyTemplate::new();
    tt.add_template("hello", TEMPLATE)?;

    let context = Context {
        name: "TinyTemplate".to_string(),
    };

    let rendered = tt.render("hello", &context)?;
    println!("{}", rendered);

    let sys = actix_rt::System::new("app");
    HttpServer::new(move || {
        App::new()
            .data(pool.clone())
            .wrap(middleware::Logger::default())
            .service(web::resource("/template/{ssr}").route(web::get().to(template)))
            .service(web::resource("/userInformation").route(web::get().to(api::user_information)))
            .service(web::resource("/dataSets").route(web::get().to(api::data_sets)))
            .service(web::resource("/dataSets/{url}").route(web::get().to(api::data_set)))
            .service(
                web::resource("/dataSetsCategories")
                    .route(web::get().to(api::data_sets_categories)),
            )
            .service(
                web::resource("/dataSetsCategories/{url}")
                    .route(web::get().to(api::data_set_category)),
            )
            .service(web::resource("/favicon").route(web::get().to(files::favicon)))
            .service(web::resource("/styles/{file:.*}").route(web::get().to(files::styles)))
            .service(web::resource("/js/{file:.*}").route(web::get().to(files::js)))
            .default_service(
                // 404 for GET request
                web::resource("")
                    .route(web::get().to(files::index))
                    // all requests that are not `GET`
                    .route(
                        web::route()
                            .guard(guard::Not(guard::Get()))
                            .to(HttpResponse::MethodNotAllowed),
                    ),
            )
    })
    .bind(&endpoint)?
    .run();
    // If you do not return `Ok(())` but just `.run()`, this will happend:
    // When main returns `Result<(), Box<dyn std::error::Error + 'static + Send + Sync>> instead of
    // `io::Result<()>`, there is error and `into()` is needed.
    // expected struct `std::boxed::Box`, found struct `std::io::Error`
    // https://users.rust-lang.org/t/boxing-errors-in-result-throws-type-mismatch/36692/2
    //.map_err(|e| e.into())

    println!("Server running at {:?}", endpoint);
    let _ = sys.run();

    Ok(())
}
<commit_msg>server: and_then replace with map<commit_after>#![recursion_limit = "256"]
// http://stackoverflow.com/questions/25877285/how-to-disable-unused-code-warnings-in-rust
// https://users.rust-lang.org/t/turning-off-compiler-warning-messages/4975/2
#![allow(non_snake_case)]

mod api;
mod files;

use serde_derive::Serialize;

use dotenv::dotenv;
use std::env;

use actix_web::{guard, middleware, web, App, Error as ActixError, HttpResponse, HttpServer};

use typed_html::elements::FlowContent;
use typed_html::types::Metadata;
use typed_html::{dom::DOMTree, html, text, OutputType};

use tinytemplate::TinyTemplate;

use deadpool_postgres::{Manager, ManagerConfig, Pool, RecyclingMethod};
use tokio_postgres::NoTls;

use std::path::Path;

static TEMPLATE: &str = "Hello {name}!";
#[derive(Serialize)]
struct Context {
    name: String,
}

#[derive(Serialize, Debug)]
struct TemplateContext {
    parent: String,
    name: String,
    content: String,
    items: Vec<String>,
}

use std::path;
use std::process::Command;

async fn template(ssr: web::Path<bool>) -> Result<HttpResponse, ActixError> {
    let s2 = getStr();
    let s: String = if *ssr { s2.to_owned() } else { "".to_owned() };
    let context = TemplateContext {
        parent: "index".to_owned(),
        name: "Roman".to_owned(),
        content: s,
        items: vec!["One", "Two", "Three"]
            .iter()
            .map(|s| s.to_string())
            .collect(),
    };

    let doc: DOMTree<String> = doc(
        html!(

"Hello Kitty"

                            "She is not a cat. She is a human girl."

                        { (0..3).map(|_| html!(

                                "Her name is Kitty White."

                        )) }

                            "We still don't know how she eats."

"Hello Kitty"

                        "DYNAMIC_CONTENT"

                ),
        context,
    );
    let mut doc_str = "<!doctype html>".to_owned() + &doc.to_string();

    // https://users.rust-lang.org/t/how-to-create-a-macro-from-dynamic-content/5079
    doc_str = doc_str.replacen("DYNAMIC_CONTENT", &s2, 1);

    Ok(HttpResponse::Ok().content_type("text/html").body(doc_str))
}

fn doc<T: OutputType + 'static + Send>(
    tree: Box<dyn FlowContent<T>>,
    context: TemplateContext,
) -> DOMTree<T> {
    let TemplateContext { name, items, .. } = context;
    html!(

                <title>"Hello Kitty"</title>
                <meta charset="utf-8" />
                <meta name="viewport" content="width=device-width, initial-scale=1.0" />
                <link rel="icon" href="/favicon.ico?v=1" />
                <link href="/styles/normalize.css" rel="stylesheet" type="text/css" media="all" />
                <link href="/styles/style.css" rel="stylesheet" type="text/css" media="all" />
                <base href="/"></base>
                <meta name=Metadata::Author content="Not Sanrio Co., Ltd" />

{text!("{}", name)}

"Here are your items:"

                    {
                     items.iter().map(|item| html!(

{text!("{}", item)}

                     ))
                    }

                 { tree }

"Try going to ""/hello/YourName"

    )
}

use std::fs;
use std::fs::OpenOptions;
use std::io::prelude::*;

fn getStr() -> String {
    fs::copy(
        "../client/src/elm/Main.elm",
        "../client/src/elm/Main.elm.bak",
    )
    .expect("file not copied");
    let stdout: Vec<u8>;
    {
        let mut main_file = OpenOptions::new()
            .append(true)
            .open("../client/src/elm/Main.elm")
            .expect("file not opened");

        let str1 = format!(
            r#"
            view : Html Msg
            view =
                viewWithModel <|
                    Model [] HomeRoute <|
                        Flags "" {time} ""
            "#,
            time = time::OffsetDateTime::unix_epoch().second()
        );

        main_file
            .write_all(str1.as_bytes())
            .expect("file content not saved");

        stdout = Command::new("node")
            .current_dir(&path::Path::new("../client"))
            .arg("./node_modules/elm-static-html/index.js")
            .arg("-f")
            .arg("src/elm/Main.elm")
            .output()
            .expect("elm-static-html command failed to start")
            .stdout;
    }
    fs::rename(
        "../client/src/elm/Main.elm.bak",
        "../client/src/elm/Main.elm",
    )
    .expect("file not renamed");

    String::from_utf8_lossy(&*stdout)
        .lines()
        .skip_while(|x| x.find(r#"id="content""#).is_none())
        .collect()
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Error: NotPresent
    // Chaning to specific file, bc `strace ./target/debug/rustommerce` shown,
    // that dotenv searches up for .env till it reaches /
    // https://galenguyer.com/blog/2020/05/19/docker-rust-notpresent
    let my_path = env::current_dir().map(|a| a.join("../.env"))?;
    dotenv::from_path(my_path.as_path());

    env::set_var("RUST_LOG", "actix_web=info");
    env_logger::init();
    let endpoint = format!("127.0.0.1:{}", env::var("SERVER_PORT")?);

    let connection_string = format!(
        "postgres://{}:{}@{}:{}/{}",
        &env::var("DBUSER")?,
        &env::var("DBPASS")?,
        &env::var("DBHOST")?,
        &env::var("DBPORT")?,
        &env::var("DBNAME")?,
    );
    // Idea for parse from
    // https://github.com/OneSignal/L3-37/blob/2031639d1f3a7aa00a5a741b87b464e19d995d21/l337-postgres/src/lib.rs#L217
    let pg_config: tokio_postgres::Config = connection_string.parse()?;
    let mgr_config = ManagerConfig {
        recycling_method: RecyclingMethod::Fast,
    };
    let mgr = Manager::from_config(pg_config, NoTls, mgr_config);
    let pool = Pool::new(mgr, 16);

    let mut tt = TinyTemplate::new();
    tt.add_template("hello", TEMPLATE)?;

    let context = Context {
        name: "TinyTemplate".to_string(),
    };

    let rendered = tt.render("hello", &context)?;
    println!("{}", rendered);

    let sys = actix_rt::System::new("app");
    HttpServer::new(move || {
        App::new()
            .data(pool.clone())
            .wrap(middleware::Logger::default())
            .service(web::resource("/template/{ssr}").route(web::get().to(template)))
            .service(web::resource("/userInformation").route(web::get().to(api::user_information)))
            .service(web::resource("/dataSets").route(web::get().to(api::data_sets)))
            .service(web::resource("/dataSets/{url}").route(web::get().to(api::data_set)))
            .service(
                web::resource("/dataSetsCategories")
                    .route(web::get().to(api::data_sets_categories)),
            )
            .service(
                web::resource("/dataSetsCategories/{url}")
                    .route(web::get().to(api::data_set_category)),
            )
            .service(web::resource("/favicon").route(web::get().to(files::favicon)))
            .service(web::resource("/styles/{file:.*}").route(web::get().to(files::styles)))
            .service(web::resource("/js/{file:.*}").route(web::get().to(files::js)))
            .default_service(
                // 404 for GET request
                web::resource("")
                    .route(web::get().to(files::index))
                    // all requests that are not `GET`
                    .route(
                        web::route()
                            .guard(guard::Not(guard::Get()))
                            .to(HttpResponse::MethodNotAllowed),
                    ),
            )
    })
    .bind(&endpoint)?
    .run();
    // If you do not return `Ok(())` but just `.run()`, this will happend:
    // When main returns `Result<(), Box<dyn std::error::Error + 'static + Send + Sync>> instead of
    // `io::Result<()>`, there is error and `into()` is needed.
    // expected struct `std::boxed::Box`, found struct `std::io::Error`
    // https://users.rust-lang.org/t/boxing-errors-in-result-throws-type-mismatch/36692/2
    //.map_err(|e| e.into())

    println!("Server running at {:?}", endpoint);
    let _ = sys.run();

    Ok(())
}
<|endoftext|>


<commit_before>/* Cymbalum, Molecular Simulation in Rust - Copyright (C) 2015 Guillaume Fraux
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/
 */

//! Potentials computation.
//!
//! A potential computation is a way of computing a potential given its
//! expression (represented by a `PotentialFunction`). The same potential can be
//! computed either direcly, or using a cutoff, or by a table interpolation, ...

use super::{PotentialFunction, PairPotential, AnglePotential, DihedralPotential};

/// A `Computation` is a way to compute a potential.
pub trait Computation: Sized {
    /// Kind of potential we can apply this computation to
    type Potential: ?Sized + PotentialFunction;

    /// Compute the energy value at `r`
    fn compute_energy(&self, r: f64) -> f64;

    /// Compute the force value at `r`
    fn compute_force(&self, r: f64) -> f64;
}

impl<P: Computation> PotentialFunction for P {
    #[inline] fn energy(&self, r:f64) -> f64 {
        self.compute_energy(r)
    }

    #[inline] fn force(&self, r:f64) -> f64 {
        self.compute_energy(r)
    }
}

// FIXME: use the custom implementation of virial here if any
impl<P> PairPotential for P where P: Computation<Potential = PairPotential>{}
impl<P> AnglePotential for P where P: Computation<Potential = AnglePotential>{}
impl<P> DihedralPotential for P where P: Computation<Potential = DihedralPotential>{}

/******************************************************************************/
/// Direct computation of the potential with a cutoff applied. The compute_energy is
/// shifted to ensure `E(rc) = 0`, where `rc` is the cutoff distance.
pub struct CutoffComputation {
    /// Potential to compute
    potential: Box<PairPotential>,
    /// Cutoff distance
    cutoff: f64,
    /// Energy at cutoff
    delta: f64,
}

impl CutoffComputation {
    /// Create a new `CutoffComputation` for `potential`, with cutoff distance
    /// of `cutoff`.
    pub fn new(potential: Box<PairPotential>, cutoff: f64) -> CutoffComputation {
        let delta = potential.energy(cutoff);
        CutoffComputation{potential: potential, cutoff: cutoff, delta: delta}
    }
}

impl Computation for CutoffComputation {
    type Potential = PairPotential;
    #[inline]
    fn compute_energy(&self, r: f64) -> f64 {
        if r > self.cutoff {
            return 0.0;
        } else {
            return self.potential.energy(r) - self.delta;
        }
    }

    #[inline]
    fn compute_force(&self, r: f64) -> f64 {
        if r > self.cutoff {
            return 0.0;
        } else {
            return self.potential.force(r);
        }
    }
}

/******************************************************************************/
/// Computation of a potential using tabulated values. This can be faster than
/// direct computation for smooth potentials, but either uses more memory or is
/// less precise than direct computation. Values are tabulated in the `[0, max)`
/// range, and a cutoff is applied after `max`. Energy is shifted before the
/// cutoff to ensure that `E(max) = 0`
pub struct TableComputation {
    // TODO: use genericity over static values here if it ever comes out
    // see https://internals.rust-lang.org/t/pre-rfc-genericity-over-static-values/1538/19

    /// Number of tabulated values
    N: usize,
    /// Step for tabulated value. compute_energy[i]/compute_force[i] contains compute_energy/compute_force at
    /// r = i * delta
    delta: f64,
    /// Tabulated potential
    compute_energy: Vec<f64>,
    /// Tabulated compute_force
    compute_force: Vec<f64>,
}

impl TableComputation {
    /// Create a new `TableComputation` for `potential`, with `N` points and a
    /// maximum value of `max`.
    pub fn new(potential: Box<PairPotential>, N: usize, max:f64) -> TableComputation {
        let delta = max/(N as f64);
        let dE = potential.energy(max);
        let mut compute_energy = Vec::with_capacity(N);
        let mut compute_force = Vec::with_capacity(N);
        for i in 0..N {
            let pos = i as f64 * delta;
            compute_energy.push(potential.energy(pos) - dE);
            compute_force.push(potential.force(pos));
        }
        TableComputation{N: N, delta: delta, compute_energy: compute_energy, compute_force: compute_force}
    }
}

impl Computation for TableComputation {
    type Potential = PairPotential;
    fn compute_energy(&self, r: f64) -> f64 {
        let bin = f64::floor(r / self.delta) as usize;
        if bin < self.N - 1 {
            let dx = r - (bin as f64)*self.delta;
            let slope = (self.compute_energy[bin + 1] - self.compute_energy[bin])/self.delta;
            return self.compute_energy[bin] + dx*slope;
        } else {
            return 0.0;
        }
    }

    fn compute_force(&self, r: f64) -> f64 {
        let bin = f64::floor(r / self.delta) as usize;
        if bin < self.N - 1 {
            let dx = r - (bin as f64)*self.delta;
            let slope = (self.compute_force[bin + 1] - self.compute_force[bin])/self.delta;
            return self.compute_force[bin] + dx*slope;
        } else {
            return 0.0;
        }
    }
}

/******************************************************************************/

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

    #[test]
    fn cutoff() {
        let cutoff = CutoffComputation::new(Box::new(Harmonic{k: 50.0, x0: 2.0}), 4.0);

        assert_eq!(cutoff.compute_force(2.5), -25.0);
        assert_eq!(cutoff.compute_energy(2.5), -93.75);

        assert_eq!(cutoff.compute_force(4.1), 0.0);
        assert_eq!(cutoff.compute_energy(4.1), 0.0);
    }

    #[test]
    fn table() {
        let table = TableComputation::new(Box::new(Harmonic{k: 50.0, x0: 2.0}), 1000, 4.0);

        assert_eq!(table.compute_force(2.5), -25.0);
        assert_eq!(table.compute_energy(2.5), -93.75);

        assert_eq!(table.compute_force(4.1), 0.0);
        assert_eq!(table.compute_energy(4.1), 0.0);
    }
}
<commit_msg>Fix a typo<commit_after>/* Cymbalum, Molecular Simulation in Rust - Copyright (C) 2015 Guillaume Fraux
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/
 */

//! Potentials computation.
//!
//! A potential computation is a way of computing a potential given its
//! expression (represented by a `PotentialFunction`). The same potential can be
//! computed either direcly, or using a cutoff, or by a table interpolation, ...

use super::{PotentialFunction, PairPotential, AnglePotential, DihedralPotential};

/// A `Computation` is a way to compute a potential.
pub trait Computation: Sized {
    /// Kind of potential we can apply this computation to
    type Potential: ?Sized + PotentialFunction;

    /// Compute the energy value at `r`
    fn compute_energy(&self, r: f64) -> f64;

    /// Compute the force value at `r`
    fn compute_force(&self, r: f64) -> f64;
}

impl<P: Computation> PotentialFunction for P {
    #[inline] fn energy(&self, r:f64) -> f64 {
        self.compute_energy(r)
    }

    #[inline] fn force(&self, r:f64) -> f64 {
        self.compute_force(r)
    }
}

// FIXME: use the custom implementation of virial here if any
impl<P> PairPotential for P where P: Computation<Potential = PairPotential>{}
impl<P> AnglePotential for P where P: Computation<Potential = AnglePotential>{}
impl<P> DihedralPotential for P where P: Computation<Potential = DihedralPotential>{}

/******************************************************************************/
/// Direct computation of the potential with a cutoff applied. The compute_energy is
/// shifted to ensure `E(rc) = 0`, where `rc` is the cutoff distance.
pub struct CutoffComputation {
    /// Potential to compute
    potential: Box<PairPotential>,
    /// Cutoff distance
    cutoff: f64,
    /// Energy at cutoff
    delta: f64,
}

impl CutoffComputation {
    /// Create a new `CutoffComputation` for `potential`, with cutoff distance
    /// of `cutoff`.
    pub fn new(potential: Box<PairPotential>, cutoff: f64) -> CutoffComputation {
        let delta = potential.energy(cutoff);
        CutoffComputation{potential: potential, cutoff: cutoff, delta: delta}
    }
}

impl Computation for CutoffComputation {
    type Potential = PairPotential;
    #[inline]
    fn compute_energy(&self, r: f64) -> f64 {
        if r > self.cutoff {
            return 0.0;
        } else {
            return self.potential.energy(r) - self.delta;
        }
    }

    #[inline]
    fn compute_force(&self, r: f64) -> f64 {
        if r > self.cutoff {
            return 0.0;
        } else {
            return self.potential.force(r);
        }
    }
}

/******************************************************************************/
/// Computation of a potential using tabulated values. This can be faster than
/// direct computation for smooth potentials, but either uses more memory or is
/// less precise than direct computation. Values are tabulated in the `[0, max)`
/// range, and a cutoff is applied after `max`. Energy is shifted before the
/// cutoff to ensure that `E(max) = 0`
pub struct TableComputation {
    // TODO: use genericity over static values here if it ever comes out
    // see https://internals.rust-lang.org/t/pre-rfc-genericity-over-static-values/1538/19

    /// Number of tabulated values
    N: usize,
    /// Step for tabulated value. compute_energy[i]/compute_force[i] contains compute_energy/compute_force at
    /// r = i * delta
    delta: f64,
    /// Tabulated potential
    compute_energy: Vec<f64>,
    /// Tabulated compute_force
    compute_force: Vec<f64>,
}

impl TableComputation {
    /// Create a new `TableComputation` for `potential`, with `N` points and a
    /// maximum value of `max`.
    pub fn new(potential: Box<PairPotential>, N: usize, max:f64) -> TableComputation {
        let delta = max/(N as f64);
        let dE = potential.energy(max);
        let mut compute_energy = Vec::with_capacity(N);
        let mut compute_force = Vec::with_capacity(N);
        for i in 0..N {
            let pos = i as f64 * delta;
            compute_energy.push(potential.energy(pos) - dE);
            compute_force.push(potential.force(pos));
        }
        TableComputation{N: N, delta: delta, compute_energy: compute_energy, compute_force: compute_force}
    }
}

impl Computation for TableComputation {
    type Potential = PairPotential;
    fn compute_energy(&self, r: f64) -> f64 {
        let bin = f64::floor(r / self.delta) as usize;
        if bin < self.N - 1 {
            let dx = r - (bin as f64)*self.delta;
            let slope = (self.compute_energy[bin + 1] - self.compute_energy[bin])/self.delta;
            return self.compute_energy[bin] + dx*slope;
        } else {
            return 0.0;
        }
    }

    fn compute_force(&self, r: f64) -> f64 {
        let bin = f64::floor(r / self.delta) as usize;
        if bin < self.N - 1 {
            let dx = r - (bin as f64)*self.delta;
            let slope = (self.compute_force[bin + 1] - self.compute_force[bin])/self.delta;
            return self.compute_force[bin] + dx*slope;
        } else {
            return 0.0;
        }
    }
}

/******************************************************************************/

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

    #[test]
    fn cutoff() {
        let cutoff = CutoffComputation::new(Box::new(Harmonic{k: 50.0, x0: 2.0}), 4.0);

        assert_eq!(cutoff.compute_force(2.5), -25.0);
        assert_eq!(cutoff.compute_energy(2.5), -93.75);

        assert_eq!(cutoff.compute_force(4.1), 0.0);
        assert_eq!(cutoff.compute_energy(4.1), 0.0);
    }

    #[test]
    fn table() {
        let table = TableComputation::new(Box::new(Harmonic{k: 50.0, x0: 2.0}), 1000, 4.0);

        assert_eq!(table.compute_force(2.5), -25.0);
        assert_eq!(table.compute_energy(2.5), -93.75);

        assert_eq!(table.compute_force(4.1), 0.0);
        assert_eq!(table.compute_energy(4.1), 0.0);
    }
}
<|endoftext|>


<commit_before>use std::collections::BTreeSet;
use super::Nearness;
use {RowId, ColumnId, Column, Accessor, MomentaryAccessor, Mutator, ColumnType, EventType,
     PredictorType};
use std::marker::PhantomData;
use serde::Serialize;

#[derive (Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
pub struct Bounds {
  pub min: [i64; 2],
  pub max: [i64; 2],
}

pub trait Basics: super::Basics {
  fn get_bounds<A: MomentaryAccessor<Basics = <Self as super::Basics>::StewardBasics>>(accessor: &A,
                                                                              who: RowId, detector: <Self as super::Basics>::DetectorId)
                                                                              -> Bounds;
  fn when_escapes <A: Accessor <Basics = <Self as super::Basics>::StewardBasics>>(accessor: & A, who: RowId, bounds: Bounds, detector: <Self as super::Basics>::DetectorId)->Option <<<Self as super::Basics>::StewardBasics as ::Basics>::Time>;
}

struct Cell<B: Basics> {
  _marker: PhantomData<B>,
}
impl<B: Basics> Column for Cell<B> {
  type FieldType = BTreeSet <RowId>;
  fn column_id() -> ColumnId {
    ColumnId(B::nearness_column_id().0 ^ 0x030fc8868af34f2c)
  }
}

// Work around private-in-public rules so that our TimeSteward types
// (which are implementation details) are not visible elsewhere.
mod hack {
  use super::*;
  use {RowId, PredictorId, EventId};
  #[derive (Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
  pub struct Member<B: Basics> {
    pub row: RowId,
    pub bounds: Bounds,
    pub detector: B::DetectorId,
  }
  time_steward_event! (
    pub struct BoundsChange <[B: Basics]> {id: RowId, member: Member <B>},
    <B as super::super::Basics>::StewardBasics,
    EventId (<B as super::super::Basics>::nearness_column_id().0 ^ 0x1be13fa07975f552),
    | &self, mutator | {
  // TODO: optimize remove-then-insert
      remove::<B,_> (mutator, self.member.row, self.member.detector, self.id, self.member.bounds);
      insert::<B,_> (mutator, self.member.row, self.member.detector);
    }
  );
  time_steward_predictor! {
    pub struct BoundsChangePredictor <[B: Basics]>,
    <B as super::super::Basics>::StewardBasics,
    PredictorId (<B as super::super::Basics>::nearness_column_id().0 ^ 0x3686689daa651bf3),
    watching Member<B>,
    | accessor, id | {
      let member;
      {
        let member_reference = accessor.get::<Member <B>> (id).expect ("row is missing the field the predictor triggered on");
        member = (*member_reference).clone();
      }
      if let Some (time) = B::when_escapes (accessor, member.row, member.bounds, member.detector) {
        accessor.predict_at_time (time, BoundsChange::new (id, member));
      }
    }
  }
}
use self::hack::{Member, BoundsChange, BoundsChangePredictor};

impl<B: Basics> Column for Member<B> {
  type FieldType = Member<B>;
  fn column_id() -> ColumnId {
    ColumnId(B::nearness_column_id().0 ^ 0xe844c90eaf2e20ab)
  }
}

fn cell_row<DetectorId: Serialize>(x: i64, y: i64, me: DetectorId) -> RowId {
  RowId::new(&(x, y, me))
}
fn get_bounds<B: Basics, M: Mutator<Basics = B::StewardBasics>>(mutator: &mut M,
                                                                who: RowId,
                                                                me: B::DetectorId)
                                                                -> Bounds {
  B::get_bounds(mutator, who, me)
}
pub fn insert<B: Basics, M: Mutator<Basics = B::StewardBasics>>(mutator: &mut M,
                                                                who: RowId,
                                                                me: B::DetectorId) {
  let member_id = RowId::new(&(who, me));
  if mutator.get::<Member<B>>(member_id).is_some() {
    // already a member – no need to do anything
    return;
  }
  let bounds = get_bounds::<B, M>(mutator, who, me);
  mutator.set::<Member<B>>(member_id,
                           Some(Member {
                             row: who,
                             bounds: bounds,
                             detector: me,
                           }));
  for next in bounds.min[0]..bounds.max[0] + 1 {
    for what in bounds.min[1]..bounds.max[1] + 1 {
      let row = cell_row(next, what, me);
      let mut members = mutator.get::<Cell<B>>(row).cloned().unwrap_or(BTreeSet::new());
      for member in members.iter() {
        let (id, contents) = Nearness::new(who, member.clone(), me);
        // check first: no need to update last_change
        if mutator.get::<Nearness<B>>(id).is_none() {
          mutator.set::<Nearness<B>>(id, Some(contents));
        }
      }
      members.insert(who);
      mutator.set::<Cell<B>>(row, Some(members));
    }
  }
}

pub fn remove<B: Basics, M: Mutator<Basics = B::StewardBasics>>(mutator: &mut M,
                                                                who: RowId,
                                                                me: B::DetectorId,
                                                                membership_id: RowId,
                                                                bounds: Bounds) {

  mutator.set::<Member<B>>(membership_id, None);
  for next in bounds.min[0]..bounds.max[0] + 1 {
    for what in bounds.min[1]..bounds.max[1] + 1 {
      let row = cell_row(next, what, me);
      let mut members = mutator.get::<Cell<B>>(row).cloned().unwrap_or(BTreeSet::new());
      for member in members.iter() {
        if *member != who {
          let (id, _) = Nearness::<B>::new(who, member.clone(), me);
          mutator.set::<Nearness<B>>(id, None);
        }
      }
      members.remove(&who);
      mutator.set::<Cell<B>>(row,
                             if members.is_empty() {
                               None
                             } else {
                               Some(members)
                             });
    }
  }
}

pub type TimeStewardTypes<B> = (ColumnType<Member<B>>,
                                ColumnType<Nearness<B>>,
                                EventType<BoundsChange<B>>,
                                PredictorType<BoundsChangePredictor<B>>);
<commit_msg>Listed missing ColumnType<commit_after>use std::collections::BTreeSet;
use super::Nearness;
use {RowId, ColumnId, Column, Accessor, MomentaryAccessor, Mutator, ColumnType, EventType,
     PredictorType};
use std::marker::PhantomData;
use serde::Serialize;

#[derive (Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
pub struct Bounds {
  pub min: [i64; 2],
  pub max: [i64; 2],
}

pub trait Basics: super::Basics {
  fn get_bounds<A: MomentaryAccessor<Basics = <Self as super::Basics>::StewardBasics>>(accessor: &A,
                                                                              who: RowId, detector: <Self as super::Basics>::DetectorId)
                                                                              -> Bounds;
  fn when_escapes <A: Accessor <Basics = <Self as super::Basics>::StewardBasics>>(accessor: & A, who: RowId, bounds: Bounds, detector: <Self as super::Basics>::DetectorId)->Option <<<Self as super::Basics>::StewardBasics as ::Basics>::Time>;
}

impl<B: Basics> Column for Cell<B> {
  type FieldType = BTreeSet <RowId>;
  fn column_id() -> ColumnId {
    ColumnId(B::nearness_column_id().0 ^ 0x030fc8868af34f2c)
  }
}

// Work around private-in-public rules so that our TimeSteward types
// (which are implementation details) are not visible elsewhere.
mod hack {
  use super::*;
  use {RowId, PredictorId, EventId};
  use std::marker::PhantomData;
  pub struct Cell<B: Basics> {
    _marker: PhantomData<B>,
  }
  #[derive (Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
  pub struct Member<B: Basics> {
    pub row: RowId,
    pub bounds: Bounds,
    pub detector: B::DetectorId,
  }
  time_steward_event! (
    pub struct BoundsChange <[B: Basics]> {id: RowId, member: Member <B>},
    <B as super::super::Basics>::StewardBasics,
    EventId (<B as super::super::Basics>::nearness_column_id().0 ^ 0x1be13fa07975f552),
    | &self, mutator | {
  // TODO: optimize remove-then-insert
      remove::<B,_> (mutator, self.member.row, self.member.detector, self.id, self.member.bounds);
      insert::<B,_> (mutator, self.member.row, self.member.detector);
    }
  );
  time_steward_predictor! {
    pub struct BoundsChangePredictor <[B: Basics]>,
    <B as super::super::Basics>::StewardBasics,
    PredictorId (<B as super::super::Basics>::nearness_column_id().0 ^ 0x3686689daa651bf3),
    watching Member<B>,
    | accessor, id | {
      let member;
      {
        let member_reference = accessor.get::<Member <B>> (id).expect ("row is missing the field the predictor triggered on");
        member = (*member_reference).clone();
      }
      if let Some (time) = B::when_escapes (accessor, member.row, member.bounds, member.detector) {
        accessor.predict_at_time (time, BoundsChange::new (id, member));
      }
    }
  }
}
use self::hack::{Cell, Member, BoundsChange, BoundsChangePredictor};

impl<B: Basics> Column for Member<B> {
  type FieldType = Member<B>;
  fn column_id() -> ColumnId {
    ColumnId(B::nearness_column_id().0 ^ 0xe844c90eaf2e20ab)
  }
}

fn cell_row<DetectorId: Serialize>(x: i64, y: i64, me: DetectorId) -> RowId {
  RowId::new(&(x, y, me))
}
fn get_bounds<B: Basics, M: Mutator<Basics = B::StewardBasics>>(mutator: &mut M,
                                                                who: RowId,
                                                                me: B::DetectorId)
                                                                -> Bounds {
  B::get_bounds(mutator, who, me)
}
pub fn insert<B: Basics, M: Mutator<Basics = B::StewardBasics>>(mutator: &mut M,
                                                                who: RowId,
                                                                me: B::DetectorId) {
  let member_id = RowId::new(&(who, me));
  if mutator.get::<Member<B>>(member_id).is_some() {
    // already a member – no need to do anything
    return;
  }
  let bounds = get_bounds::<B, M>(mutator, who, me);
  mutator.set::<Member<B>>(member_id,
                           Some(Member {
                             row: who,
                             bounds: bounds,
                             detector: me,
                           }));
  for next in bounds.min[0]..bounds.max[0] + 1 {
    for what in bounds.min[1]..bounds.max[1] + 1 {
      let row = cell_row(next, what, me);
      let mut members = mutator.get::<Cell<B>>(row).cloned().unwrap_or(BTreeSet::new());
      for member in members.iter() {
        let (id, contents) = Nearness::new(who, member.clone(), me);
        // check first: no need to update last_change
        if mutator.get::<Nearness<B>>(id).is_none() {
          mutator.set::<Nearness<B>>(id, Some(contents));
        }
      }
      members.insert(who);
      mutator.set::<Cell<B>>(row, Some(members));
    }
  }
}

pub fn remove<B: Basics, M: Mutator<Basics = B::StewardBasics>>(mutator: &mut M,
                                                                who: RowId,
                                                                me: B::DetectorId,
                                                                membership_id: RowId,
                                                                bounds: Bounds) {

  mutator.set::<Member<B>>(membership_id, None);
  for next in bounds.min[0]..bounds.max[0] + 1 {
    for what in bounds.min[1]..bounds.max[1] + 1 {
      let row = cell_row(next, what, me);
      let mut members = mutator.get::<Cell<B>>(row).cloned().unwrap_or(BTreeSet::new());
      for member in members.iter() {
        if *member != who {
          let (id, _) = Nearness::<B>::new(who, member.clone(), me);
          mutator.set::<Nearness<B>>(id, None);
        }
      }
      members.remove(&who);
      mutator.set::<Cell<B>>(row,
                             if members.is_empty() {
                               None
                             } else {
                               Some(members)
                             });
    }
  }
}

pub type TimeStewardTypes<B> = (ColumnType<Nearness<B>>,
                                ColumnType<Member<B>>,
                                ColumnType<Cell<B>>,
                                EventType<BoundsChange<B>>,
                                PredictorType<BoundsChangePredictor<B>>);
<|endoftext|>