commit 7367d7b31df365b92b016b851cefc79ead6bb434
parent 5fdc9b9ab675cd96077dbee9e383ae786035afdf
Author: triesap <tyson@radroots.org>
Date: Wed, 24 Jun 2026 22:21:26 +0000
replica: harden schema dto parity
Diffstat:
4 files changed, 106 insertions(+), 1 deletion(-)
diff --git a/crates/replica_db_schema_bindings/src/lib.rs b/crates/replica_db_schema_bindings/src/lib.rs
@@ -22,6 +22,10 @@ mod tests {
assert!(actual.contains(&"Farm"));
assert!(actual.contains(&"GcsLocation"));
+ assert!(actual.contains(&"IGcsLocationFindMany"));
assert!(actual.contains(&"IGcsLocationFindManyResolve"));
+ assert!(actual.contains(&"IMediaImageFindMany"));
+ assert!(actual.contains(&"INostrProfileFindMany"));
+ assert!(actual.contains(&"INostrRelayFindMany"));
}
}
diff --git a/packages/replica-db-schema-bindings/src/generated/types.ts b/packages/replica-db-schema-bindings/src/generated/types.ts
@@ -212,6 +212,8 @@ export type IGcsLocationFieldsFilter = { id?: string, created_at?: string, updat
export type IGcsLocationFieldsPartial = { d_tag?: ReplicaDbJsonValue | null, lat?: ReplicaDbJsonValue | null, lng?: ReplicaDbJsonValue | null, geohash?: ReplicaDbJsonValue | null, point?: ReplicaDbJsonValue | null, polygon?: ReplicaDbJsonValue | null, accuracy?: ReplicaDbJsonValue | null, altitude?: ReplicaDbJsonValue | null, tag_0?: ReplicaDbJsonValue | null, label?: ReplicaDbJsonValue | null, area?: ReplicaDbJsonValue | null, elevation?: ReplicaDbJsonValue | null, soil?: ReplicaDbJsonValue | null, climate?: ReplicaDbJsonValue | null, gc_id?: ReplicaDbJsonValue | null, gc_name?: ReplicaDbJsonValue | null, gc_admin1_id?: ReplicaDbJsonValue | null, gc_admin1_name?: ReplicaDbJsonValue | null, gc_country_id?: ReplicaDbJsonValue | null, gc_country_name?: ReplicaDbJsonValue | null, };
+export type IGcsLocationFindMany = { filter: IGcsLocationFieldsFilter | null, } | { rel: GcsLocationFindManyRel, };
+
export type IGcsLocationFindManyResolve = IResultList<GcsLocation>;
export type IGcsLocationFindOne = IGcsLocationFindOneArgs | IGcsLocationFindOneRelArgs;
@@ -274,6 +276,8 @@ export type IMediaImageFieldsFilter = { id?: string, created_at?: string, update
export type IMediaImageFieldsPartial = { file_path?: ReplicaDbJsonValue | null, mime_type?: ReplicaDbJsonValue | null, res_base?: ReplicaDbJsonValue | null, res_path?: ReplicaDbJsonValue | null, label?: ReplicaDbJsonValue | null, description?: ReplicaDbJsonValue | null, };
+export type IMediaImageFindMany = { filter: IMediaImageFieldsFilter | null, } | { rel: MediaImageFindManyRel, };
+
export type IMediaImageFindManyResolve = IResultList<MediaImage>;
export type IMediaImageFindOne = IMediaImageFindOneArgs | IMediaImageFindOneRelArgs;
@@ -336,6 +340,8 @@ export type INostrProfileFieldsFilter = { id?: string, created_at?: string, upda
export type INostrProfileFieldsPartial = { public_key?: ReplicaDbJsonValue | null, profile_type?: ReplicaDbJsonValue | null, name?: ReplicaDbJsonValue | null, display_name?: ReplicaDbJsonValue | null, about?: ReplicaDbJsonValue | null, website?: ReplicaDbJsonValue | null, picture?: ReplicaDbJsonValue | null, banner?: ReplicaDbJsonValue | null, nip05?: ReplicaDbJsonValue | null, lud06?: ReplicaDbJsonValue | null, lud16?: ReplicaDbJsonValue | null, };
+export type INostrProfileFindMany = { filter: INostrProfileFieldsFilter | null, } | { rel: NostrProfileFindManyRel, };
+
export type INostrProfileFindManyResolve = IResultList<NostrProfile>;
export type INostrProfileFindOne = INostrProfileFindOneArgs | INostrProfileFindOneRelArgs;
@@ -370,6 +376,8 @@ export type INostrRelayFieldsFilter = { id?: string, created_at?: string, update
export type INostrRelayFieldsPartial = { url?: ReplicaDbJsonValue | null, relay_id?: ReplicaDbJsonValue | null, name?: ReplicaDbJsonValue | null, description?: ReplicaDbJsonValue | null, pubkey?: ReplicaDbJsonValue | null, contact?: ReplicaDbJsonValue | null, supported_nips?: ReplicaDbJsonValue | null, software?: ReplicaDbJsonValue | null, version?: ReplicaDbJsonValue | null, data?: ReplicaDbJsonValue | null, };
+export type INostrRelayFindMany = { filter: INostrRelayFieldsFilter | null, } | { rel: NostrRelayFindManyRel, };
+
export type INostrRelayFindManyResolve = IResultList<NostrRelay>;
export type INostrRelayFindOne = INostrRelayFindOneArgs | INostrRelayFindOneRelArgs;
diff --git a/tools/xtask/src/dto_render.rs b/tools/xtask/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/xtask/src/dto_roots.rs b/tools/xtask/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()