oa_gateway/config/uci.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
//! `[uci]` section: schema documents and what to do when a payload is not one.
//!
//! The standard is not redistributed here. Conversion and validation both
//! need the files named explicitly, and both documents the catalog spans.
use std::path::PathBuf;
use oa_gateway_uci::ValidateMode;
use serde::Deserialize;
/// Where to find the UCI schema that drives JSON ↔ XML conversion.
///
/// The standard is not redistributed here, so the documents have to be
/// named explicitly. List every file the schema spans:
/// `UCI_MessageDefinitions` alone leaves the security-marking types
/// dangling, which is reported as an error rather than discovered later
/// against live traffic.
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct UciSection {
/// XSD paths, relative to the process working directory.
///
/// Empty (the default) means no conversion and no validation. That is
/// fine for routing and refused when `owp.xml_baseline` is on.
#[serde(default)]
pub(crate) schema: Vec<PathBuf>,
/// What to do about a payload that does not follow the schema: `"off"`,
/// `"warn"`, or `"reject"`.
///
/// Stored as a string so a typo is refused with `uci.validate: …`
/// rather than a generic serde error. Ignored when no schema is
/// loaded. Defaults to `"warn"`.
#[serde(default = "default_validate")]
pub(crate) validate: String,
}
// Written out rather than derived: a derived Default would leave `validate`
// empty, so a config with no [uci] section at all would be refused for naming a
// mode it never named.
impl Default for UciSection {
fn default() -> Self {
Self {
schema: Vec::new(),
validate: default_validate(),
}
}
}
impl UciSection {
/// Parses [`Self::validate`] into a [`ValidateMode`].
///
/// # Errors
///
/// Returns an error if the string is not `off`, `warn`, or `reject`.
pub(crate) fn validate_mode(&self) -> Result<ValidateMode, String> {
self.validate
.parse()
.map_err(|err| format!("uci.validate: {err}"))
}
}
fn default_validate() -> String {
ValidateMode::default().to_string()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::Config;
#[test]
fn a_validation_mode_is_read_from_the_config_or_refused_by_name() {
let config: Config = toml::from_str("[uci]\nvalidate = \"reject\"\n").unwrap();
assert_eq!(config.uci.validate_mode().unwrap(), ValidateMode::Reject);
// Loading a schema is an opt-in to the UCI layer; reporting on it is the
// default once that choice is made.
let config: Config = toml::from_str("").unwrap();
assert_eq!(config.uci.validate_mode().unwrap(), ValidateMode::Warn);
let config: Config = toml::from_str("[uci]\nvalidate = \"strict\"\n").unwrap();
let err = config.uci.validate_mode().unwrap_err();
assert!(err.contains("uci.validate"), "{err}");
assert!(err.contains("off, warn, or reject"), "{err}");
}
}