use crate::error::{Error, Result};
use crate::latent::Metadata;
pub fn reject_unknown_extra(
component: &str,
kind: &str,
extra: &Metadata,
recognized: &[&str],
) -> Result<()> {
let unknown: Vec<&str> = extra
.keys()
.map(String::as_str)
.filter(|key| !recognized.contains(key))
.collect();
if unknown.is_empty() {
return Ok(());
}
Err(Error::validation(format!(
"{component}.type {kind:?}: unrecognized setting(s) {unknown:?} \
(recognized: {recognized:?})"
)))
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn meta(pairs: &[(&str, serde_json::Value)]) -> Metadata {
pairs
.iter()
.map(|(k, v)| (k.to_string(), v.clone()))
.collect()
}
#[test]
fn empty_extra_is_accepted() {
assert!(reject_unknown_extra("audio_codec", "pcm", &meta(&[]), &["frame_size"]).is_ok());
}
#[test]
fn recognized_keys_are_accepted() {
let extra = meta(&[("frame_size", json!(4)), ("channels", json!(2))]);
assert!(
reject_unknown_extra("audio_codec", "pcm", &extra, &["frame_size", "channels"]).is_ok()
);
}
#[test]
fn unknown_key_is_rejected_and_named() {
let extra = meta(&[("frame_size", json!(4)), ("frmae_size", json!(8))]);
let err = reject_unknown_extra("audio_codec", "pcm", &extra, &["frame_size", "channels"])
.unwrap_err();
let msg = err.to_string();
assert!(matches!(err, Error::Validation(_)), "{msg}");
assert!(msg.contains("frmae_size"), "names the typo: {msg}");
assert!(msg.contains("pcm"), "names the kind: {msg}");
}
#[test]
fn no_recognized_keys_rejects_any_extra() {
let extra = meta(&[("hidden_dim", json!(64))]);
assert!(reject_unknown_extra("data_encoder", "vector", &extra, &[]).is_err());
}
#[test]
fn all_unknown_keys_are_reported_in_sorted_order() {
let extra = meta(&[("zeta", json!(1)), ("alpha", json!(2))]);
let msg = reject_unknown_extra("generator", "mlp", &extra, &[])
.unwrap_err()
.to_string();
let alpha = msg.find("alpha").expect("alpha reported");
let zeta = msg.find("zeta").expect("zeta reported");
assert!(alpha < zeta, "sorted order: {msg}");
}
}