Retire the mutable design schema
Make Rust records and fixed codec tests the implementation-shape owner, remove the Mermaid category registry, and state the ownership boundary in the authoritative specification.
This commit is contained in:
parent
a0055f67f1
commit
03bfe92ca0
2 changed files with 17 additions and 1679 deletions
|
|
@ -1,4 +1,4 @@
|
|||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use std::fmt::Debug;
|
||||
use std::num::NonZeroU16;
|
||||
|
||||
|
|
|
|||
|
|
@ -670,165 +670,14 @@ image = "example.invalid/agent:v1"
|
|||
&mut seen,
|
||||
);
|
||||
|
||||
assert_eq!(seen.len(), IMPLEMENTED_SERIALIZED_SCHEMA_TYPE_NAMES.len());
|
||||
assert_eq!(seen.len(), SERIALIZED_RECORD_TYPE_NAMES.len());
|
||||
assert!(
|
||||
IMPLEMENTED_SERIALIZED_SCHEMA_TYPE_NAMES
|
||||
SERIALIZED_RECORD_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 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());
|
||||
|
||||
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)
|
||||
where
|
||||
T: DeserializeOwned + Serialize,
|
||||
|
|
|
|||
Loading…
Reference in a new issue