#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum SpecVersion {
V1_4,
V2_0,
V2_1,
#[default]
V2_2,
V2_3,
V2_4,
}
impl SpecVersion {
pub const ALL: [SpecVersion; 6] = [
SpecVersion::V1_4,
SpecVersion::V2_0,
SpecVersion::V2_1,
SpecVersion::V2_2,
SpecVersion::V2_3,
SpecVersion::V2_4,
];
pub fn version_str(self) -> &'static str {
match self {
SpecVersion::V1_4 => "1.4",
SpecVersion::V2_0 => "2.0",
SpecVersion::V2_1 => "2.1",
SpecVersion::V2_2 => "2.2",
SpecVersion::V2_3 => "2.3",
SpecVersion::V2_4 => "2.4",
}
}
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &str) -> Option<Self> {
match s.trim() {
"1.0" | "1.1" | "1.2" | "1.3" | "1.4" => Some(SpecVersion::V1_4),
"2.0" => Some(SpecVersion::V2_0),
"2.1" => Some(SpecVersion::V2_1),
"2.2" => Some(SpecVersion::V2_2),
"2.3" => Some(SpecVersion::V2_3),
"2.4" => Some(SpecVersion::V2_4),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum OperatingMode {
#[default]
Conformance,
Regular,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum ComplianceLevel {
#[default]
CoreSpec,
ConformanceProgram,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct EngineProfile {
pub version: SpecVersion,
pub mode: OperatingMode,
pub compliance: ComplianceLevel,
pub debug: bool,
}
impl Default for EngineProfile {
fn default() -> Self {
EngineProfile::CONFORMANCE_V2_2
}
}
impl EngineProfile {
pub const CONFORMANCE_V2_2: EngineProfile = EngineProfile {
version: SpecVersion::V2_2,
mode: OperatingMode::Conformance,
compliance: ComplianceLevel::ConformanceProgram,
debug: false,
};
pub const GENEROUS: EngineProfile = EngineProfile {
version: SpecVersion::V2_4,
mode: OperatingMode::Regular,
compliance: ComplianceLevel::CoreSpec,
debug: false,
};
pub fn strict(version: SpecVersion) -> Self {
Self {
version,
mode: OperatingMode::Conformance,
compliance: ComplianceLevel::ConformanceProgram,
debug: true,
}
}
pub fn new(version: SpecVersion, mode: OperatingMode) -> Self {
Self {
version,
mode,
compliance: ComplianceLevel::CoreSpec,
debug: false,
}
}
pub fn with_compliance(mut self, compliance: ComplianceLevel) -> Self {
self.compliance = compliance;
self
}
pub fn with_debug(mut self, debug: bool) -> Self {
self.debug = debug;
self
}
pub fn version_str(self) -> &'static str {
self.version.version_str()
}
pub fn permits_mime(self, mime: &str) -> bool {
let canon = canonicalize_mime(mime);
match self.mode {
OperatingMode::Conformance if version_has_scope(self.version) => {
mime_in_version(&canon, self.version)
}
_ => mime_known(&canon),
}
}
}
pub fn canonicalize_mime(mime: &str) -> String {
let base = mime.split(';').next().unwrap_or(mime).trim();
match base.to_ascii_lowercase().as_str() {
"audio/mpa" => "audio/mpeg".to_string(),
"audio/aac" => "audio/mp4".to_string(),
"audio/wave" | "audio/vnd.wave" | "audio/x-wav" => "audio/wav".to_string(),
"audio/x-flac" => "audio/flac".to_string(),
"application/ogg" => "audio/ogg".to_string(),
"video/avi" | "video/msvideo" | "application/x-troff-msvideo" => {
"video/x-msvideo".to_string()
}
"image/dng" => "image/x-adobe-dng".to_string(),
"application/svg+xml" => "image/svg+xml".to_string(),
"text/javascript" => "application/javascript".to_string(),
"text/yaml" | "application/x-yaml" => "application/yaml".to_string(),
other => other.to_string(),
}
}
pub struct FormatEntry {
pub mime: &'static str,
pub versions: &'static [SpecVersion],
}
use SpecVersion::{V2_2, V2_4};
pub static FORMAT_REGISTRY: &[FormatEntry] = &[
FormatEntry {
mime: "application/pdf",
versions: &[V2_2, V2_4],
},
FormatEntry {
mime: "audio/mp4",
versions: &[V2_2, V2_4],
},
FormatEntry {
mime: "audio/mpeg",
versions: &[V2_2, V2_4],
},
FormatEntry {
mime: "audio/wav",
versions: &[V2_2, V2_4],
},
FormatEntry {
mime: "image/avif",
versions: &[V2_2, V2_4],
},
FormatEntry {
mime: "image/gif",
versions: &[V2_2, V2_4],
},
FormatEntry {
mime: "image/heic",
versions: &[V2_2, V2_4],
},
FormatEntry {
mime: "image/heif",
versions: &[V2_2, V2_4],
},
FormatEntry {
mime: "image/heic-sequence",
versions: &[V2_4],
},
FormatEntry {
mime: "image/heif-sequence",
versions: &[V2_4],
},
FormatEntry {
mime: "image/jpeg",
versions: &[V2_2, V2_4],
},
FormatEntry {
mime: "image/jxl",
versions: &[V2_2, V2_4],
},
FormatEntry {
mime: "image/png",
versions: &[V2_2, V2_4],
},
FormatEntry {
mime: "image/svg+xml",
versions: &[V2_2, V2_4],
},
FormatEntry {
mime: "image/tiff",
versions: &[V2_2, V2_4],
},
FormatEntry {
mime: "image/webp",
versions: &[V2_2, V2_4],
},
FormatEntry {
mime: "image/x-adobe-dng",
versions: &[V2_2, V2_4],
},
FormatEntry {
mime: "video/mp4",
versions: &[V2_2, V2_4],
},
FormatEntry {
mime: "video/quicktime",
versions: &[V2_2, V2_4],
},
FormatEntry {
mime: "video/x-msvideo",
versions: &[V2_2, V2_4],
},
FormatEntry {
mime: "audio/flac",
versions: &[V2_4],
},
FormatEntry {
mime: "audio/ogg",
versions: &[V2_4],
},
FormatEntry {
mime: "video/x-m4v",
versions: &[V2_4],
},
FormatEntry {
mime: "application/mp4",
versions: &[V2_4],
},
FormatEntry {
mime: "text/plain",
versions: &[V2_4],
}, FormatEntry {
mime: "text/csv",
versions: &[V2_4],
}, FormatEntry {
mime: "text/tab-separated-values",
versions: &[V2_4],
}, FormatEntry {
mime: "text/html",
versions: &[V2_4],
}, FormatEntry {
mime: "text/markdown",
versions: &[V2_4],
}, FormatEntry {
mime: "text/xml",
versions: &[V2_4],
}, FormatEntry {
mime: "application/xml",
versions: &[V2_4],
}, FormatEntry {
mime: "application/xhtml+xml",
versions: &[V2_4],
}, FormatEntry {
mime: "application/json",
versions: &[V2_4],
}, FormatEntry {
mime: "application/yaml",
versions: &[V2_4],
}, FormatEntry {
mime: "application/toml",
versions: &[V2_4],
}, FormatEntry {
mime: "text/css",
versions: &[V2_4],
}, FormatEntry {
mime: "application/javascript",
versions: &[V2_4],
}, FormatEntry {
mime: "text/x-python",
versions: &[V2_4],
}, FormatEntry {
mime: "application/epub+zip",
versions: &[V2_4],
},
FormatEntry {
mime: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
versions: &[V2_4],
},
FormatEntry {
mime: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
versions: &[V2_4],
},
FormatEntry {
mime: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
versions: &[V2_4],
},
FormatEntry {
mime: "application/vnd.openxmlformats-officedocument.wordprocessingml.template",
versions: &[V2_4],
},
FormatEntry {
mime: "application/vnd.ms-word.document.macroenabled.12",
versions: &[V2_4],
},
FormatEntry {
mime: "application/vnd.ms-word.template.macroenabled.12",
versions: &[V2_4],
},
FormatEntry {
mime: "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
versions: &[V2_4],
},
FormatEntry {
mime: "application/vnd.ms-excel.sheet.macroenabled.12",
versions: &[V2_4],
},
FormatEntry {
mime: "application/vnd.ms-excel.template.macroenabled.12",
versions: &[V2_4],
},
FormatEntry {
mime: "application/vnd.ms-excel.sheet.binary.macroenabled.12",
versions: &[V2_4],
},
FormatEntry {
mime: "application/vnd.openxmlformats-officedocument.presentationml.template",
versions: &[V2_4],
},
FormatEntry {
mime: "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
versions: &[V2_4],
},
FormatEntry {
mime: "application/vnd.ms-powerpoint.presentation.macroenabled.12",
versions: &[V2_4],
},
FormatEntry {
mime: "application/vnd.ms-powerpoint.template.macroenabled.12",
versions: &[V2_4],
},
FormatEntry {
mime: "application/vnd.ms-powerpoint.slideshow.macroenabled.12",
versions: &[V2_4],
},
FormatEntry {
mime: "application/vnd.ms-visio.drawing",
versions: &[V2_4],
},
FormatEntry {
mime: "application/vnd.ms-visio.drawing.macroenabled.12",
versions: &[V2_4],
},
FormatEntry {
mime: "application/vnd.ms-visio.stencil",
versions: &[V2_4],
},
FormatEntry {
mime: "application/vnd.ms-visio.stencil.macroenabled.12",
versions: &[V2_4],
},
FormatEntry {
mime: "application/vnd.ms-visio.template",
versions: &[V2_4],
},
FormatEntry {
mime: "application/vnd.ms-visio.template.macroenabled.12",
versions: &[V2_4],
},
FormatEntry {
mime: "application/vnd.oasis.opendocument.text",
versions: &[V2_4],
},
FormatEntry {
mime: "application/vnd.oasis.opendocument.spreadsheet",
versions: &[V2_4],
},
FormatEntry {
mime: "application/vnd.oasis.opendocument.presentation",
versions: &[V2_4],
},
FormatEntry {
mime: "application/vnd.oasis.opendocument.graphics",
versions: &[V2_4],
},
FormatEntry {
mime: "application/oxps",
versions: &[V2_4],
},
FormatEntry {
mime: "application/vnd.ms-xpsdocument",
versions: &[V2_4],
},
FormatEntry {
mime: "font/otf",
versions: &[V2_4],
},
FormatEntry {
mime: "font/ttf",
versions: &[V2_4],
},
FormatEntry {
mime: "font/sfnt",
versions: &[V2_4],
},
FormatEntry {
mime: "application/font-sfnt",
versions: &[V2_4],
},
FormatEntry {
mime: "application/x-font-ttf",
versions: &[V2_4],
},
];
fn mime_in_version(canon_mime: &str, version: SpecVersion) -> bool {
FORMAT_REGISTRY
.iter()
.any(|e| e.mime.eq_ignore_ascii_case(canon_mime) && e.versions.contains(&version))
}
fn mime_known(canon_mime: &str) -> bool {
FORMAT_REGISTRY
.iter()
.any(|e| e.mime.eq_ignore_ascii_case(canon_mime))
}
fn version_has_scope(version: SpecVersion) -> bool {
FORMAT_REGISTRY
.iter()
.any(|e| e.versions.contains(&version))
}
pub fn mimes_for_version(version: SpecVersion) -> Vec<&'static str> {
FORMAT_REGISTRY
.iter()
.filter(|e| e.versions.contains(&version))
.map(|e| e.mime)
.collect()
}
pub fn v2_2_certified_mimes() -> Vec<&'static str> {
let mut v = mimes_for_version(SpecVersion::V2_2);
v.push("audio/MPA");
v.push("audio/aac");
v
}
#[cfg(test)]
mod tests {
use super::*;
const CONF_2_2: EngineProfile = EngineProfile::CONFORMANCE_V2_2;
fn regular_2_2() -> EngineProfile {
EngineProfile::new(SpecVersion::V2_2, OperatingMode::Regular)
}
fn conf_2_4() -> EngineProfile {
EngineProfile::new(SpecVersion::V2_4, OperatingMode::Conformance)
}
#[test]
fn default_is_certified_v2_2() {
assert_eq!(EngineProfile::default(), CONF_2_2);
assert_eq!(CONF_2_2.version_str(), "2.2");
assert_eq!(CONF_2_2.compliance, ComplianceLevel::ConformanceProgram);
}
#[test]
fn v2_2_certified_set_is_exactly_twenty() {
let certified = v2_2_certified_mimes();
assert_eq!(
certified.len(),
20,
"certified v2.2 set is 20 accepted forms"
);
}
#[test]
fn conformance_v2_2_permits_certified_refuses_v2_4_only() {
for mime in v2_2_certified_mimes() {
assert!(
CONF_2_2.permits_mime(mime),
"conformance 2.2 must permit {mime}"
);
}
for mime in [
"text/plain",
"text/html",
"audio/flac",
"font/otf",
"application/epub+zip",
"video/x-m4v",
] {
assert!(
!CONF_2_2.permits_mime(mime),
"conformance 2.2 must REFUSE {mime}"
);
}
}
#[test]
fn conformance_v2_4_permits_text_and_media() {
for mime in [
"text/plain",
"text/html",
"text/markdown",
"image/jpeg",
"audio/flac",
] {
assert!(
conf_2_4().permits_mime(mime),
"conformance 2.4 must permit {mime}"
);
}
}
#[test]
fn regular_mode_permits_any_known_format() {
for mime in [
"text/plain",
"image/jpeg",
"font/otf",
"audio/flac",
"application/json",
] {
assert!(
regular_2_2().permits_mime(mime),
"regular must permit known {mime}"
);
}
for mime in [
"model/gguf",
"application/x-onnx",
"application/octet-stream",
] {
assert!(
!regular_2_2().permits_mime(mime),
"regular must refuse unknown {mime}"
);
assert!(!conf_2_4().permits_mime(mime));
}
}
#[test]
fn aliases_canonicalize() {
assert_eq!(canonicalize_mime("audio/MPA"), "audio/mpeg");
assert_eq!(canonicalize_mime("audio/aac"), "audio/mp4");
assert_eq!(
canonicalize_mime("image/svg+xml; charset=utf-8"),
"image/svg+xml"
);
assert!(CONF_2_2.permits_mime("audio/MPA"));
assert!(CONF_2_2.permits_mime("AUDIO/AAC"));
}
#[test]
fn container_aliases_canonicalize_to_certified_forms() {
for (alias, canon) in [
("video/avi", "video/x-msvideo"),
("video/msvideo", "video/x-msvideo"),
("application/x-troff-msvideo", "video/x-msvideo"),
("audio/wave", "audio/wav"),
("audio/vnd.wave", "audio/wav"),
("audio/x-wav", "audio/wav"),
("audio/x-flac", "audio/flac"),
("image/dng", "image/x-adobe-dng"),
("application/svg+xml", "image/svg+xml"),
] {
assert_eq!(canonicalize_mime(alias), canon, "{alias}");
}
assert!(CONF_2_2.permits_mime("video/avi"));
assert!(CONF_2_2.permits_mime("audio/x-wav"));
assert!(CONF_2_2.permits_mime("image/dng"));
assert!(CONF_2_2.permits_mime("application/svg+xml"));
assert!(!CONF_2_2.permits_mime("audio/x-flac"));
assert!(conf_2_4().permits_mime("audio/x-flac"));
}
#[test]
fn mimes_for_version_filters_correctly() {
let v22 = mimes_for_version(SpecVersion::V2_2);
let v24 = mimes_for_version(SpecVersion::V2_4);
assert!(v22.contains(&"image/jpeg") && !v22.contains(&"text/plain"));
assert!(v24.contains(&"text/plain") && v24.contains(&"image/jpeg"));
assert!(v24.len() > v22.len(), "v2.4 is a superset");
}
#[test]
fn spec_version_round_trips() {
assert_eq!(SpecVersion::from_str("2.2"), Some(SpecVersion::V2_2));
assert_eq!(SpecVersion::from_str("2.4"), Some(SpecVersion::V2_4));
assert_eq!(SpecVersion::from_str("9.9"), None);
}
#[test]
fn compliance_level_builder() {
let p = EngineProfile::new(SpecVersion::V2_4, OperatingMode::Regular)
.with_compliance(ComplianceLevel::ConformanceProgram);
assert_eq!(p.compliance, ComplianceLevel::ConformanceProgram);
assert_eq!(
EngineProfile::new(SpecVersion::V2_2, OperatingMode::Conformance).compliance,
ComplianceLevel::CoreSpec
);
}
#[test]
fn strict_mode_is_version_independent() {
for v in [SpecVersion::V2_2, SpecVersion::V2_4] {
let p = EngineProfile::strict(v);
assert_eq!(p.version, v);
assert!(p.debug, "strict implies diagnostics on");
assert_eq!(p.mode, OperatingMode::Conformance);
assert_eq!(p.compliance, ComplianceLevel::ConformanceProgram);
}
assert!(
EngineProfile::new(SpecVersion::V2_4, OperatingMode::Regular)
.with_debug(true)
.debug
);
}
}