#[cfg(all(feature = "mlt", feature = "_tiles"))]
use mlt_core::encoder::EncoderConfig;
#[cfg(all(feature = "mlt", feature = "_tiles"))]
use serde::{Deserialize, Serialize};
#[cfg(all(feature = "mlt", feature = "_tiles"))]
use crate::config::primitives::AutoOption;
#[derive(Clone, Debug, Default, PartialEq)]
pub struct ProcessConfig {
#[cfg(all(feature = "mlt", feature = "_tiles"))]
pub convert_to_mlt: Option<MltProcessConfig>,
#[cfg(all(feature = "mlt", feature = "_tiles"))]
pub convert_to_mvt: Option<MvtProcessConfig>,
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
pub type MltProcessConfig = AutoOption<MltEncoderConfig>;
#[cfg(all(feature = "mlt", feature = "_tiles"))]
pub type MvtProcessConfig = AutoOption<MvtEncoderConfig>;
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "unstable-schemas", derive(schemars::JsonSchema))]
pub struct MvtEncoderConfig {}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[serde_with::skip_serializing_none]
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "unstable-schemas", derive(schemars::JsonSchema))]
pub struct MltEncoderConfig {
pub tessellate: Option<bool>,
pub try_spatial_morton_sort: Option<bool>,
pub try_spatial_hilbert_sort: Option<bool>,
pub try_id_sort: Option<bool>,
pub allow_fsst: Option<bool>,
pub allow_fpf: Option<bool>,
pub allow_shared_dict: Option<bool>,
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
impl From<MltEncoderConfig> for EncoderConfig {
fn from(src: MltEncoderConfig) -> Self {
let MltEncoderConfig {
tessellate,
try_spatial_morton_sort,
try_spatial_hilbert_sort,
try_id_sort,
allow_fsst,
allow_fpf,
allow_shared_dict,
} = src;
Self {
tessellate: tessellate.unwrap_or(Self::default().tessellate),
try_spatial_morton_sort: try_spatial_morton_sort
.unwrap_or(Self::default().try_spatial_morton_sort),
try_spatial_hilbert_sort: try_spatial_hilbert_sort
.unwrap_or(Self::default().try_spatial_hilbert_sort),
try_id_sort: try_id_sort.unwrap_or(Self::default().try_id_sort),
allow_fsst: allow_fsst.unwrap_or(Self::default().allow_fsst),
allow_fpf: allow_fpf.unwrap_or(Self::default().allow_fpf),
allow_shared_dict: allow_shared_dict.unwrap_or(Self::default().allow_shared_dict),
}
}
}
#[must_use]
pub fn resolve_process_config(
global: &ProcessConfig,
source_type: &ProcessConfig,
per_source: &ProcessConfig,
) -> ProcessConfig {
let default = ProcessConfig::default();
if *per_source != default {
per_source.clone()
} else if *source_type != default {
source_type.clone()
} else {
global.clone()
}
}
#[cfg(test)]
mod tests {
#[cfg(all(feature = "mlt", feature = "_tiles"))]
use indoc::indoc;
use super::*;
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn parse_mlt_auto_string() {
let cfg: MltProcessConfig = serde_yaml::from_str("auto").unwrap();
assert_eq!(cfg, MltProcessConfig::Auto);
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn parse_mlt_explicit_empty() {
let cfg: MltProcessConfig = serde_yaml::from_str("{}").unwrap();
assert_eq!(cfg, MltProcessConfig::Explicit(MltEncoderConfig::default()));
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn parse_mlt_explicit_with_overrides() {
let cfg: MltProcessConfig = serde_yaml::from_str(indoc! {"
tessellate: true
allow_fsst: false
"})
.unwrap();
assert_eq!(
cfg,
MltProcessConfig::Explicit(MltEncoderConfig {
tessellate: Some(true),
allow_fsst: Some(false),
..Default::default()
})
);
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn serde_round_trip_auto() {
let cfg = MltProcessConfig::Auto;
let yaml = serde_yaml::to_string(&cfg).unwrap();
insta::assert_snapshot!(yaml, @"auto");
let parsed: MltProcessConfig = serde_yaml::from_str(&yaml).unwrap();
assert_eq!(cfg, parsed);
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn serde_round_trip_disabled() {
let cfg = MltProcessConfig::Disabled;
let yaml = serde_yaml::to_string(&cfg).unwrap();
insta::assert_snapshot!(yaml, @"disabled");
let parsed: MltProcessConfig = serde_yaml::from_str(&yaml).unwrap();
assert_eq!(cfg, parsed);
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn serde_round_trip_explicit() {
let cfg = MltProcessConfig::Explicit(MltEncoderConfig {
tessellate: Some(true),
..Default::default()
});
let yaml = serde_yaml::to_string(&cfg).unwrap();
let parsed: MltProcessConfig = serde_yaml::from_str(&yaml).unwrap();
assert_eq!(cfg, parsed);
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn parse_mlt_invalid_string() {
let result = serde_yaml::from_str::<MltProcessConfig>("invalid");
assert!(result.is_err());
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn parse_mlt_invalid_type() {
let result = serde_yaml::from_str::<MltProcessConfig>("123");
assert!(result.is_err());
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn render_failure_mlt_unknown_string() {
use crate::config::test_helpers::render_failure;
insta::assert_snapshot!(render_failure(indoc! {"
convert_to_mlt: atuo
"}), @r#"
× invalid value: string "atuo", expected a string ("auto", "enabled",
│ "disabled"), a boolean, or a map of settings
╭─[config.yaml:1:1]
1 │ convert_to_mlt: atuo
· ───────┬──────
· ╰── invalid value: string "atuo", expected a string ("auto", "enabled", "disabled"), a boolean, or a map of settings
╰────
"#);
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn render_failure_mlt_integer() {
use crate::config::test_helpers::render_failure;
insta::assert_snapshot!(render_failure(indoc! {"
convert_to_mlt: 42
"}), @r#"
× invalid type: integer `42`, expected a string ("auto", "enabled",
│ "disabled"), a boolean, or a map of settings
╭─[config.yaml:1:1]
1 │ convert_to_mlt: 42
· ───────┬──────
· ╰── invalid type: integer `42`, expected a string ("auto", "enabled", "disabled"), a boolean, or a map of settings
╰────
"#);
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn render_failure_mlt_nested_field_bad_type() {
use crate::config::test_helpers::render_failure;
insta::assert_snapshot!(render_failure(indoc! {"
convert_to_mlt:
tessellate: yes-please
"}), @r"
× invalid boolean
╭─[config.yaml:2:15]
1 │ convert_to_mlt:
2 │ tessellate: yes-please
· ─────┬────
· ╰── invalid boolean
╰────
");
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn resolve_per_source_disabled_overrides_global_auto() {
let global = ProcessConfig {
convert_to_mlt: Some(MltProcessConfig::Auto),
convert_to_mvt: None,
};
let per_source = ProcessConfig {
convert_to_mlt: Some(MltProcessConfig::Disabled),
convert_to_mvt: None,
};
let resolved = resolve_process_config(&global, &ProcessConfig::default(), &per_source);
assert_eq!(resolved.convert_to_mlt, Some(MltProcessConfig::Disabled));
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn resolve_per_source_overrides_all() {
let global = ProcessConfig {
convert_to_mlt: Some(MltProcessConfig::Auto),
convert_to_mvt: None,
};
let source_type = ProcessConfig {
convert_to_mlt: None,
convert_to_mvt: Some(MvtProcessConfig::Auto),
};
let per_source = ProcessConfig {
convert_to_mlt: Some(MltProcessConfig::Explicit(MltEncoderConfig {
tessellate: Some(true),
..Default::default()
})),
convert_to_mvt: None,
};
let resolved = resolve_process_config(&global, &source_type, &per_source);
assert_eq!(resolved, per_source);
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn resolve_source_type_overrides_global() {
let global = ProcessConfig {
convert_to_mlt: Some(MltProcessConfig::Auto),
convert_to_mvt: None,
};
let source_type = ProcessConfig {
convert_to_mlt: None,
convert_to_mvt: Some(MvtProcessConfig::Auto),
};
let resolved = resolve_process_config(&global, &source_type, &ProcessConfig::default());
assert_eq!(resolved, source_type);
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn resolve_global_used_as_fallback() {
let global = ProcessConfig {
convert_to_mlt: Some(MltProcessConfig::Auto),
convert_to_mvt: None,
};
let resolved = resolve_process_config(
&global,
&ProcessConfig::default(),
&ProcessConfig::default(),
);
assert_eq!(resolved, global);
}
#[test]
fn resolve_default_when_all_none() {
let resolved = resolve_process_config(
&ProcessConfig::default(),
&ProcessConfig::default(),
&ProcessConfig::default(),
);
assert_eq!(resolved, ProcessConfig::default());
}
#[cfg(all(feature = "mlt", feature = "unstable-schemas"))]
#[test]
fn json_schema_matches_serde_format() {
let schema = serde_json::to_value(schemars::schema_for!(MltProcessConfig)).unwrap();
let one_of = schema
.get("oneOf")
.and_then(|v| v.as_array())
.expect("MltProcessConfig schema should be a `oneOf`");
assert_eq!(one_of.len(), 4, "schema: {schema}");
let mut saw_encoder_ref = false;
for entry in one_of {
if let Some(reference) = entry.get("$ref").and_then(|v| v.as_str())
&& reference.ends_with("/MltEncoderConfig")
{
saw_encoder_ref = true;
}
}
assert!(
saw_encoder_ref,
"expected $ref to MltEncoderConfig: {schema}"
);
}
}