commit 5c9b7fc8242415722aa1845a751dd45879715c2e
parent c6e7c084bbd4ad279c7232ba3fc37b04bf6a6e3b
Author: triesap <tyson@radroots.org>
Date: Wed, 24 Jun 2026 22:21:26 +0000
replica: harden schema dto parity
Diffstat:
2 files changed, 94 insertions(+), 1 deletion(-)
diff --git a/tools/sdk_xtask_import/src/dto_render.rs b/tools/sdk_xtask_import/src/dto_render.rs
@@ -191,7 +191,9 @@ fn render_untagged_variant(
imports: &mut BTreeMap<String, BTreeSet<String>>,
) -> Result<String, String> {
let rendered: Result<String, String> = match &variant.shape {
- VariantShape::Unit => Ok("undefined".to_owned()),
+ VariantShape::Unit => {
+ Err("untagged unit variants are unsupported for JSON DTO output".to_owned())
+ }
VariantShape::Newtype(ty) => render_type_ref(ty, None, registry, options, imports),
VariantShape::Tuple(items) => {
let rendered = items
@@ -837,6 +839,27 @@ mod tests {
}
#[test]
+ fn rejects_untagged_unit_variants() {
+ let mut registry = Registry::new();
+ registry.register_type(
+ RustTypeId::new("sdk", "MaybeReady"),
+ TypeDef::Enum(
+ EnumDef::new("MaybeReady", "MaybeReady", EnumRepr::Untagged, span()).with_variant(
+ VariantDef::new("Ready", "ready", VariantShape::Unit, span()),
+ ),
+ ),
+ );
+
+ let error = render_registry_types(®istry, &DtoRegistryRenderOptions::default())
+ .expect_err("untagged unit variant blocks render");
+
+ assert_eq!(
+ error,
+ "untagged unit variants are unsupported for JSON DTO output while rendering untagged enum MaybeReady.Ready"
+ );
+ }
+
+ #[test]
fn requires_explicit_large_integer_policy() {
let mut registry = Registry::new();
registry.register_type(
diff --git a/tools/sdk_xtask_import/src/dto_roots.rs b/tools/sdk_xtask_import/src/dto_roots.rs
@@ -328,6 +328,8 @@ fn with_events_indexed_sdk_wrappers(body: &str) -> String {
#[cfg(test)]
mod tests {
+ use std::collections::BTreeSet;
+
use super::{
DTO_PACKAGE_ROOTS, MANUAL_DESCRIPTOR_FAMILIES, SDK_LOCAL_WRAPPER_ALLOWANCES,
package_root_set,
@@ -341,6 +343,28 @@ mod tests {
include_str!("../../../packages/replica-db-schema-bindings/src/generated/types.ts");
const TRADE_BINDINGS_TYPES_TS: &str =
include_str!("../../../packages/trade-bindings/src/generated/types.ts");
+ const REPLICA_SCHEMA_MODEL_SOURCES: &[&str] = &[
+ include_str!("../../../../lib/crates/replica_db_schema/src/models/farm.rs"),
+ include_str!("../../../../lib/crates/replica_db_schema/src/models/farm_gcs_location.rs"),
+ include_str!("../../../../lib/crates/replica_db_schema/src/models/farm_member.rs"),
+ include_str!("../../../../lib/crates/replica_db_schema/src/models/farm_member_claim.rs"),
+ include_str!("../../../../lib/crates/replica_db_schema/src/models/farm_tag.rs"),
+ include_str!("../../../../lib/crates/replica_db_schema/src/models/gcs_location.rs"),
+ include_str!("../../../../lib/crates/replica_db_schema/src/models/log_error.rs"),
+ include_str!("../../../../lib/crates/replica_db_schema/src/models/media_image.rs"),
+ include_str!("../../../../lib/crates/replica_db_schema/src/models/nostr_event_head.rs"),
+ include_str!("../../../../lib/crates/replica_db_schema/src/models/nostr_profile.rs"),
+ include_str!("../../../../lib/crates/replica_db_schema/src/models/nostr_profile_relay.rs"),
+ include_str!("../../../../lib/crates/replica_db_schema/src/models/nostr_relay.rs"),
+ include_str!("../../../../lib/crates/replica_db_schema/src/models/plot.rs"),
+ include_str!("../../../../lib/crates/replica_db_schema/src/models/plot_gcs_location.rs"),
+ include_str!("../../../../lib/crates/replica_db_schema/src/models/plot_tag.rs"),
+ include_str!("../../../../lib/crates/replica_db_schema/src/models/trade_product.rs"),
+ include_str!(
+ "../../../../lib/crates/replica_db_schema/src/models/trade_product_location.rs"
+ ),
+ include_str!("../../../../lib/crates/replica_db_schema/src/models/trade_product_media.rs"),
+ ];
const EVENTS_TYPE_INVENTORY: &[&str] = &[
"JobFeedbackStatus",
"JobInputType",
@@ -596,6 +620,22 @@ mod tests {
}
#[test]
+ fn replica_db_schema_generated_types_match_source_public_inventory() {
+ let actual = type_inventory(REPLICA_DB_SCHEMA_BINDINGS_TYPES_TS)
+ .into_iter()
+ .collect::<BTreeSet<_>>();
+ let missing = source_public_schema_type_inventory()
+ .into_iter()
+ .filter(|name| !actual.contains(name))
+ .collect::<Vec<_>>();
+
+ assert!(
+ missing.is_empty(),
+ "missing generated replica schema exports: {missing:?}"
+ );
+ }
+
+ #[test]
fn trade_package_imports_source_owned_support_types() {
assert!(TRADE_BINDINGS_TYPES_TS.contains("from \"@radroots/core-bindings\""));
assert!(TRADE_BINDINGS_TYPES_TS.contains("from \"@radroots/events-bindings\""));
@@ -618,6 +658,36 @@ mod tests {
.collect()
}
+ fn source_public_schema_type_inventory() -> Vec<&'static str> {
+ let mut names = BTreeSet::new();
+
+ for source in REPLICA_SCHEMA_MODEL_SOURCES {
+ for line in source.lines() {
+ if let Some(name) = public_rust_type_name(line)
+ && !name.ends_with("Ts")
+ {
+ names.insert(name);
+ }
+ }
+ }
+
+ names.into_iter().collect()
+ }
+
+ fn public_rust_type_name(line: &'static str) -> Option<&'static str> {
+ let line = line.trim_start();
+
+ ["pub struct ", "pub enum ", "pub type "]
+ .into_iter()
+ .find_map(|prefix| {
+ line.strip_prefix(prefix).map(|rest| {
+ rest.split(|char: char| !(char == '_' || char.is_ascii_alphanumeric()))
+ .next()
+ .expect("type name")
+ })
+ })
+ }
+
fn type_declaration<'a>(types_ts: &'a str, name: &str) -> &'a str {
types_ts
.lines()