Classify serialized and runtime schema records

This commit is contained in:
ms2k 2026-07-14 23:34:56 +09:00
commit f106a9f636
2 changed files with 697 additions and 110 deletions

View file

@ -2605,14 +2605,18 @@ pub trait SchemaType {
}
macro_rules! schema_types {
($($name:literal => $record:ty),+ $(,)?) => {
($($name:literal => $record:ty),+; $($declared:literal),* $(,)?) => {
$(
impl SchemaType for $record {
const NAME: &'static str = $name;
}
)+
pub const ALL_SCHEMA_TYPE_NAMES: &[&str] = &[$($name),+];
pub const IMPLEMENTED_SERIALIZED_SCHEMA_TYPE_NAMES: &[&str] = &[$($name),+];
pub const DECLARED_SERIALIZED_SCHEMA_TYPE_NAMES: &[&str] = &[
$($name),+,
$($declared),*
];
};
}
@ -2698,5 +2702,16 @@ schema_types!(
"PermittedDestinationObservation" => PermittedDestinationObservation,
"DnsObservation" => DnsObservation,
"DomainStore" => DomainStore,
"StorageCatalog" => StorageCatalog,
"StorageCatalog" => StorageCatalog;
"TrustedHostConfigurationV1",
"ConfigurationFingerprintRecordV1",
"EnvironmentReference",
"ResolvedVolume",
"ResolvedAutoApprovalRule",
);
pub const DECLARED_RUNTIME_SCHEMA_TYPE_NAMES: &[&str] = &[
"ResolvedDomainConfiguration",
"SandboxLayoutV1",
"ConfinedPathPlan",
];

View file

@ -1,4 +1,4 @@
use std::collections::HashSet;
use std::collections::{HashMap, HashSet};
use std::fmt::Debug;
use std::num::NonZeroU16;
@ -1055,23 +1055,163 @@ fn every_schema_shape_round_trips_and_rejects_malformed_or_unknown_versions() {
&mut seen,
);
assert_eq!(seen.len(), ALL_SCHEMA_TYPE_NAMES.len());
assert!(ALL_SCHEMA_TYPE_NAMES.iter().all(|name| seen.contains(name)));
assert_eq!(seen.len(), IMPLEMENTED_SERIALIZED_SCHEMA_TYPE_NAMES.len());
assert!(
IMPLEMENTED_SERIALIZED_SCHEMA_TYPE_NAMES
.iter()
.all(|name| seen.contains(name))
);
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum SchemaCategory {
Serialized,
Runtime,
}
fn parse_schema_categories(
schema: &str,
serialized_registry: &HashSet<&str>,
runtime_registry: &HashSet<&str>,
) -> Result<HashMap<String, SchemaCategory>, String> {
if let Some(name) = serialized_registry.intersection(runtime_registry).next() {
return Err(format!("schema registries overlap at {name}"));
}
let mut documented = HashMap::new();
let mut current: Option<(&str, Option<SchemaCategory>)> = None;
for line in schema.lines() {
let trimmed = line.trim();
if let Some(declaration) = trimmed.strip_prefix("class ") {
if current.is_some() {
return Err("nested class declaration".into());
}
let name = declaration
.split(|character: char| character.is_whitespace() || character == '{')
.next()
.filter(|name| !name.is_empty())
.ok_or_else(|| "class declaration has no name".to_owned())?;
if !declaration.contains('{') {
return Err(format!("class {name} has no stereotype block"));
}
if !serialized_registry.contains(name) && !runtime_registry.contains(name) {
return Err(format!("unknown schema class {name}"));
}
current = Some((name, None));
continue;
}
let Some((name, category)) = current.as_mut() else {
continue;
};
if trimmed == "}" {
let category = category.ok_or_else(|| format!("class {name} is unmarked"))?;
match documented.insert((*name).to_owned(), category) {
Some(existing) if existing != category => {
return Err(format!("class {name} has conflicting categories"));
}
_ => {}
}
current = None;
continue;
}
let Some(stereotype) = trimmed
.strip_prefix("<<")
.and_then(|value| value.strip_suffix(">>"))
else {
continue;
};
let parsed = match stereotype {
"serialized" => Some(SchemaCategory::Serialized),
"runtime" => Some(SchemaCategory::Runtime),
"enumeration" | "union" => None,
_ => return Err(format!("class {name} has unknown stereotype {stereotype}")),
};
if let Some(parsed) = parsed
&& category.replace(parsed).is_some()
{
return Err(format!("class {name} has multiple category markers"));
}
}
if let Some((name, _)) = current {
return Err(format!("class {name} has no closing brace"));
}
for name in serialized_registry.union(runtime_registry) {
if !documented.contains_key(*name) {
return Err(format!("registered schema class {name} is undocumented"));
}
}
Ok(documented)
}
#[test]
fn shape_registry_matches_the_normative_schema_classes() {
let documented = include_str!("../../../docs/schema.md")
.lines()
.filter_map(|line| line.strip_prefix(" class "))
.map(|name| name.split_whitespace().next().unwrap_or(name))
.collect::<HashSet<_>>();
let registered = ALL_SCHEMA_TYPE_NAMES
fn schema_categories_match_exact_disjoint_registries() {
let serialized = DECLARED_SERIALIZED_SCHEMA_TYPE_NAMES
.iter()
.copied()
.collect::<HashSet<_>>();
let runtime = DECLARED_RUNTIME_SCHEMA_TYPE_NAMES
.iter()
.copied()
.collect::<HashSet<_>>();
assert_eq!(
serialized.len(),
DECLARED_SERIALIZED_SCHEMA_TYPE_NAMES.len()
);
assert_eq!(runtime.len(), DECLARED_RUNTIME_SCHEMA_TYPE_NAMES.len());
assert_eq!(documented, registered);
let documented = parse_schema_categories(
include_str!("../../../docs/schema.md"),
&serialized,
&runtime,
)
.unwrap();
let documented_serialized = documented
.iter()
.filter_map(|(name, category)| {
(*category == SchemaCategory::Serialized).then_some(name.as_str())
})
.collect::<HashSet<_>>();
let documented_runtime = documented
.iter()
.filter_map(|(name, category)| {
(*category == SchemaCategory::Runtime).then_some(name.as_str())
})
.collect::<HashSet<_>>();
assert_eq!(documented_serialized, serialized);
assert_eq!(documented_runtime, runtime);
}
#[test]
fn schema_category_parser_fails_closed() {
let serialized = HashSet::from(["Known"]);
let runtime = HashSet::from(["Runtime"]);
let overlapping = HashSet::from(["Known"]);
for invalid in [
"class Known {\nfield\n}",
"class Known {\n<<serialized>>\n<<serialized>>\n}",
"class Known {\n<<serialized>>\n<<runtime>>\n}",
"class Unknown {\n<<serialized>>\n}",
"class Known {\n<<unexpected>>\n}",
] {
assert!(parse_schema_categories(invalid, &serialized, &runtime).is_err());
}
assert!(parse_schema_categories("", &serialized, &runtime).is_err());
assert!(parse_schema_categories("", &serialized, &overlapping).is_err());
assert!(
parse_schema_categories(
"class Known {\n<<serialized>>\n}\nclass Runtime {\n<<runtime>>\n}",
&serialized,
&runtime,
)
.is_ok()
);
}
fn assert_unknown_protocol_version<T>(record: &T)