use std::collections::{BTreeMap, BTreeSet};
use martin_core::tiles::BoxedSource;
use crate::MartinResult;
#[derive(Debug)]
pub struct NewSource {
pub id: String,
pub source: MartinResult<BoxedSource>,
}
#[derive(Debug, Ord, PartialOrd, PartialEq, Eq)]
pub struct DeletedSource {
pub id: String,
}
#[derive(Default, Debug)]
pub struct ReloadAdvisory {
pub additions: Vec<NewSource>,
pub updates: Vec<NewSource>,
pub removals: BTreeSet<DeletedSource>,
}
impl ReloadAdvisory {
pub async fn from_sets<F>(
previous_ids: &BTreeSet<String>,
next_ids: &BTreeSet<String>,
initializer: F,
) -> Self
where
F: AsyncFn(String) -> MartinResult<BoxedSource>,
{
let removals = previous_ids
.difference(next_ids)
.map(|id| DeletedSource { id: id.clone() })
.collect();
let mut additions: Vec<NewSource> = vec![];
for id in next_ids.difference(previous_ids) {
additions.push(NewSource {
id: id.clone(),
source: initializer(id.clone()).await,
});
}
Self {
additions,
updates: Vec::new(),
removals,
}
}
pub async fn from_maps<F, V: Eq + Copy>(
previous_map: &BTreeMap<String, V>,
next_map: &BTreeMap<String, V>,
initializer: F,
) -> Self
where
F: AsyncFn(String) -> MartinResult<BoxedSource>,
{
let mut advisory = Self::default();
for id in previous_map.keys() {
if !next_map.contains_key(id) {
advisory.removals.insert(DeletedSource { id: id.clone() });
}
}
for (id, &next_version) in next_map {
match previous_map.get(id) {
Some(&prev_version) if next_version != prev_version => {
advisory.updates.push(NewSource {
id: id.clone(),
source: initializer(id.clone()).await,
});
}
None => {
advisory.additions.push(NewSource {
id: id.clone(),
source: initializer(id.clone()).await,
});
}
_ => {} }
}
advisory
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.additions.is_empty() && self.updates.is_empty() && self.removals.is_empty()
}
}
#[cfg(test)]
mod tests {
use async_trait::async_trait;
use insta::assert_yaml_snapshot;
use martin_core::CacheZoomRange;
use martin_core::tiles::{BoxedSource, MartinCoreResult, Source, UrlQuery};
use martin_tile_utils::{Encoding, Format, TileCoord, TileData, TileInfo};
use tilejson::{TileJSON, tilejson};
use super::*;
#[derive(Debug, Clone)]
struct TestSource {
id: String,
tj: TileJSON,
}
#[async_trait]
impl Source for TestSource {
fn get_id(&self) -> &str {
&self.id
}
fn get_tilejson(&self) -> &TileJSON {
&self.tj
}
fn get_tile_info(&self) -> TileInfo {
TileInfo::new(Format::Mvt, Encoding::Uncompressed)
}
fn clone_source(&self) -> BoxedSource {
Box::new(self.clone())
}
fn cache_zoom(&self) -> CacheZoomRange {
CacheZoomRange::default()
}
async fn get_tile(
&self,
_xyz: TileCoord,
_url_query: Option<&UrlQuery>,
) -> MartinCoreResult<TileData> {
Ok(vec![1, 2, 3])
}
}
async fn make_source(id: String) -> MartinResult<BoxedSource> {
Ok(Box::new(TestSource {
id,
tj: tilejson! {
tilejson: "3.0.0".to_string(),
tiles: vec!["https://maplibre.org".to_string()],
attribution: String::new(),
name: "test_json".to_string(),
scheme: "xyz".to_string(),
},
}))
}
#[derive(serde::Serialize)]
struct AdvisorySnapshot {
additions: Vec<String>,
updates: Vec<String>,
removals: Vec<String>,
}
impl From<&ReloadAdvisory> for AdvisorySnapshot {
fn from(advisory: &ReloadAdvisory) -> Self {
Self {
additions: advisory.additions.iter().map(|s| s.id.clone()).collect(),
updates: advisory.updates.iter().map(|s| s.id.clone()).collect(),
removals: advisory.removals.iter().map(|s| s.id.clone()).collect(),
}
}
}
#[tokio::test]
async fn from_sets_empty_to_empty() {
let prev = BTreeSet::new();
let next = BTreeSet::new();
let advisory = ReloadAdvisory::from_sets(&prev, &next, make_source).await;
assert_yaml_snapshot!(AdvisorySnapshot::from(&advisory), @"
additions: []
updates: []
removals: []
");
}
#[tokio::test]
async fn from_sets_additions_only() {
let prev = BTreeSet::new();
let next: BTreeSet<String> = ["a", "b"].into_iter().map(String::from).collect();
let advisory = ReloadAdvisory::from_sets(&prev, &next, make_source).await;
assert_yaml_snapshot!(AdvisorySnapshot::from(&advisory), @"
additions:
- a
- b
updates: []
removals: []
");
}
#[tokio::test]
async fn from_sets_removals_only() {
let prev: BTreeSet<String> = ["a", "b"].into_iter().map(String::from).collect();
let next = BTreeSet::new();
let advisory = ReloadAdvisory::from_sets(&prev, &next, make_source).await;
assert_yaml_snapshot!(AdvisorySnapshot::from(&advisory), @"
additions: []
updates: []
removals:
- a
- b
");
}
#[tokio::test]
async fn from_sets_mixed() {
let prev: BTreeSet<String> = ["a", "b", "c"].into_iter().map(String::from).collect();
let next: BTreeSet<String> = ["b", "c", "d"].into_iter().map(String::from).collect();
let advisory = ReloadAdvisory::from_sets(&prev, &next, make_source).await;
assert_yaml_snapshot!(AdvisorySnapshot::from(&advisory), @"
additions:
- d
updates: []
removals:
- a
");
}
#[tokio::test]
async fn from_maps_additions_and_removals() {
let prev: BTreeMap<String, u64> = [("a".into(), 1), ("b".into(), 2)].into_iter().collect();
let next: BTreeMap<String, u64> = [("b".into(), 2), ("c".into(), 3)].into_iter().collect();
let advisory = ReloadAdvisory::from_maps(&prev, &next, make_source).await;
assert_yaml_snapshot!(AdvisorySnapshot::from(&advisory), @"
additions:
- c
updates: []
removals:
- a
");
}
#[tokio::test]
async fn from_maps_version_update() {
let prev: BTreeMap<String, u64> = [("a".into(), 1), ("b".into(), 2)].into_iter().collect();
let next: BTreeMap<String, u64> = [("a".into(), 1), ("b".into(), 5)].into_iter().collect();
let advisory = ReloadAdvisory::from_maps(&prev, &next, make_source).await;
assert_yaml_snapshot!(AdvisorySnapshot::from(&advisory), @"
additions: []
updates:
- b
removals: []
");
}
#[tokio::test]
async fn from_maps_all_changes() {
let prev: BTreeMap<String, u64> = [("a".into(), 1), ("b".into(), 2), ("c".into(), 3)]
.into_iter()
.collect();
let next: BTreeMap<String, u64> = [("b".into(), 9), ("c".into(), 3), ("d".into(), 4)]
.into_iter()
.collect();
let advisory = ReloadAdvisory::from_maps(&prev, &next, make_source).await;
assert_yaml_snapshot!(AdvisorySnapshot::from(&advisory), @"
additions:
- d
updates:
- b
removals:
- a
");
}
}