<commit_before>// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0

#![forbid(unsafe_code)]

use crate::{
    build_information::create_build_info_telemetry_event,
    constants::{
        APTOS_GA_API_SECRET, APTOS_GA_MEASUREMENT_ID, ENV_APTOS_DISABLE_TELEMETRY,
        ENV_GA_API_SECRET, ENV_GA_MEASUREMENT_ID, GA4_URL, HTTPBIN_URL,
        NODE_CORE_METRICS_FREQ_SECS, NODE_NETWORK_METRICS_FREQ_SECS, NODE_SYS_INFO_FREQ_SECS,
    },
    core_metrics::create_core_metric_telemetry_event,
    metrics,
    network_metrics::create_network_metric_telemetry_event,
    system_information::create_system_info_telemetry_event,
};
use aptos_config::config::NodeConfig;
use aptos_logger::prelude::*;
use futures::StreamExt;
use once_cell::sync::Lazy;
use rand::Rng;
use rand_core::OsRng;
use serde::{Deserialize, Serialize};
use std::{
    collections::BTreeMap,
    env,
    time::{SystemTime, UNIX_EPOCH},
};
use tokio::{
    runtime::{Builder, Runtime},
    task::JoinHandle,
};
use tokio_stream::wrappers::IntervalStream;
use uuid::Uuid;

const IP_ADDRESS_KEY: &str = "IP_ADDRESS"; // The IP address key
const TELEMETRY_TOKEN_KEY: &str = "TELEMETRY_TOKEN"; // The telemetry token key
const UNKNOWN_METRIC_VALUE: &str = "UNKNOWN"; // The default for unknown metric values

/// The random token presented by the node to connect all
/// telemetry events.
/// TODO(joshlind): leverage real authentication!
static TELEMETRY_TOKEN: Lazy<String> = Lazy::new(|| {
    let mut rng = OsRng;
    let token = rng.gen::<u32>();
    format!("TOKEN_{:?}", token)
});

/// Returns true iff telemetry is disabled
fn telemetry_is_disabled() -> bool {
    env::var(ENV_APTOS_DISABLE_TELEMETRY).is_ok()
}

/// Starts the telemetry service and returns the execution runtime.
/// Note: The service will not be created if telemetry is disabled.
pub fn start_telemetry_service(node_config: NodeConfig, chain_id: String) -> Option<Runtime> {
    // Don't start the service if telemetry has been disabled
    if telemetry_is_disabled() {
        warn!("Aptos telemetry is disabled!");
        return None;
    }

    // Create the telemetry runtime
    let telemetry_runtime = Builder::new_multi_thread()
        .thread_name("aptos-telemetry")
        .enable_all()
        .build()
        .expect("Failed to create the Aptos Telemetry runtime!");

    // Spawn the telemetry service
    let peer_id = fetch_peer_id(&node_config);
    telemetry_runtime
        .handle()
        .spawn(spawn_telemetry_service(peer_id, chain_id, node_config));

    Some(telemetry_runtime)
}

/// Returns the peer id given the node config.
/// Returns UNKNOWN otherwise.
fn fetch_peer_id(node_config: &NodeConfig) -> String {
    match node_config.peer_id() {
        Some(peer_id) => peer_id.to_string(),
        None => UNKNOWN_METRIC_VALUE.into(),
    }
}

/// Spawns the dedicated telemetry service that operates periodically
async fn spawn_telemetry_service(peer_id: String, chain_id: String, node_config: NodeConfig) {
    // Send build information once (only on startup)
    send_build_information(peer_id.clone(), chain_id.clone()).await;

    // Periodically send node core metrics
    let mut core_metrics_interval = IntervalStream::new(tokio::time::interval(
        std::time::Duration::from_secs(NODE_CORE_METRICS_FREQ_SECS),
    ))
    .fuse();

    // Periodically send node network metrics
    let mut network_metrics_interval = IntervalStream::new(tokio::time::interval(
        std::time::Duration::from_secs(NODE_NETWORK_METRICS_FREQ_SECS),
    ))
    .fuse();

    // Periodically send system information
    let mut system_information_interval = IntervalStream::new(tokio::time::interval(
        std::time::Duration::from_secs(NODE_SYS_INFO_FREQ_SECS),
    ))
    .fuse();

    info!("Telemetry service started!");
    loop {
        futures::select! {
            _ = system_information_interval.select_next_some() => {
                send_system_information(peer_id.clone()).await;
            }
            _ = core_metrics_interval.select_next_some() => {
                send_node_core_metrics(peer_id.clone(), &node_config).await;
            }
            _ = network_metrics_interval.select_next_some() => {
                send_node_network_metrics(peer_id.clone()).await;
            }
        }
    }
}

/// Collects and sends the build information via telemetry
async fn send_build_information(peer_id: String, chain_id: String) {
    let telemetry_event = create_build_info_telemetry_event(chain_id).await;
    let _join_handle = send_telemetry_event_with_ip(peer_id, telemetry_event).await;
}

/// Collects and sends the core node metrics via telemetry
async fn send_node_core_metrics(peer_id: String, node_config: &NodeConfig) {
    let telemetry_event = create_core_metric_telemetry_event(node_config).await;
    let _join_handle = send_telemetry_event_with_ip(peer_id, telemetry_event).await;
}

/// Collects and sends the node network metrics via telemetry
async fn send_node_network_metrics(peer_id: String) {
    let telemetry_event = create_network_metric_telemetry_event().await;
    let _join_handle = send_telemetry_event_with_ip(peer_id, telemetry_event).await;
}

/// Collects and sends the system information via telemetry
async fn send_system_information(peer_id: String) {
    let telemetry_event = create_system_info_telemetry_event().await;
    let _join_handle = send_telemetry_event_with_ip(peer_id, telemetry_event).await;
}

/// Fetches the IP address and sends the given telemetry event
/// along with the IP address. Also sends a randomly generated
/// token to help correlate metrics across events.
pub(crate) async fn send_telemetry_event_with_ip(
    peer_id: String,
    telemetry_event: TelemetryEvent,
) -> JoinHandle<()> {
    // Update the telemetry event with the ip address and random token
    let TelemetryEvent { name, mut params } = telemetry_event;
    params.insert(IP_ADDRESS_KEY.to_string(), get_origin_ip().await);
    params.insert(TELEMETRY_TOKEN_KEY.to_string(), TELEMETRY_TOKEN.clone());
    let telemetry_event = TelemetryEvent { name, params };

    // Send the telemetry event
    send_telemetry_event(peer_id, telemetry_event).await
}

/// Gets the IP origin of the machine by pinging a url.
/// If none is found, returns UNKNOWN.
async fn get_origin_ip() -> String {
    let resp = reqwest::get(HTTPBIN_URL).await;
    match resp {
        Ok(json) => match json.json::<OriginIP>().await {
            Ok(origin_ip) => origin_ip.origin,
            Err(_) => UNKNOWN_METRIC_VALUE.into(),
        },
        Err(_) => UNKNOWN_METRIC_VALUE.into(),
    }
}

/// Sends the given event and params to the telemetry endpoint
async fn send_telemetry_event(peer_id: String, telemetry_event: TelemetryEvent) -> JoinHandle<()> {
    // Parse the Google analytics env variables
    let api_secret =
        env::var(ENV_GA_API_SECRET).unwrap_or_else(|_| APTOS_GA_API_SECRET.to_string());
    let measurement_id =
        env::var(ENV_GA_MEASUREMENT_ID).unwrap_or_else(|_| APTOS_GA_MEASUREMENT_ID.to_string());

    // Create and send the telemetry dump
    let event_name = telemetry_event.name.clone();
    let timestamp_micros = match SystemTime::now().duration_since(UNIX_EPOCH) {
        Ok(duration) => duration.as_micros().to_string(),
        Err(_) => UNKNOWN_METRIC_VALUE.into(),
    };
    let telemetry_dump = TelemetryDump {
        client_id: Uuid::new_v4().to_string(), // We generate a random client id for each request
        user_id: peer_id,
        timestamp_micros,
        events: vec![telemetry_event],
    };
    spawn_telemetry_event_sender(api_secret, measurement_id, event_name, telemetry_dump)
}

/// Spawns the telemetry event sender on a new thread to avoid blocking
fn spawn_telemetry_event_sender(
    api_secret: String,
    measurement_id: String,
    event_name: String,
    telemetry_dump: TelemetryDump,
) -> JoinHandle<()> {
    tokio::spawn(async move {
        // Create a request client
        let client = reqwest::Client::new();

        // Send the request and wait for a response
        let send_result = client
            .post(format!(
                "{}?&measurement_id={}&api_secret={}",
                GA4_URL, measurement_id, api_secret
            ))
            .json::<TelemetryDump>(&telemetry_dump)
            .send()
            .await;

        // Process the response
        match send_result {
            Ok(response) => {
                let status_code = response.status().as_u16();
                if status_code > 200 && status_code < 299 {
                    debug!(
                        "Sent telemetry event {}, data: {:?}",
                        event_name, &telemetry_dump
                    );
                    metrics::increment_telemetry_successes(&event_name);
                } else {
                    error!(
                        "Failed to send telemetry event! Status: {}, event: {}.",
                        response.status(),
                        event_name
                    );
                    debug!("Failed telemetry response: {:?}", response.text().await);
                    metrics::increment_telemetry_failures(&event_name);
                }
            }
            Err(error) => {
                error!(
                    "Failed to send telemetry event: {}. Error: {:?}",
                    event_name, error
                );
                metrics::increment_telemetry_failures(&event_name);
            }
        }
    })
}

/// A json struct useful for fetching the machine origin/IP
#[derive(Deserialize)]
struct OriginIP {
    origin: String,
}

/// A useful struct for serialization a telemetry event
#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct TelemetryEvent {
    pub(crate) name: String,
    pub(crate) params: BTreeMap<String, String>,
}

/// A useful struct for serializing a telemetry dump
#[derive(Debug, Serialize, Deserialize)]
struct TelemetryDump {
    client_id: String,
    user_id: String,
    timestamp_micros: String,
    events: Vec<TelemetryEvent>,
}
<commit_msg>Fix status code check in metrics event sender (#1959)<commit_after>// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0

#![forbid(unsafe_code)]

use crate::{
    build_information::create_build_info_telemetry_event,
    constants::{
        APTOS_GA_API_SECRET, APTOS_GA_MEASUREMENT_ID, ENV_APTOS_DISABLE_TELEMETRY,
        ENV_GA_API_SECRET, ENV_GA_MEASUREMENT_ID, GA4_URL, HTTPBIN_URL,
        NODE_CORE_METRICS_FREQ_SECS, NODE_NETWORK_METRICS_FREQ_SECS, NODE_SYS_INFO_FREQ_SECS,
    },
    core_metrics::create_core_metric_telemetry_event,
    metrics,
    network_metrics::create_network_metric_telemetry_event,
    system_information::create_system_info_telemetry_event,
};
use aptos_config::config::NodeConfig;
use aptos_logger::prelude::*;
use futures::StreamExt;
use once_cell::sync::Lazy;
use rand::Rng;
use rand_core::OsRng;
use serde::{Deserialize, Serialize};
use std::{
    collections::BTreeMap,
    env,
    time::{SystemTime, UNIX_EPOCH},
};
use tokio::{
    runtime::{Builder, Runtime},
    task::JoinHandle,
};
use tokio_stream::wrappers::IntervalStream;
use uuid::Uuid;

const IP_ADDRESS_KEY: &str = "IP_ADDRESS"; // The IP address key
const TELEMETRY_TOKEN_KEY: &str = "TELEMETRY_TOKEN"; // The telemetry token key
const UNKNOWN_METRIC_VALUE: &str = "UNKNOWN"; // The default for unknown metric values

/// The random token presented by the node to connect all
/// telemetry events.
/// TODO(joshlind): leverage real authentication!
static TELEMETRY_TOKEN: Lazy<String> = Lazy::new(|| {
    let mut rng = OsRng;
    let token = rng.gen::<u32>();
    format!("TOKEN_{:?}", token)
});

/// Returns true iff telemetry is disabled
fn telemetry_is_disabled() -> bool {
    env::var(ENV_APTOS_DISABLE_TELEMETRY).is_ok()
}

/// Starts the telemetry service and returns the execution runtime.
/// Note: The service will not be created if telemetry is disabled.
pub fn start_telemetry_service(node_config: NodeConfig, chain_id: String) -> Option<Runtime> {
    // Don't start the service if telemetry has been disabled
    if telemetry_is_disabled() {
        warn!("Aptos telemetry is disabled!");
        return None;
    }

    // Create the telemetry runtime
    let telemetry_runtime = Builder::new_multi_thread()
        .thread_name("aptos-telemetry")
        .enable_all()
        .build()
        .expect("Failed to create the Aptos Telemetry runtime!");

    // Spawn the telemetry service
    let peer_id = fetch_peer_id(&node_config);
    telemetry_runtime
        .handle()
        .spawn(spawn_telemetry_service(peer_id, chain_id, node_config));

    Some(telemetry_runtime)
}

/// Returns the peer id given the node config.
/// Returns UNKNOWN otherwise.
fn fetch_peer_id(node_config: &NodeConfig) -> String {
    match node_config.peer_id() {
        Some(peer_id) => peer_id.to_string(),
        None => UNKNOWN_METRIC_VALUE.into(),
    }
}

/// Spawns the dedicated telemetry service that operates periodically
async fn spawn_telemetry_service(peer_id: String, chain_id: String, node_config: NodeConfig) {
    // Send build information once (only on startup)
    send_build_information(peer_id.clone(), chain_id.clone()).await;

    // Periodically send node core metrics
    let mut core_metrics_interval = IntervalStream::new(tokio::time::interval(
        std::time::Duration::from_secs(NODE_CORE_METRICS_FREQ_SECS),
    ))
    .fuse();

    // Periodically send node network metrics
    let mut network_metrics_interval = IntervalStream::new(tokio::time::interval(
        std::time::Duration::from_secs(NODE_NETWORK_METRICS_FREQ_SECS),
    ))
    .fuse();

    // Periodically send system information
    let mut system_information_interval = IntervalStream::new(tokio::time::interval(
        std::time::Duration::from_secs(NODE_SYS_INFO_FREQ_SECS),
    ))
    .fuse();

    info!("Telemetry service started!");
    loop {
        futures::select! {
            _ = system_information_interval.select_next_some() => {
                send_system_information(peer_id.clone()).await;
            }
            _ = core_metrics_interval.select_next_some() => {
                send_node_core_metrics(peer_id.clone(), &node_config).await;
            }
            _ = network_metrics_interval.select_next_some() => {
                send_node_network_metrics(peer_id.clone()).await;
            }
        }
    }
}

/// Collects and sends the build information via telemetry
async fn send_build_information(peer_id: String, chain_id: String) {
    let telemetry_event = create_build_info_telemetry_event(chain_id).await;
    let _join_handle = send_telemetry_event_with_ip(peer_id, telemetry_event).await;
}

/// Collects and sends the core node metrics via telemetry
async fn send_node_core_metrics(peer_id: String, node_config: &NodeConfig) {
    let telemetry_event = create_core_metric_telemetry_event(node_config).await;
    let _join_handle = send_telemetry_event_with_ip(peer_id, telemetry_event).await;
}

/// Collects and sends the node network metrics via telemetry
async fn send_node_network_metrics(peer_id: String) {
    let telemetry_event = create_network_metric_telemetry_event().await;
    let _join_handle = send_telemetry_event_with_ip(peer_id, telemetry_event).await;
}

/// Collects and sends the system information via telemetry
async fn send_system_information(peer_id: String) {
    let telemetry_event = create_system_info_telemetry_event().await;
    let _join_handle = send_telemetry_event_with_ip(peer_id, telemetry_event).await;
}

/// Fetches the IP address and sends the given telemetry event
/// along with the IP address. Also sends a randomly generated
/// token to help correlate metrics across events.
pub(crate) async fn send_telemetry_event_with_ip(
    peer_id: String,
    telemetry_event: TelemetryEvent,
) -> JoinHandle<()> {
    // Update the telemetry event with the ip address and random token
    let TelemetryEvent { name, mut params } = telemetry_event;
    params.insert(IP_ADDRESS_KEY.to_string(), get_origin_ip().await);
    params.insert(TELEMETRY_TOKEN_KEY.to_string(), TELEMETRY_TOKEN.clone());
    let telemetry_event = TelemetryEvent { name, params };

    // Send the telemetry event
    send_telemetry_event(peer_id, telemetry_event).await
}

/// Gets the IP origin of the machine by pinging a url.
/// If none is found, returns UNKNOWN.
async fn get_origin_ip() -> String {
    let resp = reqwest::get(HTTPBIN_URL).await;
    match resp {
        Ok(json) => match json.json::<OriginIP>().await {
            Ok(origin_ip) => origin_ip.origin,
            Err(_) => UNKNOWN_METRIC_VALUE.into(),
        },
        Err(_) => UNKNOWN_METRIC_VALUE.into(),
    }
}

/// Sends the given event and params to the telemetry endpoint
async fn send_telemetry_event(peer_id: String, telemetry_event: TelemetryEvent) -> JoinHandle<()> {
    // Parse the Google analytics env variables
    let api_secret =
        env::var(ENV_GA_API_SECRET).unwrap_or_else(|_| APTOS_GA_API_SECRET.to_string());
    let measurement_id =
        env::var(ENV_GA_MEASUREMENT_ID).unwrap_or_else(|_| APTOS_GA_MEASUREMENT_ID.to_string());

    // Create and send the telemetry dump
    let event_name = telemetry_event.name.clone();
    let timestamp_micros = match SystemTime::now().duration_since(UNIX_EPOCH) {
        Ok(duration) => duration.as_micros().to_string(),
        Err(_) => UNKNOWN_METRIC_VALUE.into(),
    };
    let telemetry_dump = TelemetryDump {
        client_id: Uuid::new_v4().to_string(), // We generate a random client id for each request
        user_id: peer_id,
        timestamp_micros,
        events: vec![telemetry_event],
    };
    spawn_telemetry_event_sender(api_secret, measurement_id, event_name, telemetry_dump)
}

/// Spawns the telemetry event sender on a new thread to avoid blocking
fn spawn_telemetry_event_sender(
    api_secret: String,
    measurement_id: String,
    event_name: String,
    telemetry_dump: TelemetryDump,
) -> JoinHandle<()> {
    tokio::spawn(async move {
        // Create a request client
        let client = reqwest::Client::new();

        // Send the request and wait for a response
        let send_result = client
            .post(format!(
                "{}?&measurement_id={}&api_secret={}",
                GA4_URL, measurement_id, api_secret
            ))
            .json::<TelemetryDump>(&telemetry_dump)
            .send()
            .await;

        // Process the response
        match send_result {
            Ok(response) => {
                let status_code = response.status();
                if status_code.is_success() {
                    debug!(
                        "Sent telemetry event {}, data: {:?}",
                        event_name, &telemetry_dump
                    );
                    metrics::increment_telemetry_successes(&event_name);
                } else {
                    error!(
                        "Failed to send telemetry event! Status: {}, event: {}.",
                        response.status(),
                        event_name
                    );
                    debug!("Failed telemetry response: {:?}", response.text().await);
                    metrics::increment_telemetry_failures(&event_name);
                }
            }
            Err(error) => {
                error!(
                    "Failed to send telemetry event: {}. Error: {:?}",
                    event_name, error
                );
                metrics::increment_telemetry_failures(&event_name);
            }
        }
    })
}

/// A json struct useful for fetching the machine origin/IP
#[derive(Deserialize)]
struct OriginIP {
    origin: String,
}

/// A useful struct for serialization a telemetry event
#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct TelemetryEvent {
    pub(crate) name: String,
    pub(crate) params: BTreeMap<String, String>,
}

/// A useful struct for serializing a telemetry dump
#[derive(Debug, Serialize, Deserialize)]
struct TelemetryDump {
    client_id: String,
    user_id: String,
    timestamp_micros: String,
    events: Vec<TelemetryEvent>,
}
<|endoftext|>


<commit_before>extern crate config;

use std::collections::{HashMap, HashSet};
use error::PathfinderError;
use self::config::{Config, Value};

#[derive(Debug)]
pub struct Endpoint {
    url: String,
    microservice: String
}

impl Endpoint {
    pub fn new(url: &str, microservice: &str) -> Endpoint {
        Endpoint {
            url: url.to_string(),
            microservice: microservice.to_string()
        }
    }
}

fn get_value_from_config_as_str(conf: &HashMap<String, Value>, key: &str) -> String {
    match conf.get(key) {
        Some(value) => value.to_owned().into_str().unwrap(),
        None => String::from("")
    }
}

/// Returns a HashMap so that it contains only mapping from URL into
/// certain Kafka topic.
pub fn extract_endpoints(conf: Box<Config>) -> HashMap<String, Endpoint> {
    let mut endpoints = HashMap::new();

    let config_endpoints: Vec<Value> = match conf.get_array("endpoints") {
        Ok(array) => array,
        Err(why) => {
            println!("{}", PathfinderError::SettingsError(why));
            Vec::new()
        }
    };

    for endpoint in &config_endpoints {

        // One the high level you have structure like
        // {"search": {"url": ..., "microservice": ...}},
        // but will be convenient for further processing, if we return
        // {"url": ..., "microservice": ...} instead.
        let configuration = match endpoint.clone().into_table() {
            Ok(table) => {
                let endpoint_name = table.keys().last().unwrap().as_str().clone();
                table[endpoint_name].clone().into_table().unwrap()
            },
            Err(_) => {
                let error = format!("endpoint \"{}\" is invalid.", endpoint);
                println!("{}", PathfinderError::InvalidEndpoint(error));
                continue;
            }
        };

        // Check on required fields
        let mut missing_keys = false;
        let required_fields : HashSet<&str> = ["url", "microservice"].iter().cloned().collect();
        for key in required_fields  {
            if !configuration.contains_key(key) {
                let error = format!("key \"{}\" for {} endpoint is missing.", key, endpoint);
                println!("{}", PathfinderError::InvalidEndpoint(error));
                missing_keys = true;
                break;
            }
        }

        if missing_keys {
            continue;
        }

        let url = get_value_from_config_as_str(&configuration, "url");
        let microservice = get_value_from_config_as_str(&configuration, "microservice");
        let endpoint = Endpoint::new(&url, &microservice);
        endpoints.insert(url, endpoint);
    }

    endpoints
}

#[cfg(test)]
mod tests {
    use super::super::config::{get_config};
    use super::{extract_endpoints};

    #[test]
    fn test_extract_endpoints_returns_an_empty_dict_by_default() {
        let conf = get_config(&"");
        let endpoints = extract_endpoints(conf);
        assert_eq!(endpoints.len(), 0);
    }

    #[test]
    fn test_extract_endpoints_returns_an_empty_dict_for_a_file_without_endpoints() {
        let conf = get_config(&"./tests/files/config_without_endpoints.yaml");
        let endpoints = extract_endpoints(conf);
        assert_eq!(endpoints.len(), 0);
    }

    #[test]
    fn test_extract_endpoints_returns_dict_for_a_file_with_valid_endpoints() {
        let conf = get_config(&"./tests/files/config_with_valid_endpoints.yaml");
        let endpoints = extract_endpoints(conf);
        assert_eq!(endpoints.len(), 3);
        assert_eq!(endpoints.contains_key("/api/matchmaking/search"), true);
        assert_eq!(endpoints.contains_key("/api/matchmaking/leaderboard"), true);
        assert_eq!(endpoints.contains_key("/api/matchmaking/player-of-the-game"), true);
    }

    #[test]
    fn test_extract_endpoints_returns_dict_without_invalid_endpoints() {
        let conf = get_config(&"./tests/files/config_with_invalid_endpoints.yaml");
        let endpoints = extract_endpoints(conf);
        assert_eq!(endpoints.len(), 1);
        assert_eq!(endpoints.contains_key("/api/matchmaking/player-of-the-game"), true);
    }
}
<commit_msg>Refactored checking missing fields<commit_after>extern crate config;

use std::collections::{HashMap, HashSet};
use error::PathfinderError;
use self::config::{Config, Value};

#[derive(Debug)]
pub struct Endpoint {
    url: String,
    microservice: String
}

impl Endpoint {
    pub fn new(url: &str, microservice: &str) -> Endpoint {
        Endpoint {
            url: url.to_string(),
            microservice: microservice.to_string()
        }
    }
}

fn get_value_from_config_as_str(conf: &HashMap<String, Value>, key: &str) -> String {
    match conf.get(key) {
        Some(value) => value.to_owned().into_str().unwrap(),
        None => String::from("")
    }
}

/// Returns a HashMap so that it contains only mapping from URL into
/// certain Kafka topic.
pub fn extract_endpoints(conf: Box<Config>) -> HashMap<String, Box<Endpoint>> {
    let mut endpoints = HashMap::new();

    let config_endpoints: Vec<Value> = match conf.get_array("endpoints") {
        Ok(array) => array,
        Err(why) => {
            println!("{}", PathfinderError::SettingsError(why));
            Vec::new()
        }
    };

    for endpoint in &config_endpoints {

        // One the high level you have structure like
        // {"search": {"url": ..., "microservice": ...}},
        // but will be convenient for further processing, if we return
        // {"url": ..., "microservice": ...} instead.
        let configuration = match endpoint.clone().into_table() {
            Ok(table) => {
                let endpoint_name = table.keys().last().unwrap().as_str().clone();
                table[endpoint_name].clone().into_table().unwrap()
            },
            Err(_) => {
                let error = format!("endpoint \"{}\" is invalid.", endpoint);
                println!("{}", PathfinderError::InvalidEndpoint(error));
                continue;
            }
        };

        // Check on required fields
        let mut missing_fields = Vec::new();
        let required_fields : HashSet<&str> = ["url", "microservice"].iter().cloned().collect();
        for key in required_fields  {
            if !configuration.contains_key(key) {
                missing_fields.push(key);
            }
        }

        if missing_fields.len() > 0 {
            let error = format!("keys {:?} for {} endpoint is missing.", missing_fields, endpoint);
            println!("{}", PathfinderError::InvalidEndpoint(error));
            continue;
        }

        let url = get_value_from_config_as_str(&configuration, "url");
        let microservice = get_value_from_config_as_str(&configuration, "microservice");
        let endpoint = Box::new(Endpoint::new(&url, &microservice));
        endpoints.insert(url, endpoint);
    }

    endpoints
}

#[cfg(test)]
mod tests {
    use super::super::config::{get_config};
    use super::{extract_endpoints};

    #[test]
    fn test_extract_endpoints_returns_an_empty_dict_by_default() {
        let conf = get_config(&"");
        let endpoints = extract_endpoints(conf);
        assert_eq!(endpoints.len(), 0);
    }

    #[test]
    fn test_extract_endpoints_returns_an_empty_dict_for_a_file_without_endpoints() {
        let conf = get_config(&"./tests/files/config_without_endpoints.yaml");
        let endpoints = extract_endpoints(conf);
        assert_eq!(endpoints.len(), 0);
    }

    #[test]
    fn test_extract_endpoints_returns_dict_for_a_file_with_valid_endpoints() {
        let conf = get_config(&"./tests/files/config_with_valid_endpoints.yaml");
        let endpoints = extract_endpoints(conf);
        assert_eq!(endpoints.len(), 3);
        assert_eq!(endpoints.contains_key("/api/matchmaking/search"), true);
        assert_eq!(endpoints.contains_key("/api/matchmaking/leaderboard"), true);
        assert_eq!(endpoints.contains_key("/api/matchmaking/player-of-the-game"), true);
    }

    #[test]
    fn test_extract_endpoints_returns_dict_without_invalid_endpoints() {
        let conf = get_config(&"./tests/files/config_with_invalid_endpoints.yaml");
        let endpoints = extract_endpoints(conf);
        assert_eq!(endpoints.len(), 1);
        assert_eq!(endpoints.contains_key("/api/matchmaking/player-of-the-game"), true);
    }
}
<|endoftext|>


<commit_before>extern crate polygon;
extern crate vcdim;

use polygon::generate::Mode;
use vcdim::*;

fn main() {
    let mut args = std::env::args().skip(1);
    let n = if let Some(n) = args.next() {
        n.parse().expect("n must be an positive integer")
    } else { 20 };
    let count = if let Some(cnt) = args.next() {
        cnt.parse().expect("count must be an positive integer")
    } else { 1 };

    let gen_mode = Mode::QuickStarLike;
    let mut vcd = VcDim::with_random_polygon(n, gen_mode);

    for _ in 0..count {
        let _ = vcd.vc_dimension();
        vcd.randomize_polygon(gen_mode)
    }
}
<commit_msg>add warning in ./examples/compute_savings.rs when `print_info` is not enabled<commit_after>extern crate polygon;
extern crate vcdim;

use polygon::generate::Mode;
use vcdim::*;

fn main() {
    #[cfg(not(feature = "print_info"))]
    {
        println!("WARNING: This does nothing if the feature `print_info` is disabled.");
        println!("         Please enable this feature.");
        return;
    }

    let mut args = std::env::args().skip(1);
    let n = if let Some(n) = args.next() {
        n.parse().expect("n must be an positive integer")
    } else { 20 };
    let count = if let Some(cnt) = args.next() {
        cnt.parse().expect("count must be an positive integer")
    } else { 1 };

    let gen_mode = Mode::QuickStarLike;
    let mut vcd = VcDim::with_random_polygon(n, gen_mode);

    for _ in 0..count {
        let _ = vcd.vc_dimension();
        vcd.randomize_polygon(gen_mode)
    }
}
<|endoftext|>


<commit_before>use crate::future::Either;
use crate::stream::{Fuse, StreamExt};
use core::fmt;
use core::pin::Pin;
use futures_core::future::{FusedFuture, Future};
use futures_core::stream::{FusedStream, Stream};
use futures_core::task::{Context, Poll};
#[cfg(feature = "sink")]
use futures_sink::Sink;
use pin_utils::{unsafe_pinned, unsafe_unpinned};

/// A `Stream` that implements a `peek` method.
///
/// The `peek` method can be used to retrieve a reference
/// to the next `Stream::Item` if available. A subsequent
/// call to `poll` will return the owned item.
#[derive(Debug)]
#[must_use = "streams do nothing unless polled"]
pub struct Peekable<St: Stream> {
    stream: Fuse<St>,
    peeked: Option<St::Item>,
}

impl<St: Stream + Unpin> Unpin for Peekable<St> {}

impl<St: Stream> Peekable<St> {
    unsafe_pinned!(stream: Fuse<St>);
    unsafe_unpinned!(peeked: Option<St::Item>);

    pub(super) fn new(stream: St) -> Peekable<St> {
        Peekable {
            stream: stream.fuse(),
            peeked: None,
        }
    }

    /// Acquires a reference to the underlying stream that this combinator is
    /// pulling from.
    pub fn get_ref(&self) -> &St {
        self.stream.get_ref()
    }

    /// Acquires a mutable reference to the underlying stream that this
    /// combinator is pulling from.
    ///
    /// Note that care must be taken to avoid tampering with the state of the
    /// stream which may otherwise confuse this combinator.
    pub fn get_mut(&mut self) -> &mut St {
        self.stream.get_mut()
    }

    /// Acquires a pinned mutable reference to the underlying stream that this
    /// combinator is pulling from.
    ///
    /// Note that care must be taken to avoid tampering with the state of the
    /// stream which may otherwise confuse this combinator.
    pub fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut St> {
        self.stream().get_pin_mut()
    }

    /// Consumes this combinator, returning the underlying stream.
    ///
    /// Note that this may discard intermediate state of this combinator, so
    /// care should be taken to avoid losing resources when this is called.
    pub fn into_inner(self) -> St {
        self.stream.into_inner()
    }

    /// Peek retrieves a reference to the next item in the stream.
    ///
    /// This method polls the underlying stream and return either a reference
    /// to the next item if the stream is ready or passes through any errors.
    pub fn peek(self: Pin<&mut Self>) -> Peek<'_, St> {
        Peek { inner: Some(self) }
    }

    fn poll_peek(
        mut self: Pin<&mut Self>,
        cx: &mut Context<'_>,
    ) -> Either<Pin<&mut Self>, Poll<Option<&St::Item>>> {
        if self.peeked.is_some() {
            let this: &Self = self.into_ref().get_ref();
            return Either::Right(Poll::Ready(this.peeked.as_ref()));
        }
        match self.as_mut().stream().poll_next(cx) {
            Poll::Ready(None) => Either::Right(Poll::Ready(None)),
            Poll::Ready(Some(item)) => {
                *self.as_mut().peeked() = Some(item);
                let this: &Self = self.into_ref().get_ref();
                Either::Right(Poll::Ready(this.peeked.as_ref()))
            }
            _ => Either::Left(self),
        }
    }
}

impl<St: Stream> FusedStream for Peekable<St> {
    fn is_terminated(&self) -> bool {
        self.peeked.is_none() && self.stream.is_terminated()
    }
}

impl<S: Stream> Stream for Peekable<S> {
    type Item = S::Item;

    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
        if let Some(item) = self.as_mut().peeked().take() {
            return Poll::Ready(Some(item));
        }
        self.as_mut().stream().poll_next(cx)
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        let peek_len = if self.peeked.is_some() { 1 } else { 0 };
        let (lower, upper) = self.stream.size_hint();
        let lower = lower.saturating_add(peek_len);
        let upper = match upper {
            Some(x) => x.checked_add(peek_len),
            None => None,
        };
        (lower, upper)
    }
}

// Forwarding impl of Sink from the underlying stream
#[cfg(feature = "sink")]
impl<S, Item> Sink<Item> for Peekable<S>
where
    S: Sink<Item> + Stream,
{
    type Error = S::Error;

    delegate_sink!(stream, Item);
}

/// Future for the [`peek()`] function from [`Peekable`]
#[must_use = "futures do nothing unless polled"]
pub struct Peek<'a, St: Stream> {
    inner: Option<Pin<&'a mut Peekable<St>>>,
}

impl<St: Stream> Unpin for Peek<'_, St> {}

impl<St: Stream> fmt::Debug for Peek<'_, St> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Peek")
            .finish()
    }
}

impl<St: Stream> FusedFuture for Peek<'_, St> {
    fn is_terminated(&self) -> bool {
        self.inner.is_none()
    }
}

impl<'a, St> Future for Peek<'a, St>
where
    St: Stream,
{
    type Output = Option<&'a St::Item>;
    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        if let Some(peekable) = self.inner.take() {
            match peekable.poll_peek(cx) {
                Either::Left(peekable) => {
                    self.inner = Some(peekable);
                    Poll::Pending
                }
                Either::Right(poll) => poll,
            }
        } else {
            Poll::Pending
        }
    }
}
<commit_msg>reinstate the previous public interface; document the interpretation of return values<commit_after>use crate::future::Either;
use crate::stream::{Fuse, StreamExt};
use core::fmt;
use core::pin::Pin;
use futures_core::future::{FusedFuture, Future};
use futures_core::stream::{FusedStream, Stream};
use futures_core::task::{Context, Poll};
#[cfg(feature = "sink")]
use futures_sink::Sink;
use pin_utils::{unsafe_pinned, unsafe_unpinned};

/// A `Stream` that implements a `peek` method.
///
/// The `peek` method can be used to retrieve a reference
/// to the next `Stream::Item` if available. A subsequent
/// call to `poll` will return the owned item.
#[derive(Debug)]
#[must_use = "streams do nothing unless polled"]
pub struct Peekable<St: Stream> {
    stream: Fuse<St>,
    peeked: Option<St::Item>,
}

impl<St: Stream + Unpin> Unpin for Peekable<St> {}

impl<St: Stream> Peekable<St> {
    unsafe_pinned!(stream: Fuse<St>);
    unsafe_unpinned!(peeked: Option<St::Item>);

    pub(super) fn new(stream: St) -> Peekable<St> {
        Peekable {
            stream: stream.fuse(),
            peeked: None,
        }
    }

    /// Acquires a reference to the underlying stream that this combinator is
    /// pulling from.
    pub fn get_ref(&self) -> &St {
        self.stream.get_ref()
    }

    /// Acquires a mutable reference to the underlying stream that this
    /// combinator is pulling from.
    ///
    /// Note that care must be taken to avoid tampering with the state of the
    /// stream which may otherwise confuse this combinator.
    pub fn get_mut(&mut self) -> &mut St {
        self.stream.get_mut()
    }

    /// Acquires a pinned mutable reference to the underlying stream that this
    /// combinator is pulling from.
    ///
    /// Note that care must be taken to avoid tampering with the state of the
    /// stream which may otherwise confuse this combinator.
    pub fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut St> {
        self.stream().get_pin_mut()
    }

    /// Consumes this combinator, returning the underlying stream.
    ///
    /// Note that this may discard intermediate state of this combinator, so
    /// care should be taken to avoid losing resources when this is called.
    pub fn into_inner(self) -> St {
        self.stream.into_inner()
    }

    /// Produces a `Peek` future which retrieves a reference to the next item
    /// in the stream, or `None` if the underlying stream terminates.
    pub fn peek(self: Pin<&mut Self>) -> Peek<'_, St> {
        Peek { inner: Some(self) }
    }

    /// Attempt to poll the underlying stream, and return the mutable borrow
    /// in case that is desirable to try for another time.
    /// In case a peeking poll is successful, the reference to the next item
    /// will be in the `Either::Right` variant; otherwise, the mutable borrow
    /// will be in the `Either::Left` variant.
    fn do_poll_peek(
        mut self: Pin<&mut Self>,
        cx: &mut Context<'_>,
    ) -> Either<Pin<&mut Self>, Option<&St::Item>> {
        if self.peeked.is_some() {
            let this: &Self = self.into_ref().get_ref();
            return Either::Right(this.peeked.as_ref());
        }
        match self.as_mut().stream().poll_next(cx) {
            Poll::Ready(None) => Either::Right(None),
            Poll::Ready(Some(item)) => {
                *self.as_mut().peeked() = Some(item);
                let this: &Self = self.into_ref().get_ref();
                Either::Right(this.peeked.as_ref())
            }
            _ => Either::Left(self),
        }
    }

    /// Peek retrieves a reference to the next item in the stream.
    ///
    /// This method polls the underlying stream and return either a reference
    /// to the next item if the stream is ready or passes through any errors.
    pub fn poll_peek(
        self: Pin<&mut Self>,
        cx: &mut Context<'_>,
    ) -> Poll<Option<&St::Item>> {
        match self.do_poll_peek(cx) {
            Either::Left(_) => Poll::Pending,
            Either::Right(poll) => Poll::Ready(poll),
        }
    }
}

impl<St: Stream> FusedStream for Peekable<St> {
    fn is_terminated(&self) -> bool {
        self.peeked.is_none() && self.stream.is_terminated()
    }
}

impl<S: Stream> Stream for Peekable<S> {
    type Item = S::Item;

    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
        if let Some(item) = self.as_mut().peeked().take() {
            return Poll::Ready(Some(item));
        }
        self.as_mut().stream().poll_next(cx)
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        let peek_len = if self.peeked.is_some() { 1 } else { 0 };
        let (lower, upper) = self.stream.size_hint();
        let lower = lower.saturating_add(peek_len);
        let upper = match upper {
            Some(x) => x.checked_add(peek_len),
            None => None,
        };
        (lower, upper)
    }
}

// Forwarding impl of Sink from the underlying stream
#[cfg(feature = "sink")]
impl<S, Item> Sink<Item> for Peekable<S>
where
    S: Sink<Item> + Stream,
{
    type Error = S::Error;

    delegate_sink!(stream, Item);
}

/// Future for the [`peek()`] function from [`Peekable`]
#[must_use = "futures do nothing unless polled"]
pub struct Peek<'a, St: Stream> {
    inner: Option<Pin<&'a mut Peekable<St>>>,
}

impl<St: Stream> Unpin for Peek<'_, St> {}

impl<St> fmt::Debug for Peek<'_, St>
where
    St: Stream + fmt::Debug,
    St::Item: fmt::Debug,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Peek")
            .field("inner", &self.inner)
            .finish()
    }
}

impl<St: Stream> FusedFuture for Peek<'_, St> {
    fn is_terminated(&self) -> bool {
        self.inner.is_none()
    }
}

impl<'a, St> Future for Peek<'a, St>
where
    St: Stream,
{
    type Output = Option<&'a St::Item>;
    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        if let Some(peekable) = self.inner.take() {
            match peekable.do_poll_peek(cx) {
                Either::Left(peekable) => {
                    self.inner = Some(peekable);
                    Poll::Pending
                }
                Either::Right(peek) => Poll::Ready(peek),
            }
        } else {
            panic!("Peek polled after completion")
        }
    }
}
<|endoftext|>


<commit_before>use askama::Template;

enum SectionOneType<'a>{
    Empty(TEmpty),
    Simple(TSecOneSimple<'a>),
    Full(TSecOneFull<'a>)
}
impl<'a> SectionOneType<'a> {
    pub fn render(&self) -> askama::Result<String>{
        match self {
            SectionOneType::Empty(v) => v.render(),
            SectionOneType::Simple(v) => v.render(),
            SectionOneType::Full(v) => v.render(),
        }
    }
}
enum SectionTwoFormat<'a>{
    Html(TSecTwoHtml<'a>),
    Text(TSecTwoText<'a>),
}
impl<'a> SectionTwoFormat<'a> {
    pub fn render(&self) -> askama::Result<String>{
        match self {
            SectionTwoFormat::Html(v) => v.render(),
            SectionTwoFormat::Text(v) => v.render(),
        }
    }
}
#[derive(Template)]
#[template(path = "render_in_place.html")]
struct RenderInPlace<'a> {
    s1: SectionOneType<'a>,
    s2: SectionTwoFormat<'a>,
    s3: &'a Vec<SectionOneType<'a>>
}

#[derive(Template)]
#[template(source = "", ext = "txt")]
struct TEmpty {
}
#[derive(Template)]
#[template(source = "{{ a }}", ext = "txt")]
struct TSecOneSimple<'a> {
    a: &'a str
}
#[derive(Template)]
#[template(source = "{{ a }}, {{ b }}", ext = "txt")]
struct TSecOneFull<'a> {
    a: &'a str,
    b: &'a str
}

#[derive(Template)]
#[template(source = "{{ c }}
{{ d }}
", ext = "html")]
struct TSecTwoHtml<'a> {
    c: &'a str,
    d: &'a str,
}
#[derive(Template)]
#[template(source = "{{ c }}, {{ d }}", ext = "txt")]
struct TSecTwoText<'a> {
    c: &'a str,
    d: &'a str,
}

#[test]
fn test_render_with_enums() {
    let t = RenderInPlace {
        s1: SectionOneType::Empty(TEmpty {}),
        s2: SectionTwoFormat::Html(TSecTwoHtml { c: "C", d: "D" }),
        s3: &vec![SectionOneType::Empty(TEmpty {}),
                  SectionOneType::Simple(TSecOneSimple { a: "A" }),
                  SectionOneType::Full(TSecOneFull { a: "A", b: "B" }),
        ]
    };
    assert_eq!(
        t.render().unwrap(),
       "Section 1: \nSection 2: C
D
\nSection 3 for:\n* \n* A\n* A, B\n"
    );
}
<commit_msg>Fmt and complete test avoiding warning unused variant.<commit_after>use askama::Template;

enum SectionOneType<'a> {
    Empty(TEmpty),
    Simple(TSecOneSimple<'a>),
    Full(TSecOneFull<'a>),
}
impl<'a> SectionOneType<'a> {
    pub fn render(&self) -> askama::Result<String> {
        match self {
            SectionOneType::Empty(v) => v.render(),
            SectionOneType::Simple(v) => v.render(),
            SectionOneType::Full(v) => v.render(),
        }
    }
}
enum SectionTwoFormat<'a> {
    Html(TSecTwoHtml<'a>),
    Text(TSecTwoText<'a>),
}
impl<'a> SectionTwoFormat<'a> {
    pub fn render(&self) -> askama::Result<String> {
        match self {
            SectionTwoFormat::Html(v) => v.render(),
            SectionTwoFormat::Text(v) => v.render(),
        }
    }
}
#[derive(Template)]
#[template(path = "render_in_place.html")]
struct RenderInPlace<'a> {
    s1: SectionOneType<'a>,
    s2: SectionTwoFormat<'a>,
    s3: &'a Vec<SectionOneType<'a>>,
}

#[derive(Template)]
#[template(source = "", ext = "txt")]
struct TEmpty {}
#[derive(Template)]
#[template(source = "{{ a }}", ext = "txt")]
struct TSecOneSimple<'a> {
    a: &'a str,
}
#[derive(Template)]
#[template(source = "{{ a }}, {{ b }}", ext = "txt")]
struct TSecOneFull<'a> {
    a: &'a str,
    b: &'a str,
}

#[derive(Template)]
#[template(source = "{{ c }}
{{ d }}
", ext = "html")]
struct TSecTwoHtml<'a> {
    c: &'a str,
    d: &'a str,
}
#[derive(Template)]
#[template(source = "{{ c }}, {{ d }}", ext = "txt")]
struct TSecTwoText<'a> {
    c: &'a str,
    d: &'a str,
}

#[test]
fn test_render_with_enums() {
    let t = RenderInPlace {
        s1: SectionOneType::Empty(TEmpty {}),
        s2: SectionTwoFormat::Html(TSecTwoHtml { c: "C", d: "D" }),
        s3: &vec![
            SectionOneType::Empty(TEmpty {}),
            SectionOneType::Simple(TSecOneSimple { a: "A" }),
            SectionOneType::Full(TSecOneFull { a: "A", b: "B" }),
        ],
    };
    assert_eq!(
        t.render().unwrap(),
        "Section 1: \nSection 2: C
D
\nSection 3 for:\n* \n* A\n* A, B\n"
    );
    let t = RenderInPlace {
        s1: SectionOneType::Empty(TEmpty {}),
        s2: SectionTwoFormat::Text(TSecTwoText { c: "C", d: "D" }),
        s3: &vec![
            SectionOneType::Empty(TEmpty {}),
            SectionOneType::Simple(TSecOneSimple { a: "A" }),
            SectionOneType::Full(TSecOneFull { a: "A", b: "B" }),
        ],
    };
    assert_eq!(
        t.render().unwrap(),
        "Section 1: \nSection 2: C, D\nSection 3 for:\n* \n* A\n* A, B\n"
    );

}
<|endoftext|>


<commit_before>pub mod dummy;
pub mod full;
pub mod stack_trace;

use super::{heap::Pointer, ChannelId, FiberId, Heap};
use crate::{compiler::hir::Id, module::Module};

/// An event that happened inside a VM.
#[derive(Clone)]
pub enum VmEvent<'event> {
    FiberCreated {
        fiber: FiberId,
    },
    FiberDone {
        fiber: FiberId,
    },
    FiberPanicked {
        fiber: FiberId,
        panicked_child: Option<FiberId>,
    },
    FiberCanceled {
        fiber: FiberId,
    },
    FiberExecutionStarted {
        fiber: FiberId,
    },
    FiberExecutionEnded {
        fiber: FiberId,
    },
    ChannelCreated {
        channel: ChannelId,
    },
    InFiber {
        fiber: FiberId,
        event: FiberEvent<'event>,
    },
}

/// An event that happened inside a fiber.
#[derive(Clone)]
pub enum FiberEvent<'event> {
    ModuleStarted {
        module: Module,
    },
    ModuleEnded {
        export_map: Pointer,
        heap: &'event Heap,
    },
    ValueEvaluated {
        id: Id,
        value: Pointer,
        heap: &'event Heap,
    },
    FoundFuzzableClosure {
        id: Id,
        closure: Pointer,
        heap: &'event Heap,
    },
    CallStarted {
        id: Id,
        closure: Pointer,
        args: Vec<Pointer>,
        heap: &'event Heap,
    },
    CallEnded {
        return_value: Pointer,
        heap: &'event Heap,
    },
    NeedsStarted {
        id: Id,
        condition: Pointer,
        reason: Pointer,
        heap: &'event Heap,
    },
    NeedsEnded,
}

pub trait Tracer {
    fn add(&mut self, event: VmEvent);

    fn fiber_created(&mut self, fiber: FiberId) {
        self.add(VmEvent::FiberCreated { fiber });
    }
    fn fiber_done(&mut self, fiber: FiberId) {
        self.add(VmEvent::FiberDone { fiber });
    }
    fn fiber_panicked(&mut self, fiber: FiberId, panicked_child: Option<FiberId>) {
        self.add(VmEvent::FiberPanicked {
            fiber,
            panicked_child,
        });
    }
    fn fiber_canceled(&mut self, fiber: FiberId) {
        self.add(VmEvent::FiberCanceled { fiber });
    }
    fn fiber_execution_started(&mut self, fiber: FiberId) {
        self.add(VmEvent::FiberExecutionStarted { fiber });
    }
    fn fiber_execution_ended(&mut self, fiber: FiberId) {
        self.add(VmEvent::FiberExecutionEnded { fiber });
    }
    fn channel_created(&mut self, channel: ChannelId) {
        self.add(VmEvent::ChannelCreated { channel });
    }

    fn for_vm<'a, 'vm>(&'a mut self) -> VmTracer<'vm>
    where
        Self: Sized,
        'a: 'vm,
    {
        VmTracer::<'vm> { tracer: self }
    }
}
pub struct VmTracer<'vm> {
    tracer: &'vm mut dyn Tracer,
}
pub struct FiberTracer<'vm, 'fiber> {
    vm_tracer: &'fiber mut VmTracer<'vm>,
    fiber: FiberId,
}

impl<'vm> VmTracer<'vm> {
    pub fn for_fiber<'fiber>(&'fiber mut self, fiber: FiberId) -> FiberTracer<'vm, 'fiber>
    where
        Self: 'fiber,
    {
        FiberTracer {
            vm_tracer: self,
            fiber,
        }
    }
}

impl<'vm> Tracer for VmTracer<'vm> {
    fn add(&mut self, event: VmEvent) {
        self.tracer.add(event);
    }
}
impl<'vm, 'fiber> FiberTracer<'vm, 'fiber> {
    fn add(&mut self, event: FiberEvent) {
        self.vm_tracer.add(VmEvent::InFiber {
            fiber: self.fiber,
            event,
        });
    }
}

impl<'vm, 'fiber> FiberTracer<'vm, 'fiber> {
    pub fn module_started(&mut self, module: Module) {
        self.add(FiberEvent::ModuleStarted { module });
    }
    pub fn module_ended(&mut self, export_map: Pointer, heap: &Heap) {
        self.add(FiberEvent::ModuleEnded { export_map, heap });
    }
    pub fn value_evaluated(&mut self, id: Id, value: Pointer, heap: &Heap) {
        self.add(FiberEvent::ValueEvaluated { id, value, heap });
    }
    pub fn found_fuzzable_closure(&mut self, id: Id, closure: Pointer, heap: &Heap) {
        self.add(FiberEvent::FoundFuzzableClosure { id, closure, heap });
    }
    pub fn call_started(&mut self, id: Id, closure: Pointer, args: Vec<Pointer>, heap: &Heap) {
        self.add(FiberEvent::CallStarted {
            id,
            closure,
            args,
            heap,
        });
    }
    pub fn call_ended(&mut self, return_value: Pointer, heap: &Heap) {
        self.add(FiberEvent::CallEnded { return_value, heap });
    }
    pub fn needs_started(&mut self, id: Id, condition: Pointer, reason: Pointer, heap: &Heap) {
        self.add(FiberEvent::NeedsStarted {
            id,
            condition,
            reason,
            heap,
        });
    }
    pub fn needs_ended(&mut self) {
        self.add(FiberEvent::NeedsEnded);
    }
}
<commit_msg>Move tracer methods<commit_after>pub mod dummy;
pub mod full;
pub mod stack_trace;

use super::{heap::Pointer, ChannelId, FiberId, Heap};
use crate::{compiler::hir::Id, module::Module};

/// An event that happened inside a VM.
#[derive(Clone)]
pub enum VmEvent<'event> {
    FiberCreated {
        fiber: FiberId,
    },
    FiberDone {
        fiber: FiberId,
    },
    FiberPanicked {
        fiber: FiberId,
        panicked_child: Option<FiberId>,
    },
    FiberCanceled {
        fiber: FiberId,
    },
    FiberExecutionStarted {
        fiber: FiberId,
    },
    FiberExecutionEnded {
        fiber: FiberId,
    },
    ChannelCreated {
        channel: ChannelId,
    },
    InFiber {
        fiber: FiberId,
        event: FiberEvent<'event>,
    },
}

/// An event that happened inside a fiber.
#[derive(Clone)]
pub enum FiberEvent<'event> {
    ModuleStarted {
        module: Module,
    },
    ModuleEnded {
        export_map: Pointer,
        heap: &'event Heap,
    },
    ValueEvaluated {
        id: Id,
        value: Pointer,
        heap: &'event Heap,
    },
    FoundFuzzableClosure {
        id: Id,
        closure: Pointer,
        heap: &'event Heap,
    },
    CallStarted {
        id: Id,
        closure: Pointer,
        args: Vec<Pointer>,
        heap: &'event Heap,
    },
    CallEnded {
        return_value: Pointer,
        heap: &'event Heap,
    },
    NeedsStarted {
        id: Id,
        condition: Pointer,
        reason: Pointer,
        heap: &'event Heap,
    },
    NeedsEnded,
}

pub trait Tracer {
    fn add(&mut self, event: VmEvent);

    fn for_vm<'a, 'vm>(&'a mut self) -> VmTracer<'vm>
    where
        Self: Sized,
        'a: 'vm,
    {
        VmTracer::<'vm> { tracer: self }
    }
}
pub struct VmTracer<'vm> {
    tracer: &'vm mut dyn Tracer,
}
pub struct FiberTracer<'vm, 'fiber> {
    vm_tracer: &'fiber mut VmTracer<'vm>,
    fiber: FiberId,
}

impl<'vm> VmTracer<'vm> {
    pub fn for_fiber<'fiber>(&'fiber mut self, fiber: FiberId) -> FiberTracer<'vm, 'fiber>
    where
        Self: 'fiber,
    {
        FiberTracer {
            vm_tracer: self,
            fiber,
        }
    }
}

impl<'vm> Tracer for VmTracer<'vm> {
    fn add(&mut self, event: VmEvent) {
        self.tracer.add(event);
    }
}
impl<'vm, 'fiber> FiberTracer<'vm, 'fiber> {
    fn add(&mut self, event: FiberEvent) {
        self.vm_tracer.add(VmEvent::InFiber {
            fiber: self.fiber,
            event,
        });
    }
}

impl<'vm> VmTracer<'vm> {
    pub fn fiber_created(&mut self, fiber: FiberId) {
        self.add(VmEvent::FiberCreated { fiber });
    }
    pub fn fiber_done(&mut self, fiber: FiberId) {
        self.add(VmEvent::FiberDone { fiber });
    }
    pub fn fiber_panicked(&mut self, fiber: FiberId, panicked_child: Option<FiberId>) {
        self.add(VmEvent::FiberPanicked {
            fiber,
            panicked_child,
        });
    }
    pub fn fiber_canceled(&mut self, fiber: FiberId) {
        self.add(VmEvent::FiberCanceled { fiber });
    }
    pub fn fiber_execution_started(&mut self, fiber: FiberId) {
        self.add(VmEvent::FiberExecutionStarted { fiber });
    }
    pub fn fiber_execution_ended(&mut self, fiber: FiberId) {
        self.add(VmEvent::FiberExecutionEnded { fiber });
    }
    pub fn channel_created(&mut self, channel: ChannelId) {
        self.add(VmEvent::ChannelCreated { channel });
    }
}
impl<'vm, 'fiber> FiberTracer<'vm, 'fiber> {
    pub fn module_started(&mut self, module: Module) {
        self.add(FiberEvent::ModuleStarted { module });
    }
    pub fn module_ended(&mut self, export_map: Pointer, heap: &Heap) {
        self.add(FiberEvent::ModuleEnded { export_map, heap });
    }
    pub fn value_evaluated(&mut self, id: Id, value: Pointer, heap: &Heap) {
        self.add(FiberEvent::ValueEvaluated { id, value, heap });
    }
    pub fn found_fuzzable_closure(&mut self, id: Id, closure: Pointer, heap: &Heap) {
        self.add(FiberEvent::FoundFuzzableClosure { id, closure, heap });
    }
    pub fn call_started(&mut self, id: Id, closure: Pointer, args: Vec<Pointer>, heap: &Heap) {
        self.add(FiberEvent::CallStarted {
            id,
            closure,
            args,
            heap,
        });
    }
    pub fn call_ended(&mut self, return_value: Pointer, heap: &Heap) {
        self.add(FiberEvent::CallEnded { return_value, heap });
    }
    pub fn needs_started(&mut self, id: Id, condition: Pointer, reason: Pointer, heap: &Heap) {
        self.add(FiberEvent::NeedsStarted {
            id,
            condition,
            reason,
            heap,
        });
    }
    pub fn needs_ended(&mut self) {
        self.add(FiberEvent::NeedsEnded);
    }
}
<|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 http://mozilla.org/MPL/2.0/. */

use darling::FromVariant;
use quote::Tokens;
use std::borrow::Cow;
use std::iter;
use syn::{AngleBracketedParameterData, Body, DeriveInput, Ident, ImplGenerics};
use syn::{Path, PathParameters, PathSegment, PolyTraitRef, QSelf};
use syn::{TraitBoundModifier, Ty, TyGenerics, TyParam, TyParamBound, TypeBinding};
use syn::{Variant, WhereBoundPredicate, WhereClause, WherePredicate};
use syn::visit::{self, Visitor};
use synstructure::{self, BindOpts, BindStyle, BindingInfo};

pub fn fmap_match<F>(
    input: &DeriveInput,
    bind_style: BindStyle,
    mut f: F,
) -> Tokens
where
    F: FnMut(BindingInfo) -> Tokens,
{
    synstructure::each_variant(input, &bind_style.into(), |fields, variant| {
        let name = variant_ctor(input, variant);
        let (mapped, mapped_fields) = value(&name, variant, "mapped");
        let fields_pairs = fields.into_iter().zip(mapped_fields);
        let mut computations = quote!();
        computations.append_all(fields_pairs.map(|(field, mapped_field)| {
            let expr = f(field);
            quote! { let #mapped_field = #expr; }
        }));
        computations.append(mapped);
        Some(computations)
    })
}

pub fn fmap_trait_parts<'a>(
    input: &'a DeriveInput,
    trait_path: &[&str],
    trait_output: &str,
) -> (ImplGenerics<'a>, TyGenerics<'a>, WhereClause, Path) {
    let (impl_generics, ty_generics, where_clause) = trait_parts(input, trait_path);
    let output_ty = PathSegment {
        ident: input.ident.clone(),
        parameters: PathParameters::AngleBracketed(AngleBracketedParameterData {
            lifetimes: input.generics.lifetimes.iter().map(|l| l.lifetime.clone()).collect(),
            types: input.generics.ty_params.iter().map(|ty| {
                Ty::Path(
                    Some(QSelf {
                        ty: Box::new(Ty::Path(None, ty.ident.clone().into())),
                        position: trait_path.len(),
                    }),
                    path(trait_path.iter().chain(iter::once(&trait_output))),
                )
            }).collect(),
            .. Default::default()
        }),
    }.into();
    (impl_generics, ty_generics, where_clause, output_ty)
}

fn fmap_trait_where_predicate(
    bounded_ty: Ty,
    trait_path: &[&str],
    trait_output: Option<(&str, Ty)>,
) -> WherePredicate {
    WherePredicate::BoundPredicate(WhereBoundPredicate {
        bound_lifetimes: vec![],
        bounded_ty,
        bounds: vec![TyParamBound::Trait(
            PolyTraitRef {
                bound_lifetimes: vec![],
                trait_ref: fmap_trait_ref(trait_path, trait_output),
            },
            TraitBoundModifier::None
        )],
    })
}

fn fmap_trait_ref(path: &[&str], output: Option<(&str, Ty)>) -> Path {
    let (name, parent) = path.split_last().unwrap();
    let last_segment = PathSegment {
        ident: (*name).into(),
        parameters: PathParameters::AngleBracketed(
            AngleBracketedParameterData {
                bindings: output.into_iter().map(|(param, ty)| {
                    TypeBinding { ident: param.into(), ty }
                }).collect(),
                .. Default::default()
            }
        )
    };
    Path {
        global: true,
        segments: {
            parent
                .iter()
                .cloned()
                .map(Into::into)
                .chain(iter::once(last_segment))
                .collect()
        },
    }
}

pub fn is_parameterized(ty: &Ty, params: &[TyParam]) -> bool {
    struct IsParameterized<'a> {
        params: &'a [TyParam],
        has_free: bool,
    }

    impl<'a> Visitor for IsParameterized<'a> {
        fn visit_path(&mut self, path: &Path) {
            if !path.global && path.segments.len() == 1 {
                if self.params.iter().any(|param| param.ident == path.segments[0].ident) {
                    self.has_free = true;
                }
            }
            visit::walk_path(self, path);
        }
    }

    let mut visitor = IsParameterized { params: params, has_free: false };
    visitor.visit_ty(ty);
    visitor.has_free
}

pub fn path<S>(segments: S) -> Path
where
    S: IntoIterator,
    <S as IntoIterator>::Item: AsRef<str>,
{
    Path {
        global: true,
        segments: segments.into_iter().map(|s| s.as_ref().into()).collect(),
    }
}

pub fn parse_variant_attrs<A>(variant: &Variant) -> A
where
    A: FromVariant,
{
    match A::from_variant(variant) {
        Ok(attrs) => attrs,
        Err(e) => panic!("failed to parse attributes: {}", e),
    }
}

pub fn ref_pattern<'a>(
    name: &Ident,
    variant: &'a Variant,
    prefix: &str,
) -> (Tokens, Vec<BindingInfo<'a>>) {
    synstructure::match_pattern(
        &name,
        &variant.data,
        &BindOpts::with_prefix(BindStyle::Ref, prefix.to_owned()),
    )
}

pub fn trait_parts<'a>(
    input: &'a DeriveInput,
    trait_path: &[&str],
) -> (ImplGenerics<'a>, TyGenerics<'a>, WhereClause) {
    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
    let mut where_clause = where_clause.clone();
    for param in &input.generics.ty_params {
        where_clause.predicates.push(fmap_trait_where_predicate(
            Ty::Path(None, param.ident.clone().into()),
            trait_path,
            None,
        ));
    }
    (impl_generics, ty_generics, where_clause)
}

pub fn value<'a>(
    name: &Ident,
    variant: &'a Variant,
    prefix: &str,
) -> (Tokens, Vec<BindingInfo<'a>>) {
    synstructure::match_pattern(
        &name,
        &variant.data,
        &BindOpts::with_prefix(BindStyle::Move, prefix.to_owned()),
    )
}

pub fn variant_ctor<'a>(
    input: &'a DeriveInput,
    variant: &Variant,
) -> Cow<'a, Ident> {
    match input.body {
        Body::Struct(_) => Cow::Borrowed(&input.ident),
        Body::Enum(_) => {
            Cow::Owned(Ident::from(
                format!("{}::{}", input.ident, variant.ident),
            ))
        },
    }
}

pub fn variants(input: &DeriveInput) -> Cow<[Variant]> {
    match input.body {
        Body::Enum(ref variants) => (&**variants).into(),
        Body::Struct(ref data) => {
            vec![Variant {
                ident: input.ident.clone(),
                attrs: input.attrs.clone(),
                data: data.clone(),
                discriminant: None,
            }].into()
        },
    }
}

pub fn where_predicate(ty: Ty, segments: &[&str]) -> WherePredicate {
    WherePredicate::BoundPredicate(WhereBoundPredicate {
        bound_lifetimes: vec![],
        bounded_ty: ty,
        bounds: vec![TyParamBound::Trait(
            PolyTraitRef {
                bound_lifetimes: vec![],
                trait_ref: path(segments),
            },
            TraitBoundModifier::None,
        )],
    })
}
<commit_msg>Remove some garbage code from style_derive<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 http://mozilla.org/MPL/2.0/. */

use darling::FromVariant;
use quote::Tokens;
use std::borrow::Cow;
use std::iter;
use syn::{AngleBracketedParameterData, Body, DeriveInput, Ident, ImplGenerics};
use syn::{Path, PathParameters, PathSegment, PolyTraitRef, QSelf};
use syn::{TraitBoundModifier, Ty, TyGenerics, TyParam, TyParamBound};
use syn::{Variant, WhereBoundPredicate, WhereClause, WherePredicate};
use syn::visit::{self, Visitor};
use synstructure::{self, BindOpts, BindStyle, BindingInfo};

pub fn fmap_match<F>(
    input: &DeriveInput,
    bind_style: BindStyle,
    mut f: F,
) -> Tokens
where
    F: FnMut(BindingInfo) -> Tokens,
{
    synstructure::each_variant(input, &bind_style.into(), |fields, variant| {
        let name = variant_ctor(input, variant);
        let (mapped, mapped_fields) = value(&name, variant, "mapped");
        let fields_pairs = fields.into_iter().zip(mapped_fields);
        let mut computations = quote!();
        computations.append_all(fields_pairs.map(|(field, mapped_field)| {
            let expr = f(field);
            quote! { let #mapped_field = #expr; }
        }));
        computations.append(mapped);
        Some(computations)
    })
}

pub fn fmap_trait_parts<'a>(
    input: &'a DeriveInput,
    trait_path: &[&str],
    trait_output: &str,
) -> (ImplGenerics<'a>, TyGenerics<'a>, WhereClause, Path) {
    let (impl_generics, ty_generics, where_clause) = trait_parts(input, trait_path);
    let output_ty = PathSegment {
        ident: input.ident.clone(),
        parameters: PathParameters::AngleBracketed(AngleBracketedParameterData {
            lifetimes: input.generics.lifetimes.iter().map(|l| l.lifetime.clone()).collect(),
            types: input.generics.ty_params.iter().map(|ty| {
                Ty::Path(
                    Some(QSelf {
                        ty: Box::new(Ty::Path(None, ty.ident.clone().into())),
                        position: trait_path.len(),
                    }),
                    path(trait_path.iter().chain(iter::once(&trait_output))),
                )
            }).collect(),
            .. Default::default()
        }),
    }.into();
    (impl_generics, ty_generics, where_clause, output_ty)
}

pub fn is_parameterized(ty: &Ty, params: &[TyParam]) -> bool {
    struct IsParameterized<'a> {
        params: &'a [TyParam],
        has_free: bool,
    }

    impl<'a> Visitor for IsParameterized<'a> {
        fn visit_path(&mut self, path: &Path) {
            if !path.global && path.segments.len() == 1 {
                if self.params.iter().any(|param| param.ident == path.segments[0].ident) {
                    self.has_free = true;
                }
            }
            visit::walk_path(self, path);
        }
    }

    let mut visitor = IsParameterized { params: params, has_free: false };
    visitor.visit_ty(ty);
    visitor.has_free
}

pub fn path<S>(segments: S) -> Path
where
    S: IntoIterator,
    <S as IntoIterator>::Item: AsRef<str>,
{
    Path {
        global: true,
        segments: segments.into_iter().map(|s| s.as_ref().into()).collect(),
    }
}

pub fn parse_variant_attrs<A>(variant: &Variant) -> A
where
    A: FromVariant,
{
    match A::from_variant(variant) {
        Ok(attrs) => attrs,
        Err(e) => panic!("failed to parse attributes: {}", e),
    }
}

pub fn ref_pattern<'a>(
    name: &Ident,
    variant: &'a Variant,
    prefix: &str,
) -> (Tokens, Vec<BindingInfo<'a>>) {
    synstructure::match_pattern(
        &name,
        &variant.data,
        &BindOpts::with_prefix(BindStyle::Ref, prefix.to_owned()),
    )
}

pub fn trait_parts<'a>(
    input: &'a DeriveInput,
    trait_path: &[&str],
) -> (ImplGenerics<'a>, TyGenerics<'a>, WhereClause) {
    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
    let mut where_clause = where_clause.clone();
    for param in &input.generics.ty_params {
        where_clause.predicates.push(where_predicate(
            Ty::Path(None, param.ident.clone().into()),
            trait_path,
        ));
    }
    (impl_generics, ty_generics, where_clause)
}

pub fn value<'a>(
    name: &Ident,
    variant: &'a Variant,
    prefix: &str,
) -> (Tokens, Vec<BindingInfo<'a>>) {
    synstructure::match_pattern(
        &name,
        &variant.data,
        &BindOpts::with_prefix(BindStyle::Move, prefix.to_owned()),
    )
}

pub fn variant_ctor<'a>(
    input: &'a DeriveInput,
    variant: &Variant,
) -> Cow<'a, Ident> {
    match input.body {
        Body::Struct(_) => Cow::Borrowed(&input.ident),
        Body::Enum(_) => {
            Cow::Owned(Ident::from(
                format!("{}::{}", input.ident, variant.ident),
            ))
        },
    }
}

pub fn variants(input: &DeriveInput) -> Cow<[Variant]> {
    match input.body {
        Body::Enum(ref variants) => (&**variants).into(),
        Body::Struct(ref data) => {
            vec![Variant {
                ident: input.ident.clone(),
                attrs: input.attrs.clone(),
                data: data.clone(),
                discriminant: None,
            }].into()
        },
    }
}

pub fn where_predicate(ty: Ty, segments: &[&str]) -> WherePredicate {
    WherePredicate::BoundPredicate(WhereBoundPredicate {
        bound_lifetimes: vec![],
        bounded_ty: ty,
        bounds: vec![TyParamBound::Trait(
            PolyTraitRef {
                bound_lifetimes: vec![],
                trait_ref: path(segments),
            },
            TraitBoundModifier::None,
        )],
    })
}
<|endoftext|>


<commit_before>use flowgger::config::Config;
use flowgger::record::{Record, StructuredData, SDValue, SEVERITY_MAX};
use flowgger::utils;
use serde_json::de;
use serde_json::value::Value;
use super::Decoder;

#[derive(Clone)]
pub struct GelfDecoder;

impl GelfDecoder {
    pub fn new(_config: &Config) -> GelfDecoder {
        GelfDecoder
    }
}

impl Decoder for GelfDecoder {
    fn decode(&self, line: &str) -> Result<Record, &'static str> {
        let mut sd = StructuredData::new(None);
        let mut ts = None;
        let mut hostname = None;
        let mut msg = None;
        let mut full_msg = None;
        let mut severity = None;

        let obj: Value = try!(de::from_str(line)
            .or(de::from_str(&line.replace('\n', r"\n")))
            .or(Err("Invalid GELF input, unable to parse as a JSON object")));
        let obj = try!(obj.as_object().ok_or("Empty GELF input"));
        for (key, value) in obj {
            match key.as_ref() {
                "timestamp" => ts = Some(try!(value.as_f64().ok_or("Invalid GELF timestamp"))),
                "host" => {
                    hostname = Some(try!(value.as_str()
                            .ok_or("GELF host name must be a string"))
                        .to_owned())
                }
                "short_message" => {
                    msg = Some(try!(value.as_str().ok_or("GELF short message must be a string"))
                        .to_owned())
                }
                "full_message" => {
                    full_msg = Some(try!(value.as_str()
                            .ok_or("GELF full message must be a string"))
                        .to_owned())
                }
                "version" => {
                    match try!(value.as_str().ok_or("GELF version must be a string")) {
                        "1.0" | "1.1" => {}
                        _ => return Err("Unsupported GELF version"),
                    }
                }
                "level" => {
                    let severity_given = try!(value.as_u64().ok_or("Invalid severity level"));
                    if severity_given > SEVERITY_MAX as u64 {
                        return Err("Invalid severity level (too high)");
                    }
                    severity = Some(severity_given as u8)
                }
                name => {
                    let sd_value: SDValue = match *value {
                        Value::String(ref value) => SDValue::String(value.to_owned()),
                        Value::Bool(value) => SDValue::Bool(value),
                        Value::F64(value) => SDValue::F64(value),
                        Value::I64(value) => SDValue::I64(value),
                        Value::U64(value) => SDValue::U64(value),
                        Value::Null => SDValue::Null,
                        _ => return Err("Invalid value type in structured data"),
                    };
                    let name = if name.starts_with('_') {
                        name.to_owned()
                    } else {
                        format!("_{}", name)
                    };
                    sd.pairs.push((name, sd_value));
                }
            }
        }
        let record = Record {
            ts: ts.unwrap_or_else(|| utils::PreciseTimestamp::now().as_f64()),
            hostname: try!(hostname.ok_or("Missing hostname")),
            facility: None,
            severity: severity,
            appname: None,
            procid: None,
            msgid: None,
            sd: if sd.pairs.is_empty() { None } else { Some(sd) },
            msg: msg,
            full_msg: full_msg,
        };
        Ok(record)
    }
}

#[test]
fn test_gelf() {
    let msg = r#"{"version":"1.1", "host": "example.org","short_message": "A short message that helps you identify what is going on", "full_message": "Backtrace here\n
more stuff", "timestamp": 1385053862.3072, "level": 1, "_user_id": 9001, "_some_info": "foo", "_some_env_var": "bar"}"#;
    let res = GelfDecoder.decode(msg).unwrap();
    assert!(res.ts == 1385053862.3072);
    assert!(res.hostname == "example.org");
    assert!(res.msg.unwrap() == "A short message that helps you identify what is going on");
    assert!(res.full_msg.unwrap() == "Backtrace here\n\nmore stuff");
    assert!(res.severity.unwrap() == 1);

    let sd = res.sd.unwrap();
    let pairs = sd.pairs;
    assert!(pairs.iter().cloned().any(|(k, v)| if let SDValue::U64(v) = v {
        k == "_user_id" && v == 9001
    } else {
        false
    }));
    assert!(pairs.iter().cloned().any(|(k, v)| if let SDValue::String(v) = v {
        k == "_some_info" && v == "foo"
    } else {
        false
    }));
    assert!(pairs.iter().cloned().any(|(k, v)| if let SDValue::String(v) = v {
        k == "_some_env_var" && v == "bar"
    } else {
        false
    }));
}
<commit_msg>Only reparse GELF input on InvalidUnicodeCodePoint errors<commit_after>use flowgger::config::Config;
use flowgger::record::{Record, StructuredData, SDValue, SEVERITY_MAX};
use flowgger::utils;
use serde_json::de;
use serde_json::error::ErrorCode;
use serde_json::error::Error::Syntax;
use serde_json::value::Value;
use super::Decoder;

#[derive(Clone)]
pub struct GelfDecoder;

impl GelfDecoder {
    pub fn new(_config: &Config) -> GelfDecoder {
        GelfDecoder
    }
}

impl Decoder for GelfDecoder {
    fn decode(&self, line: &str) -> Result<Record, &'static str> {
        let mut sd = StructuredData::new(None);
        let mut ts = None;
        let mut hostname = None;
        let mut msg = None;
        let mut full_msg = None;
        let mut severity = None;

        let obj = match de::from_str(line) {
            x @ Ok(_) => x,
            Err(Syntax(ErrorCode::InvalidUnicodeCodePoint, _, _)) => {
                de::from_str(&line.replace('\n', r"\n"))
            }
            x @ _ => x,
        };
        let obj: Value = try!(obj.or(Err("Invalid GELF input, unable to parse as a JSON object")));
        let obj = try!(obj.as_object().ok_or("Empty GELF input"));
        for (key, value) in obj {
            match key.as_ref() {
                "timestamp" => ts = Some(try!(value.as_f64().ok_or("Invalid GELF timestamp"))),
                "host" => {
                    hostname = Some(try!(value.as_str()
                            .ok_or("GELF host name must be a string"))
                        .to_owned())
                }
                "short_message" => {
                    msg = Some(try!(value.as_str().ok_or("GELF short message must be a string"))
                        .to_owned())
                }
                "full_message" => {
                    full_msg = Some(try!(value.as_str()
                            .ok_or("GELF full message must be a string"))
                        .to_owned())
                }
                "version" => {
                    match try!(value.as_str().ok_or("GELF version must be a string")) {
                        "1.0" | "1.1" => {}
                        _ => return Err("Unsupported GELF version"),
                    }
                }
                "level" => {
                    let severity_given = try!(value.as_u64().ok_or("Invalid severity level"));
                    if severity_given > SEVERITY_MAX as u64 {
                        return Err("Invalid severity level (too high)");
                    }
                    severity = Some(severity_given as u8)
                }
                name => {
                    let sd_value: SDValue = match *value {
                        Value::String(ref value) => SDValue::String(value.to_owned()),
                        Value::Bool(value) => SDValue::Bool(value),
                        Value::F64(value) => SDValue::F64(value),
                        Value::I64(value) => SDValue::I64(value),
                        Value::U64(value) => SDValue::U64(value),
                        Value::Null => SDValue::Null,
                        _ => return Err("Invalid value type in structured data"),
                    };
                    let name = if name.starts_with('_') {
                        name.to_owned()
                    } else {
                        format!("_{}", name)
                    };
                    sd.pairs.push((name, sd_value));
                }
            }
        }
        let record = Record {
            ts: ts.unwrap_or_else(|| utils::PreciseTimestamp::now().as_f64()),
            hostname: try!(hostname.ok_or("Missing hostname")),
            facility: None,
            severity: severity,
            appname: None,
            procid: None,
            msgid: None,
            sd: if sd.pairs.is_empty() { None } else { Some(sd) },
            msg: msg,
            full_msg: full_msg,
        };
        Ok(record)
    }
}

#[test]
fn test_gelf() {
    let msg = r#"{"version":"1.1", "host": "example.org","short_message": "A short message that helps you identify what is going on", "full_message": "Backtrace here\n
more stuff", "timestamp": 1385053862.3072, "level": 1, "_user_id": 9001, "_some_info": "foo", "_some_env_var": "bar"}"#;
    let res = GelfDecoder.decode(msg).unwrap();
    assert!(res.ts == 1385053862.3072);
    assert!(res.hostname == "example.org");
    assert!(res.msg.unwrap() == "A short message that helps you identify what is going on");
    assert!(res.full_msg.unwrap() == "Backtrace here\n\nmore stuff");
    assert!(res.severity.unwrap() == 1);

    let sd = res.sd.unwrap();
    let pairs = sd.pairs;
    assert!(pairs.iter().cloned().any(|(k, v)| if let SDValue::U64(v) = v {
        k == "_user_id" && v == 9001
    } else {
        false
    }));
    assert!(pairs.iter().cloned().any(|(k, v)| if let SDValue::String(v) = v {
        k == "_some_info" && v == "foo"
    } else {
        false
    }));
    assert!(pairs.iter().cloned().any(|(k, v)| if let SDValue::String(v) = v {
        k == "_some_env_var" && v == "bar"
    } else {
        false
    }));
}
<|endoftext|>