<commit_before>#[macro_use]
extern crate interpreter;

#[test]
fn reassign_different_type() {
    run_test!(
        should_warn(typecheck),
        should_pass(lex, parse)
        => r"
            x = 1;
            x = true;
        ");
}

#[test]
fn shadow_function() {
    run_test!(
        should_warn(typecheck),
        should_pass(lex, parse)
        => r"
            fn x { x }
            fn x { x*2 }
        ");
}

#[test]
fn call_non_function() {
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            x = 1;
            y = x(1, 2, 3);
        ");
}

#[test]
fn wrong_num_arguments() {
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            f x, y=1 { x+y }
            x = f(1, 2, 3);
        ");
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            f x, y=1 { x+y }
            x = f();
        ");
}

#[test]
fn misspelled_argument() {
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            fn x { x }
            x = fn(y=5);
        ");
}

#[test]
fn ambiguous_recursion() {
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            fn x { fn(x) }
            y = fn(1);
        ");
}

#[test]
fn wrong_arg_type() {
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            fn x { x }
            x = fn(1);
            y = fn(true);
        ");
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            fn x { x }
            z = fn;
            x = z(1);
            y = fn(true);
        ");
}

#[test]
fn wrong_block_type() {
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            x = {
                true;
                false;
            };
        ");
}

#[test]
fn non_boolean_condition() {
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            x = true if 1 else false;
        ");
}

#[test]
fn reassignment() {
    run_test!(
        should_pass(lex, parse, typecheck)
        => r"
            x = true;
            y = x ^^ true;
            x = 1;
            y = x * 5;
        ");
    run_test!(
        should_pass(lex, parse, typecheck)
        => r"
            x = true;
            a { x ^^ true }
            x = 1;
            b { x * 2 }
            w = b();
            z = a();
        ");
}

#[test]
fn variable_not_in_scope() {
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            x = a;
        ");
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            fn {
                a = 5;
            }
            x = a;
        ");
}

#[test]
fn mismatched_conditional_types() {
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            x = 1 if true else false;
        ");
}

#[test]
fn wrong_type_numerical_op() {
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            a = 5 + true;
        ");
}

#[test]
fn wrong_type_comparison_op() {
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            a = 5 + true;
        ");
}

#[test]
fn wrong_type_equality_op() {
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            a = 5 == true;
        ");
}

#[test]
fn wrong_type_boolean_op() {
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            a = 5 && true;
        ");
}

#[test]
fn wrong_type_prefix_op() {
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            a = -true;
        ");
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            a = !5;
        ");
}

#[test]
fn internal_block_shadowing_captured_vars() {
    run_test!(
        should_pass(lex, parse, typecheck)
        => r"
            x = 5;
            c = \{x+5};
            z = {
                x = true;
                c();
            };
        ");
}

#[test]
fn default_args_evald_in_new_scope() {
    run_test!(
        should_pass(lex, parse, typecheck)
        => r"
            x = 5;
            fn z=x {
                z^2;
            }
            w = {
                x = true;
                fn();
            };
        ");
}

#[test]
fn internal_block_overriding_captured_args() {
    run_test!(
        should_pass(lex, parse, typecheck)
        => r"
            x = true;
            c x { x+5 }
            z = {
                x = 5;
                c[];
            };
        ");
}

#[test]
fn default_args() {
    run_test!(
        should_pass(lex, parse, typecheck)
        => r"
            fn x, y=3 { x + y }
            a = fn(1);
            a = fn(1, 2);
            a = fn(1, x=2);
        ");
}

#[test]
fn partial_application() {
    run_test!(
        should_pass(lex, parse, typecheck)
        => r"
            fn x, y=3 { x + y }
            fn' = fn{x=2};
            a = fn'(1);
            a = fn'(1, 2);
            a = fn'(1, x=2);
        ");
}

#[test]
fn implicit_call() {
    run_test!(
        should_pass(lex, parse, typecheck)
        => r"
            fn x, y=3 { x + y }
            x = 1;
            y = false;
            z = fn[];
        ");
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            fn x, y { x + y }
            x = 1;
            y = false;
            z = fn[];
        ");
}

#[test]
fn unbind_expression() {
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            fn x, y=3 { x + y }
            fn' = fn{y=};
            x = 1;
            y = false;
            z = fn'[];
        ");
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            fn x, y=3 { x + y }
            fn' = fn{y=};
            x = 1;
            z = fn'[];
        ");
    run_test!(
        should_pass(lex, parse, typecheck)
        => r"
            fn x, y=3 { x + y }
            fn' = fn{y=};
            x = 1;
            y = 2;
            z = fn'[];
        ");
}

#[test]
fn layered_implicit_calls() {
    run_test!(
        should_pass(lex, parse, typecheck)
        => r"
            a x { x^2 }
            b y { y[] }
            c z { z(a) }
            z = c[b, x=4];
        ");
}

#[test]
fn recursive_partial_application() {
    run_test!(
        should_pass(lex, parse, typecheck)
        => r"
            a b { b() }
            z = a(a{a{a{a{\{5}}}}});
        ");
}

#[test]
fn recursion_numeric() {
    run_test!(
        should_pass(lex, parse, typecheck)
        => r"
            a b {
                a(b-1);
                b;
            }
            z = a(5);
        ");
}

#[test]
fn partially_apply_recursive_function() {
    run_test!(
        should_pass(lex, parse, typecheck)
        => r"
            a b {
                a(b-1);
                b;
            }
            z = a(5);
            w = a{5};
            x = w();
        ");
}
<commit_msg>rename a terribly named test<commit_after>#[macro_use]
extern crate interpreter;

#[test]
fn reassign_different_type() {
    run_test!(
        should_warn(typecheck),
        should_pass(lex, parse)
        => r"
            x = 1;
            x = true;
        ");
}

#[test]
fn shadow_function() {
    run_test!(
        should_warn(typecheck),
        should_pass(lex, parse)
        => r"
            fn x { x }
            fn x { x*2 }
        ");
}

#[test]
fn call_non_function() {
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            x = 1;
            y = x(1, 2, 3);
        ");
}

#[test]
fn wrong_num_arguments() {
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            f x, y=1 { x+y }
            x = f(1, 2, 3);
        ");
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            f x, y=1 { x+y }
            x = f();
        ");
}

#[test]
fn misspelled_argument() {
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            fn x { x }
            x = fn(y=5);
        ");
}

#[test]
fn ambiguous_recursion() {
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            fn x { fn(x) }
            y = fn(1);
        ");
}

#[test]
fn wrong_arg_type() {
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            fn x { x }
            x = fn(1);
            y = fn(true);
        ");
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            fn x { x }
            z = fn;
            x = z(1);
            y = fn(true);
        ");
}

#[test]
fn wrong_block_type() {
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            x = {
                true;
                false;
            };
        ");
}

#[test]
fn non_boolean_condition() {
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            x = true if 1 else false;
        ");
}

#[test]
fn reassignment() {
    run_test!(
        should_pass(lex, parse, typecheck)
        => r"
            x = true;
            y = x ^^ true;
            x = 1;
            y = x * 5;
        ");
    run_test!(
        should_pass(lex, parse, typecheck)
        => r"
            x = true;
            a { x ^^ true }
            x = 1;
            b { x * 2 }
            w = b();
            z = a();
        ");
}

#[test]
fn variable_not_in_scope() {
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            x = a;
        ");
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            fn {
                a = 5;
            }
            x = a;
        ");
}

#[test]
fn mismatched_conditional_types() {
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            x = 1 if true else false;
        ");
}

#[test]
fn wrong_type_numerical_op() {
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            a = 5 + true;
        ");
}

#[test]
fn wrong_type_comparison_op() {
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            a = 5 + true;
        ");
}

#[test]
fn wrong_type_equality_op() {
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            a = 5 == true;
        ");
}

#[test]
fn wrong_type_boolean_op() {
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            a = 5 && true;
        ");
}

#[test]
fn wrong_type_prefix_op() {
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            a = -true;
        ");
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            a = !5;
        ");
}

#[test]
fn internal_block_shadowing_captured_vars() {
    run_test!(
        should_pass(lex, parse, typecheck)
        => r"
            x = 5;
            c = \{x+5};
            z = {
                x = true;
                c();
            };
        ");
}

#[test]
fn default_args_evald_in_new_scope() {
    run_test!(
        should_pass(lex, parse, typecheck)
        => r"
            x = 5;
            fn z=x {
                z^2;
            }
            w = {
                x = true;
                fn();
            };
        ");
}

#[test]
fn implicit_args_read_from_correct_scope() {
    run_test!(
        should_pass(lex, parse, typecheck)
        => r"
            x = true;
            c x { x+5 }
            z = {
                x = 5;
                c[];
            };
        ");
}

#[test]
fn default_args() {
    run_test!(
        should_pass(lex, parse, typecheck)
        => r"
            fn x, y=3 { x + y }
            a = fn(1);
            a = fn(1, 2);
            a = fn(1, x=2);
        ");
}

#[test]
fn partial_application() {
    run_test!(
        should_pass(lex, parse, typecheck)
        => r"
            fn x, y=3 { x + y }
            fn' = fn{x=2};
            a = fn'(1);
            a = fn'(1, 2);
            a = fn'(1, x=2);
        ");
}

#[test]
fn implicit_call() {
    run_test!(
        should_pass(lex, parse, typecheck)
        => r"
            fn x, y=3 { x + y }
            x = 1;
            y = false;
            z = fn[];
        ");
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            fn x, y { x + y }
            x = 1;
            y = false;
            z = fn[];
        ");
}

#[test]
fn unbind_expression() {
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            fn x, y=3 { x + y }
            fn' = fn{y=};
            x = 1;
            y = false;
            z = fn'[];
        ");
    run_test!(
        should_fail(typecheck),
        should_pass(lex, parse)
        => r"
            fn x, y=3 { x + y }
            fn' = fn{y=};
            x = 1;
            z = fn'[];
        ");
    run_test!(
        should_pass(lex, parse, typecheck)
        => r"
            fn x, y=3 { x + y }
            fn' = fn{y=};
            x = 1;
            y = 2;
            z = fn'[];
        ");
}

#[test]
fn layered_implicit_calls() {
    run_test!(
        should_pass(lex, parse, typecheck)
        => r"
            a x { x^2 }
            b y { y[] }
            c z { z(a) }
            z = c[b, x=4];
        ");
}

#[test]
fn recursive_partial_application() {
    run_test!(
        should_pass(lex, parse, typecheck)
        => r"
            a b { b() }
            z = a(a{a{a{a{\{5}}}}});
        ");
}

#[test]
fn recursion_numeric() {
    run_test!(
        should_pass(lex, parse, typecheck)
        => r"
            a b {
                a(b-1);
                b;
            }
            z = a(5);
        ");
}

#[test]
fn partially_apply_recursive_function() {
    run_test!(
        should_pass(lex, parse, typecheck)
        => r"
            a b {
                a(b-1);
                b;
            }
            z = a(5);
            w = a{5};
            x = w();
        ");
}
<|endoftext|>


<commit_before>// Copyright © 2016, Canal TP and/or its affiliates. All rights reserved.
//
// This file is part of Navitia,
//     the software to build cool stuff with public transport.
//
// Hope you'll enjoy and contribute to this project,
//     powered by Canal TP (www.canaltp.fr).
// Help us simplify mobility and open public transport:
//     a non ending quest to the responsive locomotion way of traveling!
//
// LICENCE: This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU Affero General Public
// License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with this program. If not, see
// <http://www.gnu.org/licenses/>.
//
// Stay tuned using
// twitter @navitia
// IRC #navitia on freenode
// https://groups.google.com/d/forum/navitia
// www.navitia.io
use super::query;
use hyper::mime::Mime;
use iron::status::Status as IronStatus;
use iron::typemap::Key;
use mimir::rubber::Rubber;
use model;
use model::v1::*;
use params::{
    coord_param, dataset_param, get_param_array, paginate_param, shape_param, types_param,
};
use prometheus;
use prometheus::Encoder;
use rustless;
use rustless::server::header;
use rustless::{Api, Nesting};
use serde;
use serde_json;
use valico::json_dsl;

use navitia_model::objects::Coord;

const DEFAULT_LIMIT: u64 = 10u64;
const DEFAULT_OFFSET: u64 = 0u64;

struct Timer;
impl Key for Timer {
    type Value = prometheus::HistogramTimer;
}

lazy_static! {
    static ref HTTP_COUNTER: prometheus::CounterVec = register_counter_vec!(
        "bragi_http_requests_total",
        "Total number of HTTP requests made.",
        &["handler", "method", "status"]
    ).unwrap();
    static ref HTTP_REQ_HISTOGRAM: prometheus::HistogramVec = register_histogram_vec!(
        "bragi_http_request_duration_seconds",
        "The HTTP request latencies in seconds.",
        &["handler", "method"],
        prometheus::exponential_buckets(0.001, 1.5, 25).unwrap()
    ).unwrap();
}

fn add_distance(autocomp_resp: &mut model::Autocomplete, origin_coord: &Coord) {
    for feature in &mut autocomp_resp.features {
        if let ::geojson::Value::Point(p) = &feature.geometry.value {
            if let [mut lon, mut lat] = p.as_slice() {
                let feature_coord = Coord { lon, lat };
                feature.distance = Some(feature_coord.distance_to(&origin_coord) as u32);
            }
        }
    }
}

fn render<T>(
    mut client: rustless::Client,
    obj: T,
) -> Result<rustless::Client, rustless::ErrorResponse>
where
    T: serde::Serialize + model::v1::HasStatus,
{
    client.set_json_content_type();
    client.set_header(header::AccessControlAllowOrigin::Any);
    client.set_status(obj.status());
    client.text(serde_json::to_string(&obj).unwrap())
}

pub struct ApiEndPoint {
    pub es_cnx_string: String,
}

impl ApiEndPoint {
    pub fn root(&self) -> rustless::Api {
        Api::build(|api| {
            api.get("", |endpoint| {
                endpoint.handle(|client, _params| {
                    let desc = EndPoint {
                        description: "autocomplete service".to_string(),
                    };
                    render(client, desc)
                })
            });

            api.error_formatter(|error, _media| {
                let err = if error.is::<rustless::errors::Validation>() {
                    let val_err = error.downcast::<rustless::errors::Validation>().unwrap();
                    // TODO better message, we shouldn't use {:?} but access the `path`
                    // and `detail` of all errrors in val_err.reason
                    CustomError {
                        short: "validation error".to_string(),
                        long: format!("invalid arguments {:?}", val_err.reason),
                        status: IronStatus::BadRequest,
                    }
                } else {
                    CustomError {
                        short: "bad_request".to_string(),
                        long: format!("bad request, error: {}", error),
                        status: IronStatus::BadRequest,
                    }
                };
                let mut resp = rustless::Response::from(
                    err.status,
                    Box::new(serde_json::to_string(&err).unwrap()),
                );
                resp.set_json_content_type();
                Some(resp)
            });

            api.before(|client, _params| {
                let method = client.endpoint.method.to_string();

                HTTP_REQ_HISTOGRAM
                    .get_metric_with(&labels!{
                        "handler" => client.endpoint.path.path.as_str(),
                        "method" => method.as_str(),
                    })
                    .map(|timer| {
                        client.ext.insert::<Timer>(timer.start_timer());
                    })
                    .unwrap_or_else(|err| {
                        error!("impossible to get HTTP_REQ_HISTOGRAM metrics";
                               "err" => err.to_string());
                    });
                Ok(())
            });

            api.after(|client, _params| {
                let method = client.endpoint.method.to_string();
                let code = client.status().to_string();

                HTTP_COUNTER
                    .get_metric_with(&labels!{
                        "handler" => client.endpoint.path.path.as_str(),
                        "method" => method.as_str(),
                        "status" => code.as_str(),
                    })
                    .map(|counter| counter.inc())
                    .unwrap_or_else(|err| {
                        error!("impossible to get HTTP_COUNTER metrics"; "err" => err.to_string());
                    });
                client
                    .ext
                    .remove::<Timer>()
                    .map(|timer| timer.observe_duration())
                    .unwrap_or_else(|| error!("impossible to get timers from typemap"));
                Ok(())
            });
            api.mount(self.v1());
        })
    }

    fn v1(&self) -> rustless::Api {
        Api::build(|api| {
            api.mount(self.status());
            api.mount(self.autocomplete());
            api.mount(self.features());
            api.mount(self.reverse());
            api.mount(self.metrics());
        })
    }

    fn status(&self) -> rustless::Api {
        Api::build(|api| {
            api.get("status", |endpoint| {
                let cnx = self.es_cnx_string.clone();
                endpoint.handle(move |client, _params| {
                    let status = Status {
                        version: env!("CARGO_PKG_VERSION").to_string(),
                        es: cnx.to_string(),
                        status: "good".to_string(),
                    };
                    render(client, status)
                })
            });
        })
    }

    fn metrics(&self) -> rustless::Api {
        Api::build(|api| {
            api.get("metrics", |endpoint| {
                endpoint.handle(move |mut client, _params| {
                    let encoder = prometheus::TextEncoder::new();
                    let metric_familys = prometheus::gather();
                    let mut buffer = vec![];
                    encoder.encode(&metric_familys, &mut buffer).unwrap();
                    client.set_content_type(encoder.format_type().parse::<Mime>().unwrap());
                    client.text(String::from_utf8(buffer).unwrap())
                })
            });
        })
    }

    fn reverse(&self) -> rustless::Api {
        Api::build(|api| {
            api.get("reverse", |endpoint| {
                endpoint.params(|params| {
                    coord_param(params, false);
                });
                let cnx = self.es_cnx_string.clone();
                endpoint.handle(move |client, params| {
                    let coord = ::mimir::Coord::new(
                        params.find("lon").and_then(|p| p.as_f64()).unwrap(),
                        params.find("lat").and_then(|p| p.as_f64()).unwrap(),
                    );
                    let mut rubber = Rubber::new(&cnx);
                    let model_autocomplete =
                        rubber.get_address(&coord).map_err(model::BragiError::from);

                    let response = model::v1::AutocompleteResponse::from(model_autocomplete);
                    render(client, response)
                })
            });
        })
    }

    fn features(&self) -> rustless::Api {
        Api::build(|api| {
            api.get("features/:id", |endpoint| {
                endpoint.params(|params| {
                    params.opt_typed("id", json_dsl::string());
                    dataset_param(params);
                });

                let cnx = self.es_cnx_string.clone();
                endpoint.handle(move |client, params| {
                    let id = params.find("id").unwrap().as_str().unwrap();
                    let pt_datasets = get_param_array(params, "pt_dataset");
                    let all_data = params
                        .find("_all_data")
                        .map_or(false, |val| val.as_bool().unwrap());
                    let features = query::features(&pt_datasets, all_data, &cnx, &id);
                    let response = model::v1::AutocompleteResponse::from(features);
                    render(client, response)
                })
            });
        })
    }

    fn autocomplete(&self) -> rustless::Api {
        Api::build(|api| {
            api.post("autocomplete", |endpoint| {
                endpoint.params(|params| {
                    params.opt_typed("q", json_dsl::string());
                    dataset_param(params);
                    paginate_param(params);
                    shape_param(params);
                    types_param(params);
                });

                let cnx = self.es_cnx_string.clone();
                endpoint.handle(move |client, params| {
                    let q = params
                        .find("q")
                        .and_then(|val| val.as_str())
                        .unwrap_or("")
                        .to_string();
                    let pt_datasets = get_param_array(params, "pt_dataset");
                    let all_data = params
                        .find("_all_data")
                        .map_or(false, |val| val.as_bool().unwrap());
                    let offset = params
                        .find("offset")
                        .and_then(|val| val.as_u64())
                        .unwrap_or(DEFAULT_OFFSET);
                    let limit = params
                        .find("limit")
                        .and_then(|val| val.as_u64())
                        .unwrap_or(DEFAULT_LIMIT);
                    let geometry = params.find_path(&["shape", "geometry"]).unwrap();
                    let coordinates = geometry
                        .find_path(&["coordinates"])
                        .unwrap()
                        .as_array()
                        .unwrap();
                    let mut shape = Vec::new();
                    for ar in coordinates[0].as_array().unwrap() {
                        // (Lat, Lon)
                        shape.push((
                            ar.as_array().unwrap()[1].as_f64().unwrap(),
                            ar.as_array().unwrap()[0].as_f64().unwrap(),
                        ));
                    }
                    let types = get_param_array(params, "type");
                    let model_autocomplete = query::autocomplete(
                        &q,
                        &pt_datasets,
                        all_data,
                        offset,
                        limit,
                        None,
                        &cnx,
                        Some(shape),
                        &types,
                    );
                    let response = model::v1::AutocompleteResponse::from(model_autocomplete);
                    render(client, response)
                })
            });
            api.get("autocomplete", |endpoint| {
                endpoint.params(|params| {
                    params.opt_typed("q", json_dsl::string());
                    dataset_param(params);
                    paginate_param(params);
                    coord_param(params, true);
                    types_param(params);
                });
                let cnx = self.es_cnx_string.clone();
                endpoint.handle(move |client, params| {
                    let q = params
                        .find("q")
                        .and_then(|val| val.as_str())
                        .unwrap_or("")
                        .to_string();
                    let pt_datasets = get_param_array(params, "pt_dataset");
                    let all_data = params
                        .find("_all_data")
                        .map_or(false, |val| val.as_bool().unwrap());
                    let offset = params
                        .find("offset")
                        .and_then(|val| val.as_u64())
                        .unwrap_or(DEFAULT_OFFSET);
                    let limit = params
                        .find("limit")
                        .and_then(|val| val.as_u64())
                        .unwrap_or(DEFAULT_LIMIT);
                    let lon = params.find("lon").and_then(|p| p.as_f64());
                    let lat = params.find("lat").and_then(|p| p.as_f64());
                    // we have already checked that if there is a lon, lat
                    // is not None so we can unwrap
                    let coord = lon.and_then(|lon| {
                        Some(Coord {
                            lon: lon,
                            lat: lat.unwrap(),
                        })
                    });

                    let types = get_param_array(params, "type");

                    let model_autocomplete = query::autocomplete(
                        &q,
                        &pt_datasets,
                        all_data,
                        offset,
                        limit,
                        coord,
                        &cnx,
                        None,
                        &types,
                    );

                    let mut response = model::v1::AutocompleteResponse::from(model_autocomplete);

                    // Optional : add distance for each feature (in meters)
                    if let (
                        Some(coord),
                        model::v1::AutocompleteResponse::Autocomplete(autocomplete_resp),
                    ) = (&coord, &mut response)
                    {
                        add_distance(autocomplete_resp, coord);
                    }

                    render(client, response)
                })
            });
        })
    }
}
<commit_msg>use for Autocomplete struct<commit_after>// Copyright © 2016, Canal TP and/or its affiliates. All rights reserved.
//
// This file is part of Navitia,
//     the software to build cool stuff with public transport.
//
// Hope you'll enjoy and contribute to this project,
//     powered by Canal TP (www.canaltp.fr).
// Help us simplify mobility and open public transport:
//     a non ending quest to the responsive locomotion way of traveling!
//
// LICENCE: This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU Affero General Public
// License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with this program. If not, see
// <http://www.gnu.org/licenses/>.
//
// Stay tuned using
// twitter @navitia
// IRC #navitia on freenode
// https://groups.google.com/d/forum/navitia
// www.navitia.io
use super::query;
use hyper::mime::Mime;
use iron::status::Status as IronStatus;
use iron::typemap::Key;
use mimir::rubber::Rubber;
use model;
use model::v1::*;
use params::{
    coord_param, dataset_param, get_param_array, paginate_param, shape_param, types_param,
};
use prometheus;
use prometheus::Encoder;
use rustless;
use rustless::server::header;
use rustless::{Api, Nesting};
use serde;
use serde_json;
use valico::json_dsl;

use navitia_model::objects::Coord;

const DEFAULT_LIMIT: u64 = 10u64;
const DEFAULT_OFFSET: u64 = 0u64;

struct Timer;
impl Key for Timer {
    type Value = prometheus::HistogramTimer;
}

lazy_static! {
    static ref HTTP_COUNTER: prometheus::CounterVec = register_counter_vec!(
        "bragi_http_requests_total",
        "Total number of HTTP requests made.",
        &["handler", "method", "status"]
    ).unwrap();
    static ref HTTP_REQ_HISTOGRAM: prometheus::HistogramVec = register_histogram_vec!(
        "bragi_http_request_duration_seconds",
        "The HTTP request latencies in seconds.",
        &["handler", "method"],
        prometheus::exponential_buckets(0.001, 1.5, 25).unwrap()
    ).unwrap();
}

fn add_distance(autocomp_resp: &mut model::Autocomplete, origin_coord: &Coord) {
    for feature in &mut autocomp_resp.features {
        if let ::geojson::Value::Point(p) = &feature.geometry.value {
            if let [mut lon, mut lat] = p.as_slice() {
                let feature_coord = Coord { lon, lat };
                feature.distance = Some(feature_coord.distance_to(&origin_coord) as u32);
            }
        }
    }
}

fn render<T>(
    mut client: rustless::Client,
    obj: T,
) -> Result<rustless::Client, rustless::ErrorResponse>
where
    T: serde::Serialize + model::v1::HasStatus,
{
    client.set_json_content_type();
    client.set_header(header::AccessControlAllowOrigin::Any);
    client.set_status(obj.status());
    client.text(serde_json::to_string(&obj).unwrap())
}

pub struct ApiEndPoint {
    pub es_cnx_string: String,
}

impl ApiEndPoint {
    pub fn root(&self) -> rustless::Api {
        Api::build(|api| {
            api.get("", |endpoint| {
                endpoint.handle(|client, _params| {
                    let desc = EndPoint {
                        description: "autocomplete service".to_string(),
                    };
                    render(client, desc)
                })
            });

            api.error_formatter(|error, _media| {
                let err = if error.is::<rustless::errors::Validation>() {
                    let val_err = error.downcast::<rustless::errors::Validation>().unwrap();
                    // TODO better message, we shouldn't use {:?} but access the `path`
                    // and `detail` of all errrors in val_err.reason
                    CustomError {
                        short: "validation error".to_string(),
                        long: format!("invalid arguments {:?}", val_err.reason),
                        status: IronStatus::BadRequest,
                    }
                } else {
                    CustomError {
                        short: "bad_request".to_string(),
                        long: format!("bad request, error: {}", error),
                        status: IronStatus::BadRequest,
                    }
                };
                let mut resp = rustless::Response::from(
                    err.status,
                    Box::new(serde_json::to_string(&err).unwrap()),
                );
                resp.set_json_content_type();
                Some(resp)
            });

            api.before(|client, _params| {
                let method = client.endpoint.method.to_string();

                HTTP_REQ_HISTOGRAM
                    .get_metric_with(&labels!{
                        "handler" => client.endpoint.path.path.as_str(),
                        "method" => method.as_str(),
                    })
                    .map(|timer| {
                        client.ext.insert::<Timer>(timer.start_timer());
                    })
                    .unwrap_or_else(|err| {
                        error!("impossible to get HTTP_REQ_HISTOGRAM metrics";
                               "err" => err.to_string());
                    });
                Ok(())
            });

            api.after(|client, _params| {
                let method = client.endpoint.method.to_string();
                let code = client.status().to_string();

                HTTP_COUNTER
                    .get_metric_with(&labels!{
                        "handler" => client.endpoint.path.path.as_str(),
                        "method" => method.as_str(),
                        "status" => code.as_str(),
                    })
                    .map(|counter| counter.inc())
                    .unwrap_or_else(|err| {
                        error!("impossible to get HTTP_COUNTER metrics"; "err" => err.to_string());
                    });
                client
                    .ext
                    .remove::<Timer>()
                    .map(|timer| timer.observe_duration())
                    .unwrap_or_else(|| error!("impossible to get timers from typemap"));
                Ok(())
            });
            api.mount(self.v1());
        })
    }

    fn v1(&self) -> rustless::Api {
        Api::build(|api| {
            api.mount(self.status());
            api.mount(self.autocomplete());
            api.mount(self.features());
            api.mount(self.reverse());
            api.mount(self.metrics());
        })
    }

    fn status(&self) -> rustless::Api {
        Api::build(|api| {
            api.get("status", |endpoint| {
                let cnx = self.es_cnx_string.clone();
                endpoint.handle(move |client, _params| {
                    let status = Status {
                        version: env!("CARGO_PKG_VERSION").to_string(),
                        es: cnx.to_string(),
                        status: "good".to_string(),
                    };
                    render(client, status)
                })
            });
        })
    }

    fn metrics(&self) -> rustless::Api {
        Api::build(|api| {
            api.get("metrics", |endpoint| {
                endpoint.handle(move |mut client, _params| {
                    let encoder = prometheus::TextEncoder::new();
                    let metric_familys = prometheus::gather();
                    let mut buffer = vec![];
                    encoder.encode(&metric_familys, &mut buffer).unwrap();
                    client.set_content_type(encoder.format_type().parse::<Mime>().unwrap());
                    client.text(String::from_utf8(buffer).unwrap())
                })
            });
        })
    }

    fn reverse(&self) -> rustless::Api {
        Api::build(|api| {
            api.get("reverse", |endpoint| {
                endpoint.params(|params| {
                    coord_param(params, false);
                });
                let cnx = self.es_cnx_string.clone();
                endpoint.handle(move |client, params| {
                    let coord = ::mimir::Coord::new(
                        params.find("lon").and_then(|p| p.as_f64()).unwrap(),
                        params.find("lat").and_then(|p| p.as_f64()).unwrap(),
                    );
                    let mut rubber = Rubber::new(&cnx);
                    let model_autocomplete =
                        rubber.get_address(&coord).map_err(model::BragiError::from);

                    let response = model::v1::AutocompleteResponse::from(model_autocomplete);
                    render(client, response)
                })
            });
        })
    }

    fn features(&self) -> rustless::Api {
        Api::build(|api| {
            api.get("features/:id", |endpoint| {
                endpoint.params(|params| {
                    params.opt_typed("id", json_dsl::string());
                    dataset_param(params);
                });

                let cnx = self.es_cnx_string.clone();
                endpoint.handle(move |client, params| {
                    let id = params.find("id").unwrap().as_str().unwrap();
                    let pt_datasets = get_param_array(params, "pt_dataset");
                    let all_data = params
                        .find("_all_data")
                        .map_or(false, |val| val.as_bool().unwrap());
                    let features = query::features(&pt_datasets, all_data, &cnx, &id);
                    let response = model::v1::AutocompleteResponse::from(features);
                    render(client, response)
                })
            });
        })
    }

    fn autocomplete(&self) -> rustless::Api {
        Api::build(|api| {
            api.post("autocomplete", |endpoint| {
                endpoint.params(|params| {
                    params.opt_typed("q", json_dsl::string());
                    dataset_param(params);
                    paginate_param(params);
                    shape_param(params);
                    types_param(params);
                });

                let cnx = self.es_cnx_string.clone();
                endpoint.handle(move |client, params| {
                    let q = params
                        .find("q")
                        .and_then(|val| val.as_str())
                        .unwrap_or("")
                        .to_string();
                    let pt_datasets = get_param_array(params, "pt_dataset");
                    let all_data = params
                        .find("_all_data")
                        .map_or(false, |val| val.as_bool().unwrap());
                    let offset = params
                        .find("offset")
                        .and_then(|val| val.as_u64())
                        .unwrap_or(DEFAULT_OFFSET);
                    let limit = params
                        .find("limit")
                        .and_then(|val| val.as_u64())
                        .unwrap_or(DEFAULT_LIMIT);
                    let geometry = params.find_path(&["shape", "geometry"]).unwrap();
                    let coordinates = geometry
                        .find_path(&["coordinates"])
                        .unwrap()
                        .as_array()
                        .unwrap();
                    let mut shape = Vec::new();
                    for ar in coordinates[0].as_array().unwrap() {
                        // (Lat, Lon)
                        shape.push((
                            ar.as_array().unwrap()[1].as_f64().unwrap(),
                            ar.as_array().unwrap()[0].as_f64().unwrap(),
                        ));
                    }
                    let types = get_param_array(params, "type");
                    let model_autocomplete = query::autocomplete(
                        &q,
                        &pt_datasets,
                        all_data,
                        offset,
                        limit,
                        None,
                        &cnx,
                        Some(shape),
                        &types,
                    );
                    let response = model::v1::AutocompleteResponse::from(model_autocomplete);
                    render(client, response)
                })
            });
            api.get("autocomplete", |endpoint| {
                endpoint.params(|params| {
                    params.opt_typed("q", json_dsl::string());
                    dataset_param(params);
                    paginate_param(params);
                    coord_param(params, true);
                    types_param(params);
                });
                let cnx = self.es_cnx_string.clone();
                endpoint.handle(move |client, params| {
                    let q = params
                        .find("q")
                        .and_then(|val| val.as_str())
                        .unwrap_or("")
                        .to_string();
                    let pt_datasets = get_param_array(params, "pt_dataset");
                    let all_data = params
                        .find("_all_data")
                        .map_or(false, |val| val.as_bool().unwrap());
                    let offset = params
                        .find("offset")
                        .and_then(|val| val.as_u64())
                        .unwrap_or(DEFAULT_OFFSET);
                    let limit = params
                        .find("limit")
                        .and_then(|val| val.as_u64())
                        .unwrap_or(DEFAULT_LIMIT);
                    let lon = params.find("lon").and_then(|p| p.as_f64());
                    let lat = params.find("lat").and_then(|p| p.as_f64());
                    // we have already checked that if there is a lon, lat
                    // is not None so we can unwrap
                    let coord = lon.and_then(|lon| {
                        Some(Coord {
                            lon: lon,
                            lat: lat.unwrap(),
                        })
                    });

                    let types = get_param_array(params, "type");

                    let model_autocomplete = query::autocomplete(
                        &q,
                        &pt_datasets,
                        all_data,
                        offset,
                        limit,
                        coord,
                        &cnx,
                        None,
                        &types,
                    );

                    let mut response = model::v1::AutocompleteResponse::from(model_autocomplete);

                    // Optional : add distance for each feature (in meters)
                    use model::v1::AutocompleteResponse::Autocomplete;
                    if let (Some(coord), Autocomplete(autocomplete_resp)) = (&coord, &mut response)
                    {
                        add_distance(autocomplete_resp, coord);
                    }

                    render(client, response)
                })
            });
        })
    }
}
<|endoftext|>


<commit_before>#[doc = "Rust wrappers around the raw JS apis"];

import bg = jsapi::bindgen;
import libc::types::os::arch::c95::{size_t, c_uint};
import std::map::{hashmap, str_hash};

export rt;
export cx;
export jsobj;
export methods;
export compartment;

// ___________________________________________________________________________
// friendly Rustic API to runtimes

type rt = @rt_rsrc;

struct rt_rsrc {
    let ptr : *JSRuntime;
    new(p : {ptr: *JSRuntime}) {
        self.ptr = p.ptr;
    }
    drop {
        JS_Finish(self.ptr);
    }
}

fn rt() -> rt {
    @rt_rsrc({ptr: JS_Init(default_heapsize)})
}

impl rt {
    fn cx() -> cx {
        @cx_rsrc({ptr: JS_NewContext(self.ptr, default_stacksize as size_t),
                  rt: self})
    }
}

// ___________________________________________________________________________
// contexts

type cx = @cx_rsrc;

struct cx_rsrc {
    let ptr : *JSContext;
    let rt: rt;
    let classes: hashmap<~str, @JSClass>;

    new(rec : {ptr: *JSContext, rt: rt}) {
        self.ptr = rec.ptr;
        self.rt = rec.rt;
        self.classes = str_hash();
    }
    drop {
        JS_DestroyContext(self.ptr);
    }
}

impl cx {
    fn rooted_obj(obj: *JSObject) -> jsobj {
        let jsobj = @jsobj_rsrc({cx: self, cxptr: self.ptr, ptr: obj});
        JS_AddObjectRoot(self.ptr, ptr::addr_of(jsobj.ptr));
        jsobj
    }

    fn set_default_options_and_version() {
        self.set_options(JSOPTION_VAROBJFIX | JSOPTION_METHODJIT);
        self.set_version(JSVERSION_LATEST);
    }

    fn set_options(v: c_uint) {
        JS_SetOptions(self.ptr, v);
    }

    fn set_version(v: i32) {
        JS_SetVersion(self.ptr, v);
    }

    fn set_logging_error_reporter() {
        JS_SetErrorReporter(self.ptr, reportError);
    }

    fn set_error_reporter(reportfn: *u8) {
        JS_SetErrorReporter(self.ptr, reportfn);
    }

    fn new_compartment(globclsfn: fn(name_pool) -> JSClass) -> result<compartment,()> {
        let np = name_pool();
        let globcls = @globclsfn(np);
        let globobj = JS_NewCompartmentAndGlobalObject(self.ptr, ptr::assimilate(&*globcls), null());
        result(JS_InitStandardClasses(self.ptr, globobj)).chain(|_ok| {
            let compartment = @{cx: self,
                                name_pool: np,
                                mut global_funcs: ~[],
                                mut global_props: ~[],
                                global_class: globcls,
                                global_obj: self.rooted_obj(globobj),
                                global_protos: str_hash()
                               };
            self.set_cx_private(ptr::assimilate(&*compartment) as *());
            ok(compartment)
        })
    }

    fn evaluate_script(glob: jsobj, bytes: ~[u8], filename: ~str, line_num: uint)
        -> result<(),()> {
        vec::as_buf(bytes, |bytes_ptr, bytes_len| {
            str::as_c_str(filename, |filename_cstr| {
                let bytes_ptr = bytes_ptr as *c_char;
                let v: jsval = 0_u64;
                #debug["Evaluating script from %s with bytes %?", filename, bytes];
                if JS_EvaluateScript(self.ptr, glob.ptr,
                                     bytes_ptr, bytes_len as c_uint,
                                     filename_cstr, line_num as c_uint,
                                     ptr::addr_of(v)) == ERR {
                    #debug["...err!"];
                    err(())
                } else {
                    // we could return the script result but then we'd have
                    // to root it and so forth and, really, who cares?
                    #debug["...ok!"];
                    ok(())
                }
            })
        })
    }

    unsafe fn get_cx_private() -> *() {
        unsafe::reinterpret_cast(JS_GetContextPrivate(self.ptr))
    }

    unsafe fn set_cx_private(data: *()) {
        JS_SetContextPrivate(self.ptr, unsafe::reinterpret_cast(data));
    }

    unsafe fn get_obj_private(obj: *JSObject) -> *() {
        unsafe::reinterpret_cast(JS_GetPrivate(obj))
    }

    unsafe fn set_obj_private(obj: *JSObject, data: *()) {
        JS_SetPrivate(obj, unsafe::reinterpret_cast(data));
    }
}

extern fn reportError(_cx: *JSContext,
                     msg: *c_char,
                     report: *JSErrorReport) {
    unsafe {
        let fnptr = (*report).filename;
        let fname = if fnptr.is_not_null() {from_c_str(fnptr)} else {~"none"};
        let lineno = (*report).lineno;
        let msg = from_c_str(msg);
        #error["Error at %s:%?: %s\n", fname, lineno, msg];
    }
}

// ___________________________________________________________________________
// compartment

type bare_compartment = {
    cx: cx,
    name_pool: name_pool,
    mut global_funcs: ~[@~[JSFunctionSpec]],
    mut global_props: ~[@~[JSPropertySpec]],
    global_class: @JSClass,
    global_obj: jsobj,
    global_protos: hashmap<~str, jsobj>
};

trait methods {
    fn define_functions(specfn: fn(name_pool) -> ~[JSFunctionSpec]) -> result<(),()>;
    fn define_properties(specfn: fn() -> ~[JSPropertySpec]) -> result<(),()>;
    fn define_property(name: ~str, value: jsval, getter: JSPropertyOp,
                       setter: JSStrictPropertyOp, attrs: c_uint) -> result<(),()>;
    fn new_object(class_name: ~str, proto: *JSObject, parent: *JSObject)
        -> result<jsobj, ()>;
    fn new_object_with_proto(class_name: ~str, proto_name: ~str, parent: *JSObject)
        -> result<jsobj, ()>;
    fn register_class(class_fn: fn(bare_compartment) -> JSClass);
    fn add_name(name: ~str) -> *c_char;
}

type compartment = @bare_compartment;

impl bare_compartment : methods {
    fn define_functions(specfn: fn(name_pool) -> ~[JSFunctionSpec]) -> result<(),()> {
        let specvec = @specfn(self.name_pool);
        vec::push(self.global_funcs, specvec);
        vec::as_buf(*specvec, |specs, _len| {
            result(JS_DefineFunctions(self.cx.ptr, self.global_obj.ptr, specs))
        })
    }
    fn define_properties(specfn: fn() -> ~[JSPropertySpec]) -> result<(),()> {
        let specvec = @specfn();
        vec::push(self.global_props, specvec);
        vec::as_buf(*specvec, |specs, _len| {
            result(JS_DefineProperties(self.cx.ptr, self.global_obj.ptr, specs))
        })
    }
    fn define_property(name: ~str, value: jsval, getter: JSPropertyOp,
                       setter: JSStrictPropertyOp, attrs: c_uint) -> result<(),()> {
        result(JS_DefineProperty(self.cx.ptr, self.global_obj.ptr, self.add_name(name),
                                 value, getter, setter, attrs))
    }
    fn new_object(class_name: ~str, proto: *JSObject, parent: *JSObject)
        -> result<jsobj, ()> {
        let classptr = self.cx.classes.get(class_name);
        let obj = self.cx.rooted_obj(JS_NewObject(self.cx.ptr, ptr::assimilate(&*classptr),
                                                  proto, parent));
        if obj.ptr.is_not_null() {
            self.global_protos.insert(
                unsafe { str::unsafe::from_c_str(classptr.name) }, obj);
        }
        result_obj(obj)
    }
    fn new_object_with_proto(class_name: ~str, proto_name: ~str, parent: *JSObject)
        -> result<jsobj, ()> {
        let classptr = self.cx.classes.get(class_name);
        let proto = self.global_protos.get(proto_name);
        let obj = self.cx.rooted_obj(JS_NewObject(self.cx.ptr, ptr::assimilate(&*classptr),
                                                  proto.ptr, parent));
        result_obj(obj)
    }
    fn register_class(class_fn: fn(bare_compartment) -> JSClass) {
        let classptr = @class_fn(self);
        if !self.cx.classes.insert(
            unsafe { str::unsafe::from_c_str(classptr.name) },
            classptr) {
            fail ~"Duplicate JSClass registered; you're gonna have a bad time."
        }
    }
    fn add_name(name: ~str) -> *c_char {
        self.name_pool.add(copy name)
    }
}

// ___________________________________________________________________________
// objects

type jsobj = @jsobj_rsrc;

struct jsobj_rsrc {
    let cx : cx;
    let cxptr : *JSContext;
    let ptr : *JSObject;
    new(rec : {cx: cx, cxptr: *JSContext, ptr: *JSObject}) {
        self.cx = rec.cx;
        self.cxptr = rec.cxptr;
        self.ptr = rec.ptr;
    }
    drop {
        JS_RemoveObjectRoot(self.cxptr, ptr::addr_of(self.ptr));
    }
}

// ___________________________________________________________________________
// random utilities

trait to_jsstr {
    fn to_jsstr(cx: cx) -> *JSString;
}

impl ~str : to_jsstr {
    fn to_jsstr(cx: cx) -> *JSString {
        str::as_buf(self, |buf, len| {
            let cbuf = unsafe { unsafe::reinterpret_cast(buf) };
            bg::JS_NewStringCopyN(cx.ptr, cbuf, len as size_t)
        })
    }
}

#[cfg(test)]
mod test {

    #[test]
    fn dummy() {
        let rt = rt();
        let cx = rt.cx();
        cx.set_default_options_and_version();
        cx.set_logging_error_reporter();
        cx.new_compartment(global::global_class).chain(|comp| {
            comp.define_functions(global::debug_fns);

            let bytes = str::bytes(~"debug(22);");
            cx.evaluate_script(comp.global_obj, bytes, ~"test", 1u)
        });
    }

}
<commit_msg>Avoid a use of option::get<commit_after>#[doc = "Rust wrappers around the raw JS apis"];

import bg = jsapi::bindgen;
import libc::types::os::arch::c95::{size_t, c_uint};
import std::map::{hashmap, str_hash};

export rt;
export cx;
export jsobj;
export methods;
export compartment;

// ___________________________________________________________________________
// friendly Rustic API to runtimes

type rt = @rt_rsrc;

struct rt_rsrc {
    let ptr : *JSRuntime;
    new(p : {ptr: *JSRuntime}) {
        self.ptr = p.ptr;
    }
    drop {
        JS_Finish(self.ptr);
    }
}

fn rt() -> rt {
    @rt_rsrc({ptr: JS_Init(default_heapsize)})
}

impl rt {
    fn cx() -> cx {
        @cx_rsrc({ptr: JS_NewContext(self.ptr, default_stacksize as size_t),
                  rt: self})
    }
}

// ___________________________________________________________________________
// contexts

type cx = @cx_rsrc;

struct cx_rsrc {
    let ptr : *JSContext;
    let rt: rt;
    let classes: hashmap<~str, @JSClass>;

    new(rec : {ptr: *JSContext, rt: rt}) {
        self.ptr = rec.ptr;
        self.rt = rec.rt;
        self.classes = str_hash();
    }
    drop {
        JS_DestroyContext(self.ptr);
    }
}

impl cx {
    fn rooted_obj(obj: *JSObject) -> jsobj {
        let jsobj = @jsobj_rsrc({cx: self, cxptr: self.ptr, ptr: obj});
        JS_AddObjectRoot(self.ptr, ptr::addr_of(jsobj.ptr));
        jsobj
    }

    fn set_default_options_and_version() {
        self.set_options(JSOPTION_VAROBJFIX | JSOPTION_METHODJIT);
        self.set_version(JSVERSION_LATEST);
    }

    fn set_options(v: c_uint) {
        JS_SetOptions(self.ptr, v);
    }

    fn set_version(v: i32) {
        JS_SetVersion(self.ptr, v);
    }

    fn set_logging_error_reporter() {
        JS_SetErrorReporter(self.ptr, reportError);
    }

    fn set_error_reporter(reportfn: *u8) {
        JS_SetErrorReporter(self.ptr, reportfn);
    }

    fn new_compartment(globclsfn: fn(name_pool) -> JSClass) -> result<compartment,()> {
        let np = name_pool();
        let globcls = @globclsfn(np);
        let globobj = JS_NewCompartmentAndGlobalObject(self.ptr, ptr::assimilate(&*globcls), null());
        result(JS_InitStandardClasses(self.ptr, globobj)).chain(|_ok| {
            let compartment = @{cx: self,
                                name_pool: np,
                                mut global_funcs: ~[],
                                mut global_props: ~[],
                                global_class: globcls,
                                global_obj: self.rooted_obj(globobj),
                                global_protos: str_hash()
                               };
            self.set_cx_private(ptr::assimilate(&*compartment) as *());
            ok(compartment)
        })
    }

    fn evaluate_script(glob: jsobj, bytes: ~[u8], filename: ~str, line_num: uint)
        -> result<(),()> {
        vec::as_buf(bytes, |bytes_ptr, bytes_len| {
            str::as_c_str(filename, |filename_cstr| {
                let bytes_ptr = bytes_ptr as *c_char;
                let v: jsval = 0_u64;
                #debug["Evaluating script from %s with bytes %?", filename, bytes];
                if JS_EvaluateScript(self.ptr, glob.ptr,
                                     bytes_ptr, bytes_len as c_uint,
                                     filename_cstr, line_num as c_uint,
                                     ptr::addr_of(v)) == ERR {
                    #debug["...err!"];
                    err(())
                } else {
                    // we could return the script result but then we'd have
                    // to root it and so forth and, really, who cares?
                    #debug["...ok!"];
                    ok(())
                }
            })
        })
    }

    fn lookup_class_name(s: ~str) ->  @JSClass {
      option::expect(self.classes.find(s),
           #fmt("Class %s not found in class table", s))
    }

    unsafe fn get_cx_private() -> *() {
        unsafe::reinterpret_cast(JS_GetContextPrivate(self.ptr))
    }

    unsafe fn set_cx_private(data: *()) {
        JS_SetContextPrivate(self.ptr, unsafe::reinterpret_cast(data));
    }

    unsafe fn get_obj_private(obj: *JSObject) -> *() {
        unsafe::reinterpret_cast(JS_GetPrivate(obj))
    }

    unsafe fn set_obj_private(obj: *JSObject, data: *()) {
        JS_SetPrivate(obj, unsafe::reinterpret_cast(data));
    }
}

extern fn reportError(_cx: *JSContext,
                     msg: *c_char,
                     report: *JSErrorReport) {
    unsafe {
        let fnptr = (*report).filename;
        let fname = if fnptr.is_not_null() {from_c_str(fnptr)} else {~"none"};
        let lineno = (*report).lineno;
        let msg = from_c_str(msg);
        #error["Error at %s:%?: %s\n", fname, lineno, msg];
    }
}

// ___________________________________________________________________________
// compartment

type bare_compartment = {
    cx: cx,
    name_pool: name_pool,
    mut global_funcs: ~[@~[JSFunctionSpec]],
    mut global_props: ~[@~[JSPropertySpec]],
    global_class: @JSClass,
    global_obj: jsobj,
    global_protos: hashmap<~str, jsobj>
};

trait methods {
    fn define_functions(specfn: fn(name_pool) -> ~[JSFunctionSpec]) -> result<(),()>;
    fn define_properties(specfn: fn() -> ~[JSPropertySpec]) -> result<(),()>;
    fn define_property(name: ~str, value: jsval, getter: JSPropertyOp,
                       setter: JSStrictPropertyOp, attrs: c_uint) -> result<(),()>;
    fn new_object(class_name: ~str, proto: *JSObject, parent: *JSObject)
        -> result<jsobj, ()>;
    fn new_object_with_proto(class_name: ~str, proto_name: ~str, parent: *JSObject)
        -> result<jsobj, ()>;
    fn register_class(class_fn: fn(bare_compartment) -> JSClass);
    fn add_name(name: ~str) -> *c_char;
}

type compartment = @bare_compartment;

impl bare_compartment : methods {
    fn define_functions(specfn: fn(name_pool) -> ~[JSFunctionSpec]) -> result<(),()> {
        let specvec = @specfn(self.name_pool);
        vec::push(self.global_funcs, specvec);
        vec::as_buf(*specvec, |specs, _len| {
            result(JS_DefineFunctions(self.cx.ptr, self.global_obj.ptr, specs))
        })
    }
    fn define_properties(specfn: fn() -> ~[JSPropertySpec]) -> result<(),()> {
        let specvec = @specfn();
        vec::push(self.global_props, specvec);
        vec::as_buf(*specvec, |specs, _len| {
            result(JS_DefineProperties(self.cx.ptr, self.global_obj.ptr, specs))
        })
    }
    fn define_property(name: ~str, value: jsval, getter: JSPropertyOp,
                       setter: JSStrictPropertyOp, attrs: c_uint) -> result<(),()> {
        result(JS_DefineProperty(self.cx.ptr, self.global_obj.ptr, self.add_name(name),
                                 value, getter, setter, attrs))
    }
    fn new_object(class_name: ~str, proto: *JSObject, parent: *JSObject)
        -> result<jsobj, ()> {
        let classptr = self.cx.lookup_class_name(class_name);
        let obj = self.cx.rooted_obj(JS_NewObject(self.cx.ptr, ptr::assimilate(&*classptr),
                                                  proto, parent));
        if obj.ptr.is_not_null() {
            self.global_protos.insert(
                unsafe { str::unsafe::from_c_str(classptr.name) }, obj);
        }
        result_obj(obj)
    }
    fn new_object_with_proto(class_name: ~str, proto_name: ~str, parent: *JSObject)
        -> result<jsobj, ()> {
        let classptr = self.cx.lookup_class_name(class_name);
        let proto = option::expect(self.global_protos.find(proto_name),
           #fmt("new_object_with_proto: expected to find %s in the proto \
              table", proto_name));
        let obj = self.cx.rooted_obj(JS_NewObject(self.cx.ptr, ptr::assimilate(&*classptr),
                                                  proto.ptr, parent));
        result_obj(obj)
    }
    fn register_class(class_fn: fn(bare_compartment) -> JSClass) {
        let classptr = @class_fn(self);
        if !self.cx.classes.insert(
            unsafe { str::unsafe::from_c_str(classptr.name) },
            classptr) {
            fail ~"Duplicate JSClass registered; you're gonna have a bad time."
        }
    }
    fn add_name(name: ~str) -> *c_char {
        self.name_pool.add(copy name)
    }
}

// ___________________________________________________________________________
// objects

type jsobj = @jsobj_rsrc;

struct jsobj_rsrc {
    let cx : cx;
    let cxptr : *JSContext;
    let ptr : *JSObject;
    new(rec : {cx: cx, cxptr: *JSContext, ptr: *JSObject}) {
        self.cx = rec.cx;
        self.cxptr = rec.cxptr;
        self.ptr = rec.ptr;
    }
    drop {
        JS_RemoveObjectRoot(self.cxptr, ptr::addr_of(self.ptr));
    }
}

// ___________________________________________________________________________
// random utilities

trait to_jsstr {
    fn to_jsstr(cx: cx) -> *JSString;
}

impl ~str : to_jsstr {
    fn to_jsstr(cx: cx) -> *JSString {
        str::as_buf(self, |buf, len| {
            let cbuf = unsafe { unsafe::reinterpret_cast(buf) };
            bg::JS_NewStringCopyN(cx.ptr, cbuf, len as size_t)
        })
    }
}

#[cfg(test)]
mod test {

    #[test]
    fn dummy() {
        let rt = rt();
        let cx = rt.cx();
        cx.set_default_options_and_version();
        cx.set_logging_error_reporter();
        cx.new_compartment(global::global_class).chain(|comp| {
            comp.define_functions(global::debug_fns);

            let bytes = str::bytes(~"debug(22);");
            cx.evaluate_script(comp.global_obj, bytes, ~"test", 1u)
        });
    }

}
<|endoftext|>