<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 cache::{Nocache, Tilecache};
use core::grid::Extent;
use core::grid::Grid;
use core::layer::Layer;
use core::Config;
use datasource::{DatasourceInput, PostgisInput};
use datasource_type::{Datasource, Datasources};
use mvt_service::MvtService;
use service::tileset::Tileset;

fn mvt_service() -> MvtService {
    use std::env;

    let pg: PostgisInput = match env::var("DBCONN") {
        Result::Ok(val) => Some(PostgisInput::new(&val).connected()),
        Result::Err(_) => panic!("DBCONN undefined"),
    }.unwrap();
    let mut datasources = Datasources::new();
    datasources.add(&"pg".to_string(), Datasource::Postgis(pg));
    datasources.setup();
    let grid = Grid::web_mercator();
    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"));
    layer.query_limit = Some(1);
    let tileset = Tileset {
        name: "points".to_string(),
        minzoom: Some(0),
        maxzoom: Some(22),
        center: None,
        start_zoom: Some(3),
        attribution: Some("Attribution".to_string()),
        extent: Some(Extent {
            minx: -179.58998,
            miny: -90.00000,
            maxx: 179.38330,
            maxy: 82.48332,
        }),
        layers: vec![layer],
    };
    let mut service = MvtService {
        datasources: datasources,
        grid: grid,
        tilesets: vec![tileset],
        cache: Tilecache::Nocache(Nocache),
    };
    service.prepare_feature_queries();
    service
}

#[test]
#[ignore]
fn test_tile_query() {
    let service = mvt_service();

    let mvt_tile = service.tile("points", 33, 41, 6);
    println!("{:#?}", mvt_tile);
    let expected = r#"Tile {
    layers: [
        Tile_Layer {
            version: Some(
                2
            ),
            name: Some("points"),
            features: [
                Tile_Feature {
                    id: None,
                    tags: [
                        0,
                        0,
                        1,
                        1,
                        2,
                        2,
                        3,
                        3
                    ],
                    field_type: Some(
                        POINT
                    ),
                    geometry: [
                        9,
                        2504,
                        3390
                    ],
                    unknown_fields: UnknownFields {
                        fields: None
                    },
                    cached_size: Cell {
                        value: 0
                    }
                }
            ],
            keys: [
                "fid",
                "scalerank",
                "name",
                "pop_max"
            ],
            values: [
                Tile_Value {
                    string_value: None,
                    float_value: None,
                    double_value: None,
                    int_value: Some(
                        106
                    ),
                    uint_value: None,
                    sint_value: None,
                    bool_value: None,
                    unknown_fields: UnknownFields {
                        fields: None
                    },
                    cached_size: Cell {
                        value: 0
                    }
                },
                Tile_Value {
                    string_value: None,
                    float_value: None,
                    double_value: Some(
                        10
                    ),
                    int_value: None,
                    uint_value: None,
                    sint_value: None,
                    bool_value: None,
                    unknown_fields: UnknownFields {
                        fields: None
                    },
                    cached_size: Cell {
                        value: 0
                    }
                },
                Tile_Value {
                    string_value: Some("Delemont"),
                    float_value: None,
                    double_value: None,
                    int_value: None,
                    uint_value: None,
                    sint_value: None,
                    bool_value: None,
                    unknown_fields: UnknownFields {
                        fields: None
                    },
                    cached_size: Cell {
                        value: 0
                    }
                },
                Tile_Value {
                    string_value: None,
                    float_value: None,
                    double_value: Some(
                        11315
                    ),
                    int_value: None,
                    uint_value: None,
                    sint_value: None,
                    bool_value: None,
                    unknown_fields: UnknownFields {
                        fields: None
                    },
                    cached_size: Cell {
                        value: 0
                    }
                }
            ],
            extent: Some(
                4096
            ),
            unknown_fields: UnknownFields {
                fields: None
            },
            cached_size: Cell {
                value: 0
            }
        }
    ],
    unknown_fields: UnknownFields {
        fields: None
    },
    cached_size: Cell {
        value: 0
    }
}"#;
    assert_eq!(expected, &*format!("{:#.0?}", mvt_tile));
}

#[test]
#[ignore]
fn test_projected_extent() {
    let service = mvt_service();

    let extent_wgs84 = Extent {
        minx: 4.0,
        miny: 52.0,
        maxx: 5.0,
        maxy: 53.0,
    };
    #[cfg(not(target_os = "macos"))]
    let extent_3857 = Extent {
        minx: 445277.9631730949,
        miny: 6800125.454397307,
        maxx: 556597.4539663672,
        maxy: 6982997.920389788,
    };
    #[cfg(target_os = "macos")]
    let extent_3857 = Extent {
        minx: 445277.9631730949,
        miny: 6800125.454397305,
        maxx: 556597.4539663672,
        maxy: 6982997.920389788,
    };

    assert_eq!(service.extent_from_wgs84(&extent_wgs84), extent_3857);
}

#[test]
#[ignore]
fn test_generate() {
    let service = mvt_service();

    // Single tile level 23
    let extent = Extent {
        minx: 9.43743,
        miny: 47.05001,
        maxx: 9.43751,
        maxy: 47.05006,
    };

    assert_eq!(service.grid.maxzoom(), 22);
    service.generate(
        Some("points"),
        Some(20),
        Some(23),
        Some(extent),
        None,
        None,
        false,
        false,
    );
}

#[test]
fn test_mvt_metadata() {
    use core::read_config;

    let config = read_config("src/test/example.toml").unwrap();
    let service = MvtService::from_config(&config).unwrap();

    let metadata = format!("{:#}", service.get_mvt_metadata().unwrap());
    let expected = r#"{
  "tilesets": [
    {
      "bounds": [
        -180.0,
        -90.0,
        180.0,
        90.0
      ],
      "layers": [
        {
          "geometry_type": "POINT",
          "name": "points"
        },
        {
          "geometry_type": "POLYGON",
          "name": "buildings"
        },
        {
          "geometry_type": "POLYGON",
          "name": "admin_0_countries"
        }
      ],
      "name": "osm",
      "supported": true,
      "tilejson": "osm.json",
      "tileurl": "/osm/{z}/{x}/{y}.pbf"
    }
  ]
}"#;
    println!("{}", metadata);
    assert_eq!(metadata, expected);
}

#[test]
#[ignore]
fn test_tilejson() {
    use core::read_config;
    use std::env;

    match env::var("DBCONN") {
        Err(_) => panic!("DBCONN undefined"),
        // Overwrite PG connection in example.toml
        Ok(dbconn) => env::set_var("TREX_DATASOURCE_URL", dbconn),
    }

    let config = read_config("src/test/example.toml").unwrap();
    let mut service = MvtService::from_config(&config).unwrap();
    service.connect();
    service.prepare_feature_queries();
    let metadata = format!(
        "{:#}",
        service.get_tilejson("http://127.0.0.1", "osm").unwrap()
    );
    println!("{}", metadata);
    let expected = r#"{
  "attribution": "",
  "basename": "osm",
  "bounds": [
    -180.0,
    -90.0,
    180.0,
    90.0
  ],
  "center": [
    0.0,
    0.0,
    2
  ],
  "description": "osm",
  "format": "pbf",
  "id": "osm",
  "maxzoom": 22,
  "minzoom": 0,
  "name": "osm",
  "scheme": "xyz",
  "tiles": [
    "http://127.0.0.1/osm/{z}/{x}/{y}.pbf"
  ],
  "vector_layers": [
    {
      "description": "",
      "fields": {},
      "id": "points",
      "maxzoom": 22,
      "minzoom": 0
    },
    {
      "description": "",
      "fields": {},
      "id": "buildings",
      "maxzoom": 22,
      "minzoom": 0
    },
    {
      "description": "",
      "fields": {},
      "id": "admin_0_countries",
      "maxzoom": 22,
      "minzoom": 0
    }
  ],
  "version": "2.0.0"
}"#;
    assert_eq!(metadata, expected);
}

#[test]
fn test_stylejson() {
    use core::read_config;

    let config = read_config("src/test/example.toml").unwrap();
    let service = MvtService::from_config(&config).unwrap();
    let json = format!(
        "{:#}",
        service.get_stylejson("http://127.0.0.1", "osm").unwrap()
    );
    println!("{}", json);
    let expected = r#"
  "name": "t-rex",
  "sources": {
    "osm": {
      "type": "vector",
      "url": "http://127.0.0.1/osm.json"
    }
  },
  "version": 8
"#;
    assert!(json.contains(expected));
    let expected = r#"
  "layers": [
    {
      "id": "background_",
      "paint": {
        "background-color": "rgba(255, 255, 255, 1)"
      },
      "type": "background"
    },
    {
      "id": "points","#;
    assert!(json.contains(expected));

    let expected = r##"
      "paint": {
        "fill-color": "#d8e8c8",
        "fill-opacity": 0.5
      },"##;
    assert!(json.contains(expected));

    let expected = r#"
      "id": "buildings","#;
    assert!(json.contains(expected));
}

#[test]
#[ignore]
fn test_mbtiles_metadata() {
    use core::read_config;
    use std::env;

    match env::var("DBCONN") {
        Err(_) => panic!("DBCONN undefined"),
        // Overwrite PG connection in example.toml
        Ok(dbconn) => env::set_var("TREX_DATASOURCE_URL", dbconn),
    }

    let config = read_config("src/test/example.toml").unwrap();
    let mut service = MvtService::from_config(&config).unwrap();
    service.connect();
    let metadata = format!("{:#}", service.get_mbtiles_metadata("osm").unwrap());
    println!("{}", metadata);
    let expected = r#"{
  "attribution": "",
  "basename": "osm",
  "bounds": "[-180.0,-90.0,180.0,90.0]",
  "center": "[0.0,0.0,2]",
  "description": "osm",
  "format": "pbf",
  "id": "osm",
  "json": "{\"Layer\":[{\"description\":\"\",\"fields\":{},\"id\":\"points\",\"name\":\"points\",\"properties\":{\"buffer-size\":0,\"maxzoom\":22,\"minzoom\":0},\"srs\":\"+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over\"},{\"description\":\"\",\"fields\":{},\"id\":\"buildings\",\"name\":\"buildings\",\"properties\":{\"buffer-size\":10,\"maxzoom\":22,\"minzoom\":0},\"srs\":\"+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over\"},{\"description\":\"\",\"fields\":{},\"id\":\"admin_0_countries\",\"name\":\"admin_0_countries\",\"properties\":{\"buffer-size\":1,\"maxzoom\":22,\"minzoom\":0},\"srs\":\"+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over\"}],\"vector_layers\":[{\"description\":\"\",\"fields\":{},\"id\":\"points\",\"maxzoom\":22,\"minzoom\":0},{\"description\":\"\",\"fields\":{},\"id\":\"buildings\",\"maxzoom\":22,\"minzoom\":0},{\"description\":\"\",\"fields\":{},\"id\":\"admin_0_countries\",\"maxzoom\":22,\"minzoom\":0}]}",
  "maxzoom": 22,
  "minzoom": 0,
  "name": "osm",
  "scheme": "xyz",
  "version": "2.0.0"
}"#;
    assert_eq!(metadata, expected);
}

#[test]
fn test_gen_config() {
    #[cfg(feature = "with-gdal")]
    let gdal_ds_cfg = r#"
[[datasource]]
name = "ds"
# Dataset specification (http://gdal.org/ogr_formats.html)
path = "<filename-or-connection-spec>"
"#;
    #[cfg(not(feature = "with-gdal"))]
    let gdal_ds_cfg = "";

    let expected = format!(
        r#"# t-rex configuration

[service.mvt]
viewer = true

[[datasource]]
name = "database"
# PostgreSQL connection specification (https://github.com/sfackler/rust-postgres#connecting)
dbconn = "postgresql://user:pass@host/database"
{}
[grid]
# Predefined grids: web_mercator, wgs84
predefined = "web_mercator"

[[tileset]]
name = "points"
minzoom = 0 # Optional override of zoom limits broadcasted to tilejson descriptor
maxzoom = 22
attribution = "© Contributeurs de OpenStreetMap" # Acknowledgment of ownership, authorship or copyright.

[[tileset.layer]]
name = "points"
table_name = "mytable"
geometry_field = "wkb_geometry"
geometry_type = "POINT"
#fid_field = "id"
#tile_size = 4096
#simplify = true
#buffer_size = 10
#[[tileset.layer.query]]
#minzoom = 0
#maxzoom = 22
#sql = "SELECT name,wkb_geometry FROM mytable"

#[cache.file]
#base = "/tmp/mvtcache"
#baseurl = "http://example.com/tiles"
"#,
        gdal_ds_cfg
    );
    println!("{}", &MvtService::gen_config());
    assert_eq!(&expected, &MvtService::gen_config());
}
<commit_msg>Fix config generation test<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 cache::{Nocache, Tilecache};
use core::grid::Extent;
use core::grid::Grid;
use core::layer::Layer;
use core::Config;
use datasource::{DatasourceInput, PostgisInput};
use datasource_type::{Datasource, Datasources};
use mvt_service::MvtService;
use service::tileset::Tileset;

fn mvt_service() -> MvtService {
    use std::env;

    let pg: PostgisInput = match env::var("DBCONN") {
        Result::Ok(val) => Some(PostgisInput::new(&val).connected()),
        Result::Err(_) => panic!("DBCONN undefined"),
    }.unwrap();
    let mut datasources = Datasources::new();
    datasources.add(&"pg".to_string(), Datasource::Postgis(pg));
    datasources.setup();
    let grid = Grid::web_mercator();
    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"));
    layer.query_limit = Some(1);
    let tileset = Tileset {
        name: "points".to_string(),
        minzoom: Some(0),
        maxzoom: Some(22),
        center: None,
        start_zoom: Some(3),
        attribution: Some("Attribution".to_string()),
        extent: Some(Extent {
            minx: -179.58998,
            miny: -90.00000,
            maxx: 179.38330,
            maxy: 82.48332,
        }),
        layers: vec![layer],
    };
    let mut service = MvtService {
        datasources: datasources,
        grid: grid,
        tilesets: vec![tileset],
        cache: Tilecache::Nocache(Nocache),
    };
    service.prepare_feature_queries();
    service
}

#[test]
#[ignore]
fn test_tile_query() {
    let service = mvt_service();

    let mvt_tile = service.tile("points", 33, 41, 6);
    println!("{:#?}", mvt_tile);
    let expected = r#"Tile {
    layers: [
        Tile_Layer {
            version: Some(
                2
            ),
            name: Some("points"),
            features: [
                Tile_Feature {
                    id: None,
                    tags: [
                        0,
                        0,
                        1,
                        1,
                        2,
                        2,
                        3,
                        3
                    ],
                    field_type: Some(
                        POINT
                    ),
                    geometry: [
                        9,
                        2504,
                        3390
                    ],
                    unknown_fields: UnknownFields {
                        fields: None
                    },
                    cached_size: Cell {
                        value: 0
                    }
                }
            ],
            keys: [
                "fid",
                "scalerank",
                "name",
                "pop_max"
            ],
            values: [
                Tile_Value {
                    string_value: None,
                    float_value: None,
                    double_value: None,
                    int_value: Some(
                        106
                    ),
                    uint_value: None,
                    sint_value: None,
                    bool_value: None,
                    unknown_fields: UnknownFields {
                        fields: None
                    },
                    cached_size: Cell {
                        value: 0
                    }
                },
                Tile_Value {
                    string_value: None,
                    float_value: None,
                    double_value: Some(
                        10
                    ),
                    int_value: None,
                    uint_value: None,
                    sint_value: None,
                    bool_value: None,
                    unknown_fields: UnknownFields {
                        fields: None
                    },
                    cached_size: Cell {
                        value: 0
                    }
                },
                Tile_Value {
                    string_value: Some("Delemont"),
                    float_value: None,
                    double_value: None,
                    int_value: None,
                    uint_value: None,
                    sint_value: None,
                    bool_value: None,
                    unknown_fields: UnknownFields {
                        fields: None
                    },
                    cached_size: Cell {
                        value: 0
                    }
                },
                Tile_Value {
                    string_value: None,
                    float_value: None,
                    double_value: Some(
                        11315
                    ),
                    int_value: None,
                    uint_value: None,
                    sint_value: None,
                    bool_value: None,
                    unknown_fields: UnknownFields {
                        fields: None
                    },
                    cached_size: Cell {
                        value: 0
                    }
                }
            ],
            extent: Some(
                4096
            ),
            unknown_fields: UnknownFields {
                fields: None
            },
            cached_size: Cell {
                value: 0
            }
        }
    ],
    unknown_fields: UnknownFields {
        fields: None
    },
    cached_size: Cell {
        value: 0
    }
}"#;
    assert_eq!(expected, &*format!("{:#.0?}", mvt_tile));
}

#[test]
#[ignore]
fn test_projected_extent() {
    let service = mvt_service();

    let extent_wgs84 = Extent {
        minx: 4.0,
        miny: 52.0,
        maxx: 5.0,
        maxy: 53.0,
    };
    #[cfg(not(target_os = "macos"))]
    let extent_3857 = Extent {
        minx: 445277.9631730949,
        miny: 6800125.454397307,
        maxx: 556597.4539663672,
        maxy: 6982997.920389788,
    };
    #[cfg(target_os = "macos")]
    let extent_3857 = Extent {
        minx: 445277.9631730949,
        miny: 6800125.454397305,
        maxx: 556597.4539663672,
        maxy: 6982997.920389788,
    };

    assert_eq!(service.extent_from_wgs84(&extent_wgs84), extent_3857);
}

#[test]
#[ignore]
fn test_generate() {
    let service = mvt_service();

    // Single tile level 23
    let extent = Extent {
        minx: 9.43743,
        miny: 47.05001,
        maxx: 9.43751,
        maxy: 47.05006,
    };

    assert_eq!(service.grid.maxzoom(), 22);
    service.generate(
        Some("points"),
        Some(20),
        Some(23),
        Some(extent),
        None,
        None,
        false,
        false,
    );
}

#[test]
fn test_mvt_metadata() {
    use core::read_config;

    let config = read_config("src/test/example.toml").unwrap();
    let service = MvtService::from_config(&config).unwrap();

    let metadata = format!("{:#}", service.get_mvt_metadata().unwrap());
    let expected = r#"{
  "tilesets": [
    {
      "bounds": [
        -180.0,
        -90.0,
        180.0,
        90.0
      ],
      "layers": [
        {
          "geometry_type": "POINT",
          "name": "points"
        },
        {
          "geometry_type": "POLYGON",
          "name": "buildings"
        },
        {
          "geometry_type": "POLYGON",
          "name": "admin_0_countries"
        }
      ],
      "name": "osm",
      "supported": true,
      "tilejson": "osm.json",
      "tileurl": "/osm/{z}/{x}/{y}.pbf"
    }
  ]
}"#;
    println!("{}", metadata);
    assert_eq!(metadata, expected);
}

#[test]
#[ignore]
fn test_tilejson() {
    use core::read_config;
    use std::env;

    match env::var("DBCONN") {
        Err(_) => panic!("DBCONN undefined"),
        // Overwrite PG connection in example.toml
        Ok(dbconn) => env::set_var("TREX_DATASOURCE_URL", dbconn),
    }

    let config = read_config("src/test/example.toml").unwrap();
    let mut service = MvtService::from_config(&config).unwrap();
    service.connect();
    service.prepare_feature_queries();
    let metadata = format!(
        "{:#}",
        service.get_tilejson("http://127.0.0.1", "osm").unwrap()
    );
    println!("{}", metadata);
    let expected = r#"{
  "attribution": "",
  "basename": "osm",
  "bounds": [
    -180.0,
    -90.0,
    180.0,
    90.0
  ],
  "center": [
    0.0,
    0.0,
    2
  ],
  "description": "osm",
  "format": "pbf",
  "id": "osm",
  "maxzoom": 22,
  "minzoom": 0,
  "name": "osm",
  "scheme": "xyz",
  "tiles": [
    "http://127.0.0.1/osm/{z}/{x}/{y}.pbf"
  ],
  "vector_layers": [
    {
      "description": "",
      "fields": {},
      "id": "points",
      "maxzoom": 22,
      "minzoom": 0
    },
    {
      "description": "",
      "fields": {},
      "id": "buildings",
      "maxzoom": 22,
      "minzoom": 0
    },
    {
      "description": "",
      "fields": {},
      "id": "admin_0_countries",
      "maxzoom": 22,
      "minzoom": 0
    }
  ],
  "version": "2.0.0"
}"#;
    assert_eq!(metadata, expected);
}

#[test]
fn test_stylejson() {
    use core::read_config;

    let config = read_config("src/test/example.toml").unwrap();
    let service = MvtService::from_config(&config).unwrap();
    let json = format!(
        "{:#}",
        service.get_stylejson("http://127.0.0.1", "osm").unwrap()
    );
    println!("{}", json);
    let expected = r#"
  "name": "t-rex",
  "sources": {
    "osm": {
      "type": "vector",
      "url": "http://127.0.0.1/osm.json"
    }
  },
  "version": 8
"#;
    assert!(json.contains(expected));
    let expected = r#"
  "layers": [
    {
      "id": "background_",
      "paint": {
        "background-color": "rgba(255, 255, 255, 1)"
      },
      "type": "background"
    },
    {
      "id": "points","#;
    assert!(json.contains(expected));

    let expected = r##"
      "paint": {
        "fill-color": "#d8e8c8",
        "fill-opacity": 0.5
      },"##;
    assert!(json.contains(expected));

    let expected = r#"
      "id": "buildings","#;
    assert!(json.contains(expected));
}

#[test]
#[ignore]
fn test_mbtiles_metadata() {
    use core::read_config;
    use std::env;

    match env::var("DBCONN") {
        Err(_) => panic!("DBCONN undefined"),
        // Overwrite PG connection in example.toml
        Ok(dbconn) => env::set_var("TREX_DATASOURCE_URL", dbconn),
    }

    let config = read_config("src/test/example.toml").unwrap();
    let mut service = MvtService::from_config(&config).unwrap();
    service.connect();
    let metadata = format!("{:#}", service.get_mbtiles_metadata("osm").unwrap());
    println!("{}", metadata);
    let expected = r#"{
  "attribution": "",
  "basename": "osm",
  "bounds": "[-180.0,-90.0,180.0,90.0]",
  "center": "[0.0,0.0,2]",
  "description": "osm",
  "format": "pbf",
  "id": "osm",
  "json": "{\"Layer\":[{\"description\":\"\",\"fields\":{},\"id\":\"points\",\"name\":\"points\",\"properties\":{\"buffer-size\":0,\"maxzoom\":22,\"minzoom\":0},\"srs\":\"+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over\"},{\"description\":\"\",\"fields\":{},\"id\":\"buildings\",\"name\":\"buildings\",\"properties\":{\"buffer-size\":10,\"maxzoom\":22,\"minzoom\":0},\"srs\":\"+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over\"},{\"description\":\"\",\"fields\":{},\"id\":\"admin_0_countries\",\"name\":\"admin_0_countries\",\"properties\":{\"buffer-size\":1,\"maxzoom\":22,\"minzoom\":0},\"srs\":\"+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over\"}],\"vector_layers\":[{\"description\":\"\",\"fields\":{},\"id\":\"points\",\"maxzoom\":22,\"minzoom\":0},{\"description\":\"\",\"fields\":{},\"id\":\"buildings\",\"maxzoom\":22,\"minzoom\":0},{\"description\":\"\",\"fields\":{},\"id\":\"admin_0_countries\",\"maxzoom\":22,\"minzoom\":0}]}",
  "maxzoom": 22,
  "minzoom": 0,
  "name": "osm",
  "scheme": "xyz",
  "version": "2.0.0"
}"#;
    assert_eq!(metadata, expected);
}

#[test]
fn test_gen_config() {
    #[cfg(feature = "with-gdal")]
    let gdal_ds_cfg = r#"
[[datasource]]
name = "ds"
# Dataset specification (http://gdal.org/ogr_formats.html)
path = "<filename-or-connection-spec>"
"#;
    #[cfg(not(feature = "with-gdal"))]
    let gdal_ds_cfg = "";

    let expected = format!(
        r#"# t-rex configuration

[service.mvt]
viewer = true

[[datasource]]
name = "database"
# PostgreSQL connection specification (https://github.com/sfackler/rust-postgres#connecting)
dbconn = "postgresql://user:pass@host/database"
{}
[grid]
# Predefined grids: web_mercator, wgs84
predefined = "web_mercator"

[[tileset]]
name = "points"
#minzoom = 0 # Optional override of zoom limits broadcasted to tilejson descriptor
#maxzoom = 22
#attribution = "© Contributeurs de OpenStreetMap" # Acknowledgment of ownership, authorship or copyright.

[[tileset.layer]]
name = "points"
table_name = "mytable"
geometry_field = "wkb_geometry"
geometry_type = "POINT"
#fid_field = "id"
#simplify = true
#buffer_size = 10
#[[tileset.layer.query]]
#minzoom = 0
#maxzoom = 22
#sql = "SELECT name,wkb_geometry FROM mytable"

#[cache.file]
#base = "/tmp/mvtcache"
#baseurl = "http://example.com/tiles"
"#,
        gdal_ds_cfg
    );
    println!("{}", &MvtService::gen_config());
    assert_eq!(&expected, &MvtService::gen_config());
}
<|endoftext|>


<commit_before>mod entities;
mod input;
mod updates;
mod util;

use chrono::{DateTime, Duration as OldDuration, Utc};
use log::{error, trace};
use rand_core::{OsRng, RngCore};
use serde_derive::Serialize;
use std::collections::HashMap;
use std::mem;

pub use self::entities::*;
pub use self::input::*;
pub use self::updates::*;
pub use self::util::*;

use crate::server::{ArenaId, ArenaPoint, Direction, Line, Player, PlayerId};

const ARENA_WIDTH: usize = 560;
const ARENA_HEIGHT: usize = 560;
const ARENA_MAX_PLAYERS: usize = 8;
const ARENA_START_TIMER_SECONDS: i64 = 5;
const LIGHTCYCLE_SPEED: isize = 1;

#[derive(Debug, Clone, Serialize)]
pub struct Arena {
    pub id: ArenaId,
    pub name: String,
    pub width: usize,
    pub height: usize,
    pub max_players: usize,

    pub started: Option<DateTime<Utc>>,

    pub players: HashMap<PlayerId, Player>,
    pub lightcycles: HashMap<PlayerId, Lightcycle>,
    pub trails: HashMap<PlayerId, Trail>,

    #[serde(skip)]
    pub updates: Vec<ArenaUpdate>,
    #[serde(skip)]
    updates_applied_so_far: usize,
}

impl Arena {
    pub fn with_name(name: &str) -> Self {
        Self {
            name: name.to_uppercase(),
            ..Default::default()
        }
    }

    pub fn add_player(&mut self, player: Player) {
        self.updates.push(ArenaUpdate::AddPlayer(player.id, player));
    }

    pub fn remove_player(&mut self, player_id: PlayerId) {
        self.updates.push(ArenaUpdate::RemovePlayer(player_id));
    }

    pub fn clear_updates(&mut self) {
        self.updates.clear();
        self.updates_applied_so_far = 0;
    }

    pub fn apply_updates(&mut self) -> &mut Self {
        let updates = mem::replace(&mut self.updates, Vec::new());
        for update in updates.iter().skip(self.updates_applied_so_far) {
            update.apply(self);
            self.updates_applied_so_far += 1;
        }
        mem::replace(&mut self.updates, updates);

        self
    }

    pub fn process_input(&mut self, player_id: PlayerId, input_event: ArenaInput) {
        input_event
            .process_into_updates(self, player_id)
            .drain(..)
            .for_each(|update| self.updates.push(update));
    }

    pub fn update(&mut self) {
        // apply process_input updates
        self.apply_updates();

        let started = match self.started {
            Some(started) => started,
            None => return,
        };

        let now = Utc::now();
        if now < started {
            return;
        }

        self.update_lightcycle_positions()
            .apply_updates()
            .calculate_lightcycle_collisions()
            .apply_updates()
            .update_trail_positions()
            .apply_updates()
            .test_round_end()
            .apply_updates();
    }

    //
    // update helpers
    //

    fn update_lightcycle_positions(&mut self) -> &mut Self {
        for (id, lightcycle) in self.lightcycles.iter() {
            if lightcycle.dead {
                continue;
            };

            self.updates.push(ArenaUpdate::UpdateLightcyclePosition(
                *id,
                lightcycle.position + lightcycle.direction.as_velocity() * lightcycle.speed,
            ))
        }
        self
    }

    fn calculate_lightcycle_collisions(&mut self) -> &mut Self {
        'next_lightcycle: for (id, lightcycle) in self.lightcycles.iter() {
            if lightcycle.dead {
                continue 'next_lightcycle;
            };

            // test for trail collisions
            for trail in self.trails.values() {
                for line in trail.points.windows(2) {
                    let start = line[0];
                    let end = line[1];

                    if is_point_on_line_2d(lightcycle.position, Line(start, end)) {
                        self.updates
                            .push(ArenaUpdate::UpdateLightcycleApplyDeath(*id));
                        continue 'next_lightcycle;
                    }
                }
            }

            // test for arena bounds collisions
            if lightcycle.position.x < 0
                || lightcycle.position.y < 0
                || lightcycle.position.x > self.width as isize
                || lightcycle.position.y > self.height as isize
            {
                self.updates
                    .push(ArenaUpdate::UpdateLightcycleApplyDeath(*id));
                continue 'next_lightcycle;
            }

            // test for collisions with other lightcycles
            if self
                .lightcycles
                .iter()
                .filter(|(_, other_lightcycle)| !other_lightcycle.dead)
                .filter(|(other_id, _)| id != *other_id)
                .any(|(_, other_lightcycle)| lightcycle.position == other_lightcycle.position)
            {
                self.updates
                    .push(ArenaUpdate::UpdateLightcycleApplyDeath(*id));
                continue 'next_lightcycle;
            }
        }
        self
    }

    fn update_trail_positions(&mut self) -> &mut Self {
        for id in self.trails.keys() {
            let latest_point = match self.lightcycles.get(id) {
                Some(lightcycle) => {
                    if lightcycle.dead {
                        continue;
                    };

                    lightcycle.position
                }
                None => {
                    error!(
                        "Failed to update trail position: No lightcycle with id {}",
                        id
                    );
                    continue;
                }
            };

            self.updates
                .push(ArenaUpdate::UpdateTrailReplaceLatestPoint(
                    *id,
                    latest_point,
                ));
        }
        self
    }

    fn test_round_end(&mut self) -> &mut Self {
        if self.lightcycles.values().all(|lightcycle| lightcycle.dead) {
            self.updates.push(ArenaUpdate::End);
        }
        self
    }
}

impl Default for Arena {
    fn default() -> Self {
        Self {
            id: ArenaId::new_v4(),
            name: Default::default(),
            width: ARENA_WIDTH,
            height: ARENA_HEIGHT,
            max_players: ARENA_MAX_PLAYERS,

            started: None,

            players: Default::default(),
            lightcycles: Default::default(),
            trails: Default::default(),

            updates: Default::default(),
            updates_applied_so_far: 0,
        }
    }
}

/// ArenaOverview represents an overview of an Arena for the arena selection screen.
#[derive(Debug, Clone, Serialize)]
pub struct ArenaOverview {
    id: ArenaId,
    name: String,
    max_players: usize,
    started: Option<DateTime<Utc>>,
    players: HashMap<PlayerId, Player>,
}

impl From<&Arena> for ArenaOverview {
    fn from(arena: &Arena) -> Self {
        Self {
            id: arena.id,
            name: arena.name.clone(),
            max_players: arena.max_players,
            started: arena.started,
            players: arena.players.clone(),
        }
    }
}
<commit_msg>reduced ARENA_START_TIMER_SECONDS to 1 second<commit_after>mod entities;
mod input;
mod updates;
mod util;

use chrono::{DateTime, Duration as OldDuration, Utc};
use log::{error, trace};
use rand_core::{OsRng, RngCore};
use serde_derive::Serialize;
use std::collections::HashMap;
use std::mem;

pub use self::entities::*;
pub use self::input::*;
pub use self::updates::*;
pub use self::util::*;

use crate::server::{ArenaId, ArenaPoint, Direction, Line, Player, PlayerId};

const ARENA_WIDTH: usize = 560;
const ARENA_HEIGHT: usize = 560;
const ARENA_MAX_PLAYERS: usize = 8;
const ARENA_START_TIMER_SECONDS: i64 = 1;
const LIGHTCYCLE_SPEED: isize = 1;

#[derive(Debug, Clone, Serialize)]
pub struct Arena {
    pub id: ArenaId,
    pub name: String,
    pub width: usize,
    pub height: usize,
    pub max_players: usize,

    pub started: Option<DateTime<Utc>>,

    pub players: HashMap<PlayerId, Player>,
    pub lightcycles: HashMap<PlayerId, Lightcycle>,
    pub trails: HashMap<PlayerId, Trail>,

    #[serde(skip)]
    pub updates: Vec<ArenaUpdate>,
    #[serde(skip)]
    updates_applied_so_far: usize,
}

impl Arena {
    pub fn with_name(name: &str) -> Self {
        Self {
            name: name.to_uppercase(),
            ..Default::default()
        }
    }

    pub fn add_player(&mut self, player: Player) {
        self.updates.push(ArenaUpdate::AddPlayer(player.id, player));
    }

    pub fn remove_player(&mut self, player_id: PlayerId) {
        self.updates.push(ArenaUpdate::RemovePlayer(player_id));
    }

    pub fn clear_updates(&mut self) {
        self.updates.clear();
        self.updates_applied_so_far = 0;
    }

    pub fn apply_updates(&mut self) -> &mut Self {
        let updates = mem::replace(&mut self.updates, Vec::new());
        for update in updates.iter().skip(self.updates_applied_so_far) {
            update.apply(self);
            self.updates_applied_so_far += 1;
        }
        mem::replace(&mut self.updates, updates);

        self
    }

    pub fn process_input(&mut self, player_id: PlayerId, input_event: ArenaInput) {
        input_event
            .process_into_updates(self, player_id)
            .drain(..)
            .for_each(|update| self.updates.push(update));
    }

    pub fn update(&mut self) {
        // apply process_input updates
        self.apply_updates();

        let started = match self.started {
            Some(started) => started,
            None => return,
        };

        let now = Utc::now();
        if now < started {
            return;
        }

        self.update_lightcycle_positions()
            .apply_updates()
            .calculate_lightcycle_collisions()
            .apply_updates()
            .update_trail_positions()
            .apply_updates()
            .test_round_end()
            .apply_updates();
    }

    //
    // update helpers
    //

    fn update_lightcycle_positions(&mut self) -> &mut Self {
        for (id, lightcycle) in self.lightcycles.iter() {
            if lightcycle.dead {
                continue;
            };

            self.updates.push(ArenaUpdate::UpdateLightcyclePosition(
                *id,
                lightcycle.position + lightcycle.direction.as_velocity() * lightcycle.speed,
            ))
        }
        self
    }

    fn calculate_lightcycle_collisions(&mut self) -> &mut Self {
        'next_lightcycle: for (id, lightcycle) in self.lightcycles.iter() {
            if lightcycle.dead {
                continue 'next_lightcycle;
            };

            // test for trail collisions
            for trail in self.trails.values() {
                for line in trail.points.windows(2) {
                    let start = line[0];
                    let end = line[1];

                    if is_point_on_line_2d(lightcycle.position, Line(start, end)) {
                        self.updates
                            .push(ArenaUpdate::UpdateLightcycleApplyDeath(*id));
                        continue 'next_lightcycle;
                    }
                }
            }

            // test for arena bounds collisions
            if lightcycle.position.x < 0
                || lightcycle.position.y < 0
                || lightcycle.position.x > self.width as isize
                || lightcycle.position.y > self.height as isize
            {
                self.updates
                    .push(ArenaUpdate::UpdateLightcycleApplyDeath(*id));
                continue 'next_lightcycle;
            }

            // test for collisions with other lightcycles
            if self
                .lightcycles
                .iter()
                .filter(|(_, other_lightcycle)| !other_lightcycle.dead)
                .filter(|(other_id, _)| id != *other_id)
                .any(|(_, other_lightcycle)| lightcycle.position == other_lightcycle.position)
            {
                self.updates
                    .push(ArenaUpdate::UpdateLightcycleApplyDeath(*id));
                continue 'next_lightcycle;
            }
        }
        self
    }

    fn update_trail_positions(&mut self) -> &mut Self {
        for id in self.trails.keys() {
            let latest_point = match self.lightcycles.get(id) {
                Some(lightcycle) => {
                    if lightcycle.dead {
                        continue;
                    };

                    lightcycle.position
                }
                None => {
                    error!(
                        "Failed to update trail position: No lightcycle with id {}",
                        id
                    );
                    continue;
                }
            };

            self.updates
                .push(ArenaUpdate::UpdateTrailReplaceLatestPoint(
                    *id,
                    latest_point,
                ));
        }
        self
    }

    fn test_round_end(&mut self) -> &mut Self {
        if self.lightcycles.values().all(|lightcycle| lightcycle.dead) {
            self.updates.push(ArenaUpdate::End);
        }
        self
    }
}

impl Default for Arena {
    fn default() -> Self {
        Self {
            id: ArenaId::new_v4(),
            name: Default::default(),
            width: ARENA_WIDTH,
            height: ARENA_HEIGHT,
            max_players: ARENA_MAX_PLAYERS,

            started: None,

            players: Default::default(),
            lightcycles: Default::default(),
            trails: Default::default(),

            updates: Default::default(),
            updates_applied_so_far: 0,
        }
    }
}

/// ArenaOverview represents an overview of an Arena for the arena selection screen.
#[derive(Debug, Clone, Serialize)]
pub struct ArenaOverview {
    id: ArenaId,
    name: String,
    max_players: usize,
    started: Option<DateTime<Utc>>,
    players: HashMap<PlayerId, Player>,
}

impl From<&Arena> for ArenaOverview {
    fn from(arena: &Arena) -> Self {
        Self {
            id: arena.id,
            name: arena.name.clone(),
            max_players: arena.max_players,
            started: arena.started,
            players: arena.players.clone(),
        }
    }
}
<|endoftext|>


<commit_before>use serenity::model::id::{ChannelId, GuildId, RoleId, UserId};
use std::num::NonZeroU32;
use typemap_rev::{TypeMap, TypeMapKey};

use crate::database::mutes::{Mute, MutesRepository};
use crate::database::settings::{get_action_duration_for_auto_mod_action, Setting};
use crate::discord::util::mod_log::{create_mod_log_entry, CreateModLogEntryError, ModLogAction};
use crate::discord::util::user_dm::{notify_user_for_mod_action, ModActionKind};
use crate::discord::util::{execute_mod_action, SerenityErrorExt};
use crate::service::guild::{GetRolesFailure, GuildService};
use crate::util::now;
use serenity::http::Http;
use serenity::model::channel::{PermissionOverwrite, PermissionOverwriteType};
use serenity::model::user::User;
use serenity::model::Permissions;
use std::time::Duration;
use tracing::error;

impl TypeMapKey for MuteService {
    type Value = MuteService;
}

pub struct MuteService {
    pub repository: MutesRepository,
}

pub enum MuteFailure {
    Unauthorized,
    UnauthorizedFetchRoles,
    UnauthorizedCreateRole,
    UnauthorizedChannelOverride,
    ModLogError(CreateModLogEntryError),
    Unknown,
}

pub enum UnmuteFailure {
    RoleNotFound,
    Unauthorized,
    Unknown,
}

impl MuteService {
    pub async fn fetch_muted_role(
        &self,
        http: &Http,
        services: &TypeMap,
        guild_id: GuildId,
    ) -> Result<RoleId, MuteFailure> {
        let guild_service = if let Some(service) = services.get::<GuildService>() {
            service
        } else {
            error!("couldn't get guild service!");
            return Err(MuteFailure::Unknown);
        };

        let roles = match guild_service.get_roles(guild_id).await {
            Ok(roles) => roles,
            Err(GetRolesFailure::FetchFailed) => return Err(MuteFailure::UnauthorizedFetchRoles),
        };

        for (id, role) in roles.iter() {
            if role.name == "Muted" {
                return Ok(*id);
            }
        }

        let role = match guild_id.create_role(http, |role| role.name("Muted")).await {
            Ok(role) => role,
            Err(err) => {
                return match err.discord_error_code() {
                    Some(50013) => Err(MuteFailure::UnauthorizedCreateRole),
                    _ => {
                        error!("failed to create role for guild: {} {}", guild_id, err);
                        Err(MuteFailure::Unknown)
                    }
                };
            }
        };

        let channels = match guild_id.channels(http).await {
            Ok(channels) => channels,
            Err(err) => {
                error!("failed to fetch channels of guild: {} {}", guild_id, err);
                return Err(MuteFailure::Unknown);
            }
        };

        for (_, channel) in channels {
            if let Err(err) = channel
                .create_permission(
                    http,
                    &PermissionOverwrite {
                        allow: Default::default(),
                        deny: Permissions::SEND_MESSAGES
                            | Permissions::ADD_REACTIONS
                            | Permissions::SPEAK,
                        kind: PermissionOverwriteType::Role(role.id),
                    },
                )
                .await
            {
                return match err.discord_error_code() {
                    Some(50013) => Err(MuteFailure::UnauthorizedChannelOverride),
                    _ => {
                        error!(
                            "failed to create permission override in guild: {} {}",
                            guild_id, err
                        );
                        Err(MuteFailure::Unknown)
                    }
                };
            }
        }

        Ok(role.id)
    }

    pub async fn issue_mute(
        &self,
        http: &Http,
        guild_id: GuildId,
        guild_name: &str,
        setting: &Setting,
        services: &TypeMap,
        channel_id: Option<ChannelId>,
        mod_user_id: UserId,
        mod_user_tag_and_id: &str,
        target_user: &User,
        reason: String,
        duration: Option<Duration>,
        call_depth: i32,
    ) -> Result<(), MuteFailure> {
        let now = now();
        let expiration_time = duration.map(|duration| now + duration.as_secs());
        let mod_log_channel_id = if setting.mod_log {
            Some(ChannelId(setting.mod_log_channel_id as u64))
        } else {
            None
        };

        notify_user_for_mod_action(
            http,
            target_user.id,
            ModActionKind::Mute { expiration_time },
            &reason,
            now,
            guild_name,
            mod_user_tag_and_id,
        )
        .await;

        let role = match self.fetch_muted_role(http, services, guild_id).await {
            Ok(role) => role,
            Err(err) => {
                return Err(err);
            }
        };

        match http
            .add_member_role(
                guild_id.0,
                target_user.id.0,
                role.0,
                Some("Muting member because of mute command"),
            )
            .await
        {
            Ok(_) => (),
            Err(err) => {
                return match err.discord_error_code() {
                    Some(50013) => Err(MuteFailure::Unauthorized),
                    _ => {
                        error!("failed to issue discord member role add {}", err);
                        Err(MuteFailure::Unknown)
                    }
                }
            }
        }

        let mute_entry = Mute {
            id: 0,
            user_id: target_user.id.0 as i64,
            moderator_user_id: mod_user_id.0 as i64,
            guild_id: guild_id.0 as i64,
            mute_time: now as i64,
            expire_time: expiration_time.unwrap_or(0) as i64,
            reason: reason.clone(),
            expires: duration.is_some(),
            unmuted: false,
            pardoned: false,
        };

        self.invalidate_previous_user_mutes(guild_id, target_user.id)
            .await;
        let mute_id = self.insert_mute(mute_entry).await.map(|mute| mute.id);

        if let Some(mute_id) = mute_id {
            if let Some(mod_log_channel_id) = mod_log_channel_id {
                if let Err(err) = create_mod_log_entry(
                    http,
                    mod_log_channel_id,
                    channel_id,
                    mod_user_tag_and_id,
                    target_user,
                    ModLogAction::Mute { expiration_time },
                    &reason,
                    mute_id,
                    now,
                )
                .await
                {
                    return Err(MuteFailure::ModLogError(err));
                }
            }
        }

        if setting.mute_threshold != 0 {
            let count = self
                .fetch_actionable_mute_count(guild_id, target_user.id)
                .await;
            if count >= setting.mute_threshold.into() {
                let duration = get_action_duration_for_auto_mod_action(
                    setting.mute_action,
                    setting.mute_action_duration_type,
                    setting.mute_action_duration,
                );

                execute_mod_action(
                    setting.mute_action,
                    http,
                    guild_id,
                    guild_name,
                    setting,
                    services,
                    channel_id,
                    mod_user_id,
                    mod_user_tag_and_id,
                    target_user,
                    reason,
                    duration,
                    call_depth,
                )
                .await;
            }
        }

        Ok(())
    }

    pub async fn unmute(
        &self,
        http: &Http,
        services: &TypeMap,
        guild_id: GuildId,
        target_user_id: UserId,
        mod_user_tag_and_id: &str,
    ) -> Result<(), UnmuteFailure> {
        // use cached roles
        let guild_service = if let Some(service) = services.get::<GuildService>() {
            service
        } else {
            error!("couldn't get guild service!");
            return Err(UnmuteFailure::Unknown);
        };

        let roles = match guild_service.get_roles(guild_id).await {
            Ok(roles) => roles,
            Err(GetRolesFailure::FetchFailed) => return Err(UnmuteFailure::Unknown),
        };

        let muted_role = roles.iter().find(|(_, role)| role.name == "Muted");

        let audit_log_reason = format!("Unmuted by {}", mod_user_tag_and_id);

        if let Some((role_id, _)) = muted_role {
            match http
                .remove_member_role(
                    guild_id.0,
                    target_user_id.0,
                    role_id.0,
                    Some(&audit_log_reason),
                )
                .await
            {
                Ok(_) => (),
                Err(err) => {
                    return match err.discord_error_code() {
                        Some(50013) => Err(UnmuteFailure::Unauthorized),
                        _ => {
                            error!("failed to issue discord member role remove {}", err);
                            Err(UnmuteFailure::Unknown)
                        }
                    }
                }
            }

            self.invalidate_previous_user_mutes(guild_id, target_user_id)
                .await;
        } else {
            return Err(UnmuteFailure::RoleNotFound);
        }

        Ok(())
    }

    pub async fn fetch_mute(&self, id: i32) -> Option<Mute> {
        self.repository
            .fetch_mute(id)
            .await
            .map_err(|err| {
                error!("failed to fetch mute {:?}", err);
                err
            })
            .ok()
            .flatten()
    }

    pub async fn fetch_guild_mutes(&self, guild_id: GuildId, page: NonZeroU32) -> Vec<Mute> {
        self.repository
            .fetch_guild_mutes(guild_id.0 as i64, page.get())
            .await
            .map_err(|err| {
                error!("failed to fetch guild mutes {:?}", err);
                err
            })
            .ok()
            .unwrap_or_else(Vec::new)
    }

    pub async fn fetch_guild_mute_count(&self, guild_id: GuildId) -> i64 {
        self.repository
            .fetch_guild_mute_count(guild_id.0 as i64)
            .await
            .map_err(|err| {
                error!("failed to fetch guild mute count {:?}", err);
                err
            })
            .ok()
            .unwrap_or(0)
    }

    pub async fn fetch_expired_mutes(&self) -> Vec<Mute> {
        self.repository
            .fetch_expired_mutes()
            .await
            .map_err(|err| {
                error!("failed to fetch expired mutes {:?}", err);
                err
            })
            .ok()
            .unwrap_or_else(Vec::new)
    }

    pub async fn fetch_valid_mutes(&self, guild_id: GuildId, user_id: UserId) -> Vec<Mute> {
        self.repository
            .fetch_valid_mutes(guild_id.0 as i64, user_id.0 as i64)
            .await
            .map_err(|err| {
                error!("failed to fetch valid mutes {:?}", err);
                err
            })
            .ok()
            .unwrap_or_else(Vec::new)
    }

    pub async fn fetch_actionable_mute_count(&self, guild_id: GuildId, user_id: UserId) -> i64 {
        self.repository
            .fetch_actionable_mute_count(guild_id.0 as i64, user_id.0 as i64)
            .await
            .map_err(|err| {
                error!("failed to fetch actionable mute count {:?}", err);
                err
            })
            .ok()
            .unwrap_or(0)
    }

    pub async fn insert_mute(&self, mute: Mute) -> Option<Mute> {
        self.repository
            .insert_mute(mute)
            .await
            .map_err(|err| {
                error!("failed to insert mute {:?}", err);
                err
            })
            .ok()
    }

    pub async fn update_mute(&self, mute: Mute) -> Option<()> {
        self.repository
            .update_mute(mute)
            .await
            .map_err(|err| {
                error!("failed to update mute {:?}", err);
                err
            })
            .ok()
    }

    pub async fn invalidate_previous_user_mutes(&self, guild_id: GuildId, user_id: UserId) {
        let _ = self
            .repository
            .invalidate_previous_user_mutes(guild_id.0 as i64, user_id.0 as i64)
            .await
            .map_err(|err| {
                error!("failed to invalidate previous user mutes {:?}", err);
                err
            });
    }

    pub async fn invalidate_mute(&self, id: i32) {
        let _ = self.repository.invalidate_mute(id).await.map_err(|err| {
            error!("failed to invalidate mute {:?}", err);
            err
        });
    }
}
<commit_msg>Make mute audit log reason consistent with unmute<commit_after>use serenity::model::id::{ChannelId, GuildId, RoleId, UserId};
use std::num::NonZeroU32;
use typemap_rev::{TypeMap, TypeMapKey};

use crate::database::mutes::{Mute, MutesRepository};
use crate::database::settings::{get_action_duration_for_auto_mod_action, Setting};
use crate::discord::util::mod_log::{create_mod_log_entry, CreateModLogEntryError, ModLogAction};
use crate::discord::util::user_dm::{notify_user_for_mod_action, ModActionKind};
use crate::discord::util::{execute_mod_action, SerenityErrorExt};
use crate::service::guild::{GetRolesFailure, GuildService};
use crate::util::now;
use serenity::http::Http;
use serenity::model::channel::{PermissionOverwrite, PermissionOverwriteType};
use serenity::model::user::User;
use serenity::model::Permissions;
use std::time::Duration;
use tracing::error;

impl TypeMapKey for MuteService {
    type Value = MuteService;
}

pub struct MuteService {
    pub repository: MutesRepository,
}

pub enum MuteFailure {
    Unauthorized,
    UnauthorizedFetchRoles,
    UnauthorizedCreateRole,
    UnauthorizedChannelOverride,
    ModLogError(CreateModLogEntryError),
    Unknown,
}

pub enum UnmuteFailure {
    RoleNotFound,
    Unauthorized,
    Unknown,
}

impl MuteService {
    pub async fn fetch_muted_role(
        &self,
        http: &Http,
        services: &TypeMap,
        guild_id: GuildId,
    ) -> Result<RoleId, MuteFailure> {
        let guild_service = if let Some(service) = services.get::<GuildService>() {
            service
        } else {
            error!("couldn't get guild service!");
            return Err(MuteFailure::Unknown);
        };

        let roles = match guild_service.get_roles(guild_id).await {
            Ok(roles) => roles,
            Err(GetRolesFailure::FetchFailed) => return Err(MuteFailure::UnauthorizedFetchRoles),
        };

        for (id, role) in roles.iter() {
            if role.name == "Muted" {
                return Ok(*id);
            }
        }

        let role = match guild_id.create_role(http, |role| role.name("Muted")).await {
            Ok(role) => role,
            Err(err) => {
                return match err.discord_error_code() {
                    Some(50013) => Err(MuteFailure::UnauthorizedCreateRole),
                    _ => {
                        error!("failed to create role for guild: {} {}", guild_id, err);
                        Err(MuteFailure::Unknown)
                    }
                };
            }
        };

        let channels = match guild_id.channels(http).await {
            Ok(channels) => channels,
            Err(err) => {
                error!("failed to fetch channels of guild: {} {}", guild_id, err);
                return Err(MuteFailure::Unknown);
            }
        };

        for (_, channel) in channels {
            if let Err(err) = channel
                .create_permission(
                    http,
                    &PermissionOverwrite {
                        allow: Default::default(),
                        deny: Permissions::SEND_MESSAGES
                            | Permissions::ADD_REACTIONS
                            | Permissions::SPEAK,
                        kind: PermissionOverwriteType::Role(role.id),
                    },
                )
                .await
            {
                return match err.discord_error_code() {
                    Some(50013) => Err(MuteFailure::UnauthorizedChannelOverride),
                    _ => {
                        error!(
                            "failed to create permission override in guild: {} {}",
                            guild_id, err
                        );
                        Err(MuteFailure::Unknown)
                    }
                };
            }
        }

        Ok(role.id)
    }

    pub async fn issue_mute(
        &self,
        http: &Http,
        guild_id: GuildId,
        guild_name: &str,
        setting: &Setting,
        services: &TypeMap,
        channel_id: Option<ChannelId>,
        mod_user_id: UserId,
        mod_user_tag_and_id: &str,
        target_user: &User,
        reason: String,
        duration: Option<Duration>,
        call_depth: i32,
    ) -> Result<(), MuteFailure> {
        let now = now();
        let expiration_time = duration.map(|duration| now + duration.as_secs());
        let mod_log_channel_id = if setting.mod_log {
            Some(ChannelId(setting.mod_log_channel_id as u64))
        } else {
            None
        };

        notify_user_for_mod_action(
            http,
            target_user.id,
            ModActionKind::Mute { expiration_time },
            &reason,
            now,
            guild_name,
            mod_user_tag_and_id,
        )
        .await;

        let role = match self.fetch_muted_role(http, services, guild_id).await {
            Ok(role) => role,
            Err(err) => {
                return Err(err);
            }
        };

        let audit_log_reason = format!("Muted by {} - {}", mod_user_tag_and_id, reason);
        match http
            .add_member_role(
                guild_id.0,
                target_user.id.0,
                role.0,
                Some(&audit_log_reason),
            )
            .await
        {
            Ok(_) => (),
            Err(err) => {
                return match err.discord_error_code() {
                    Some(50013) => Err(MuteFailure::Unauthorized),
                    _ => {
                        error!("failed to issue discord member role add {}", err);
                        Err(MuteFailure::Unknown)
                    }
                }
            }
        }

        let mute_entry = Mute {
            id: 0,
            user_id: target_user.id.0 as i64,
            moderator_user_id: mod_user_id.0 as i64,
            guild_id: guild_id.0 as i64,
            mute_time: now as i64,
            expire_time: expiration_time.unwrap_or(0) as i64,
            reason: reason.clone(),
            expires: duration.is_some(),
            unmuted: false,
            pardoned: false,
        };

        self.invalidate_previous_user_mutes(guild_id, target_user.id)
            .await;
        let mute_id = self.insert_mute(mute_entry).await.map(|mute| mute.id);

        if let Some(mute_id) = mute_id {
            if let Some(mod_log_channel_id) = mod_log_channel_id {
                if let Err(err) = create_mod_log_entry(
                    http,
                    mod_log_channel_id,
                    channel_id,
                    mod_user_tag_and_id,
                    target_user,
                    ModLogAction::Mute { expiration_time },
                    &reason,
                    mute_id,
                    now,
                )
                .await
                {
                    return Err(MuteFailure::ModLogError(err));
                }
            }
        }

        if setting.mute_threshold != 0 {
            let count = self
                .fetch_actionable_mute_count(guild_id, target_user.id)
                .await;
            if count >= setting.mute_threshold.into() {
                let duration = get_action_duration_for_auto_mod_action(
                    setting.mute_action,
                    setting.mute_action_duration_type,
                    setting.mute_action_duration,
                );

                execute_mod_action(
                    setting.mute_action,
                    http,
                    guild_id,
                    guild_name,
                    setting,
                    services,
                    channel_id,
                    mod_user_id,
                    mod_user_tag_and_id,
                    target_user,
                    reason,
                    duration,
                    call_depth,
                )
                .await;
            }
        }

        Ok(())
    }

    pub async fn unmute(
        &self,
        http: &Http,
        services: &TypeMap,
        guild_id: GuildId,
        target_user_id: UserId,
        mod_user_tag_and_id: &str,
    ) -> Result<(), UnmuteFailure> {
        // use cached roles
        let guild_service = if let Some(service) = services.get::<GuildService>() {
            service
        } else {
            error!("couldn't get guild service!");
            return Err(UnmuteFailure::Unknown);
        };

        let roles = match guild_service.get_roles(guild_id).await {
            Ok(roles) => roles,
            Err(GetRolesFailure::FetchFailed) => return Err(UnmuteFailure::Unknown),
        };

        let muted_role = roles.iter().find(|(_, role)| role.name == "Muted");

        let audit_log_reason = format!("Unmuted by {}", mod_user_tag_and_id);

        if let Some((role_id, _)) = muted_role {
            match http
                .remove_member_role(
                    guild_id.0,
                    target_user_id.0,
                    role_id.0,
                    Some(&audit_log_reason),
                )
                .await
            {
                Ok(_) => (),
                Err(err) => {
                    return match err.discord_error_code() {
                        Some(50013) => Err(UnmuteFailure::Unauthorized),
                        _ => {
                            error!("failed to issue discord member role remove {}", err);
                            Err(UnmuteFailure::Unknown)
                        }
                    }
                }
            }

            self.invalidate_previous_user_mutes(guild_id, target_user_id)
                .await;
        } else {
            return Err(UnmuteFailure::RoleNotFound);
        }

        Ok(())
    }

    pub async fn fetch_mute(&self, id: i32) -> Option<Mute> {
        self.repository
            .fetch_mute(id)
            .await
            .map_err(|err| {
                error!("failed to fetch mute {:?}", err);
                err
            })
            .ok()
            .flatten()
    }

    pub async fn fetch_guild_mutes(&self, guild_id: GuildId, page: NonZeroU32) -> Vec<Mute> {
        self.repository
            .fetch_guild_mutes(guild_id.0 as i64, page.get())
            .await
            .map_err(|err| {
                error!("failed to fetch guild mutes {:?}", err);
                err
            })
            .ok()
            .unwrap_or_else(Vec::new)
    }

    pub async fn fetch_guild_mute_count(&self, guild_id: GuildId) -> i64 {
        self.repository
            .fetch_guild_mute_count(guild_id.0 as i64)
            .await
            .map_err(|err| {
                error!("failed to fetch guild mute count {:?}", err);
                err
            })
            .ok()
            .unwrap_or(0)
    }

    pub async fn fetch_expired_mutes(&self) -> Vec<Mute> {
        self.repository
            .fetch_expired_mutes()
            .await
            .map_err(|err| {
                error!("failed to fetch expired mutes {:?}", err);
                err
            })
            .ok()
            .unwrap_or_else(Vec::new)
    }

    pub async fn fetch_valid_mutes(&self, guild_id: GuildId, user_id: UserId) -> Vec<Mute> {
        self.repository
            .fetch_valid_mutes(guild_id.0 as i64, user_id.0 as i64)
            .await
            .map_err(|err| {
                error!("failed to fetch valid mutes {:?}", err);
                err
            })
            .ok()
            .unwrap_or_else(Vec::new)
    }

    pub async fn fetch_actionable_mute_count(&self, guild_id: GuildId, user_id: UserId) -> i64 {
        self.repository
            .fetch_actionable_mute_count(guild_id.0 as i64, user_id.0 as i64)
            .await
            .map_err(|err| {
                error!("failed to fetch actionable mute count {:?}", err);
                err
            })
            .ok()
            .unwrap_or(0)
    }

    pub async fn insert_mute(&self, mute: Mute) -> Option<Mute> {
        self.repository
            .insert_mute(mute)
            .await
            .map_err(|err| {
                error!("failed to insert mute {:?}", err);
                err
            })
            .ok()
    }

    pub async fn update_mute(&self, mute: Mute) -> Option<()> {
        self.repository
            .update_mute(mute)
            .await
            .map_err(|err| {
                error!("failed to update mute {:?}", err);
                err
            })
            .ok()
    }

    pub async fn invalidate_previous_user_mutes(&self, guild_id: GuildId, user_id: UserId) {
        let _ = self
            .repository
            .invalidate_previous_user_mutes(guild_id.0 as i64, user_id.0 as i64)
            .await
            .map_err(|err| {
                error!("failed to invalidate previous user mutes {:?}", err);
                err
            });
    }

    pub async fn invalidate_mute(&self, id: i32) {
        let _ = self.repository.invalidate_mute(id).await.map_err(|err| {
            error!("failed to invalidate mute {:?}", err);
            err
        });
    }
}
<|endoftext|>


<commit_before>#[derive(Debug)]
pub enum FileHeaderSpec {
    Null,
    Bool,
    Integer,
    UInteger,
    Float,
    Text,
    Key { name: String, value_type: Box<FileHeaderSpec> },
    Array { allowed_types: Box<Vec<FileHeaderSpec>> },
}

#[derive(Debug)]
pub enum FileHeaderData {
    Null,
    Bool(bool),
    Integer(i64),
    UInteger(u64),
    Float(f64),
    Text(String),
    Key { name: String, value: Box<FileHeaderData> },
    Array { values: Box<Vec<FileHeaderData>> },
}

pub trait FileData : Sized {
    fn get_fulltext(&self) -> String;
    fn get_abbrev(&self) -> String;
}

<commit_msg>Add type MatchError<commit_after>use std::error::Error;
use std::fmt::{Debug, Display, Formatter};
use std::fmt;

#[derive(Debug)]
pub enum FileHeaderSpec {
    Null,
    Bool,
    Integer,
    UInteger,
    Float,
    Text,
    Key { name: String, value_type: Box<FileHeaderSpec> },
    Array { allowed_types: Box<Vec<FileHeaderSpec>> },
}

#[derive(Debug)]
pub enum FileHeaderData {
    Null,
    Bool(bool),
    Integer(i64),
    UInteger(u64),
    Float(f64),
    Text(String),
    Key { name: String, value: Box<FileHeaderData> },
    Array { values: Box<Vec<FileHeaderData>> },
}

pub trait FileData : Sized {
    fn get_fulltext(&self) -> String;
    fn get_abbrev(&self) -> String;
}

pub struct MatchError {
    summary: String,
    path: Vec<FileHeaderSpec>,
    expected: FileHeaderSpec,
    found: FileHeaderSpec
}

impl MatchError {
    pub fn format(&self) -> String {
        format!("MatchError: {:?}\n\nHaving: {:?}\nExpected: {:?}\nFound: {:?}\n",
               self.summary, self.path, self.expected, self.found)
    }
}

impl Error for MatchError {

    fn description(&self) -> &str {
        &self.summary[..]
    }

    fn cause(&self) -> Option<&Error> {
        None
    }

}

impl Debug for MatchError {

    fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
        write!(fmt, "{}", self.format());
        Ok(())
    }

}

impl Display for MatchError {

    fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
        write!(fmt, "{}", self.format());
        Ok(())
    }

}

<|endoftext|>


<commit_before>use std::ffi;
use std::fmt;
use std::str;

use Predicate;

/// Predicate adaper that trims the variable being tested.
///
/// This is created by `pred.trim()`.
#[derive(Copy, Clone, Debug)]
pub struct TrimPedicate

where
    P: Predicate<str>,
{
    p: P,
}

impl
 Predicate<str> for TrimPedicate

where
    P: Predicate<str>,
{
    fn eval(&self, variable: &str) -> bool {
        self.p.eval(variable.trim())
    }
}

impl
 fmt::Display for TrimPedicate

where
    P: Predicate<str>,
{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.p)
    }
}

/// Predicate adaper that converts a `str` predicate to byte predicate.
///
/// This is created by `pred.from_utf8()`.
#[derive(Copy, Clone, Debug)]
pub struct Utf8Pedicate

where
    P: Predicate<str>,
{
    p: P,
}

impl
 Predicate<ffi::OsStr> for Utf8Pedicate

where
    P: Predicate<str>,
{
    fn eval(&self, variable: &ffi::OsStr) -> bool {
        variable.to_str().map(|s| self.p.eval(s)).unwrap_or(false)
    }
}

impl
 Predicate<[u8]> for Utf8Pedicate

where
    P: Predicate<str>,
{
    fn eval(&self, variable: &[u8]) -> bool {
        str::from_utf8(variable)
            .map(|s| self.p.eval(s))
            .unwrap_or(false)
    }
}

impl
 fmt::Display for Utf8Pedicate

where
    P: Predicate<str>,
{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.p)
    }
}

/// `Predicate` extension adapting a `str` Predicate.
pub trait PredicateStrExt
where
    Self: Predicate<str>,
    Self: Sized,
{
    /// Returns a `TrimPedicate` that ensures the data passed to `Self` is trimmed.
    ///
    /// # Examples
    ///
    /// ```
    /// use predicates::prelude::*;
    ///
    /// let predicate_fn = predicate::str::is_empty().trim();
    /// assert_eq!(true, predicate_fn.eval("    "));
    /// assert_eq!(false, predicate_fn.eval("    Hello    "));
    /// ```
    fn trim(self) -> TrimPedicate<Self> {
        TrimPedicate { p: self }
    }

    /// Returns a `Utf8Pedicate` that adapts `Self` to a `[u8]` `Predicate`.
    ///
    /// # Examples
    ///
    /// ```
    /// use predicates::prelude::*;
    /// use std::ffi::OsStr;
    ///
    /// let predicate_fn = predicate::str::is_empty().not().from_utf8();
    /// assert_eq!(true, predicate_fn.eval(OsStr::new("Hello")));
    /// assert_eq!(false, predicate_fn.eval(OsStr::new("")));
    /// let variable: &[u8] = b"";
    /// assert_eq!(false, predicate_fn.eval(variable));
    /// ```
    fn from_utf8(self) -> Utf8Pedicate<Self> {
        Utf8Pedicate { p: self }
    }
}

impl
 PredicateStrExt for P
where
    P: Predicate<str>,
{
}
<commit_msg>Rename pedicate to predicate<commit_after>use std::ffi;
use std::fmt;
use std::str;

use Predicate;

/// Predicate adaper that trims the variable being tested.
///
/// This is created by `pred.trim()`.
#[derive(Copy, Clone, Debug)]
pub struct TrimPredicate

where
    P: Predicate<str>,
{
    p: P,
}

impl
 Predicate<str> for TrimPredicate

where
    P: Predicate<str>,
{
    fn eval(&self, variable: &str) -> bool {
        self.p.eval(variable.trim())
    }
}

impl
 fmt::Display for TrimPredicate

where
    P: Predicate<str>,
{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.p)
    }
}

/// Predicate adaper that converts a `str` predicate to byte predicate.
///
/// This is created by `pred.from_utf8()`.
#[derive(Copy, Clone, Debug)]
pub struct Utf8Predicate

where
    P: Predicate<str>,
{
    p: P,
}

impl
 Predicate<ffi::OsStr> for Utf8Predicate

where
    P: Predicate<str>,
{
    fn eval(&self, variable: &ffi::OsStr) -> bool {
        variable.to_str().map(|s| self.p.eval(s)).unwrap_or(false)
    }
}

impl
 Predicate<[u8]> for Utf8Predicate

where
    P: Predicate<str>,
{
    fn eval(&self, variable: &[u8]) -> bool {
        str::from_utf8(variable)
            .map(|s| self.p.eval(s))
            .unwrap_or(false)
    }
}

impl
 fmt::Display for Utf8Predicate

where
    P: Predicate<str>,
{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.p)
    }
}

/// `Predicate` extension adapting a `str` Predicate.
pub trait PredicateStrExt
where
    Self: Predicate<str>,
    Self: Sized,
{
    /// Returns a `TrimPredicate` that ensures the data passed to `Self` is trimmed.
    ///
    /// # Examples
    ///
    /// ```
    /// use predicates::prelude::*;
    ///
    /// let predicate_fn = predicate::str::is_empty().trim();
    /// assert_eq!(true, predicate_fn.eval("    "));
    /// assert_eq!(false, predicate_fn.eval("    Hello    "));
    /// ```
    fn trim(self) -> TrimPredicate<Self> {
        TrimPredicate { p: self }
    }

    /// Returns a `Utf8Predicate` that adapts `Self` to a `[u8]` `Predicate`.
    ///
    /// # Examples
    ///
    /// ```
    /// use predicates::prelude::*;
    /// use std::ffi::OsStr;
    ///
    /// let predicate_fn = predicate::str::is_empty().not().from_utf8();
    /// assert_eq!(true, predicate_fn.eval(OsStr::new("Hello")));
    /// assert_eq!(false, predicate_fn.eval(OsStr::new("")));
    /// let variable: &[u8] = b"";
    /// assert_eq!(false, predicate_fn.eval(variable));
    /// ```
    fn from_utf8(self) -> Utf8Predicate<Self> {
        Utf8Predicate { p: self }
    }
}

impl
 PredicateStrExt for P
where
    P: Predicate<str>,
{
}
<|endoftext|>


<commit_before><commit_msg>Method to get name of meta item<commit_after><|endoftext|>