<commit_before>use rocket::response::{Response, Responder};
use rocket::{self, State};
use rocket_contrib::Json;
use rocket::request::{self, FromRequest, Request};
use rocket::Outcome;
use rocket::http::{Status, Cookie, Cookies};
use adapters::json;
use adapters::user_communication;
use entities::*;
use business::db::Db;
use business::error::{Error, RepoError, ParameterError};
use infrastructure::error::AppError;
use serde_json::ser::to_string;
use business::{usecase, geo};
use business::duplicates::{self, DuplicateType};
use std::result;
use super::util;

#[cfg(all(not(test), feature = "sqlite"))]
use super::sqlite;

#[cfg(all(not(test), feature = "sqlite"))]
type DbPool = sqlite::ConnectionPool;

#[cfg(test)]
type DbPool = super::mockdb::ConnectionPool;

type Result<T> = result::Result<Json<T>, AppError>;

const COOKIE_USER_KEY: &str = "user_id";

#[derive(FromForm)]
struct SearchQuery {
    bbox: String,
    categories: Option<String>,
    text: Option<String>,
    tags: Option<String>,
}

impl<'a, 'r> FromRequest<'a, 'r> for Login {
    type Error = ();

    fn from_request(request: &'a Request<'r>) -> request::Outcome<Login, ()> {
        let user = request
            .cookies()
            .get_private(COOKIE_USER_KEY)
            .and_then(|cookie| cookie.value().parse().ok())
            .map(|id| Login(id));
        match user {
            Some(user) => Outcome::Success(user),
            None => Outcome::Failure((Status::Unauthorized, ())),
        }
    }
}

#[get("/search?<search>")]
fn get_search(db: State<DbPool>, search: SearchQuery) -> Result<json::SearchResponse> {

    let bbox = geo::extract_bbox(&search.bbox)
        .map_err(Error::Parameter)
        .map_err(AppError::Business)?;

    let categories = match search.categories {
        Some(cat_str) => Some(util::extract_ids(&cat_str)),
        None => None,
    };

    let mut tags = vec![];

    if let Some(ref txt) = search.text {
        tags = util::extract_hash_tags(txt);
    }

    if let Some(tags_str) = search.tags {
        for t in util::extract_ids(&tags_str) {
            tags.push(t);
        }
    }

    let text = match search.text {
        Some(txt) => util::remove_hash_tags(&txt),
        None => "".into(),
    };

    let avg_ratings = match super::ENTRY_RATINGS.lock() {
        Ok(guard) => guard,
        Err(poisoned) => poisoned.into_inner(),
    };

    let req = usecase::SearchRequest {
        bbox,
        categories,
        text,
        tags,
        entry_ratings: &*avg_ratings,
    };

    let (visible, invisible) = usecase::search(&mut *db.get()?, req)?;

    let visible = visible
        .into_iter()
        .map(|e| json::EntryIdWithCoordinates{id:e.id, lat: e.lat, lng: e.lng })
        .collect();

    let invisible = invisible
        .into_iter()
        .map(|e| json::EntryIdWithCoordinates{id:e.id, lat: e.lat, lng: e.lng })
        .collect();

    Ok(Json(json::SearchResponse { visible, invisible }))
}

#[derive(Deserialize, Debug, Clone)]
struct Login(String);

#[derive(Deserialize, Debug, Clone)]
struct UserId {
    u_id: String,
}

#[get("/entries/<ids>")]
fn get_entry(db: State<DbPool>, ids: String) -> Result<Vec<json::Entry>> {
    let ids = util::extract_ids(&ids);
    let entries = usecase::get_entries(&*db.get()?, &ids)?;
    let ratings = usecase::get_ratings_by_entry_ids(&*db.get()?, &ids)?;
    Ok(Json(
        entries
            .into_iter()
            .map(|e| {
                let r = ratings.get(&e.id).cloned().unwrap_or_else(|| vec![]);
                json::Entry::from_entry_with_ratings(e, r)
            })
            .collect::<Vec<json::Entry>>(),
    ))
}

#[get("/duplicates")]
fn get_duplicates(db: State<DbPool>) -> Result<Vec<(String, String, DuplicateType)>> {
    let entries = db.get()?.all_entries()?;
    let ids = duplicates::find_duplicates(&entries);
    Ok(Json(ids))
}

#[get("/count/entries")]
fn get_count_entries(db: State<DbPool>) -> Result<usize> {
    let entries = db.get()?.all_entries()?;
    Ok(Json(entries.len()))
}

#[get("/count/tags")]
fn get_count_tags(db: State<DbPool>) -> Result<usize> {
    Ok(Json(db.get()?.all_tags()?.len()))
}

#[get("/server/version")]
fn get_version() -> &'static str {
    env!("CARGO_PKG_VERSION")
}

#[post("/users", format = "application/json", data = "<u>")]
fn post_user(db: State<DbPool>, u: Json<usecase::NewUser>) -> Result<()> {
    let new_user = u.into_inner();
    usecase::create_new_user(&mut *db.get()?, new_user.clone())?;
    let user = db.get()?.get_user(&new_user.username)?;
    let subject = "Karte von Morgen: bitte bestätige deine Email-Adresse";
    let body = user_communication::email_confirmation_email(&user.id);
    util::send_mails(vec![user.email.clone()], &subject, &body);
    Ok(Json(()))
}

#[delete("/users/<u_id>")]
fn delete_user(db: State<DbPool>, user: Login, u_id: String) -> Result<()> {
    usecase::delete_user(&mut *db.get()?, &user.0, &u_id)?;
    Ok(Json(()))
}

#[post("/ratings", format = "application/json", data = "<u>")]
fn post_rating(db: State<DbPool>, u: Json<usecase::RateEntry>) -> Result<()> {
    let u = u.into_inner();
    let e_id = u.entry.clone();
    usecase::rate_entry(&mut *db.get()?, u)?;
    super::calculate_rating_for_entry(&mut *db.get()?, &e_id)?;
    Ok(Json(()))
}

#[get("/ratings/<id>")]
fn get_ratings(db: State<DbPool>, id: String) -> Result<Vec<json::Rating>> {
    let ratings = usecase::get_ratings(&*db.get()?, &util::extract_ids(&id))?;
    let r_ids: Vec<String> = ratings.iter().map(|r| r.id.clone()).collect();
    let comments = usecase::get_comments_by_rating_ids(&*db.get()?, &r_ids)?;
    let triples = db.get()?.all_triples()?;
    let result = ratings
        .into_iter()
        .map(|x| {
            json::Rating {
                id: x.id.clone(),
                created: x.created,
                title: x.title,
                user: usecase::get_user_id_for_rating_id(&triples, &x.id),
                value: x.value,
                context: x.context,
                source: x.source.unwrap_or("".into()),
                comments: comments
                    .get(&x.id)
                    .cloned()
                    .unwrap_or_else(|| vec![])
                    .into_iter()
                    .map(|c| {
                        json::Comment {
                            id: c.id.clone(),
                            created: c.created,
                            text: c.text,
                            user: usecase::get_user_id_for_comment_id(&triples, &c.id),
                        }
                    })
                    .collect(),
            }
        })
        .collect();
    Ok(Json(result))
}

#[post("/login", format = "application/json", data = "<login>")]
fn login(db: State<DbPool>, mut cookies: Cookies, login: Json<usecase::Login>) -> Result<()> {
    let id = usecase::login(&mut *db.get()?, login.into_inner())?;
    cookies.add_private(Cookie::new(COOKIE_USER_KEY, id));
    Ok(Json(()))
}

#[post("/logout", format = "application/json")]
fn logout(mut cookies: Cookies) -> Result<()> {
    cookies.remove_private(Cookie::named(COOKIE_USER_KEY));
    Ok(Json(()))
}

#[post("/confirm-email-address", format = "application/json", data = "<user>")]
fn confirm_email_address(user: Json<UserId>, db: State<DbPool>) -> Result<()> {
    let u_id = user.into_inner().u_id;
    let u = db.get()?.confirm_email_address(&u_id)?;
    if u.id == u_id {
        Ok(Json(()))
    } else {
        Err(AppError::Business(Error::Repo(RepoError::NotFound)))
    }
}

#[post("/subscribe-to-bbox", format = "application/json", data = "<coordinates>")]
fn subscribe_to_bbox(
    user: Login,
    coordinates: Json<Vec<Coordinate>>,
    db: State<DbPool>,
) -> Result<()> {
    let coordinates = coordinates.into_inner();
    let Login(username) = user;
    usecase::subscribe_to_bbox(&coordinates, &username, &mut *db.get()?)?;
    Ok(Json(()))
}

#[delete("/unsubscribe-all-bboxes")]
fn unsubscribe_all_bboxes(user: Login, db: State<DbPool>) -> Result<()> {
    let Login(username) = user;
    usecase::unsubscribe_all_bboxes(&username, &mut *db.get()?)?;
    Ok(Json(()))
}

#[get("/bbox-subscriptions")]
fn get_bbox_subscriptions(db: State<DbPool>, user: Login) -> Result<Vec<json::BboxSubscription>> {
    let Login(username) = user;
    let user_subscriptions = usecase::get_bbox_subscriptions(&username, &*db.get()?)?
        .into_iter()
        .map(|s| {
            json::BboxSubscription {
                id: s.id,
                south_west_lat: s.south_west_lat,
                south_west_lng: s.south_west_lng,
                north_east_lat: s.north_east_lat,
                north_east_lng: s.north_east_lng,
            }
        })
        .collect();
    Ok(Json(user_subscriptions))
}

#[get("/users/<username>", format = "application/json")]
fn get_user(db: State<DbPool>, user: Login, username: String) -> Result<json::User> {
    let (u_id, email) = usecase::get_user(&mut *db.get()?, &user.0, &username)?;
    Ok(Json(json::User { u_id, email }))
}

#[post("/entries", format = "application/json", data = "<e>")]
fn post_entry(db: State<DbPool>, e: Json<usecase::NewEntry>) -> Result<String> {
    let e = e.into_inner();
    let id = usecase::create_new_entry(&mut *db.get()?, e.clone())?;
    let email_addresses = usecase::email_addresses_to_notify(&e.lat, &e.lng, &mut *db.get()?);
    let all_categories = db.get()?.all_categories()?;
    util::notify_create_entry(email_addresses, &e, &id, all_categories);
    Ok(Json(id))
}

#[put("/entries/<id>", format = "application/json", data = "<e>")]
fn put_entry(db: State<DbPool>, id: String, e: Json<usecase::UpdateEntry>) -> Result<String> {
    let e = e.into_inner();
    usecase::update_entry(&mut *db.get()?, e.clone())?;
    let email_addresses = usecase::email_addresses_to_notify(&e.lat, &e.lng, &mut *db.get()?);
    let all_categories = db.get()?.all_categories()?;
    util::notify_update_entry(email_addresses, &e, all_categories);
    Ok(Json(id))
}

#[get("/tags")]
fn get_tags(db: State<DbPool>) -> Result<Vec<String>> {
    Ok(Json(
        db.get()?.all_tags()?.into_iter().map(|t| t.id).collect(),
    ))
}

#[get("/categories")]
fn get_categories(db: State<DbPool>) -> Result<Vec<Category>> {
    let categories = db.get()?.all_categories()?;
    Ok(Json(categories))
}

#[get("/categories/<id>")]
fn get_category(db: State<DbPool>, id: String) -> Result<String> {
    let ids = util::extract_ids(&id);
    let categories = db.get()?.all_categories()?;
    let res = match ids.len() {
        0 => to_string(&categories),

        1 => {
            let id = ids[0].clone();
            let e = categories.into_iter().find(|x| x.id == id).ok_or(
                RepoError::NotFound,
            )?;
            to_string(&e)
        }
        _ => {
            to_string(&categories
                .into_iter()
                .filter(|e| ids.iter().any(|id| e.id == id.clone()))
                .collect::<Vec<Category>>())
        }
    }?;
    Ok(Json(res))
}

impl<'r> Responder<'r> for AppError {
    fn respond_to(self, _: &rocket::Request) -> result::Result<Response<'r>, Status> {
        Err(match self {
            AppError::Business(ref err) => {
                match *err {
                    Error::Parameter(ref err) => {
                        match *err {
                            ParameterError::Credentials => Status::Unauthorized,
                            ParameterError::UserExists => <Status>::new(400, "UserExists"),
                            ParameterError::EmailNotConfirmed => {
                                <Status>::new(403, "EmailNotConfirmed")
                            }
                            _ => Status::BadRequest,
                        }
                    }
                    Error::Repo(ref err) => {
                        match *err {
                            RepoError::NotFound => Status::NotFound,
                            _ => Status::InternalServerError,
                        }
                    }
                    Error::Pwhash(_) => Status::InternalServerError,
                }
            }

            _ => Status::InternalServerError,
        })
    }
}
<commit_msg>new(web): log internal server errors<commit_after>use rocket::response::{Response, Responder};
use rocket::{self, State};
use rocket_contrib::Json;
use rocket::request::{self, FromRequest, Request};
use rocket::Outcome;
use rocket::http::{Status, Cookie, Cookies};
use adapters::json;
use adapters::user_communication;
use entities::*;
use business::db::Db;
use business::error::{Error, RepoError, ParameterError};
use infrastructure::error::AppError;
use serde_json::ser::to_string;
use business::{usecase, geo};
use business::duplicates::{self, DuplicateType};
use std::result;
use super::util;

#[cfg(all(not(test), feature = "sqlite"))]
use super::sqlite;

#[cfg(all(not(test), feature = "sqlite"))]
type DbPool = sqlite::ConnectionPool;

#[cfg(test)]
type DbPool = super::mockdb::ConnectionPool;

type Result<T> = result::Result<Json<T>, AppError>;

const COOKIE_USER_KEY: &str = "user_id";

#[derive(FromForm)]
struct SearchQuery {
    bbox: String,
    categories: Option<String>,
    text: Option<String>,
    tags: Option<String>,
}

impl<'a, 'r> FromRequest<'a, 'r> for Login {
    type Error = ();

    fn from_request(request: &'a Request<'r>) -> request::Outcome<Login, ()> {
        let user = request
            .cookies()
            .get_private(COOKIE_USER_KEY)
            .and_then(|cookie| cookie.value().parse().ok())
            .map(|id| Login(id));
        match user {
            Some(user) => Outcome::Success(user),
            None => Outcome::Failure((Status::Unauthorized, ())),
        }
    }
}

#[get("/search?<search>")]
fn get_search(db: State<DbPool>, search: SearchQuery) -> Result<json::SearchResponse> {

    let bbox = geo::extract_bbox(&search.bbox)
        .map_err(Error::Parameter)
        .map_err(AppError::Business)?;

    let categories = match search.categories {
        Some(cat_str) => Some(util::extract_ids(&cat_str)),
        None => None,
    };

    let mut tags = vec![];

    if let Some(ref txt) = search.text {
        tags = util::extract_hash_tags(txt);
    }

    if let Some(tags_str) = search.tags {
        for t in util::extract_ids(&tags_str) {
            tags.push(t);
        }
    }

    let text = match search.text {
        Some(txt) => util::remove_hash_tags(&txt),
        None => "".into(),
    };

    let avg_ratings = match super::ENTRY_RATINGS.lock() {
        Ok(guard) => guard,
        Err(poisoned) => poisoned.into_inner(),
    };

    let req = usecase::SearchRequest {
        bbox,
        categories,
        text,
        tags,
        entry_ratings: &*avg_ratings,
    };

    let (visible, invisible) = usecase::search(&mut *db.get()?, req)?;

    let visible = visible
        .into_iter()
        .map(|e| {
            json::EntryIdWithCoordinates {
                id: e.id,
                lat: e.lat,
                lng: e.lng,
            }
        })
        .collect();

    let invisible = invisible
        .into_iter()
        .map(|e| {
            json::EntryIdWithCoordinates {
                id: e.id,
                lat: e.lat,
                lng: e.lng,
            }
        })
        .collect();

    Ok(Json(json::SearchResponse { visible, invisible }))
}

#[derive(Deserialize, Debug, Clone)]
struct Login(String);

#[derive(Deserialize, Debug, Clone)]
struct UserId {
    u_id: String,
}

#[get("/entries/<ids>")]
fn get_entry(db: State<DbPool>, ids: String) -> Result<Vec<json::Entry>> {
    let ids = util::extract_ids(&ids);
    let entries = usecase::get_entries(&*db.get()?, &ids)?;
    let ratings = usecase::get_ratings_by_entry_ids(&*db.get()?, &ids)?;
    Ok(Json(
        entries
            .into_iter()
            .map(|e| {
                let r = ratings.get(&e.id).cloned().unwrap_or_else(|| vec![]);
                json::Entry::from_entry_with_ratings(e, r)
            })
            .collect::<Vec<json::Entry>>(),
    ))
}

#[get("/duplicates")]
fn get_duplicates(db: State<DbPool>) -> Result<Vec<(String, String, DuplicateType)>> {
    let entries = db.get()?.all_entries()?;
    let ids = duplicates::find_duplicates(&entries);
    Ok(Json(ids))
}

#[get("/count/entries")]
fn get_count_entries(db: State<DbPool>) -> Result<usize> {
    let entries = db.get()?.all_entries()?;
    Ok(Json(entries.len()))
}

#[get("/count/tags")]
fn get_count_tags(db: State<DbPool>) -> Result<usize> {
    Ok(Json(db.get()?.all_tags()?.len()))
}

#[get("/server/version")]
fn get_version() -> &'static str {
    env!("CARGO_PKG_VERSION")
}

#[post("/users", format = "application/json", data = "<u>")]
fn post_user(db: State<DbPool>, u: Json<usecase::NewUser>) -> Result<()> {
    let new_user = u.into_inner();
    usecase::create_new_user(&mut *db.get()?, new_user.clone())?;
    let user = db.get()?.get_user(&new_user.username)?;
    let subject = "Karte von Morgen: bitte bestätige deine Email-Adresse";
    let body = user_communication::email_confirmation_email(&user.id);
    util::send_mails(vec![user.email.clone()], &subject, &body);
    Ok(Json(()))
}

#[delete("/users/<u_id>")]
fn delete_user(db: State<DbPool>, user: Login, u_id: String) -> Result<()> {
    usecase::delete_user(&mut *db.get()?, &user.0, &u_id)?;
    Ok(Json(()))
}

#[post("/ratings", format = "application/json", data = "<u>")]
fn post_rating(db: State<DbPool>, u: Json<usecase::RateEntry>) -> Result<()> {
    let u = u.into_inner();
    let e_id = u.entry.clone();
    usecase::rate_entry(&mut *db.get()?, u)?;
    super::calculate_rating_for_entry(&mut *db.get()?, &e_id)?;
    Ok(Json(()))
}

#[get("/ratings/<id>")]
fn get_ratings(db: State<DbPool>, id: String) -> Result<Vec<json::Rating>> {
    let ratings = usecase::get_ratings(&*db.get()?, &util::extract_ids(&id))?;
    let r_ids: Vec<String> = ratings.iter().map(|r| r.id.clone()).collect();
    let comments = usecase::get_comments_by_rating_ids(&*db.get()?, &r_ids)?;
    let triples = db.get()?.all_triples()?;
    let result = ratings
        .into_iter()
        .map(|x| {
            json::Rating {
                id: x.id.clone(),
                created: x.created,
                title: x.title,
                user: usecase::get_user_id_for_rating_id(&triples, &x.id),
                value: x.value,
                context: x.context,
                source: x.source.unwrap_or("".into()),
                comments: comments
                    .get(&x.id)
                    .cloned()
                    .unwrap_or_else(|| vec![])
                    .into_iter()
                    .map(|c| {
                        json::Comment {
                            id: c.id.clone(),
                            created: c.created,
                            text: c.text,
                            user: usecase::get_user_id_for_comment_id(&triples, &c.id),
                        }
                    })
                    .collect(),
            }
        })
        .collect();
    Ok(Json(result))
}

#[post("/login", format = "application/json", data = "<login>")]
fn login(db: State<DbPool>, mut cookies: Cookies, login: Json<usecase::Login>) -> Result<()> {
    let id = usecase::login(&mut *db.get()?, login.into_inner())?;
    cookies.add_private(Cookie::new(COOKIE_USER_KEY, id));
    Ok(Json(()))
}

#[post("/logout", format = "application/json")]
fn logout(mut cookies: Cookies) -> Result<()> {
    cookies.remove_private(Cookie::named(COOKIE_USER_KEY));
    Ok(Json(()))
}

#[post("/confirm-email-address", format = "application/json", data = "<user>")]
fn confirm_email_address(user: Json<UserId>, db: State<DbPool>) -> Result<()> {
    let u_id = user.into_inner().u_id;
    let u = db.get()?.confirm_email_address(&u_id)?;
    if u.id == u_id {
        Ok(Json(()))
    } else {
        Err(AppError::Business(Error::Repo(RepoError::NotFound)))
    }
}

#[post("/subscribe-to-bbox", format = "application/json", data = "<coordinates>")]
fn subscribe_to_bbox(
    user: Login,
    coordinates: Json<Vec<Coordinate>>,
    db: State<DbPool>,
) -> Result<()> {
    let coordinates = coordinates.into_inner();
    let Login(username) = user;
    usecase::subscribe_to_bbox(&coordinates, &username, &mut *db.get()?)?;
    Ok(Json(()))
}

#[delete("/unsubscribe-all-bboxes")]
fn unsubscribe_all_bboxes(user: Login, db: State<DbPool>) -> Result<()> {
    let Login(username) = user;
    usecase::unsubscribe_all_bboxes(&username, &mut *db.get()?)?;
    Ok(Json(()))
}

#[get("/bbox-subscriptions")]
fn get_bbox_subscriptions(db: State<DbPool>, user: Login) -> Result<Vec<json::BboxSubscription>> {
    let Login(username) = user;
    let user_subscriptions = usecase::get_bbox_subscriptions(&username, &*db.get()?)?
        .into_iter()
        .map(|s| {
            json::BboxSubscription {
                id: s.id,
                south_west_lat: s.south_west_lat,
                south_west_lng: s.south_west_lng,
                north_east_lat: s.north_east_lat,
                north_east_lng: s.north_east_lng,
            }
        })
        .collect();
    Ok(Json(user_subscriptions))
}

#[get("/users/<username>", format = "application/json")]
fn get_user(db: State<DbPool>, user: Login, username: String) -> Result<json::User> {
    let (u_id, email) = usecase::get_user(&mut *db.get()?, &user.0, &username)?;
    Ok(Json(json::User { u_id, email }))
}

#[post("/entries", format = "application/json", data = "<e>")]
fn post_entry(db: State<DbPool>, e: Json<usecase::NewEntry>) -> Result<String> {
    let e = e.into_inner();
    let id = usecase::create_new_entry(&mut *db.get()?, e.clone())?;
    let email_addresses = usecase::email_addresses_to_notify(&e.lat, &e.lng, &mut *db.get()?);
    let all_categories = db.get()?.all_categories()?;
    util::notify_create_entry(email_addresses, &e, &id, all_categories);
    Ok(Json(id))
}

#[put("/entries/<id>", format = "application/json", data = "<e>")]
fn put_entry(db: State<DbPool>, id: String, e: Json<usecase::UpdateEntry>) -> Result<String> {
    let e = e.into_inner();
    usecase::update_entry(&mut *db.get()?, e.clone())?;
    let email_addresses = usecase::email_addresses_to_notify(&e.lat, &e.lng, &mut *db.get()?);
    let all_categories = db.get()?.all_categories()?;
    util::notify_update_entry(email_addresses, &e, all_categories);
    Ok(Json(id))
}

#[get("/tags")]
fn get_tags(db: State<DbPool>) -> Result<Vec<String>> {
    Ok(Json(
        db.get()?.all_tags()?.into_iter().map(|t| t.id).collect(),
    ))
}

#[get("/categories")]
fn get_categories(db: State<DbPool>) -> Result<Vec<Category>> {
    let categories = db.get()?.all_categories()?;
    Ok(Json(categories))
}

#[get("/categories/<id>")]
fn get_category(db: State<DbPool>, id: String) -> Result<String> {
    let ids = util::extract_ids(&id);
    let categories = db.get()?.all_categories()?;
    let res = match ids.len() {
        0 => to_string(&categories),

        1 => {
            let id = ids[0].clone();
            let e = categories.into_iter().find(|x| x.id == id).ok_or(
                RepoError::NotFound,
            )?;
            to_string(&e)
        }
        _ => {
            to_string(&categories
                .into_iter()
                .filter(|e| ids.iter().any(|id| e.id == id.clone()))
                .collect::<Vec<Category>>())
        }
    }?;
    Ok(Json(res))
}

impl<'r> Responder<'r> for AppError {
    fn respond_to(self, _: &rocket::Request) -> result::Result<Response<'r>, Status> {
        if let AppError::Business(ref err) = self {
            match *err {
                Error::Parameter(ref err) => {
                    return Err(match *err {
                        ParameterError::Credentials => Status::Unauthorized,
                        ParameterError::UserExists => <Status>::new(400, "UserExists"),
                        ParameterError::EmailNotConfirmed => {
                            <Status>::new(403, "EmailNotConfirmed")
                        }
                        _ => Status::BadRequest,
                    })
                }
                Error::Repo(ref err) => {
                    match *err {
                        RepoError::NotFound => return Err(Status::NotFound),
                        _ => {}
                    }
                }
                _ => {}
            }
        }
        error!("Error: {}", self);
        Err(Status::InternalServerError)
    }
}
<|endoftext|>


<commit_before>use crate::pubsub::publish;
use defaults::Defaults;
use eyre::Result;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::path::Path;
use strum::ToString;
use tracing::Event;
use validator::Validate;

/// # Logging level
#[derive(
    Debug, PartialEq, PartialOrd, Clone, Copy, JsonSchema, Deserialize, Serialize, ToString,
)]
#[serde(rename_all = "lowercase")]
pub enum LoggingLevel {
    Trace = 0,
    Debug = 1,
    Info = 2,
    Warn = 3,
    Error = 4,
    Never = 9,
}

impl LoggingLevel {
    pub fn as_tracing_level(&self) -> tracing::Level {
        match self {
            Self::Trace => tracing::Level::TRACE,
            Self::Debug => tracing::Level::DEBUG,
            Self::Info => tracing::Level::INFO,
            Self::Warn => tracing::Level::WARN,
            Self::Error => tracing::Level::ERROR,
            Self::Never => tracing::Level::ERROR,
        }
    }
}

/// # Logging format
#[derive(Debug, PartialEq, Clone, Copy, JsonSchema, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum LoggingFormat {
    Plain,
    Pretty,
    Json,
}

#[cfg(feature = "config")]
pub mod config {
    use super::*;
    use crate::util::dirs;

    /// # Logging to standard error stream
    ///
    /// Configuration settings for log entries printed to stderr when using the CLI
    #[derive(
        Debug, Defaults, PartialEq, Clone, Copy, JsonSchema, Deserialize, Serialize, Validate,
    )]
    pub struct LoggingStdErrConfig {
        /// The maximum log level to emit
        #[def = "LoggingLevel::Info"]
        pub level: LoggingLevel,

        /// The format for the logs entries
        #[def = "LoggingFormat::Pretty"]
        pub format: LoggingFormat,
    }

    /// # Logging to desktop notifications
    ///
    /// Configuration settings for log entries shown to the user in the desktop
    #[derive(
        Debug, Defaults, PartialEq, Clone, Copy, JsonSchema, Deserialize, Serialize, Validate,
    )]
    pub struct LoggingDesktopConfig {
        /// The maximum log level to emit
        #[def = "LoggingLevel::Info"]
        pub level: LoggingLevel,
    }

    /// # Logging to file
    ///
    /// Configuration settings for logs entries written to file
    #[derive(Debug, Defaults, PartialEq, Clone, JsonSchema, Deserialize, Serialize, Validate)]
    pub struct LoggingFileConfig {
        /// The path of the log file
        #[def = "default_file_path()"]
        pub path: String,

        /// The maximum log level to emit
        #[def = "LoggingLevel::Info"]
        pub level: LoggingLevel,
    }

    /// Get the default value for `logging.file.path`
    pub fn default_file_path() -> String {
        dirs::logs(true)
            .expect("Unable to get logs directory")
            .join("log.json")
            .into_os_string()
            .into_string()
            .expect("Unable to convert path to string")
    }

    /// # Logging
    ///
    /// Configuration settings for logging
    #[derive(Debug, Default, PartialEq, Clone, JsonSchema, Deserialize, Serialize, Validate)]
    pub struct LoggingConfig {
        pub stderr: LoggingStdErrConfig,
        pub desktop: LoggingDesktopConfig,
        pub file: LoggingFileConfig,
    }
}

/// Create a preliminary logging subscriber.
///
/// This can be necessary to ensure that any log events that get emitted during
/// initialization are displayed to the user.
pub fn prelim() -> tracing::subscriber::DefaultGuard {
    let subscriber = tracing_subscriber::fmt()
        .pretty()
        .with_max_level(tracing::Level::INFO)
        .with_writer(std::io::stderr)
        .finish();
    tracing::subscriber::set_default(subscriber)
}

/// Custom tracing_subscriber layer that publishes events
/// under the pubsub "logging" topic as a JSON value.
struct PubSubLayer {
    level: LoggingLevel,
}

impl<S: tracing::subscriber::Subscriber> tracing_subscriber::layer::Layer<S> for PubSubLayer {
    fn on_event(&self, event: &Event<'_>, _ctx: tracing_subscriber::layer::Context<'_, S>) {
        use tracing_serde::AsSerde;
        if *event.metadata().level() <= self.level.as_tracing_level() {
            let value = serde_json::json!(event.as_serde());
            if publish("logging", value).is_err() {
                // Ignore any error in publishing logging event
                // Doing otherwise (e.g. logging another event) could be very circular
            }
        }
    }
}

/// Initialize logging
///
/// This initializes a logging subscriber based on configuration and
/// context (e.g. stderr should not be written to if the context
/// is the desktop application).
///
/// # Arguments
///
/// - `level`: the override stderr logging level for example set by the `--debug` flag
/// - `stderr`: should stderr logging be enabled
/// - `pubsub`: should pubsub logging be enabled (for desktop notifications)
/// - `file`: should file logging be enabled
pub fn init(
    level: Option<LoggingLevel>,
    stderr: bool,
    pubsub: bool,
    file: bool,
    config: &config::LoggingConfig,
) -> Result<[tracing_appender::non_blocking::WorkerGuard; 2]> {
    use tracing_error::ErrorLayer;
    use tracing_subscriber::prelude::*;
    use tracing_subscriber::{fmt, layer::SubscriberExt, EnvFilter};

    // Stderr logging layer
    let stderr_level = if level.is_some() || stderr {
        level.unwrap_or(config.stderr.level)
    } else {
        LoggingLevel::Never
    };
    let (stderr_writer, stderr_guard) = if stderr_level != LoggingLevel::Never {
        tracing_appender::non_blocking(std::io::stderr())
    } else {
        tracing_appender::non_blocking(std::io::sink())
    };
    let stderr_layer = fmt::Layer::new().with_writer(stderr_writer);

    // Pubsub logging layer (used for desktop notifications)
    let pubsub_level = if pubsub {
        config.desktop.level
    } else {
        LoggingLevel::Never
    };
    let pubsub_layer = PubSubLayer {
        level: pubsub_level,
    };

    // File logging layer
    let file_level = if file {
        config.file.level
    } else {
        LoggingLevel::Never
    };
    let (file_writer, file_guard) = if file_level != LoggingLevel::Never {
        let path = Path::new(&config.file.path);
        let file_appender =
            tracing_appender::rolling::daily(&path.parent().unwrap(), &path.file_name().unwrap());
        tracing_appender::non_blocking(file_appender)
    } else {
        tracing_appender::non_blocking(std::io::sink())
    };
    let file_layer = fmt::Layer::new().json().with_writer(file_writer);

    // Error reporting layer (necessary for using `eyre` crate)
    let error_layer = ErrorLayer::default();

    // tracing_subscriber does not currently allow for different layers to have different
    // so work out the minimal debug level and filter by that in the root subscriber.
    let mut min_level = LoggingLevel::Never;
    if stderr_level < min_level {
        min_level = stderr_level
    }
    if pubsub_level < min_level {
        min_level = pubsub_level
    }
    if file_level < min_level {
        min_level = file_level
    }

    let registry = tracing_subscriber::registry()
        .with(EnvFilter::new(min_level.to_string()))
        .with(pubsub_layer)
        .with(file_layer)
        .with(error_layer);

    if stderr_level == LoggingLevel::Debug {
        let stderr = stderr_layer.pretty();
        registry.with(stderr).init();
    } else {
        let stderr = stderr_layer
            .without_time()
            .with_thread_names(false)
            .with_thread_ids(false)
            .with_target(false)
            .compact();
        registry.with(stderr).init();
    }

    Ok([stderr_guard, file_guard])
}

/// Generate some test tracing events.
///
/// Can be used for testing that events are propagated
/// to subscribers as expected.
#[tracing::instrument]
pub fn test_events() {
    tracing::trace!("A trace event");
    tracing::debug!("A debug event");
    tracing::info!("An info event");
    tracing::warn!("A warn event");
    tracing::error!("An error event");
}
<commit_msg>refactor(Logging): Changes to how levels are represented<commit_after>use crate::pubsub::publish;
use defaults::Defaults;
use eyre::Result;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::path::Path;
use strum::ToString;
use tracing::Event;
use validator::Validate;

/// # Logging level
#[derive(
    Debug, Clone, Copy, PartialEq, PartialOrd, JsonSchema, Deserialize, Serialize, ToString,
)]
#[serde(rename_all = "lowercase")]
pub enum LoggingLevel {
    Trace,
    Debug,
    Info,
    Warn,
    Error,
    Never,
}

/// Create a `LoggingLevel` from a `tracing::Level`
impl From<&tracing::Level> for LoggingLevel {
    fn from(level: &tracing::Level) -> Self {
        match *level {
            tracing::Level::TRACE => Self::Trace,
            tracing::Level::DEBUG => Self::Debug,
            tracing::Level::INFO => Self::Info,
            tracing::Level::WARN => Self::Warn,
            tracing::Level::ERROR => Self::Error,
        }
    }
}

/// # Logging format
#[derive(Debug, PartialEq, Clone, Copy, JsonSchema, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum LoggingFormat {
    Plain,
    Pretty,
    Json,
}

#[cfg(feature = "config")]
pub mod config {
    use super::*;
    use crate::util::dirs;

    /// # Logging to standard error stream
    ///
    /// Configuration settings for log entries printed to stderr when using the CLI
    #[derive(
        Debug, Defaults, PartialEq, Clone, Copy, JsonSchema, Deserialize, Serialize, Validate,
    )]
    pub struct LoggingStdErrConfig {
        /// The maximum log level to emit
        #[def = "LoggingLevel::Info"]
        pub level: LoggingLevel,

        /// The format for the logs entries
        #[def = "LoggingFormat::Pretty"]
        pub format: LoggingFormat,
    }

    /// # Logging to desktop notifications
    ///
    /// Configuration settings for log entries shown to the user in the desktop
    #[derive(
        Debug, Defaults, PartialEq, Clone, Copy, JsonSchema, Deserialize, Serialize, Validate,
    )]
    pub struct LoggingDesktopConfig {
        /// The maximum log level to emit
        #[def = "LoggingLevel::Info"]
        pub level: LoggingLevel,
    }

    /// # Logging to file
    ///
    /// Configuration settings for logs entries written to file
    #[derive(Debug, Defaults, PartialEq, Clone, JsonSchema, Deserialize, Serialize, Validate)]
    pub struct LoggingFileConfig {
        /// The path of the log file
        #[def = "default_file_path()"]
        pub path: String,

        /// The maximum log level to emit
        #[def = "LoggingLevel::Info"]
        pub level: LoggingLevel,
    }

    /// Get the default value for `logging.file.path`
    pub fn default_file_path() -> String {
        dirs::logs(true)
            .expect("Unable to get logs directory")
            .join("log.json")
            .into_os_string()
            .into_string()
            .expect("Unable to convert path to string")
    }

    /// # Logging
    ///
    /// Configuration settings for logging
    #[derive(Debug, Default, PartialEq, Clone, JsonSchema, Deserialize, Serialize, Validate)]
    pub struct LoggingConfig {
        pub stderr: LoggingStdErrConfig,
        pub desktop: LoggingDesktopConfig,
        pub file: LoggingFileConfig,
    }
}

/// Create a preliminary logging subscriber.
///
/// This can be necessary to ensure that any log events that get emitted during
/// initialization are displayed to the user.
pub fn prelim() -> tracing::subscriber::DefaultGuard {
    let subscriber = tracing_subscriber::fmt()
        .pretty()
        .with_max_level(tracing::Level::INFO)
        .with_writer(std::io::stderr)
        .finish();
    tracing::subscriber::set_default(subscriber)
}

/// Custom tracing_subscriber layer that publishes events
/// under the pubsub "logging" topic as a JSON value.
struct PubSubLayer {
    level: LoggingLevel,
}

impl<S: tracing::subscriber::Subscriber> tracing_subscriber::layer::Layer<S> for PubSubLayer {
    fn on_event(&self, event: &Event<'_>, _ctx: tracing_subscriber::layer::Context<'_, S>) {
        use tracing_serde::AsSerde;
        if LoggingLevel::from(event.metadata().level()) >= self.level {
            let value = serde_json::json!(event.as_serde());
            if publish("logging", value).is_err() {
                // Ignore any error in publishing logging event
                // Doing otherwise (e.g. logging another event) could be very circular
            }
        }
    }
}

/// Initialize logging
///
/// This initializes a logging subscriber based on configuration and
/// context (e.g. stderr should not be written to if the context
/// is the desktop application).
///
/// # Arguments
///
/// - `level`: the override stderr logging level for example set by the `--debug` flag
/// - `stderr`: should stderr logging be enabled
/// - `pubsub`: should pubsub logging be enabled (for desktop notifications)
/// - `file`: should file logging be enabled
pub fn init(
    level: Option<LoggingLevel>,
    stderr: bool,
    pubsub: bool,
    file: bool,
    config: &config::LoggingConfig,
) -> Result<[tracing_appender::non_blocking::WorkerGuard; 2]> {
    use tracing_error::ErrorLayer;
    use tracing_subscriber::prelude::*;
    use tracing_subscriber::{fmt, layer::SubscriberExt, EnvFilter};

    // Stderr logging layer
    let stderr_level = if level.is_some() || stderr {
        level.unwrap_or(config.stderr.level)
    } else {
        LoggingLevel::Never
    };
    let (stderr_writer, stderr_guard) = if stderr_level != LoggingLevel::Never {
        tracing_appender::non_blocking(std::io::stderr())
    } else {
        tracing_appender::non_blocking(std::io::sink())
    };
    let stderr_layer = fmt::Layer::new().with_writer(stderr_writer);

    // Pubsub logging layer (used for desktop notifications)
    let pubsub_level = if pubsub {
        config.desktop.level
    } else {
        LoggingLevel::Never
    };
    let pubsub_layer = PubSubLayer {
        level: pubsub_level,
    };

    // File logging layer
    let file_level = if file {
        config.file.level
    } else {
        LoggingLevel::Never
    };
    let (file_writer, file_guard) = if file_level != LoggingLevel::Never {
        let path = Path::new(&config.file.path);
        let file_appender =
            tracing_appender::rolling::daily(&path.parent().unwrap(), &path.file_name().unwrap());
        tracing_appender::non_blocking(file_appender)
    } else {
        tracing_appender::non_blocking(std::io::sink())
    };
    let file_layer = fmt::Layer::new().json().with_writer(file_writer);

    // Error reporting layer (necessary for using `eyre` crate)
    let error_layer = ErrorLayer::default();

    // tracing_subscriber does not currently allow for different layers to have different
    // so work out the minimal debug level and filter by that in the root subscriber.
    let mut min_level = LoggingLevel::Never;
    if stderr_level < min_level {
        min_level = stderr_level
    }
    if pubsub_level < min_level {
        min_level = pubsub_level
    }
    if file_level < min_level {
        min_level = file_level
    }

    let registry = tracing_subscriber::registry()
        .with(EnvFilter::new(min_level.to_string()))
        .with(pubsub_layer)
        .with(file_layer)
        .with(error_layer);

    if stderr_level == LoggingLevel::Debug {
        let stderr = stderr_layer.pretty();
        registry.with(stderr).init();
    } else {
        let stderr = stderr_layer
            .without_time()
            .with_thread_names(false)
            .with_thread_ids(false)
            .with_target(false)
            .compact();
        registry.with(stderr).init();
    }

    Ok([stderr_guard, file_guard])
}

/// Generate some test tracing events.
///
/// Can be used for testing that events are propagated
/// to subscribers as expected.
#[tracing::instrument]
pub fn test_events() {
    tracing::trace!("A trace event");
    tracing::debug!("A debug event");
    tracing::info!("An info event");
    tracing::warn!("A warn event");
    tracing::error!("An error event");
}

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

    #[test]
    fn test_logging_level_ordering() -> Result<()> {
        assert!(LoggingLevel::Debug > LoggingLevel::Trace);
        assert!(LoggingLevel::Debug >= LoggingLevel::Debug);
        assert!(LoggingLevel::Debug == LoggingLevel::Debug);
        assert!(LoggingLevel::Trace <= LoggingLevel::Debug);
        assert!(LoggingLevel::Trace < LoggingLevel::Debug);

        assert!(LoggingLevel::Info > LoggingLevel::Debug);
        assert!(LoggingLevel::Warn > LoggingLevel::Info);
        assert!(LoggingLevel::Error > LoggingLevel::Warn);
        Ok(())
    }
}
<|endoftext|>


<commit_before>use ::libc;
use ::cssparser::{Parser, ParserInput, Token, BasicParseError};
use ::glib::translate::*;
use ::glib_sys;

use std::f64::consts::*;
use std::mem;
use std::ptr;
use std::slice;
use std::str;

use util::utf8_cstr;

#[derive(Debug, Clone, PartialEq)]
pub struct ParseError {
    pub display: String
}

impl ParseError {
    pub fn new<T: AsRef<str>> (msg: T) -> ParseError {
        ParseError { display: msg.as_ref ().to_string () }
    }
}

impl<'a> From<BasicParseError<'a>> for ParseError {
    fn from (_: BasicParseError) -> ParseError {
        ParseError::new ("parse error")
    }
}

pub trait Parse: Sized {
    type Data;
    type Err;

    fn parse (s: &str, data: Self::Data) -> Result<Self, Self::Err>;
}

// angle:
// https://www.w3.org/TR/SVG/types.html#DataTypeAngle
//
// angle ::= number ("deg" | "grad" | "rad")?
//
// Returns an f64 angle in degrees

pub fn angle_degrees (s: &str) -> Result <f64, ParseError> {
    let mut input = ParserInput::new (s);
    let mut parser = Parser::new (&mut input);

    let angle = {
        let token = parser.next ()
            .map_err (|_| ParseError::new ("expected angle"))?;

        match *token {
            Token::Number { value, .. } => f64::from(value),

            Token::Dimension { value, ref unit, .. } => {
                let value = f64::from(value);

                match unit.as_ref () {
                    "deg"  => value,
                    "grad" => value * 360.0 / 400.0,
                    "rad"  => value * 180.0 / PI,
                    _      => return Err (ParseError::new ("expected 'deg' | 'grad' | 'rad'"))
                }
            },

            _ => return Err (ParseError::new ("expected angle"))
        }
    };

    parser.expect_exhausted ().map_err (|_| ParseError::new ("expected angle"))?;

    Ok (angle)
}

pub fn optional_comma (parser: &mut Parser) {
    let _ = parser.try (|p| p.expect_comma ());
}

// number-optional-number
//
// https://www.w3.org/TR/SVG/types.html#DataTypeNumberOptionalNumber

pub fn number_optional_number (s: &str) -> Result <(f64, f64), ParseError> {
    let mut input = ParserInput::new (s);
    let mut parser = Parser::new (&mut input);

    let x = f64::from(parser.expect_number ()?);

    if !parser.is_exhausted () {
        let state = parser.state ();

        match *parser.next ()? {
            Token::Comma => {},
            _ => parser.reset (&state)
        };

        let y = f64::from(parser.expect_number ()?);

        parser.expect_exhausted ()?;

        Ok ((x, y))
    } else {
        Ok ((x, x))
    }
}

#[no_mangle]
pub extern fn rsvg_css_parse_number_optional_number (s: *const libc::c_char,
                                                     out_x: *mut f64,
                                                     out_y: *mut f64) -> glib_sys::gboolean {
    assert! (!s.is_null ());
    assert! (!out_x.is_null ());
    assert! (!out_y.is_null ());

    let string = unsafe { utf8_cstr(s) };

    match number_optional_number (string) {
        Ok ((x, y)) => {
            unsafe {
                *out_x = x;
                *out_y = y;
            }
            true
        },

        Err (_) => {
            unsafe {
                *out_x = 0.0;
                *out_y = 0.0;
            }
            false
        }
    }.to_glib ()
}

// Parse a list-of-points as for polyline and polygon elements
// https://www.w3.org/TR/SVG/shapes.html#PointsBNF

pub fn list_of_points (string: &str) -> Result <Vec<(f64, f64)>, ParseError> {
    let mut input = ParserInput::new (string);
    let mut parser = Parser::new (&mut input);

    let mut v = Vec::new ();

    loop {
        let x = f64::from(parser.expect_number ()?);

        optional_comma (&mut parser);

        let y = f64::from(parser.expect_number ()?);

        v.push ((x, y));

        if parser.is_exhausted () {
            break;
        }

        match parser.next_including_whitespace () {
            Ok (&Token::WhiteSpace(_)) => (),
            _ => optional_comma (&mut parser)
        }
    }

    Ok (v)
}

// Lists of number values

pub enum ListLength {
    Exact (usize),
    Maximum (usize)
}

#[derive(Debug, PartialEq)]
pub enum NumberListError {
    IncorrectNumberOfElements,
    Parse (ParseError)
}

pub fn number_list (s: &str, length: ListLength) -> Result <Vec<f64>, NumberListError> {
    let n;

    match length {
        ListLength::Exact (l)   => { assert! (l > 0); n = l; }
        ListLength::Maximum (l) => { assert! (l > 0); n = l; }
    }

    let mut input = ParserInput::new (s);
    let mut parser = Parser::new (&mut input);

    let mut v = Vec::<f64>::with_capacity (n);

    for i in 0..n {
        v.push (f64::from(parser.expect_number ().map_err (|_| NumberListError::Parse (ParseError::new ("expected number")))?));

        if i != n - 1 {
            optional_comma (&mut parser);
        }

        if parser.is_exhausted () {
            if let ListLength::Maximum (_) = length {
                break;
            }
        }
    }

    parser.expect_exhausted ().map_err (|_| NumberListError::IncorrectNumberOfElements)?;

    Ok(v)
}

#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum NumberListLength {
    Exact,
    Maximum
}

#[no_mangle]
pub extern fn rsvg_css_parse_number_list (in_str:   *const libc::c_char,
                                          nlength:  NumberListLength,
                                          size:     libc::size_t,
                                          out_list: *mut *const libc::c_double,
                                          out_list_length: *mut libc::size_t) -> glib_sys::gboolean {
    assert! (!in_str.is_null ());
    assert! (!out_list.is_null ());
    assert! (!out_list_length.is_null ());

    let length = match nlength {
        NumberListLength::Exact   => ListLength::Exact (size),
        NumberListLength::Maximum => ListLength::Maximum (size)
    };

    let s = unsafe { String::from_glib_none (in_str) };

    let result = number_list (&s, length);

    match result {
        Ok (number_list) => {
            let num_elems = number_list.len ();

            let c_array = unsafe {
                glib_sys::g_malloc_n (num_elems,
                                      mem::size_of::<libc::c_double> ())
                    as *mut libc::c_double
            };

            let array = unsafe { slice::from_raw_parts_mut (c_array, num_elems) };

            array.copy_from_slice (&number_list);

            unsafe {
                *out_list = c_array;
                *out_list_length = num_elems;
            }

            true
        },

        Err (_) => {
            unsafe {
                *out_list = ptr::null ();
                *out_list_length = 0;
            }
            false
        }
    }.to_glib ()
}

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

    #[test]
    fn parses_number_optional_number () {
        assert_eq! (number_optional_number ("1, 2"), Ok ((1.0, 2.0)));
        assert_eq! (number_optional_number ("1 2"),  Ok ((1.0, 2.0)));
        assert_eq! (number_optional_number ("1"),    Ok ((1.0, 1.0)));

        assert_eq! (number_optional_number ("-1, -2"), Ok ((-1.0, -2.0)));
        assert_eq! (number_optional_number ("-1 -2"),  Ok ((-1.0, -2.0)));
        assert_eq! (number_optional_number ("-1"),     Ok ((-1.0, -1.0)));
    }

    #[test]
    fn invalid_number_optional_number () {
        assert! (number_optional_number ("").is_err ());
        assert! (number_optional_number ("1x").is_err ());
        assert! (number_optional_number ("x1").is_err ());
        assert! (number_optional_number ("1 x").is_err ());
        assert! (number_optional_number ("1 , x").is_err ());
        assert! (number_optional_number ("1 , 2x").is_err ());
        assert! (number_optional_number ("1 2 x").is_err ());
    }

    #[test]
    fn parses_list_of_points () {
        assert_eq! (list_of_points (" 1 2 "),      Ok (vec! [(1.0, 2.0)]));
        assert_eq! (list_of_points ("1 2 3 4"),  Ok (vec! [(1.0, 2.0), (3.0, 4.0)]));
        assert_eq! (list_of_points ("1,2,3,4"),  Ok (vec! [(1.0, 2.0), (3.0, 4.0)]));
        assert_eq! (list_of_points ("1,2 3,4"),  Ok (vec! [(1.0, 2.0), (3.0, 4.0)]));
        assert_eq! (list_of_points ("1,2 -3,4"), Ok (vec! [(1.0, 2.0), (-3.0, 4.0)]));
        assert_eq! (list_of_points ("1,2,-3,4"), Ok (vec! [(1.0, 2.0), (-3.0, 4.0)]));
    }

    #[test]
    fn errors_on_invalid_list_of_points () {
        assert! (list_of_points ("-1-2-3-4").is_err ());
        assert! (list_of_points ("1 2-3,-4").is_err ());
    }

    #[test]
    fn parses_angle () {
        assert_eq! (angle_degrees ("0"),        Ok (0.0));
        assert_eq! (angle_degrees ("15"),       Ok (15.0));
        assert_eq! (angle_degrees ("180.5deg"), Ok (180.5));
        assert_eq! (angle_degrees ("1rad"),     Ok (180.0 / PI));
        assert_eq! (angle_degrees ("-400grad"), Ok (-360.0));

        assert! (angle_degrees ("").is_err ());
        assert! (angle_degrees ("foo").is_err ());
        assert! (angle_degrees ("300foo").is_err ());
    }

    #[test]
    fn parses_number_list () {
        assert_eq! (number_list ("5", ListLength::Exact (1)),
                    Ok (vec! [5.0]));

        assert_eq! (number_list ("1 2 3 4", ListLength::Exact (4)),
                    Ok (vec! [1.0, 2.0, 3.0, 4.0]));

        assert_eq! (number_list ("5", ListLength::Maximum (1)),
                    Ok (vec! [5.0]));

        assert_eq! (number_list ("1.0, -2.5", ListLength::Maximum (2)),
                    Ok (vec! [1.0, -2.5]));

        assert_eq! (number_list ("5 6", ListLength::Maximum (3)),
                    Ok (vec! [5.0, 6.0]));
    }

    #[test]
    fn errors_on_invalid_number_list () {
        // empty
        assert! (number_list ("", ListLength::Exact (1)).is_err ());

        // garbage
        assert! (number_list ("foo", ListLength::Exact (1)).is_err ());
        assert! (number_list ("1foo", ListLength::Exact (2)).is_err ());
        assert! (number_list ("1 foo", ListLength::Exact (2)).is_err ());
        assert! (number_list ("1 foo 2", ListLength::Exact (2)).is_err ());
        assert! (number_list ("1,foo", ListLength::Exact (2)).is_err ());

        // too many
        assert! (number_list ("1 2", ListLength::Exact (1)).is_err ());
        assert! (number_list ("1,2,3", ListLength::Maximum (2)).is_err ());

        // extra token
        assert! (number_list ("1,", ListLength::Exact (1)).is_err ());

        // too few
        assert! (number_list ("1", ListLength::Exact (2)).is_err ());
        assert! (number_list ("1 2", ListLength::Exact (3)).is_err ());
    }
}
<commit_msg>rsvg_css_parse_number_list(): Don't copy the incoming string<commit_after>use ::libc;
use ::cssparser::{Parser, ParserInput, Token, BasicParseError};
use ::glib::translate::*;
use ::glib_sys;

use std::f64::consts::*;
use std::mem;
use std::ptr;
use std::slice;
use std::str;

use util::utf8_cstr;

#[derive(Debug, Clone, PartialEq)]
pub struct ParseError {
    pub display: String
}

impl ParseError {
    pub fn new<T: AsRef<str>> (msg: T) -> ParseError {
        ParseError { display: msg.as_ref ().to_string () }
    }
}

impl<'a> From<BasicParseError<'a>> for ParseError {
    fn from (_: BasicParseError) -> ParseError {
        ParseError::new ("parse error")
    }
}

pub trait Parse: Sized {
    type Data;
    type Err;

    fn parse (s: &str, data: Self::Data) -> Result<Self, Self::Err>;
}

// angle:
// https://www.w3.org/TR/SVG/types.html#DataTypeAngle
//
// angle ::= number ("deg" | "grad" | "rad")?
//
// Returns an f64 angle in degrees

pub fn angle_degrees (s: &str) -> Result <f64, ParseError> {
    let mut input = ParserInput::new (s);
    let mut parser = Parser::new (&mut input);

    let angle = {
        let token = parser.next ()
            .map_err (|_| ParseError::new ("expected angle"))?;

        match *token {
            Token::Number { value, .. } => f64::from(value),

            Token::Dimension { value, ref unit, .. } => {
                let value = f64::from(value);

                match unit.as_ref () {
                    "deg"  => value,
                    "grad" => value * 360.0 / 400.0,
                    "rad"  => value * 180.0 / PI,
                    _      => return Err (ParseError::new ("expected 'deg' | 'grad' | 'rad'"))
                }
            },

            _ => return Err (ParseError::new ("expected angle"))
        }
    };

    parser.expect_exhausted ().map_err (|_| ParseError::new ("expected angle"))?;

    Ok (angle)
}

pub fn optional_comma (parser: &mut Parser) {
    let _ = parser.try (|p| p.expect_comma ());
}

// number-optional-number
//
// https://www.w3.org/TR/SVG/types.html#DataTypeNumberOptionalNumber

pub fn number_optional_number (s: &str) -> Result <(f64, f64), ParseError> {
    let mut input = ParserInput::new (s);
    let mut parser = Parser::new (&mut input);

    let x = f64::from(parser.expect_number ()?);

    if !parser.is_exhausted () {
        let state = parser.state ();

        match *parser.next ()? {
            Token::Comma => {},
            _ => parser.reset (&state)
        };

        let y = f64::from(parser.expect_number ()?);

        parser.expect_exhausted ()?;

        Ok ((x, y))
    } else {
        Ok ((x, x))
    }
}

#[no_mangle]
pub extern fn rsvg_css_parse_number_optional_number (s: *const libc::c_char,
                                                     out_x: *mut f64,
                                                     out_y: *mut f64) -> glib_sys::gboolean {
    assert! (!s.is_null ());
    assert! (!out_x.is_null ());
    assert! (!out_y.is_null ());

    let string = unsafe { utf8_cstr(s) };

    match number_optional_number (string) {
        Ok ((x, y)) => {
            unsafe {
                *out_x = x;
                *out_y = y;
            }
            true
        },

        Err (_) => {
            unsafe {
                *out_x = 0.0;
                *out_y = 0.0;
            }
            false
        }
    }.to_glib ()
}

// Parse a list-of-points as for polyline and polygon elements
// https://www.w3.org/TR/SVG/shapes.html#PointsBNF

pub fn list_of_points (string: &str) -> Result <Vec<(f64, f64)>, ParseError> {
    let mut input = ParserInput::new (string);
    let mut parser = Parser::new (&mut input);

    let mut v = Vec::new ();

    loop {
        let x = f64::from(parser.expect_number ()?);

        optional_comma (&mut parser);

        let y = f64::from(parser.expect_number ()?);

        v.push ((x, y));

        if parser.is_exhausted () {
            break;
        }

        match parser.next_including_whitespace () {
            Ok (&Token::WhiteSpace(_)) => (),
            _ => optional_comma (&mut parser)
        }
    }

    Ok (v)
}

// Lists of number values

pub enum ListLength {
    Exact (usize),
    Maximum (usize)
}

#[derive(Debug, PartialEq)]
pub enum NumberListError {
    IncorrectNumberOfElements,
    Parse (ParseError)
}

pub fn number_list (s: &str, length: ListLength) -> Result <Vec<f64>, NumberListError> {
    let n;

    match length {
        ListLength::Exact (l)   => { assert! (l > 0); n = l; }
        ListLength::Maximum (l) => { assert! (l > 0); n = l; }
    }

    let mut input = ParserInput::new (s);
    let mut parser = Parser::new (&mut input);

    let mut v = Vec::<f64>::with_capacity (n);

    for i in 0..n {
        v.push (f64::from(parser.expect_number ().map_err (|_| NumberListError::Parse (ParseError::new ("expected number")))?));

        if i != n - 1 {
            optional_comma (&mut parser);
        }

        if parser.is_exhausted () {
            if let ListLength::Maximum (_) = length {
                break;
            }
        }
    }

    parser.expect_exhausted ().map_err (|_| NumberListError::IncorrectNumberOfElements)?;

    Ok(v)
}

#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum NumberListLength {
    Exact,
    Maximum
}

#[no_mangle]
pub extern fn rsvg_css_parse_number_list (in_str:   *const libc::c_char,
                                          nlength:  NumberListLength,
                                          size:     libc::size_t,
                                          out_list: *mut *const libc::c_double,
                                          out_list_length: *mut libc::size_t) -> glib_sys::gboolean {
    assert! (!in_str.is_null ());
    assert! (!out_list.is_null ());
    assert! (!out_list_length.is_null ());

    let length = match nlength {
        NumberListLength::Exact   => ListLength::Exact (size),
        NumberListLength::Maximum => ListLength::Maximum (size)
    };

    let s = unsafe { utf8_cstr(in_str) };

    let result = number_list (&s, length);

    match result {
        Ok (number_list) => {
            let num_elems = number_list.len ();

            let c_array = unsafe {
                glib_sys::g_malloc_n (num_elems,
                                      mem::size_of::<libc::c_double> ())
                    as *mut libc::c_double
            };

            let array = unsafe { slice::from_raw_parts_mut (c_array, num_elems) };

            array.copy_from_slice (&number_list);

            unsafe {
                *out_list = c_array;
                *out_list_length = num_elems;
            }

            true
        },

        Err (_) => {
            unsafe {
                *out_list = ptr::null ();
                *out_list_length = 0;
            }
            false
        }
    }.to_glib ()
}

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

    #[test]
    fn parses_number_optional_number () {
        assert_eq! (number_optional_number ("1, 2"), Ok ((1.0, 2.0)));
        assert_eq! (number_optional_number ("1 2"),  Ok ((1.0, 2.0)));
        assert_eq! (number_optional_number ("1"),    Ok ((1.0, 1.0)));

        assert_eq! (number_optional_number ("-1, -2"), Ok ((-1.0, -2.0)));
        assert_eq! (number_optional_number ("-1 -2"),  Ok ((-1.0, -2.0)));
        assert_eq! (number_optional_number ("-1"),     Ok ((-1.0, -1.0)));
    }

    #[test]
    fn invalid_number_optional_number () {
        assert! (number_optional_number ("").is_err ());
        assert! (number_optional_number ("1x").is_err ());
        assert! (number_optional_number ("x1").is_err ());
        assert! (number_optional_number ("1 x").is_err ());
        assert! (number_optional_number ("1 , x").is_err ());
        assert! (number_optional_number ("1 , 2x").is_err ());
        assert! (number_optional_number ("1 2 x").is_err ());
    }

    #[test]
    fn parses_list_of_points () {
        assert_eq! (list_of_points (" 1 2 "),      Ok (vec! [(1.0, 2.0)]));
        assert_eq! (list_of_points ("1 2 3 4"),  Ok (vec! [(1.0, 2.0), (3.0, 4.0)]));
        assert_eq! (list_of_points ("1,2,3,4"),  Ok (vec! [(1.0, 2.0), (3.0, 4.0)]));
        assert_eq! (list_of_points ("1,2 3,4"),  Ok (vec! [(1.0, 2.0), (3.0, 4.0)]));
        assert_eq! (list_of_points ("1,2 -3,4"), Ok (vec! [(1.0, 2.0), (-3.0, 4.0)]));
        assert_eq! (list_of_points ("1,2,-3,4"), Ok (vec! [(1.0, 2.0), (-3.0, 4.0)]));
    }

    #[test]
    fn errors_on_invalid_list_of_points () {
        assert! (list_of_points ("-1-2-3-4").is_err ());
        assert! (list_of_points ("1 2-3,-4").is_err ());
    }

    #[test]
    fn parses_angle () {
        assert_eq! (angle_degrees ("0"),        Ok (0.0));
        assert_eq! (angle_degrees ("15"),       Ok (15.0));
        assert_eq! (angle_degrees ("180.5deg"), Ok (180.5));
        assert_eq! (angle_degrees ("1rad"),     Ok (180.0 / PI));
        assert_eq! (angle_degrees ("-400grad"), Ok (-360.0));

        assert! (angle_degrees ("").is_err ());
        assert! (angle_degrees ("foo").is_err ());
        assert! (angle_degrees ("300foo").is_err ());
    }

    #[test]
    fn parses_number_list () {
        assert_eq! (number_list ("5", ListLength::Exact (1)),
                    Ok (vec! [5.0]));

        assert_eq! (number_list ("1 2 3 4", ListLength::Exact (4)),
                    Ok (vec! [1.0, 2.0, 3.0, 4.0]));

        assert_eq! (number_list ("5", ListLength::Maximum (1)),
                    Ok (vec! [5.0]));

        assert_eq! (number_list ("1.0, -2.5", ListLength::Maximum (2)),
                    Ok (vec! [1.0, -2.5]));

        assert_eq! (number_list ("5 6", ListLength::Maximum (3)),
                    Ok (vec! [5.0, 6.0]));
    }

    #[test]
    fn errors_on_invalid_number_list () {
        // empty
        assert! (number_list ("", ListLength::Exact (1)).is_err ());

        // garbage
        assert! (number_list ("foo", ListLength::Exact (1)).is_err ());
        assert! (number_list ("1foo", ListLength::Exact (2)).is_err ());
        assert! (number_list ("1 foo", ListLength::Exact (2)).is_err ());
        assert! (number_list ("1 foo 2", ListLength::Exact (2)).is_err ());
        assert! (number_list ("1,foo", ListLength::Exact (2)).is_err ());

        // too many
        assert! (number_list ("1 2", ListLength::Exact (1)).is_err ());
        assert! (number_list ("1,2,3", ListLength::Maximum (2)).is_err ());

        // extra token
        assert! (number_list ("1,", ListLength::Exact (1)).is_err ());

        // too few
        assert! (number_list ("1", ListLength::Exact (2)).is_err ());
        assert! (number_list ("1 2", ListLength::Exact (3)).is_err ());
    }
}
<|endoftext|>


<commit_before>use super::header::{FromHeader,HeaderMap};
use super::rfc5322::Rfc5322Parser;
use super::rfc2045::Rfc2045Parser;

use std::collections::HashMap;

/// Content-Type string, major/minor as the first and second elements
/// respectively.
pub type MimeContentType = (String, String);

/// Special header type for the Content-Type header.
pub struct MimeContentTypeHeader {
    pub content_type: MimeContentType,
    pub params: HashMap<String, String>,
}

impl FromHeader for MimeContentTypeHeader {
    fn from_header(value: String) -> Option<MimeContentTypeHeader> {
        let mut parser = Rfc2045Parser::new(value.as_slice());
        let (value, params) = parser.consume_all();

        let mime_parts: Vec<&str> = value.as_slice().splitn(2, '/').collect();

        if mime_parts.len() == 2 {
            Some(MimeContentTypeHeader {
                content_type: (mime_parts[0].to_string(), mime_parts[1].to_string()),
                params: params
            })
        } else {
            None
        }
    }
}

/// Represents the common data of a MIME message
#[deriving(Show)]
pub struct MimeMessageData {
    /// The headers for this message
    pub headers: HeaderMap,
    /// The content of this message
    pub body: String
}

/// Marks the type of a multipart message
#[deriving(Eq,PartialEq,Show)]
pub enum MimeMultipartType {
    /// Entries which are independent.
    ///
    /// This value is the default.
    ///
    /// As defined by Section 5.1.3 of RFC 2046
    MimeMultipartMixed,
    /// Entries which are interchangeable, such that the system can choose
    /// whichever is "best" for its use.
    ///
    /// As defined by Section 5.1.4 of RFC 2046
    MimeMultipartAlternate,
    /// Entries are (typically) a collection of messages.
    ///
    /// As defined by Section 5.1.5 of RFC 2046
    MimeMultipartDigest,
    /// Entry order does not matter, and could be displayed simultaneously.
    ///
    /// As defined by Section 5.1.6 of RFC 2046
    MimeMultipartParallel,
}

impl MimeMultipartType {
    /// Returns the appropriate `MimeMultipartType` for the given MimeContentType
    pub fn from_content_type(ct: MimeContentType) -> MimeMultipartType {
        let (major, minor) = ct;
        match (major.as_slice(), minor.as_slice()) {
            ("multipart", "alternate") => MimeMultipartAlternate,
            ("multipart", "digest") => MimeMultipartDigest,
            ("multipart", "parallel") => MimeMultipartParallel,
            ("multipart", "mixed") | ("multipart", _) => MimeMultipartMixed,
            _ => fail!("ContentType is not multipart"),
        }
    }

    pub fn to_content_type(&self) -> MimeContentType {
        let multipart = "multipart".to_string();
        match *self {
            MimeMultipartMixed => (multipart, "mixed".to_string()),
            MimeMultipartAlternate => (multipart, "alternate".to_string()),
            MimeMultipartDigest => (multipart, "digest".to_string()),
            MimeMultipartParallel => (multipart, "parallel".to_string()),
        }
    }
}

/// Enum type over the different types of multipart message.
#[deriving(Show)]
pub enum MimeMessage {
    /// This message is made of multiple sub parts.
    ///
    /// The `body` of MimeMessageData is the content of the message between
    /// the final header and the first boundary.
    MimeMultipart(MimeMessageData, MimeMultipartType, Vec<MimeMessage>),
    /// A simple non-multipart message.
    MimeNonMultipart(MimeMessageData),
}

impl MimeMessage {
    /// Get a reference to the headers for this message.
    pub fn headers(&self) -> &HeaderMap {
        match *self {
            MimeMultipart(ref data, _, _) => &data.headers,
            MimeNonMultipart(ref data) => &data.headers,
        }
    }

    // Make a message from a header map and body, parsing out any multi-part
    // messages that are discovered by looking at the Content-Type header.
    fn from_headers(headers: HeaderMap, body: String) -> Option<MimeMessage> {
        let content_type = {
            let header =  headers.get("Content-Type".to_string());
            match header {
                Some(h) => h.get_value(),
                None => Some(MimeContentTypeHeader{
                    content_type: ("text".to_string(), "plain".to_string()),
                    params: HashMap::new(),
                })
            }
        };

        if content_type.is_none() {
            // If we failed to parse the Content-Type header, something went wrong, so bail.
            None
        } else {
            let content_type = content_type.unwrap();
            // Pull out the major mime type and the boundary (if it exists)
            let (mime_type, sub_mime_type) = content_type.content_type;
            let boundary = content_type.params.find(&"boundary".to_string());

            let message = match mime_type.as_slice() {
                // Only consider a multipart message if we have a boundary, otherwise don't
                // bother and just assume it's a single message.
                "multipart" if boundary.is_some() => {
                    let boundary = boundary.unwrap();
                    // Pull apart the message on the boundary.
                    let mut parts = MimeMessage::split_boundary(body, boundary.clone());
                    // Pop off the first message, as it's part of the parent.
                    let body = parts.remove(0).unwrap_or("".to_string());
                    // Parse out each of the child parts, recursively downwards.
                    // Filtering out and unwrapping None as we go.
                    let message_parts: Vec<MimeMessage> = parts.iter()
                                                               .map(|part| { MimeMessage::parse(part.as_slice()) })
                                                               .filter(|part| { part.is_some() })
                                                               .map(|part| { part.unwrap() })
                                                               .collect();
                    let data = MimeMessageData {
                        headers: headers,
                        body: body,
                    };
                    let multipart_type = MimeMultipartType::from_content_type((mime_type, sub_mime_type));
                    MimeMultipart(data, multipart_type, message_parts)
                },
                _ => {
                    // Boring message, bung the headers & body together and return.
                    let data = MimeMessageData {
                        headers: headers,
                        body: body,
                    };
                    MimeNonMultipart(data)
                },
            };

            Some(message)
        }
    }

    // Split `body` up on the `boundary` string.
    fn split_boundary(body: String, boundary: String) -> Vec<String> {
        let mut lines = body.as_slice().split('\n');

        let mut parts = Vec::new();
        let mut current_part = String::new();

        for line in lines {
            let mut is_boundary = false;
            if line.starts_with("--") {
                let boundary_value = line.slice_from(2).trim_right();
                is_boundary = boundary_value.to_string() == boundary;
            }
            if is_boundary {
                // Clip off the final \r\n
                parts.push(current_part);
                current_part = String::new()
            } else {
                current_part.push_str(line);
                current_part.push_str("\n");
            }
        }

        if current_part.len() > 0 {
            // Push what remains as the last message part
            current_part.pop(); // Clear the final \n that we put in
            parts.push(current_part);
        }

        parts
    }

    /// Parse `s` into a MimeMessage.
    ///
    /// Recurses down into each message, supporting an unlimited depth of messages.
    ///
    /// Be warned that each sub-message that fails to be parsed will be thrown away.
    pub fn parse(s: &str) -> Option<MimeMessage> {
        let mut parser = Rfc5322Parser::new(s);
        match parser.consume_message() {
            Some((headers, body)) => MimeMessage::from_headers(headers, body),
            None => None,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use super::super::header::{Header,HeaderMap};

    #[deriving(Show)]
    struct MessageTestResult<'s> {
        headers: Vec<(&'s str, &'s str)>,
        body: &'s str,
        children: Option<Vec<MessageTestResult<'s>>>,
    }

    impl<'s> Equiv<MimeMessage> for MessageTestResult<'s> {
        fn equiv(&self, other: &MimeMessage) -> bool {
            let mut headers = HeaderMap::new();
            for &(name, value) in self.headers.iter() {
                let header = Header::new(name.to_string(), value.to_string());
                headers.insert(header);
            }

            match (&self.children, other) {
                (&Some(ref our_messages), &MimeMultipart(ref data, _, ref other_messages)) => {
                    let mut children_match = true;
                    for (index, child) in our_messages.iter().enumerate() {
                        if !child.equiv(&other_messages[index]) {
                            children_match = false;
                            break;
                        }
                    }
                    let header_match = headers == data.headers;
                    let body_match = self.body.to_string() == data.body;

                    if !children_match {
                        println!("Children do not match!");
                    }
                    if !header_match {
                        println!("Headers do not match!");
                    }
                    if !body_match {
                        println!("Body does not match! ({} != {})", self.body, data.body);
                    }

                    header_match && body_match && children_match
                },
                (&None, &MimeNonMultipart(ref data)) => {
                    let header_match = headers == data.headers;
                    let body_match = self.body.to_string() == data.body;

                    if !header_match {
                        println!("Headers do not match!");
                    }
                    if !body_match {
                        println!("Body does not match! ({} != {})", self.body, data.body);
                    }

                    header_match && body_match
                },
                (_, _) => {
                    println!("Expected different message type than what was given");
                    false
                },
            }
        }
    }

    struct ParseTest<'s> {
        input: &'s str,
        output: Option<MessageTestResult<'s>>,
        name: &'s str,
    }

    #[test]
    fn test_message_parse() {
        let tests = vec![
            ParseTest {
                input: "From: joe@example.org\r\nTo: john@example.org\r\n\r\nHello!",
                output: Some(MessageTestResult {
                    headers: vec![
                        ("From", "joe@example.org"),
                        ("To", "john@example.org"),
                    ],
                    body: "Hello!",
                    children: None,
                }),
                name: "Simple single part message parse",
            },

            ParseTest {
                input: "From: joe@example.org\r\n\
                        To: john@example.org\r\n\
                        Content-Type: multipart/alternate; boundary=foo\r\n\
                        \r\n\
                        Parent\r\n\
                        --foo\r\n\
                        Hello!\r\n\
                        --foo\r\n\
                        Other\r\n",
                output: Some(MessageTestResult {
                    headers: vec![
                        ("From", "joe@example.org"),
                        ("To", "john@example.org"),
                        ("Content-Type", "multipart/alternate; boundary=foo"),
                    ],
                    body: "Parent\r\n",
                    children: Some(vec![
                        MessageTestResult {
                            headers: vec![ ],
                            body: "Hello!\r\n",
                            children: None,
                        },
                        MessageTestResult {
                            headers: vec![ ],
                            body: "Other\r\n",
                            children: None,
                        },
                    ]),
                }),
                name: "Simple multipart message parse",
            },
        ];

        for test in tests.into_iter() {
            println!("--- Next test: {}", test.name);
            let message = MimeMessage::parse(test.input);
            let result = match (test.output, message) {
                (Some(ref expected), Some(ref given)) => expected.equiv(given),
                (None, None) => true,
                (_, _) => false,
            };
            assert!(result, test.name);
        }
    }

    #[test]
    fn test_multipart_type_type_parsing() {
        let multipart = "multipart".to_string();
        assert_eq!(MimeMultipartType::from_content_type((multipart.clone(), "mixed".to_string())), MimeMultipartMixed);
        assert_eq!(MimeMultipartType::from_content_type((multipart.clone(), "alternate".to_string())), MimeMultipartAlternate);
        assert_eq!(MimeMultipartType::from_content_type((multipart.clone(), "digest".to_string())), MimeMultipartDigest);
        assert_eq!(MimeMultipartType::from_content_type((multipart.clone(), "parallel".to_string())), MimeMultipartParallel);

        // Test failback onto multipart/mixed
        assert_eq!(MimeMultipartType::from_content_type((multipart.clone(), "potato".to_string())), MimeMultipartMixed);
    }

    #[test]
    fn test_multipart_type_to_content_type() {
        let multipart = "multipart".to_string();

        assert_eq!(MimeMultipartMixed.to_content_type(),     (multipart.clone(), "mixed".to_string()));
        assert_eq!(MimeMultipartAlternate.to_content_type(), (multipart.clone(), "alternate".to_string()));
        assert_eq!(MimeMultipartDigest.to_content_type(),    (multipart.clone(), "digest".to_string()));
        assert_eq!(MimeMultipartParallel.to_content_type(),  (multipart.clone(), "parallel".to_string()));
    }
}
<commit_msg>Add multi-level multipart test.<commit_after>use super::header::{FromHeader,HeaderMap};
use super::rfc5322::Rfc5322Parser;
use super::rfc2045::Rfc2045Parser;

use std::collections::HashMap;

/// Content-Type string, major/minor as the first and second elements
/// respectively.
pub type MimeContentType = (String, String);

/// Special header type for the Content-Type header.
pub struct MimeContentTypeHeader {
    pub content_type: MimeContentType,
    pub params: HashMap<String, String>,
}

impl FromHeader for MimeContentTypeHeader {
    fn from_header(value: String) -> Option<MimeContentTypeHeader> {
        let mut parser = Rfc2045Parser::new(value.as_slice());
        let (value, params) = parser.consume_all();

        let mime_parts: Vec<&str> = value.as_slice().splitn(2, '/').collect();

        if mime_parts.len() == 2 {
            Some(MimeContentTypeHeader {
                content_type: (mime_parts[0].to_string(), mime_parts[1].to_string()),
                params: params
            })
        } else {
            None
        }
    }
}

/// Represents the common data of a MIME message
#[deriving(Show)]
pub struct MimeMessageData {
    /// The headers for this message
    pub headers: HeaderMap,
    /// The content of this message
    pub body: String
}

/// Marks the type of a multipart message
#[deriving(Eq,PartialEq,Show)]
pub enum MimeMultipartType {
    /// Entries which are independent.
    ///
    /// This value is the default.
    ///
    /// As defined by Section 5.1.3 of RFC 2046
    MimeMultipartMixed,
    /// Entries which are interchangeable, such that the system can choose
    /// whichever is "best" for its use.
    ///
    /// As defined by Section 5.1.4 of RFC 2046
    MimeMultipartAlternate,
    /// Entries are (typically) a collection of messages.
    ///
    /// As defined by Section 5.1.5 of RFC 2046
    MimeMultipartDigest,
    /// Entry order does not matter, and could be displayed simultaneously.
    ///
    /// As defined by Section 5.1.6 of RFC 2046
    MimeMultipartParallel,
}

impl MimeMultipartType {
    /// Returns the appropriate `MimeMultipartType` for the given MimeContentType
    pub fn from_content_type(ct: MimeContentType) -> MimeMultipartType {
        let (major, minor) = ct;
        match (major.as_slice(), minor.as_slice()) {
            ("multipart", "alternate") => MimeMultipartAlternate,
            ("multipart", "digest") => MimeMultipartDigest,
            ("multipart", "parallel") => MimeMultipartParallel,
            ("multipart", "mixed") | ("multipart", _) => MimeMultipartMixed,
            _ => fail!("ContentType is not multipart"),
        }
    }

    pub fn to_content_type(&self) -> MimeContentType {
        let multipart = "multipart".to_string();
        match *self {
            MimeMultipartMixed => (multipart, "mixed".to_string()),
            MimeMultipartAlternate => (multipart, "alternate".to_string()),
            MimeMultipartDigest => (multipart, "digest".to_string()),
            MimeMultipartParallel => (multipart, "parallel".to_string()),
        }
    }
}

/// Enum type over the different types of multipart message.
#[deriving(Show)]
pub enum MimeMessage {
    /// This message is made of multiple sub parts.
    ///
    /// The `body` of MimeMessageData is the content of the message between
    /// the final header and the first boundary.
    MimeMultipart(MimeMessageData, MimeMultipartType, Vec<MimeMessage>),
    /// A simple non-multipart message.
    MimeNonMultipart(MimeMessageData),
}

impl MimeMessage {
    /// Get a reference to the headers for this message.
    pub fn headers(&self) -> &HeaderMap {
        match *self {
            MimeMultipart(ref data, _, _) => &data.headers,
            MimeNonMultipart(ref data) => &data.headers,
        }
    }

    // Make a message from a header map and body, parsing out any multi-part
    // messages that are discovered by looking at the Content-Type header.
    fn from_headers(headers: HeaderMap, body: String) -> Option<MimeMessage> {
        let content_type = {
            let header =  headers.get("Content-Type".to_string());
            match header {
                Some(h) => h.get_value(),
                None => Some(MimeContentTypeHeader{
                    content_type: ("text".to_string(), "plain".to_string()),
                    params: HashMap::new(),
                })
            }
        };

        if content_type.is_none() {
            // If we failed to parse the Content-Type header, something went wrong, so bail.
            None
        } else {
            let content_type = content_type.unwrap();
            // Pull out the major mime type and the boundary (if it exists)
            let (mime_type, sub_mime_type) = content_type.content_type;
            let boundary = content_type.params.find(&"boundary".to_string());

            let message = match mime_type.as_slice() {
                // Only consider a multipart message if we have a boundary, otherwise don't
                // bother and just assume it's a single message.
                "multipart" if boundary.is_some() => {
                    let boundary = boundary.unwrap();
                    // Pull apart the message on the boundary.
                    let mut parts = MimeMessage::split_boundary(body, boundary.clone());
                    // Pop off the first message, as it's part of the parent.
                    let body = parts.remove(0).unwrap_or("".to_string());
                    // Parse out each of the child parts, recursively downwards.
                    // Filtering out and unwrapping None as we go.
                    let message_parts: Vec<MimeMessage> = parts.iter()
                                                               .map(|part| { MimeMessage::parse(part.as_slice()) })
                                                               .filter(|part| { part.is_some() })
                                                               .map(|part| { part.unwrap() })
                                                               .collect();
                    let data = MimeMessageData {
                        headers: headers,
                        body: body,
                    };
                    let multipart_type = MimeMultipartType::from_content_type((mime_type, sub_mime_type));
                    MimeMultipart(data, multipart_type, message_parts)
                },
                _ => {
                    // Boring message, bung the headers & body together and return.
                    let data = MimeMessageData {
                        headers: headers,
                        body: body,
                    };
                    MimeNonMultipart(data)
                },
            };

            Some(message)
        }
    }

    // Split `body` up on the `boundary` string.
    fn split_boundary(body: String, boundary: String) -> Vec<String> {
        let mut lines = body.as_slice().split('\n');

        let mut parts = Vec::new();
        let mut current_part = String::new();

        for line in lines {
            let mut is_boundary = false;
            if line.starts_with("--") {
                let boundary_value = line.slice_from(2).trim_right();
                is_boundary = boundary_value.to_string() == boundary;
            }
            if is_boundary {
                // Clip off the final \r\n
                parts.push(current_part);
                current_part = String::new()
            } else {
                current_part.push_str(line);
                current_part.push_str("\n");
            }
        }

        if current_part.len() > 0 {
            // Push what remains as the last message part
            current_part.pop(); // Clear the final \n that we put in
            parts.push(current_part);
        }

        parts
    }

    /// Parse `s` into a MimeMessage.
    ///
    /// Recurses down into each message, supporting an unlimited depth of messages.
    ///
    /// Be warned that each sub-message that fails to be parsed will be thrown away.
    pub fn parse(s: &str) -> Option<MimeMessage> {
        let mut parser = Rfc5322Parser::new(s);
        match parser.consume_message() {
            Some((headers, body)) => MimeMessage::from_headers(headers, body),
            None => None,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use super::super::header::{Header,HeaderMap};

    #[deriving(Show)]
    struct MessageTestResult<'s> {
        headers: Vec<(&'s str, &'s str)>,
        body: &'s str,
        children: Option<Vec<MessageTestResult<'s>>>,
    }

    impl<'s> Equiv<MimeMessage> for MessageTestResult<'s> {
        fn equiv(&self, other: &MimeMessage) -> bool {
            let mut headers = HeaderMap::new();
            for &(name, value) in self.headers.iter() {
                let header = Header::new(name.to_string(), value.to_string());
                headers.insert(header);
            }

            match (&self.children, other) {
                (&Some(ref our_messages), &MimeMultipart(ref data, _, ref other_messages)) => {
                    let mut children_match = true;
                    for (index, child) in our_messages.iter().enumerate() {
                        if !child.equiv(&other_messages[index]) {
                            children_match = false;
                            break;
                        }
                    }
                    let header_match = headers == data.headers;
                    let body_match = self.body.to_string() == data.body;

                    if !children_match {
                        println!("Children do not match!");
                    }
                    if !header_match {
                        println!("Headers do not match!");
                    }
                    if !body_match {
                        println!("Body does not match! ({} != {})", self.body, data.body);
                    }

                    header_match && body_match && children_match
                },
                (&None, &MimeNonMultipart(ref data)) => {
                    let header_match = headers == data.headers;
                    let body_match = self.body.to_string() == data.body;

                    if !header_match {
                        println!("Headers do not match!");
                    }
                    if !body_match {
                        println!("Body does not match! ({} != {})", self.body, data.body);
                    }

                    header_match && body_match
                },
                (_, _) => {
                    println!("Expected different message type than what was given");
                    false
                },
            }
        }
    }

    struct ParseTest<'s> {
        input: &'s str,
        output: Option<MessageTestResult<'s>>,
        name: &'s str,
    }

    #[test]
    fn test_message_parse() {
        let tests = vec![
            ParseTest {
                input: "From: joe@example.org\r\nTo: john@example.org\r\n\r\nHello!",
                output: Some(MessageTestResult {
                    headers: vec![
                        ("From", "joe@example.org"),
                        ("To", "john@example.org"),
                    ],
                    body: "Hello!",
                    children: None,
                }),
                name: "Simple single part message parse",
            },

            ParseTest {
                input: "From: joe@example.org\r\n\
                        To: john@example.org\r\n\
                        Content-Type: multipart/alternate; boundary=foo\r\n\
                        \r\n\
                        Parent\r\n\
                        --foo\r\n\
                        Hello!\r\n\
                        --foo\r\n\
                        Other\r\n",
                output: Some(MessageTestResult {
                    headers: vec![
                        ("From", "joe@example.org"),
                        ("To", "john@example.org"),
                        ("Content-Type", "multipart/alternate; boundary=foo"),
                    ],
                    body: "Parent\r\n",
                    children: Some(vec![
                        MessageTestResult {
                            headers: vec![ ],
                            body: "Hello!\r\n",
                            children: None,
                        },
                        MessageTestResult {
                            headers: vec![ ],
                            body: "Other\r\n",
                            children: None,
                        },
                    ]),
                }),
                name: "Simple multipart message parse",
            },

            ParseTest {
                input: "From: joe@example.org\r\n\
                        To: john@example.org\r\n\
                        Content-Type: multipart/mixed; boundary=foo\r\n\
                        \r\n\
                        Parent\r\n\
                        --foo\r\n\
                        Content-Type: multipart/alternate; boundary=bar\r\n\

                        --bar\r\n\
                        Hello!\r\n\
                        --bar\r\n\
                        Other\r\n\
                        --foo\r\n\
                        Outside\r\n\
                        --foo\r\n
                        ",
                output: Some(MessageTestResult {
                    headers: vec![
                        ("From", "joe@example.org"),
                        ("To", "john@example.org"),
                        ("Content-Type", "multipart/mixed; boundary=foo"),
                    ],
                    body: "Parent\r\n",
                    children: Some(vec![
                        MessageTestResult {
                            headers: vec![
                                ("Content-Type", "multipart/alternate; boundary=bar"),
                            ],
                            body: "",
                            children: Some(vec![
                                MessageTestResult {
                                    headers: vec![ ],
                                    body: "Hello!\r\n",
                                    children: None,
                                },
                                MessageTestResult {
                                    headers: vec![ ],
                                    body: "Other\r\n",
                                    children: None,
                                },
                            ]),
                        },
                        MessageTestResult {
                            headers: vec![ ],
                            body: "Outside\r\n",
                            children: None,
                        },
                    ]),
                }),
                name: "Deeply nested multipart test",
            },
        ];

        for test in tests.into_iter() {
            println!("--- Next test: {}", test.name);
            let message = MimeMessage::parse(test.input);
            let result = match (test.output, message) {
                (Some(ref expected), Some(ref given)) => expected.equiv(given),
                (None, None) => true,
                (_, _) => false,
            };
            assert!(result, test.name);
        }
    }

    #[test]
    fn test_multipart_type_type_parsing() {
        let multipart = "multipart".to_string();
        assert_eq!(MimeMultipartType::from_content_type((multipart.clone(), "mixed".to_string())), MimeMultipartMixed);
        assert_eq!(MimeMultipartType::from_content_type((multipart.clone(), "alternate".to_string())), MimeMultipartAlternate);
        assert_eq!(MimeMultipartType::from_content_type((multipart.clone(), "digest".to_string())), MimeMultipartDigest);
        assert_eq!(MimeMultipartType::from_content_type((multipart.clone(), "parallel".to_string())), MimeMultipartParallel);

        // Test failback onto multipart/mixed
        assert_eq!(MimeMultipartType::from_content_type((multipart.clone(), "potato".to_string())), MimeMultipartMixed);
    }

    #[test]
    fn test_multipart_type_to_content_type() {
        let multipart = "multipart".to_string();

        assert_eq!(MimeMultipartMixed.to_content_type(),     (multipart.clone(), "mixed".to_string()));
        assert_eq!(MimeMultipartAlternate.to_content_type(), (multipart.clone(), "alternate".to_string()));
        assert_eq!(MimeMultipartDigest.to_content_type(),    (multipart.clone(), "digest".to_string()));
        assert_eq!(MimeMultipartParallel.to_content_type(),  (multipart.clone(), "parallel".to_string()));
    }
}
<|endoftext|>