<commit_before>/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use super::get_applied_fragment_name;
use crate::util::{remove_directive, replace_directive};
use common::WithLocation;
use graphql_ir::{
    Argument, ConstantValue, Directive, FragmentDefinition, FragmentSpread, InlineFragment,
    LinkedField, NamedItem, OperationDefinition, Program, ScalarField, Selection, Transformed,
    Transformer, ValidationError, ValidationMessage, ValidationResult, Value,
};
use interner::{Intern, StringKey};
use lazy_static::lazy_static;
use std::{collections::HashMap, sync::Arc};

pub struct DeferStreamConstants {
    pub defer_name: StringKey,
    pub stream_name: StringKey,
    pub if_arg: StringKey,
    pub label_arg: StringKey,
    pub initial_count_arg: StringKey,
    pub use_customized_batch_arg: StringKey,
}

impl Default for DeferStreamConstants {
    fn default() -> Self {
        Self {
            defer_name: "defer".intern(),
            stream_name: "stream".intern(),
            if_arg: "if".intern(),
            label_arg: "label".intern(),
            initial_count_arg: "initial_count".intern(),
            use_customized_batch_arg: "use_customized_batch_arg".intern(),
        }
    }
}

lazy_static! {
    pub static ref DEFER_STREAM_CONSTANTS: DeferStreamConstants = Default::default();
}

pub fn transform_defer_stream<'s>(program: &Program<'s>) -> ValidationResult<Program<'s>> {
    let mut transformer = DeferStreamTransform {
        program,
        current_document_name: None,
        labels: Default::default(),
        errors: Default::default(),
    };
    let next_program = transformer.transform_program(program);

    if transformer.errors.is_empty() {
        Ok(next_program.replace_or_else(|| program.clone()))
    } else {
        Err(transformer.errors)
    }
}

struct DeferStreamTransform<'s> {
    program: &'s Program<'s>,
    current_document_name: Option<StringKey>,
    labels: HashMap<StringKey, Directive>,
    errors: Vec<ValidationError>,
}

impl DeferStreamTransform<'_> {
    fn set_current_document_name(&mut self, document_name: StringKey) {
        self.current_document_name = Some(document_name)
    }

    fn record_label(&mut self, label: StringKey, directive: &Directive) {
        let prev_directive = self.labels.get(&label);
        match prev_directive {
            Some(prev) => {
                self.errors.push(ValidationError::new(
                    ValidationMessage::LabelNotUniqueForDeferStream {
                        directive_name: DEFER_STREAM_CONSTANTS.defer_name,
                    },
                    vec![prev.name.location, directive.name.location],
                ));
            }
            None => {
                self.labels.insert(label, directive.to_owned());
            }
        };
    }

    fn transform_defer(
        &mut self,
        spread: &FragmentSpread,
        defer: &Directive,
    ) -> Result<Transformed<Selection>, ValidationError> {
        let if_arg = &defer.arguments.named(DEFER_STREAM_CONSTANTS.if_arg);
        if let Some(arg) = if_arg {
            if is_literal_false(arg) {
                return Ok(Transformed::Replace(Selection::FragmentSpread(Arc::new(
                    FragmentSpread {
                        directives: remove_directive(&spread.directives, defer.name.item),
                        ..spread.clone()
                    },
                ))));
            }
        }

        let label_value = get_literal_string_argument(&defer, DEFER_STREAM_CONSTANTS.label_arg)?;

        let is_user_provided_label_empty = label_value == None;
        let label = label_value
            .unwrap_or_else(|| get_applied_fragment_name(spread.fragment.item, &spread.arguments));

        let transformed_label = transform_label(
            self.current_document_name
                .expect("We expect the parent name to be defined here."),
            DEFER_STREAM_CONSTANTS.defer_name,
            label,
        );
        self.record_label(transformed_label, defer);
        let next_label_value = Value::Constant(ConstantValue::String(transformed_label));

        let next_arguments = if is_user_provided_label_empty {
            let mut args = defer.arguments.to_owned();
            args.push(Argument {
                name: WithLocation {
                    item: DEFER_STREAM_CONSTANTS.label_arg,
                    location: defer.name.location,
                },
                value: WithLocation {
                    item: next_label_value,
                    location: defer.name.location,
                },
            });
            args
        } else {
            replace_argument_value(
                &defer.arguments,
                DEFER_STREAM_CONSTANTS.label_arg,
                next_label_value,
            )
        };

        let next_defer = Directive {
            name: defer.name,
            arguments: next_arguments,
        };

        Ok(Transformed::Replace(Selection::FragmentSpread(Arc::new(
            FragmentSpread {
                directives: replace_directive(&spread.directives, next_defer),
                ..spread.clone()
            },
        ))))
    }

    fn transform_stream(
        &mut self,
        linked_field: &LinkedField,
        stream: &Directive,
    ) -> Result<Transformed<Selection>, ValidationError> {
        let if_arg = &stream.arguments.named(DEFER_STREAM_CONSTANTS.if_arg);
        if let Some(arg) = if_arg {
            if is_literal_false(arg) {
                return Ok(Transformed::Replace(Selection::LinkedField(Arc::new(
                    LinkedField {
                        directives: remove_directive(&linked_field.directives, stream.name.item),
                        ..linked_field.clone()
                    },
                ))));
            }
        }

        let initial_count_arg = stream
            .arguments
            .named(DEFER_STREAM_CONSTANTS.initial_count_arg);
        if initial_count_arg.is_none() {
            return Err(ValidationError::new(
                ValidationMessage::StreamInitialCountRequired,
                vec![stream.name.location],
            ));
        }

        let label_value = get_literal_string_argument(&stream, DEFER_STREAM_CONSTANTS.label_arg)?;

        let is_user_provided_label_empty = label_value == None;
        let label = label_value.unwrap_or_else(|| {
            get_applied_fragment_name(
                linked_field.alias_or_name(self.program.schema()),
                &linked_field.arguments,
            )
        });

        let transformed_label = transform_label(
            self.current_document_name
                .expect("We expect the parent name to be defined here."),
            DEFER_STREAM_CONSTANTS.stream_name,
            label,
        );

        self.record_label(transformed_label, stream);

        let next_label_value = Value::Constant(ConstantValue::String(transformed_label));
        let next_arguments = if is_user_provided_label_empty {
            let mut args = stream.arguments.to_owned();
            args.push(Argument {
                name: WithLocation {
                    item: DEFER_STREAM_CONSTANTS.label_arg,
                    location: stream.name.location,
                },
                value: WithLocation {
                    item: next_label_value,
                    location: stream.name.location,
                },
            });
            args
        } else {
            replace_argument_value(
                &stream.arguments,
                DEFER_STREAM_CONSTANTS.label_arg,
                next_label_value,
            )
        };

        let next_stream = Directive {
            name: stream.name,
            arguments: next_arguments,
        };

        Ok(Transformed::Replace(Selection::LinkedField(Arc::new(
            LinkedField {
                directives: replace_directive(&linked_field.directives, next_stream),
                ..linked_field.clone()
            },
        ))))
    }
}

impl<'s> Transformer for DeferStreamTransform<'s> {
    const NAME: &'static str = "DeferStreamTransform";
    const VISIT_ARGUMENTS: bool = false;
    const VISIT_DIRECTIVES: bool = false;

    fn transform_operation(
        &mut self,
        operation: &OperationDefinition,
    ) -> Transformed<OperationDefinition> {
        self.set_current_document_name(operation.name.item);
        self.default_transform_operation(operation)
    }

    fn transform_fragment(
        &mut self,
        fragment: &FragmentDefinition,
    ) -> Transformed<FragmentDefinition> {
        self.set_current_document_name(fragment.name.item);
        self.default_transform_fragment(fragment)
    }

    /// Validates @defer is not allowed on inline fragments.
    fn transform_inline_fragment(
        &mut self,
        inline_fragment: &InlineFragment,
    ) -> Transformed<Selection> {
        let defer_directive = inline_fragment
            .directives
            .named(DEFER_STREAM_CONSTANTS.defer_name);
        if let Some(directive) = defer_directive {
            self.errors.push(ValidationError::new(
                ValidationMessage::InvalidDeferOnInlineFragment,
                vec![directive.name.location],
            ));
        }

        self.default_transform_inline_fragment(inline_fragment)
    }

    /// Transform of fragment spread with @defer is delegated to `transform_defer`.
    fn transform_fragment_spread(&mut self, spread: &FragmentSpread) -> Transformed<Selection> {
        let defer_directive = spread.directives.named(DEFER_STREAM_CONSTANTS.defer_name);
        if let Some(defer) = defer_directive {
            match self.transform_defer(spread, defer) {
                Ok(transformed) => transformed,
                Err(err) => {
                    self.errors.push(err);
                    self.default_transform_fragment_spread(spread)
                }
            }
        } else {
            self.default_transform_fragment_spread(spread)
        }
    }

    /// Validates @stream is not allowed on scalar fields.
    fn transform_scalar_field(&mut self, scalar_field: &ScalarField) -> Transformed<Selection> {
        let stream_directive = &scalar_field
            .directives
            .named(DEFER_STREAM_CONSTANTS.stream_name);
        if let Some(directive) = stream_directive {
            self.errors.push(ValidationError::new(
                ValidationMessage::InvalidStreamOnScalarField {
                    field_name: scalar_field.alias_or_name(self.program.schema()),
                },
                vec![directive.name.location],
            ));
        }
        self.default_transform_scalar_field(scalar_field)
    }

    /// Transform of linked field with @stream is delegated to `transform_stream`.
    fn transform_linked_field(&mut self, linked_field: &LinkedField) -> Transformed<Selection> {
        let stream_directive = linked_field
            .directives
            .named(DEFER_STREAM_CONSTANTS.stream_name);
        if let Some(stream) = stream_directive {
            match self.transform_stream(linked_field, stream) {
                Ok(transformed) => transformed,
                Err(err) => {
                    self.errors.push(err);
                    self.default_transform_linked_field(linked_field)
                }
            }
        } else {
            self.default_transform_linked_field(linked_field)
        }
    }
}

fn is_literal_false(arg: &Argument) -> bool {
    match arg.value.item {
        Value::Constant(ConstantValue::Boolean(val)) => !val,
        _ => false,
    }
}

fn transform_label(
    parent_name: StringKey,
    directive_name: StringKey,
    label: StringKey,
) -> StringKey {
    format!("{}${}${}", parent_name, directive_name, label).intern()
}

fn get_literal_string_argument(
    directive: &Directive,
    arg_name: StringKey,
) -> Result<Option<StringKey>, ValidationError> {
    let argument = directive.arguments.named(arg_name);
    match argument {
        Some(arg) => match arg.value.item {
            Value::Constant(ConstantValue::String(val)) => Ok(Some(val)),
            _ => Err(ValidationError::new(
                ValidationMessage::LiteralStringArgumentExpectedForDirective {
                    arg_name: DEFER_STREAM_CONSTANTS.label_arg,
                    directive_name: directive.name.item,
                },
                vec![directive.name.location],
            )),
        },
        None => Ok(None),
    }
}

fn replace_argument_value(
    current_arguments: &[Argument],
    replacement_name: StringKey,
    value: Value,
) -> Vec<Argument> {
    current_arguments
        .iter()
        .map(|arg| {
            if arg.name.item == replacement_name {
                return Argument {
                    name: arg.name,
                    value: WithLocation {
                        location: arg.value.location,
                        item: value.to_owned(),
                    },
                };
            }
            arg.to_owned()
        })
        .collect()
}
<commit_msg>small refactor to simplify defer_stream<commit_after>/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use super::get_applied_fragment_name;
use crate::util::{remove_directive, replace_directive};
use common::WithLocation;
use graphql_ir::{
    Argument, ConstantValue, Directive, FragmentDefinition, FragmentSpread, InlineFragment,
    LinkedField, NamedItem, OperationDefinition, Program, ScalarField, Selection, Transformed,
    Transformer, ValidationError, ValidationMessage, ValidationResult, Value,
};
use interner::{Intern, StringKey};
use lazy_static::lazy_static;
use std::{collections::HashMap, sync::Arc};

pub struct DeferStreamConstants {
    pub defer_name: StringKey,
    pub stream_name: StringKey,
    pub if_arg: StringKey,
    pub label_arg: StringKey,
    pub initial_count_arg: StringKey,
    pub use_customized_batch_arg: StringKey,
}

impl Default for DeferStreamConstants {
    fn default() -> Self {
        Self {
            defer_name: "defer".intern(),
            stream_name: "stream".intern(),
            if_arg: "if".intern(),
            label_arg: "label".intern(),
            initial_count_arg: "initial_count".intern(),
            use_customized_batch_arg: "use_customized_batch_arg".intern(),
        }
    }
}

lazy_static! {
    pub static ref DEFER_STREAM_CONSTANTS: DeferStreamConstants = Default::default();
}

pub fn transform_defer_stream<'s>(program: &Program<'s>) -> ValidationResult<Program<'s>> {
    let mut transformer = DeferStreamTransform {
        program,
        current_document_name: None,
        labels: Default::default(),
        errors: Default::default(),
    };
    let next_program = transformer.transform_program(program);

    if transformer.errors.is_empty() {
        Ok(next_program.replace_or_else(|| program.clone()))
    } else {
        Err(transformer.errors)
    }
}

struct DeferStreamTransform<'s> {
    program: &'s Program<'s>,
    current_document_name: Option<StringKey>,
    labels: HashMap<StringKey, Directive>,
    errors: Vec<ValidationError>,
}

impl DeferStreamTransform<'_> {
    fn set_current_document_name(&mut self, document_name: StringKey) {
        self.current_document_name = Some(document_name)
    }

    fn record_label(&mut self, label: StringKey, directive: &Directive) {
        let prev_directive = self.labels.get(&label);
        match prev_directive {
            Some(prev) => {
                self.errors.push(ValidationError::new(
                    ValidationMessage::LabelNotUniqueForDeferStream {
                        directive_name: DEFER_STREAM_CONSTANTS.defer_name,
                    },
                    vec![prev.name.location, directive.name.location],
                ));
            }
            None => {
                self.labels.insert(label, directive.to_owned());
            }
        };
    }

    fn transform_defer(
        &mut self,
        spread: &FragmentSpread,
        defer: &Directive,
    ) -> Result<Transformed<Selection>, ValidationError> {
        let if_arg = defer.arguments.named(DEFER_STREAM_CONSTANTS.if_arg);
        if is_literal_false_arg(if_arg) {
            return Ok(Transformed::Replace(Selection::FragmentSpread(Arc::new(
                FragmentSpread {
                    directives: remove_directive(&spread.directives, defer.name.item),
                    ..spread.clone()
                },
            ))));
        }

        let label_value = get_literal_string_argument(&defer, DEFER_STREAM_CONSTANTS.label_arg)?;

        let is_user_provided_label_empty = label_value == None;
        let label = label_value
            .unwrap_or_else(|| get_applied_fragment_name(spread.fragment.item, &spread.arguments));

        let transformed_label = transform_label(
            self.current_document_name
                .expect("We expect the parent name to be defined here."),
            DEFER_STREAM_CONSTANTS.defer_name,
            label,
        );
        self.record_label(transformed_label, defer);
        let next_label_value = Value::Constant(ConstantValue::String(transformed_label));

        let next_arguments = if is_user_provided_label_empty {
            let mut args = defer.arguments.to_owned();
            args.push(Argument {
                name: WithLocation {
                    item: DEFER_STREAM_CONSTANTS.label_arg,
                    location: defer.name.location,
                },
                value: WithLocation {
                    item: next_label_value,
                    location: defer.name.location,
                },
            });
            args
        } else {
            replace_argument_value(
                &defer.arguments,
                DEFER_STREAM_CONSTANTS.label_arg,
                next_label_value,
            )
        };

        let next_defer = Directive {
            name: defer.name,
            arguments: next_arguments,
        };

        Ok(Transformed::Replace(Selection::FragmentSpread(Arc::new(
            FragmentSpread {
                directives: replace_directive(&spread.directives, next_defer),
                ..spread.clone()
            },
        ))))
    }

    fn transform_stream(
        &mut self,
        linked_field: &LinkedField,
        stream: &Directive,
    ) -> Result<Transformed<Selection>, ValidationError> {
        let if_arg = stream.arguments.named(DEFER_STREAM_CONSTANTS.if_arg);
        if is_literal_false_arg(if_arg) {
            return Ok(Transformed::Replace(Selection::LinkedField(Arc::new(
                LinkedField {
                    directives: remove_directive(&linked_field.directives, stream.name.item),
                    ..linked_field.clone()
                },
            ))));
        }

        let initial_count_arg = stream
            .arguments
            .named(DEFER_STREAM_CONSTANTS.initial_count_arg);
        if initial_count_arg.is_none() {
            return Err(ValidationError::new(
                ValidationMessage::StreamInitialCountRequired,
                vec![stream.name.location],
            ));
        }

        let label_value = get_literal_string_argument(&stream, DEFER_STREAM_CONSTANTS.label_arg)?;

        let is_user_provided_label_empty = label_value == None;
        let label = label_value.unwrap_or_else(|| {
            get_applied_fragment_name(
                linked_field.alias_or_name(self.program.schema()),
                &linked_field.arguments,
            )
        });

        let transformed_label = transform_label(
            self.current_document_name
                .expect("We expect the parent name to be defined here."),
            DEFER_STREAM_CONSTANTS.stream_name,
            label,
        );

        self.record_label(transformed_label, stream);

        let next_label_value = Value::Constant(ConstantValue::String(transformed_label));
        let next_arguments = if is_user_provided_label_empty {
            let mut args = stream.arguments.to_owned();
            args.push(Argument {
                name: WithLocation {
                    item: DEFER_STREAM_CONSTANTS.label_arg,
                    location: stream.name.location,
                },
                value: WithLocation {
                    item: next_label_value,
                    location: stream.name.location,
                },
            });
            args
        } else {
            replace_argument_value(
                &stream.arguments,
                DEFER_STREAM_CONSTANTS.label_arg,
                next_label_value,
            )
        };

        let next_stream = Directive {
            name: stream.name,
            arguments: next_arguments,
        };

        Ok(Transformed::Replace(Selection::LinkedField(Arc::new(
            LinkedField {
                directives: replace_directive(&linked_field.directives, next_stream),
                ..linked_field.clone()
            },
        ))))
    }
}

impl<'s> Transformer for DeferStreamTransform<'s> {
    const NAME: &'static str = "DeferStreamTransform";
    const VISIT_ARGUMENTS: bool = false;
    const VISIT_DIRECTIVES: bool = false;

    fn transform_operation(
        &mut self,
        operation: &OperationDefinition,
    ) -> Transformed<OperationDefinition> {
        self.set_current_document_name(operation.name.item);
        self.default_transform_operation(operation)
    }

    fn transform_fragment(
        &mut self,
        fragment: &FragmentDefinition,
    ) -> Transformed<FragmentDefinition> {
        self.set_current_document_name(fragment.name.item);
        self.default_transform_fragment(fragment)
    }

    /// Validates @defer is not allowed on inline fragments.
    fn transform_inline_fragment(
        &mut self,
        inline_fragment: &InlineFragment,
    ) -> Transformed<Selection> {
        let defer_directive = inline_fragment
            .directives
            .named(DEFER_STREAM_CONSTANTS.defer_name);
        if let Some(directive) = defer_directive {
            self.errors.push(ValidationError::new(
                ValidationMessage::InvalidDeferOnInlineFragment,
                vec![directive.name.location],
            ));
        }

        self.default_transform_inline_fragment(inline_fragment)
    }

    /// Transform of fragment spread with @defer is delegated to `transform_defer`.
    fn transform_fragment_spread(&mut self, spread: &FragmentSpread) -> Transformed<Selection> {
        let defer_directive = spread.directives.named(DEFER_STREAM_CONSTANTS.defer_name);
        if let Some(defer) = defer_directive {
            match self.transform_defer(spread, defer) {
                Ok(transformed) => transformed,
                Err(err) => {
                    self.errors.push(err);
                    self.default_transform_fragment_spread(spread)
                }
            }
        } else {
            self.default_transform_fragment_spread(spread)
        }
    }

    /// Validates @stream is not allowed on scalar fields.
    fn transform_scalar_field(&mut self, scalar_field: &ScalarField) -> Transformed<Selection> {
        let stream_directive = &scalar_field
            .directives
            .named(DEFER_STREAM_CONSTANTS.stream_name);
        if let Some(directive) = stream_directive {
            self.errors.push(ValidationError::new(
                ValidationMessage::InvalidStreamOnScalarField {
                    field_name: scalar_field.alias_or_name(self.program.schema()),
                },
                vec![directive.name.location],
            ));
        }
        self.default_transform_scalar_field(scalar_field)
    }

    /// Transform of linked field with @stream is delegated to `transform_stream`.
    fn transform_linked_field(&mut self, linked_field: &LinkedField) -> Transformed<Selection> {
        let stream_directive = linked_field
            .directives
            .named(DEFER_STREAM_CONSTANTS.stream_name);
        if let Some(stream) = stream_directive {
            match self.transform_stream(linked_field, stream) {
                Ok(transformed) => transformed,
                Err(err) => {
                    self.errors.push(err);
                    self.default_transform_linked_field(linked_field)
                }
            }
        } else {
            self.default_transform_linked_field(linked_field)
        }
    }
}

fn is_literal_false_arg(arg: Option<&Argument>) -> bool {
    if let Some(arg) = arg {
        match arg.value.item {
            Value::Constant(ConstantValue::Boolean(false)) => true,
            _ => false,
        }
    } else {
        false
    }
}

fn transform_label(
    parent_name: StringKey,
    directive_name: StringKey,
    label: StringKey,
) -> StringKey {
    format!("{}${}${}", parent_name, directive_name, label).intern()
}

fn get_literal_string_argument(
    directive: &Directive,
    arg_name: StringKey,
) -> Result<Option<StringKey>, ValidationError> {
    let argument = directive.arguments.named(arg_name);
    match argument {
        Some(arg) => match arg.value.item {
            Value::Constant(ConstantValue::String(val)) => Ok(Some(val)),
            _ => Err(ValidationError::new(
                ValidationMessage::LiteralStringArgumentExpectedForDirective {
                    arg_name: DEFER_STREAM_CONSTANTS.label_arg,
                    directive_name: directive.name.item,
                },
                vec![directive.name.location],
            )),
        },
        None => Ok(None),
    }
}

fn replace_argument_value(
    current_arguments: &[Argument],
    replacement_name: StringKey,
    value: Value,
) -> Vec<Argument> {
    current_arguments
        .iter()
        .map(|arg| {
            if arg.name.item == replacement_name {
                return Argument {
                    name: arg.name,
                    value: WithLocation {
                        location: arg.value.location,
                        item: value.to_owned(),
                    },
                };
            }
            arg.to_owned()
        })
        .collect()
}
<|endoftext|>


<commit_before>extern crate serde;
extern crate serde_json;
extern crate hyper;

#[macro_use]
extern crate serde_derive;

use std::process::{Command};
use std::io::Read;
use hyper::header::ContentType;
use hyper::client::Client;
use serde_json::Value;

#[derive(Serialize, Deserialize)]
struct RPCRequest {
    jsonrpc: String,
    method: String,
    params: Vec<String>,
    id: usize,
}

#[derive(Serialize, Deserialize)]
struct RPCResponse<T> {
    jsonrpc: String,
    result: T,
    id: usize,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct RPCTransaction {
    pub hash: String,
    pub nonce: String,
    pub blockHash: String,
    pub blockNumber: String,
    pub transactionIndex: String,
    pub from: String,
    pub to: String,
    pub value: String,
    pub gas: String,
    pub gasPrice: String,
    pub input: String,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct RPCBlock {
    pub number: String,
    pub hash: String,
    pub parentHash: String,
    pub nonce: String,
    pub sha3Uncles: String,
    pub logsBloom: String,
    pub transactionsRoot: String,
    pub stateRoot: String,
    pub miner: String,
    pub difficulty: String,
    pub totalDifficulty: String,
    pub extraData: String,
    pub size: String,
    pub gasLimit: String,
    pub gasUsed: String,
    pub timestamp: String,
    pub transactions: Vec<String>,
    pub uncles: Vec<String>
}

pub struct GethRPCClient {
    endpoint: String,
    free_id: usize,
}

impl GethRPCClient {
    pub fn new(endpoint: &str) -> Self {
        GethRPCClient {
            endpoint: endpoint.to_string(),
            free_id: 1,
        }
    }

    fn rpc_request<T: serde::Deserialize>(&mut self, method: &str, params: Vec<String>) -> T {
        let request = RPCRequest {
            jsonrpc: "2.0".to_string(),
            method: method.to_string(),
            params: params,
            id: self.free_id,
        };
        self.free_id = self.free_id + 1;

        let client = Client::new();
        let mut response_raw = client.post(&self.endpoint)
            .header(ContentType::json())
            .body(&serde_json::to_string(&request).unwrap())
            .send().unwrap();
        let mut buffer = String::new();
        response_raw.read_to_string(&mut buffer).unwrap();

        let response: RPCResponse<T> = serde_json::from_str(&buffer).unwrap();
        response.result
    }

    pub fn get_block_transaction_count_by_number(&mut self, number: &str) -> String {
        self.rpc_request::<String>("eth_getBlockTransactionCountByNumber",
                                   vec![number.to_string()])
    }

    pub fn get_transaction_by_block_number_and_index(&mut self, number: &str, index: &str)
                                                 -> RPCTransaction {
        self.rpc_request::<RPCTransaction>("eth_getTransactionByBlockNumberAndIndex",
                                           vec![number.to_string(), index.to_string()])
    }

    pub fn get_block_by_number(&mut self, number: &str) -> RPCBlock {
        self.rpc_request::<RPCBlock>("eth_getBlockByNumber",
                                     vec![number.to_string(), "false".to_string()])
    }

    pub fn block_number(&mut self) -> String {
        self.rpc_request::<String>("eth_blockNumber", vec![])
    }

    pub fn get_code(&mut self, address: &str, number: &str) -> String {
        self.rpc_request::<String>("eth_getCode", vec![address.to_string(), number.to_string()])
    }
}

pub fn regression(address: &str) -> bool {
    let mut client = GethRPCClient::new("http://localhost:8545");
    let height = usize::from_str_radix(&client.block_number(), 16).unwrap();
    println!("height: {}", height);

    for blk_no in 49439..height {
        let block = client.get_block_by_number(format!("0x{:x}", blk_no).as_str());
        println!("{:?}", block);
    }
    false
}
<commit_msg>Problem: new feature<commit_after>extern crate serde;
extern crate serde_json;
extern crate hyper;

#[macro_use]
extern crate serde_derive;

use std::process::{Command};
use std::io::Read;
use hyper::header::ContentType;
use hyper::client::Client;
use serde_json::Value;

#[derive(Serialize, Deserialize)]
struct RPCRequest {
    jsonrpc: String,
    method: String,
    params: Vec<String>,
    id: usize,
}

#[derive(Serialize, Deserialize)]
struct RPCResponse<T> {
    jsonrpc: String,
    result: T,
    id: usize,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct RPCTransaction {
    pub hash: String,
    pub nonce: String,
    pub blockHash: String,
    pub blockNumber: String,
    pub transactionIndex: String,
    pub from: String,
    pub to: String,
    pub value: String,
    pub gas: String,
    pub gasPrice: String,
    pub input: String,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct RPCBlock {
    pub number: String,
    pub hash: String,
    pub parentHash: String,
    pub nonce: String,
    pub sha3Uncles: String,
    pub logsBloom: String,
    pub transactionsRoot: String,
    pub stateRoot: String,
    pub miner: String,
    pub difficulty: String,
    pub totalDifficulty: String,
    pub extraData: String,
    pub size: String,
    pub gasLimit: String,
    pub gasUsed: String,
    pub timestamp: String,
    pub transactions: Vec<String>,
    pub uncles: Vec<String>
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
pub enum RPCSyncStatus {
    NotSync(bool),
    Sync {
        startingBlock: String,
        currentBlock: String,
        highestBlock: String,
    }
}

#[derive(Serialize, Deserialize, Debug)]
pub struct RPCTransactionReceipt {
    pub transactionHash: String,
    pub transactionIndex: String,
    pub blockHash: String,
    pub blockNumber: String,
    pub cumulativeGasUsed: String,
    pub gasUsed: String,
    pub contractAddress: String,
    pub logs: Vec<String>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct RPCFilter {
    pub fromBlock: String,
    pub toBlock: String,
    pub address: String,
    pub topics: Vec<String>,
}

pub struct GethRPCClient {
    endpoint: String,
    free_id: usize,
}

impl GethRPCClient {
    pub fn new(endpoint: &str) -> Self {
        GethRPCClient {
            endpoint: endpoint.to_string(),
            free_id: 1,
        }
    }

    fn rpc_request<T: serde::Deserialize>(&mut self, method: &str, params: Vec<String>) -> T {
        let request = RPCRequest {
            jsonrpc: "2.0".to_string(),
            method: method.to_string(),
            params: params,
            id: self.free_id,
        };
        self.free_id = self.free_id + 1;

        let client = Client::new();
        let mut response_raw = client.post(&self.endpoint)
            .header(ContentType::json())
            .body(&serde_json::to_string(&request).unwrap())
            .send().unwrap();
        let mut buffer = String::new();
        response_raw.read_to_string(&mut buffer).unwrap();

        let response: RPCResponse<T> = serde_json::from_str(&buffer).unwrap();
        response.result
    }

    pub fn client_version(&mut self) -> String {
        self.rpc_request::<String>("web3_clientVersion", vec![])
    }

    pub fn net_version(&mut self) -> String {
        self.rpc_request::<String>("net_version", vec![])
    }

    pub fn net_listening(&mut self) -> bool {
        self.rpc_request::<bool>("net_listening", vec![])
    }

    pub fn net_peer_count(&mut self) -> String {
        self.rpc_request::<String>("net_peerCount", vec![])
    }

    pub fn sha3(&mut self, data: &str) -> String {
        self.rpc_request::<String>("web3_sha3", vec![])
    }

    pub fn protocol_version(&mut self) -> String {
        self.rpc_request::<String>("eth_protocolVersion", vec![])
    }

    pub fn syncing(&mut self) -> RPCSyncStatus {
        self.rpc_request::<RPCSyncStatus>("eth_syncing", vec![])
    }

    pub fn coinbase(&mut self) -> String {
        self.rpc_request::<String>("eth_coinbase", vec![])
    }

    pub fn mining(&mut self) -> bool {
        self.rpc_request::<bool>("eth_mining", vec![])
    }

    pub fn hashrate(&mut self) -> String {
        self.rpc_request::<String>("eth_hashrate", vec![])
    }

    pub fn gas_price(&mut self) -> String {
        self.rpc_request::<String>("eth_gasPrice", vec![])
    }

    pub fn accounts(&mut self) -> Vec<String> {
        self.rpc_request::<Vec<String>>("eth_accounts", vec![])
    }

    pub fn block_number(&mut self) -> String {
        self.rpc_request::<String>("eth_blockNumber", vec![])
    }

    pub fn get_balance(&mut self, address: &str, number: &str) -> String {
        self.rpc_request::<String>("eth_getBalance",
                                   vec![address.to_string(), number.to_string()])
    }

    pub fn get_storage_at(&mut self, address: &str, index: &str, number: &str) -> String {
        self.rpc_request::<String>("eth_getStorageAt",
                                   vec![address.to_string(), index.to_string(), number.to_string()])
    }

    pub fn get_transaction_count(&mut self, address: &str, number: &str) -> String {
        self.rpc_request::<String>("eth_getTransactionCount",
                                   vec![address.to_string(), number.to_string()])
    }

    pub fn get_block_transaction_count_by_hash(&mut self, hash: &str) -> String {
        self.rpc_request::<String>("eth_getBlockTransactionCountByHash",
                                   vec![hash.to_string()])
    }

    pub fn get_block_transaction_count_by_number(&mut self, number: &str) -> String {
        self.rpc_request::<String>("eth_getBlockTransactionCountByNumber",
                                   vec![number.to_string()])
    }

    pub fn get_uncle_count_by_block_hash(&mut self, hash: &str) -> String {
        self.rpc_request::<String>("eth_getUncleCountByBlockHash",
                                   vec![hash.to_string()])
    }

    pub fn get_uncle_count_by_block_number(&mut self, number: &str) -> String {
        self.rpc_request::<String>("eth_getUncleCountByBlockNumber",
                                   vec![number.to_string()])
    }

    pub fn get_code(&mut self, address: &str, number: &str) -> String {
        self.rpc_request::<String>("eth_getCode", vec![address.to_string(), number.to_string()])
    }

    pub fn sign(&mut self, address: &str, message: &str) -> String {
        self.rpc_request::<String>("eth_sign", vec![address.to_string(), message.to_string()])
    }

    pub fn send_transaction(&mut self, transaction: RPCTransaction) -> String {
        unimplemented!()
    }

    pub fn send_raw_transaction(&mut self, data: &str) -> String {
        self.rpc_request::<String>("eth_sendRawTransaction", vec![data.to_string()])
    }

    pub fn call(&mut self, transaction: RPCTransaction) -> String {
        unimplemented!()
    }

    pub fn estimate_gas(&mut self, transaction: RPCTransaction) -> String {
        unimplemented!()
    }

    pub fn get_block_by_hash(&mut self, hash: &str) -> RPCBlock {
        self.rpc_request::<RPCBlock>("eth_getBlockByHash",
                                     vec![hash.to_string(), "false".to_string()])
    }

    pub fn get_block_by_number(&mut self, number: &str) -> RPCBlock {
        self.rpc_request::<RPCBlock>("eth_getBlockByNumber",
                                     vec![number.to_string(), "false".to_string()])
    }

    pub fn get_transaction_by_hash(&mut self, hash: &str) -> RPCTransaction {
        self.rpc_request::<RPCTransaction>("eth_getTransactionByHash",
                                           vec![hash.to_string()])
    }

    pub fn get_transaction_by_block_hash_and_index(&mut self, hash: &str, index: &str) -> RPCTransaction {
        self.rpc_request::<RPCTransaction>("eth_getTransactionByBlockHashAndIndex",
                                           vec![hash.to_string(), index.to_string()])
    }

    pub fn get_transaction_by_block_number_and_index(&mut self, number: &str, index: &str)
                                                 -> RPCTransaction {
        self.rpc_request::<RPCTransaction>("eth_getTransactionByBlockNumberAndIndex",
                                           vec![number.to_string(), index.to_string()])
    }

    pub fn get_transaction_receipt(&mut self, hash: &str) -> RPCTransactionReceipt {
        self.rpc_request::<RPCTransactionReceipt>("eth_getTransactionReceipt",
                                                 vec![hash.to_string()])
    }

    pub fn get_uncle_by_block_hash_and_index(&mut self, hash: &str, index: &str) -> RPCBlock {
        self.rpc_request::<RPCBlock>("eth_getUncleByBlockHashAndIndex",
                                     vec![hash.to_string(), index.to_string()])
    }

    pub fn get_uncle_by_block_number_and_index(&mut self, number: &str, index: &str) -> RPCBlock {
        self.rpc_request::<RPCBlock>("eth_getUncleByBlockNumberAndIndex",
                                     vec![number.to_string(), index.to_string()])
    }

    pub fn get_compilers(&mut self) -> Vec<String> {
        self.rpc_request::<Vec<String>>("eth_getCompilers", vec![])
    }

    pub fn compile_solidity(&mut self, source: &str) -> String {
        unimplemented!()
    }

    pub fn compile_lll(&mut self, source: &str) -> String {
        self.rpc_request::<String>("eth_compileLLL", vec![source.to_string()])
    }

    pub fn compile_serpent(&mut self, source: &str) -> String {
        self.rpc_request::<String>("eth_compileSerpent", vec![source.to_string()])
    }

    pub fn new_filter(&mut self, filter: RPCFilter) -> String {
        unimplemented!()
    }

    pub fn new_block_filter(&mut self) -> String {
        self.rpc_request::<String>("eth_newBlockFilter", vec![])
    }

    pub fn new_pending_transaction_filter(&mut self) -> String {
        self.rpc_request::<String>("eth_newPendingTransactionFilter", vec![])
    }

    pub fn uninstall_filter(&mut self, id: &str) -> bool {
        self.rpc_request::<bool>("eth_uninstallFilter", vec![id.to_string()])
    }

    pub fn get_filter_changes(&mut self, id: &str) -> String {
        unimplemented!()
    }

    pub fn get_filter_logs(&mut self, id: &str) -> String {
        unimplemented!()
    }

    pub fn get_logs(&mut self, id: &str) -> String {
        unimplemented!()
    }

    pub fn get_work(&mut self) -> Vec<String> {
        self.rpc_request::<Vec<String>>("eth_getWork", vec![])
    }

    pub fn submit_work(&mut self, nonce: &str, pow: &str, mix: &str) -> bool {
        self.rpc_request::<bool>("eth_submitWork",
                                 vec![nonce.to_string(), pow.to_string(), mix.to_string()])
    }

    pub fn submit_hashrate(&mut self, hashrate: &str, id: &str) -> bool {
        self.rpc_request::<bool>("eth_submitHashrate",
                                 vec![hashrate.to_string(), id.to_string()])
    }

    pub fn put_string(&mut self, db: &str, key: &str, value: &str) -> bool {
        self.rpc_request::<bool>("db_putString",
                                 vec![db.to_string(), key.to_string(), value.to_string()])
    }

    pub fn get_string(&mut self, db: &str, key: &str) -> String {
        self.rpc_request::<String>("db_getString",
                                   vec![db.to_string(), key.to_string()])
    }

    pub fn put_hex(&mut self, db: &str, key: &str, value: &str) -> bool {
        self.rpc_request::<bool>("db_putHex",
                                 vec![db.to_string(), key.to_string(), value.to_string()])
    }

    pub fn get_hex(&mut self, db: &str, key: &str) -> String {
        self.rpc_request::<String>("db_getHex",
                                   vec![db.to_string(), key.to_string()])
    }
}

pub fn regression(address: &str) -> bool {
    let mut client = GethRPCClient::new("http://localhost:8545");
    let height = usize::from_str_radix(&client.block_number(), 16).unwrap();
    println!("height: {}", height);

    for blk_no in 49439..height {
        let block = client.get_block_by_number(format!("0x{:x}", blk_no).as_str());
        println!("{:?}", block);
    }
    false
}
<|endoftext|>


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

use crate::dom::bindings::codegen::Bindings::ServiceWorkerContainerBinding::RegistrationOptions;
use crate::dom::bindings::codegen::Bindings::ServiceWorkerContainerBinding::{
    ServiceWorkerContainerMethods, Wrap,
};
use crate::dom::bindings::error::Error;
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject};
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
use crate::dom::bindings::str::USVString;
use crate::dom::client::Client;
use crate::dom::eventtarget::EventTarget;
use crate::dom::globalscope::GlobalScope;
use crate::dom::promise::Promise;
use crate::dom::serviceworker::ServiceWorker;
use crate::script_thread::ScriptThread;
use crate::serviceworkerjob::{Job, JobType};
use dom_struct::dom_struct;
use std::default::Default;
use std::rc::Rc;

#[dom_struct]
pub struct ServiceWorkerContainer {
    eventtarget: EventTarget,
    controller: MutNullableDom<ServiceWorker>,
    client: Dom<Client>,
}

impl ServiceWorkerContainer {
    fn new_inherited(client: &Client) -> ServiceWorkerContainer {
        ServiceWorkerContainer {
            eventtarget: EventTarget::new_inherited(),
            controller: Default::default(),
            client: Dom::from_ref(client),
        }
    }

    #[allow(unrooted_must_root)]
    pub fn new(global: &GlobalScope) -> DomRoot<ServiceWorkerContainer> {
        let client = Client::new(&global.as_window());
        let container = ServiceWorkerContainer::new_inherited(&*client);
        reflect_dom_object(Box::new(container), global, Wrap)
    }
}

impl ServiceWorkerContainerMethods for ServiceWorkerContainer {
    // https://w3c.github.io/ServiceWorker/#service-worker-container-controller-attribute
    fn GetController(&self) -> Option<DomRoot<ServiceWorker>> {
        self.client.get_controller()
    }

    #[allow(unrooted_must_root)]
    // https://w3c.github.io/ServiceWorker/#service-worker-container-register-method and - A
    // https://w3c.github.io/ServiceWorker/#start-register-algorithm - B
    fn Register(&self, script_url: USVString, options: &RegistrationOptions) -> Rc<Promise> {
        // A: Step 1
        let promise = Promise::new(&*self.global());
        let USVString(ref script_url) = script_url;
        let api_base_url = self.global().api_base_url();
        // A: Step 3-5
        let script_url = match api_base_url.join(script_url) {
            Ok(url) => url,
            Err(_) => {
                promise.reject_error(Error::Type("Invalid script URL".to_owned()));
                return promise;
            },
        };
        // B: Step 2
        match script_url.scheme() {
            "https" | "http" => {},
            _ => {
                promise.reject_error(Error::Type("Only secure origins are allowed".to_owned()));
                return promise;
            },
        }
        // B: Step 3
        if script_url.path().to_ascii_lowercase().contains("%2f") ||
            script_url.path().to_ascii_lowercase().contains("%5c")
        {
            promise.reject_error(Error::Type(
                "Script URL contains forbidden characters".to_owned(),
            ));
            return promise;
        }
        // B: Step 4-5
        let scope = match options.scope {
            Some(ref scope) => {
                let &USVString(ref inner_scope) = scope;
                match api_base_url.join(inner_scope) {
                    Ok(url) => url,
                    Err(_) => {
                        promise.reject_error(Error::Type("Invalid scope URL".to_owned()));
                        return promise;
                    },
                }
            },
            None => script_url.join("./").unwrap(),
        };
        // B: Step 6
        match scope.scheme() {
            "https" | "http" => {},
            _ => {
                promise.reject_error(Error::Type("Only secure origins are allowed".to_owned()));
                return promise;
            },
        }
        // B: Step 7
        if scope.path().to_ascii_lowercase().contains("%2f") ||
            scope.path().to_ascii_lowercase().contains("%5c")
        {
            promise.reject_error(Error::Type(
                "Scope URL contains forbidden characters".to_owned(),
            ));
            return promise;
        }

        // B: Step 8
        let job = Job::create_job(
            JobType::Register,
            scope,
            script_url,
            promise.clone(),
            &*self.client,
        );
        ScriptThread::schedule_job(job);
        promise
    }
}
<commit_msg>Explain unrooted_must_root on ServiceWorkerContainer::Register<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use crate::dom::bindings::codegen::Bindings::ServiceWorkerContainerBinding::RegistrationOptions;
use crate::dom::bindings::codegen::Bindings::ServiceWorkerContainerBinding::{
    ServiceWorkerContainerMethods, Wrap,
};
use crate::dom::bindings::error::Error;
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject};
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
use crate::dom::bindings::str::USVString;
use crate::dom::client::Client;
use crate::dom::eventtarget::EventTarget;
use crate::dom::globalscope::GlobalScope;
use crate::dom::promise::Promise;
use crate::dom::serviceworker::ServiceWorker;
use crate::script_thread::ScriptThread;
use crate::serviceworkerjob::{Job, JobType};
use dom_struct::dom_struct;
use std::default::Default;
use std::rc::Rc;

#[dom_struct]
pub struct ServiceWorkerContainer {
    eventtarget: EventTarget,
    controller: MutNullableDom<ServiceWorker>,
    client: Dom<Client>,
}

impl ServiceWorkerContainer {
    fn new_inherited(client: &Client) -> ServiceWorkerContainer {
        ServiceWorkerContainer {
            eventtarget: EventTarget::new_inherited(),
            controller: Default::default(),
            client: Dom::from_ref(client),
        }
    }

    #[allow(unrooted_must_root)]
    pub fn new(global: &GlobalScope) -> DomRoot<ServiceWorkerContainer> {
        let client = Client::new(&global.as_window());
        let container = ServiceWorkerContainer::new_inherited(&*client);
        reflect_dom_object(Box::new(container), global, Wrap)
    }
}

impl ServiceWorkerContainerMethods for ServiceWorkerContainer {
    // https://w3c.github.io/ServiceWorker/#service-worker-container-controller-attribute
    fn GetController(&self) -> Option<DomRoot<ServiceWorker>> {
        self.client.get_controller()
    }

    /// https://w3c.github.io/ServiceWorker/#service-worker-container-register-method and - A
    /// https://w3c.github.io/ServiceWorker/#start-register-algorithm - B
    #[allow(unrooted_must_root)] // Job is unrooted
    fn Register(&self, script_url: USVString, options: &RegistrationOptions) -> Rc<Promise> {
        // A: Step 1
        let promise = Promise::new(&*self.global());
        let USVString(ref script_url) = script_url;
        let api_base_url = self.global().api_base_url();
        // A: Step 3-5
        let script_url = match api_base_url.join(script_url) {
            Ok(url) => url,
            Err(_) => {
                promise.reject_error(Error::Type("Invalid script URL".to_owned()));
                return promise;
            },
        };
        // B: Step 2
        match script_url.scheme() {
            "https" | "http" => {},
            _ => {
                promise.reject_error(Error::Type("Only secure origins are allowed".to_owned()));
                return promise;
            },
        }
        // B: Step 3
        if script_url.path().to_ascii_lowercase().contains("%2f") ||
            script_url.path().to_ascii_lowercase().contains("%5c")
        {
            promise.reject_error(Error::Type(
                "Script URL contains forbidden characters".to_owned(),
            ));
            return promise;
        }
        // B: Step 4-5
        let scope = match options.scope {
            Some(ref scope) => {
                let &USVString(ref inner_scope) = scope;
                match api_base_url.join(inner_scope) {
                    Ok(url) => url,
                    Err(_) => {
                        promise.reject_error(Error::Type("Invalid scope URL".to_owned()));
                        return promise;
                    },
                }
            },
            None => script_url.join("./").unwrap(),
        };
        // B: Step 6
        match scope.scheme() {
            "https" | "http" => {},
            _ => {
                promise.reject_error(Error::Type("Only secure origins are allowed".to_owned()));
                return promise;
            },
        }
        // B: Step 7
        if scope.path().to_ascii_lowercase().contains("%2f") ||
            scope.path().to_ascii_lowercase().contains("%5c")
        {
            promise.reject_error(Error::Type(
                "Scope URL contains forbidden characters".to_owned(),
            ));
            return promise;
        }

        // B: Step 8
        let job = Job::create_job(
            JobType::Register,
            scope,
            script_url,
            promise.clone(),
            &*self.client,
        );
        // Job is unrooted here, do not do anything other than immediately scheduling
        ScriptThread::schedule_job(job);
        promise
    }
}
<|endoftext|>


<commit_before>//
// Copyright (c) Pirmin Kalberer. All rights reserved.
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
//

use core::config::Config;
use core::config::{TilesetCacheCfg, TilesetCfg};
use core::grid::Extent;
use core::layer::Layer;

#[derive(Debug)]
pub struct CacheLimits {
    pub minzoom: u8,
    pub maxzoom: Option<u8>,
    pub no_cache: bool,
}

impl<'a> Config<'a, TilesetCacheCfg> for CacheLimits {
    fn from_config(cfg: &TilesetCacheCfg) -> Result<Self, String> {
        Ok(CacheLimits {
            minzoom: cfg.minzoom,
            maxzoom: cfg.maxzoom.clone(),
            no_cache: cfg.no_cache,
        })
    }
    fn gen_config() -> String {
        "".to_string()
    }
}

/// Collection of layers in one MVT
pub struct Tileset {
    pub name: String,
    pub minzoom: Option<u8>,
    pub maxzoom: Option<u8>,
    pub attribution: Option<String>,
    pub extent: Option<Extent>,
    pub center: Option<(f64, f64)>,
    pub start_zoom: Option<u8>,
    pub layers: Vec<Layer>,
    pub cache_limits: Option<CacheLimits>,
}

pub static WORLD_EXTENT: Extent = Extent {
    minx: -180.0,
    miny: -90.0,
    maxx: 180.0,
    maxy: 90.0,
};

impl Tileset {
    pub fn minzoom(&self) -> u8 {
        self.minzoom
            .unwrap_or(self.layers.iter().map(|l| l.minzoom()).min().unwrap_or(0))
    }
    pub fn maxzoom(&self) -> u8 {
        self.maxzoom.unwrap_or(
            self.layers
                .iter()
                .map(|l| l.maxzoom(22))
                .max()
                .unwrap_or(22),
        )
    }
    pub fn attribution(&self) -> String {
        self.attribution.clone().unwrap_or("".to_string())
    }
    pub fn get_extent(&self) -> &Extent {
        self.extent.as_ref().unwrap_or(&WORLD_EXTENT)
    }
    pub fn get_center(&self) -> (f64, f64) {
        if self.center.is_none() {
            let ext = self.get_extent();
            (
                ext.maxx - (ext.maxx - ext.minx) / 2.0,
                ext.maxy - (ext.maxy - ext.miny) / 2.0,
            )
        } else {
            self.center.unwrap()
        }
    }
    pub fn get_start_zoom(&self) -> u8 {
        self.start_zoom.unwrap_or(2)
    }
    pub fn is_cachable_at(&self, zoom: u8) -> bool {
        match self.cache_limits {
            Some(ref cl) => !cl.no_cache && cl.minzoom <= zoom && cl.maxzoom.unwrap_or(22) >= zoom,
            None => true,
        }
    }
}

impl<'a> Config<'a, TilesetCfg> for Tileset {
    fn from_config(tileset_cfg: &TilesetCfg) -> Result<Self, String> {
        let layers = tileset_cfg
            .layers
            .iter()
            .map(|layer| Layer::from_config(layer).unwrap())
            .collect();
        let cache_limits: Option<CacheLimits> = match tileset_cfg.cache_limits {
            Some(ref cfg) => match CacheLimits::from_config(&cfg) {
                Ok(cl) => Some(cl),
                _ => None,
            },
            None => None,
        };
        Ok(Tileset {
            name: tileset_cfg.name.clone(),
            minzoom: tileset_cfg.minzoom.clone(),
            maxzoom: tileset_cfg.maxzoom.clone(),
            attribution: tileset_cfg.attribution.clone(),
            extent: tileset_cfg.extent.clone(),
            center: tileset_cfg.center.clone(),
            start_zoom: tileset_cfg.start_zoom.clone(),
            layers: layers,
            cache_limits: cache_limits,
        })
    }
    fn gen_config() -> String {
        let mut config = String::new();
        config.push_str(&Layer::gen_config());
        config
    }
    fn gen_runtime_config(&self) -> String {
        let mut config = String::new();
        for layer in &self.layers {
            config.push_str(&layer.gen_runtime_config());
        }
        config
    }
}

#[test]
fn test_zoom() {
    let mut layer = Layer::new("points");
    layer.table_name = Some(String::from("ne_10m_populated_places"));
    layer.geometry_field = Some(String::from("wkb_geometry"));
    layer.geometry_type = Some(String::from("POINT"));
    let mut tileset = Tileset {
        name: "points".to_string(),
        minzoom: None,
        maxzoom: None,
        center: None,
        start_zoom: Some(3),
        attribution: None,
        extent: Some(Extent {
            minx: -179.58998,
            miny: -90.00000,
            maxx: 179.38330,
            maxy: 82.48332,
        }),
        layers: vec![layer],
        cache_limits: None,
    };

    assert_eq!(tileset.minzoom(), 0);
    assert_eq!(tileset.maxzoom(), 22);

    tileset.layers[0].maxzoom = Some(8);
    assert_eq!(tileset.maxzoom(), 8);

    tileset.layers[0].minzoom = Some(3);
    assert_eq!(tileset.minzoom(), 3);

    tileset.minzoom = Some(2);
    assert_eq!(tileset.minzoom(), 2);
}
<commit_msg>Fix cache_limits maxzoom handling<commit_after>//
// Copyright (c) Pirmin Kalberer. All rights reserved.
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
//

use core::config::Config;
use core::config::{TilesetCacheCfg, TilesetCfg};
use core::grid::Extent;
use core::layer::Layer;

#[derive(Debug)]
pub struct CacheLimits {
    pub minzoom: u8,
    pub maxzoom: Option<u8>,
    pub no_cache: bool,
}

impl<'a> Config<'a, TilesetCacheCfg> for CacheLimits {
    fn from_config(cfg: &TilesetCacheCfg) -> Result<Self, String> {
        Ok(CacheLimits {
            minzoom: cfg.minzoom,
            maxzoom: cfg.maxzoom.clone(),
            no_cache: cfg.no_cache,
        })
    }
    fn gen_config() -> String {
        "".to_string()
    }
}

/// Collection of layers in one MVT
pub struct Tileset {
    pub name: String,
    pub minzoom: Option<u8>,
    pub maxzoom: Option<u8>,
    pub attribution: Option<String>,
    pub extent: Option<Extent>,
    pub center: Option<(f64, f64)>,
    pub start_zoom: Option<u8>,
    pub layers: Vec<Layer>,
    pub cache_limits: Option<CacheLimits>,
}

pub static WORLD_EXTENT: Extent = Extent {
    minx: -180.0,
    miny: -90.0,
    maxx: 180.0,
    maxy: 90.0,
};

impl Tileset {
    pub fn minzoom(&self) -> u8 {
        self.minzoom
            .unwrap_or(self.layers.iter().map(|l| l.minzoom()).min().unwrap_or(0))
    }
    pub fn maxzoom(&self) -> u8 {
        self.maxzoom.unwrap_or(
            self.layers
                .iter()
                .map(|l| l.maxzoom(22))
                .max()
                .unwrap_or(22),
        )
    }
    pub fn attribution(&self) -> String {
        self.attribution.clone().unwrap_or("".to_string())
    }
    pub fn get_extent(&self) -> &Extent {
        self.extent.as_ref().unwrap_or(&WORLD_EXTENT)
    }
    pub fn get_center(&self) -> (f64, f64) {
        if self.center.is_none() {
            let ext = self.get_extent();
            (
                ext.maxx - (ext.maxx - ext.minx) / 2.0,
                ext.maxy - (ext.maxy - ext.miny) / 2.0,
            )
        } else {
            self.center.unwrap()
        }
    }
    pub fn get_start_zoom(&self) -> u8 {
        self.start_zoom.unwrap_or(2)
    }
    pub fn is_cachable_at(&self, zoom: u8) -> bool {
        match self.cache_limits {
            Some(ref cl) => !cl.no_cache && cl.minzoom <= zoom && cl.maxzoom.unwrap_or(99) >= zoom,
            None => true,
        }
    }
}

impl<'a> Config<'a, TilesetCfg> for Tileset {
    fn from_config(tileset_cfg: &TilesetCfg) -> Result<Self, String> {
        let layers = tileset_cfg
            .layers
            .iter()
            .map(|layer| Layer::from_config(layer).unwrap())
            .collect();
        let cache_limits: Option<CacheLimits> = match tileset_cfg.cache_limits {
            Some(ref cfg) => match CacheLimits::from_config(&cfg) {
                Ok(cl) => Some(cl),
                _ => None,
            },
            None => None,
        };
        Ok(Tileset {
            name: tileset_cfg.name.clone(),
            minzoom: tileset_cfg.minzoom.clone(),
            maxzoom: tileset_cfg.maxzoom.clone(),
            attribution: tileset_cfg.attribution.clone(),
            extent: tileset_cfg.extent.clone(),
            center: tileset_cfg.center.clone(),
            start_zoom: tileset_cfg.start_zoom.clone(),
            layers: layers,
            cache_limits: cache_limits,
        })
    }
    fn gen_config() -> String {
        let mut config = String::new();
        config.push_str(&Layer::gen_config());
        config
    }
    fn gen_runtime_config(&self) -> String {
        let mut config = String::new();
        for layer in &self.layers {
            config.push_str(&layer.gen_runtime_config());
        }
        config
    }
}

#[test]
fn test_zoom() {
    let mut layer = Layer::new("points");
    layer.table_name = Some(String::from("ne_10m_populated_places"));
    layer.geometry_field = Some(String::from("wkb_geometry"));
    layer.geometry_type = Some(String::from("POINT"));
    let mut tileset = Tileset {
        name: "points".to_string(),
        minzoom: None,
        maxzoom: None,
        center: None,
        start_zoom: Some(3),
        attribution: None,
        extent: Some(Extent {
            minx: -179.58998,
            miny: -90.00000,
            maxx: 179.38330,
            maxy: 82.48332,
        }),
        layers: vec![layer],
        cache_limits: None,
    };

    assert_eq!(tileset.minzoom(), 0);
    assert_eq!(tileset.maxzoom(), 22);

    tileset.layers[0].maxzoom = Some(8);
    assert_eq!(tileset.maxzoom(), 8);

    tileset.layers[0].minzoom = Some(3);
    assert_eq!(tileset.minzoom(), 3);

    tileset.minzoom = Some(2);
    assert_eq!(tileset.minzoom(), 2);
}
<|endoftext|>


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

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

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

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

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

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

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

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

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

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

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

impl Reject for InvalidRequest {}

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

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

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

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

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

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

pub fn forward_geocoder_body(
) -> impl Filter<Extract = (Option<Geometry>,), Error = Rejection> + Copy {
    warp::body::content_length_limit(1024 * 32)
        .and(warp::body::json())
        .and_then(validate_geojson_shape)
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    // #[tokio::test]
    // async fn should_report_valid_reverse() {
    //     let filter = reverse_geocoder();
    //     let resp = warp::test::request()
    //         .path("/api/v1/reverse?lat=48.85406&lon=2.33027")
    //         .reply(&filter)
    //         .await;
    //     assert_eq!(
    //         resp.status(),
    //         warp::http::status::StatusCode::OK,
    //         "Expected Status::OK, Got {}: Error Message: {}",
    //         resp.status(),
    //         String::from_utf8(resp.body().to_vec()).unwrap()
    //     );
    // }
}
<commit_msg>Fix Clippy<commit_after>use geojson::{GeoJson, Geometry};
use std::convert::Infallible;
use warp::{http::StatusCode, path, reject::Reject, Filter, Rejection, Reply};

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

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

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

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

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

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

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

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

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

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

impl Reject for InvalidRequest {}

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

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

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

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

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

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

pub fn forward_geocoder_body(
) -> impl Filter<Extract = (Option<Geometry>,), Error = Rejection> + Copy {
    warp::body::content_length_limit(1024 * 32)
        .and(warp::body::json())
        .and_then(validate_geojson_shape)
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    // #[tokio::test]
    // async fn should_report_valid_reverse() {
    //     let filter = reverse_geocoder();
    //     let resp = warp::test::request()
    //         .path("/api/v1/reverse?lat=48.85406&lon=2.33027")
    //         .reply(&filter)
    //         .await;
    //     assert_eq!(
    //         resp.status(),
    //         warp::http::status::StatusCode::OK,
    //         "Expected Status::OK, Got {}: Error Message: {}",
    //         resp.status(),
    //         String::from_utf8(resp.body().to_vec()).unwrap()
    //     );
    // }
}
<|endoftext|>


<commit_before>use std::sync::Arc;

use base_db::SourceDatabaseExt;

use super::*;

fn check_def_map_is_not_recomputed(ra_fixture_initial: &str, ra_fixture_change: &str) {
    let (mut db, pos) = TestDB::with_position(ra_fixture_initial);
    let krate = db.test_crate();
    {
        let events = db.log_executed(|| {
            db.crate_def_map(krate);
        });
        assert!(format!("{:?}", events).contains("crate_def_map"), "{:#?}", events)
    }
    db.set_file_text(pos.file_id, Arc::new(ra_fixture_change.to_string()));

    {
        let events = db.log_executed(|| {
            db.crate_def_map(krate);
        });
        assert!(!format!("{:?}", events).contains("crate_def_map"), "{:#?}", events)
    }
}

#[test]
fn typing_inside_a_function_should_not_invalidate_def_map() {
    check_def_map_is_not_recomputed(
        r"
        //- /lib.rs
        mod foo;$0

        use crate::foo::bar::Baz;

        enum E { A, B }
        use E::*;

        fn foo() -> i32 {
            1 + 1
        }

        #[cfg(never)]
        fn no() {}
        //- /foo/mod.rs
        pub mod bar;

        //- /foo/bar.rs
        pub struct Baz;
        ",
        r"
        mod foo;

        use crate::foo::bar::Baz;

        enum E { A, B }
        use E::*;

        fn foo() -> i32 { 92 }

        #[cfg(never)]
        fn no() {}
        ",
    );
}

#[test]
fn typing_inside_a_macro_should_not_invalidate_def_map() {
    let (mut db, pos) = TestDB::with_position(
        r"
        //- /lib.rs
        macro_rules! m {
            ($ident:ident) => {
                fn f() {
                    $ident + $ident;
                };
            }
        }
        mod foo;

        //- /foo/mod.rs
        pub mod bar;

        //- /foo/bar.rs
        $0
        m!(X);
        ",
    );
    let krate = db.test_crate();
    {
        let events = db.log_executed(|| {
            let crate_def_map = db.crate_def_map(krate);
            let (_, module_data) = crate_def_map.modules.iter().last().unwrap();
            assert_eq!(module_data.scope.resolutions().count(), 1);
        });
        assert!(format!("{:?}", events).contains("crate_def_map"), "{:#?}", events)
    }
    db.set_file_text(pos.file_id, Arc::new("m!(Y);".to_string()));

    {
        let events = db.log_executed(|| {
            let crate_def_map = db.crate_def_map(krate);
            let (_, module_data) = crate_def_map.modules.iter().last().unwrap();
            assert_eq!(module_data.scope.resolutions().count(), 1);
        });
        assert!(!format!("{:?}", events).contains("crate_def_map"), "{:#?}", events)
    }
}

#[test]
fn typing_inside_a_function_should_not_invalidate_expansions() {
    let (mut db, pos) = TestDB::with_position(
        r#"
//- /lib.rs
macro_rules! m {
    ($ident:ident) => {
        fn $ident() { };
    }
}
mod foo;

//- /foo/mod.rs
pub mod bar;

//- /foo/bar.rs
m!(X);
fn quux() { 1$0 }
m!(Y);
m!(Z);
"#,
    );
    let krate = db.test_crate();
    {
        let events = db.log_executed(|| {
            let crate_def_map = db.crate_def_map(krate);
            let (_, module_data) = crate_def_map.modules.iter().last().unwrap();
            assert_eq!(module_data.scope.resolutions().count(), 4);
        });
        let n_recalculated_item_trees = events.iter().filter(|it| it.contains("item_tree")).count();
        assert_eq!(n_recalculated_item_trees, 6);
        let n_reparsed_macros =
            events.iter().filter(|it| it.contains("parse_macro_expansion")).count();
        assert_eq!(n_reparsed_macros, 3);
    }

    let new_text = r#"
m!(X);
fn quux() { 92 }
m!(Y);
m!(Z);
"#;
    db.set_file_text(pos.file_id, Arc::new(new_text.to_string()));

    {
        let events = db.log_executed(|| {
            let crate_def_map = db.crate_def_map(krate);
            let (_, module_data) = crate_def_map.modules.iter().last().unwrap();
            assert_eq!(module_data.scope.resolutions().count(), 4);
        });
        let n_recalculated_item_trees = events.iter().filter(|it| it.contains("item_tree")).count();
        assert_eq!(n_recalculated_item_trees, 1);
        let n_reparsed_macros =
            events.iter().filter(|it| it.contains("parse_macro_expansion")).count();
        assert_eq!(n_reparsed_macros, 0);
    }
}
<commit_msg>Test that `ItemTree` works as intended<commit_after>use std::sync::Arc;

use base_db::{salsa::SweepStrategy, SourceDatabaseExt};

use crate::{AdtId, ModuleDefId};

use super::*;

fn check_def_map_is_not_recomputed(ra_fixture_initial: &str, ra_fixture_change: &str) {
    let (mut db, pos) = TestDB::with_position(ra_fixture_initial);
    let krate = db.test_crate();
    {
        let events = db.log_executed(|| {
            db.crate_def_map(krate);
        });
        assert!(format!("{:?}", events).contains("crate_def_map"), "{:#?}", events)
    }
    db.set_file_text(pos.file_id, Arc::new(ra_fixture_change.to_string()));

    {
        let events = db.log_executed(|| {
            db.crate_def_map(krate);
        });
        assert!(!format!("{:?}", events).contains("crate_def_map"), "{:#?}", events)
    }
}

#[test]
fn typing_inside_a_function_should_not_invalidate_def_map() {
    check_def_map_is_not_recomputed(
        r"
        //- /lib.rs
        mod foo;$0

        use crate::foo::bar::Baz;

        enum E { A, B }
        use E::*;

        fn foo() -> i32 {
            1 + 1
        }

        #[cfg(never)]
        fn no() {}
        //- /foo/mod.rs
        pub mod bar;

        //- /foo/bar.rs
        pub struct Baz;
        ",
        r"
        mod foo;

        use crate::foo::bar::Baz;

        enum E { A, B }
        use E::*;

        fn foo() -> i32 { 92 }

        #[cfg(never)]
        fn no() {}
        ",
    );
}

#[test]
fn typing_inside_a_macro_should_not_invalidate_def_map() {
    let (mut db, pos) = TestDB::with_position(
        r"
        //- /lib.rs
        macro_rules! m {
            ($ident:ident) => {
                fn f() {
                    $ident + $ident;
                };
            }
        }
        mod foo;

        //- /foo/mod.rs
        pub mod bar;

        //- /foo/bar.rs
        $0
        m!(X);
        ",
    );
    let krate = db.test_crate();
    {
        let events = db.log_executed(|| {
            let crate_def_map = db.crate_def_map(krate);
            let (_, module_data) = crate_def_map.modules.iter().last().unwrap();
            assert_eq!(module_data.scope.resolutions().count(), 1);
        });
        assert!(format!("{:?}", events).contains("crate_def_map"), "{:#?}", events)
    }
    db.set_file_text(pos.file_id, Arc::new("m!(Y);".to_string()));

    {
        let events = db.log_executed(|| {
            let crate_def_map = db.crate_def_map(krate);
            let (_, module_data) = crate_def_map.modules.iter().last().unwrap();
            assert_eq!(module_data.scope.resolutions().count(), 1);
        });
        assert!(!format!("{:?}", events).contains("crate_def_map"), "{:#?}", events)
    }
}

#[test]
fn typing_inside_a_function_should_not_invalidate_expansions() {
    let (mut db, pos) = TestDB::with_position(
        r#"
//- /lib.rs
macro_rules! m {
    ($ident:ident) => {
        fn $ident() { };
    }
}
mod foo;

//- /foo/mod.rs
pub mod bar;

//- /foo/bar.rs
m!(X);
fn quux() { 1$0 }
m!(Y);
m!(Z);
"#,
    );
    let krate = db.test_crate();
    {
        let events = db.log_executed(|| {
            let crate_def_map = db.crate_def_map(krate);
            let (_, module_data) = crate_def_map.modules.iter().last().unwrap();
            assert_eq!(module_data.scope.resolutions().count(), 4);
        });
        let n_recalculated_item_trees = events.iter().filter(|it| it.contains("item_tree")).count();
        assert_eq!(n_recalculated_item_trees, 6);
        let n_reparsed_macros =
            events.iter().filter(|it| it.contains("parse_macro_expansion")).count();
        assert_eq!(n_reparsed_macros, 3);
    }

    let new_text = r#"
m!(X);
fn quux() { 92 }
m!(Y);
m!(Z);
"#;
    db.set_file_text(pos.file_id, Arc::new(new_text.to_string()));

    {
        let events = db.log_executed(|| {
            let crate_def_map = db.crate_def_map(krate);
            let (_, module_data) = crate_def_map.modules.iter().last().unwrap();
            assert_eq!(module_data.scope.resolutions().count(), 4);
        });
        let n_recalculated_item_trees = events.iter().filter(|it| it.contains("item_tree")).count();
        assert_eq!(n_recalculated_item_trees, 1);
        let n_reparsed_macros =
            events.iter().filter(|it| it.contains("parse_macro_expansion")).count();
        assert_eq!(n_reparsed_macros, 0);
    }
}

#[test]
fn item_tree_prevents_reparsing() {
    // The `ItemTree` is used by both name resolution and the various queries in `adt.rs` and
    // `data.rs`. After computing the `ItemTree` and deleting the parse tree, we should be able to
    // run those other queries without triggering a reparse.

    let (db, pos) = TestDB::with_position(
        r#"
pub struct S;
pub union U {}
pub enum E {
    Variant,
}
pub fn f(_: S) { $0 }
pub trait Tr {}
impl Tr for () {}
pub const C: u8 = 0;
pub static ST: u8 = 0;
pub type Ty = ();
"#,
    );
    let krate = db.test_crate();
    {
        let events = db.log_executed(|| {
            db.file_item_tree(pos.file_id.into());
        });
        let n_calculated_item_trees = events.iter().filter(|it| it.contains("item_tree")).count();
        assert_eq!(n_calculated_item_trees, 1);
        let n_parsed_files = events.iter().filter(|it| it.contains("parse(")).count();
        assert_eq!(n_parsed_files, 1);
    }

    // Delete the parse tree.
    let sweep = SweepStrategy::default().discard_values().sweep_all_revisions();
    base_db::ParseQuery.in_db(&db).sweep(sweep);

    {
        let events = db.log_executed(|| {
            let crate_def_map = db.crate_def_map(krate);
            let (_, module_data) = crate_def_map.modules.iter().last().unwrap();
            assert_eq!(module_data.scope.resolutions().count(), 8);
            assert_eq!(module_data.scope.impls().count(), 1);

            for imp in module_data.scope.impls() {
                db.impl_data(imp);
            }

            for (_, res) in module_data.scope.resolutions() {
                match res.values.or(res.types).unwrap().0 {
                    ModuleDefId::FunctionId(f) => drop(db.function_data(f)),
                    ModuleDefId::AdtId(adt) => match adt {
                        AdtId::StructId(it) => drop(db.struct_data(it)),
                        AdtId::UnionId(it) => drop(db.union_data(it)),
                        AdtId::EnumId(it) => drop(db.enum_data(it)),
                    },
                    ModuleDefId::ConstId(it) => drop(db.const_data(it)),
                    ModuleDefId::StaticId(it) => drop(db.static_data(it)),
                    ModuleDefId::TraitId(it) => drop(db.trait_data(it)),
                    ModuleDefId::TypeAliasId(it) => drop(db.type_alias_data(it)),
                    ModuleDefId::EnumVariantId(_)
                    | ModuleDefId::ModuleId(_)
                    | ModuleDefId::BuiltinType(_) => unreachable!(),
                }
            }
        });
        let n_reparsed_files = events.iter().filter(|it| it.contains("parse(")).count();
        assert_eq!(n_reparsed_files, 0);
    }
}
<|endoftext|>