lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

commit e8c3c6c886ce94a8b989b099396b65e57da82e07
parent 86dba23e631c9cc50d3cf03785e16cdd14150a4e
Author: triesap <tyson@radroots.org>
Date:   Wed, 24 Jun 2026 06:25:18 +0000

dto: add source descriptor features

Diffstat:
MCargo.lock | 16++++++++++++++++
MCargo.toml | 1+
Mcrates/core/Cargo.toml | 2++
Acrates/core/src/dto.rs | 375+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/core/src/lib.rs | 2++
Mcrates/events/Cargo.toml | 2++
Acrates/events/src/dto.rs | 327+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/events/src/lib.rs | 2++
8 files changed, 727 insertions(+), 0 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -1500,6 +1500,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] +name = "dto_bindgen_core" +version = "0.1.0" +source = "git+https://github.com/triesap/dto_bindgen?rev=96ed6c691aacab31860828d25da2e0167b13d92c#96ed6c691aacab31860828d25da2e0167b13d92c" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "syn 2.0.117", + "toml", +] + +[[package]] name = "dynasm" version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4222,6 +4236,7 @@ dependencies = [ name = "radroots_core" version = "0.1.0-alpha.2" dependencies = [ + "dto_bindgen_core", "rust_decimal", "rust_decimal_macros", "serde", @@ -4246,6 +4261,7 @@ dependencies = [ name = "radroots_events" version = "0.1.0-alpha.2" dependencies = [ + "dto_bindgen_core", "hex", "radroots_core", "serde", diff --git a/Cargo.toml b/Cargo.toml @@ -58,6 +58,7 @@ homepage = "https://radroots.org" readme = "README" [workspace.dependencies] +dto_bindgen_core = { git = "https://github.com/triesap/dto_bindgen", rev = "96ed6c691aacab31860828d25da2e0167b13d92c", package = "dto_bindgen_core" } radroots_core = { path = "crates/core", version = "0.1.0-alpha.2", default-features = false } radroots_events = { path = "crates/events", version = "0.1.0-alpha.2", default-features = false } radroots_event_store = { path = "crates/event_store", version = "0.1.0-alpha.2", default-features = false } diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml @@ -14,10 +14,12 @@ readme = "README" [features] default = ["std", "serde"] +dto-bindgen = ["std", "serde", "dep:dto_bindgen_core"] std = [] serde = ["dep:serde", "rust_decimal/serde"] [dependencies] +dto_bindgen_core = { workspace = true, optional = true } rust_decimal = { workspace = true, default-features = false } rust_decimal_macros = { workspace = true } serde = { workspace = true, default-features = false, features = [ diff --git a/crates/core/src/dto.rs b/crates/core/src/dto.rs @@ -0,0 +1,375 @@ +use dto_bindgen_core::{ + DescribeCtx, Dto, EnumDef, EnumRepr, FieldDef, FieldPresence, IdentName, RootDescriptor, + RustTypeId, SourceSpan, StructDef, TargetFieldNames, TypeDef, TypeRef, VariantDef, + VariantShape, WireFieldNames, +}; + +use crate::{ + RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreDiscount, RadrootsCoreDiscountScope, + RadrootsCoreDiscountThreshold, RadrootsCoreDiscountValue, RadrootsCoreMoney, + RadrootsCorePercent, RadrootsCoreQuantity, RadrootsCoreQuantityPrice, RadrootsCoreUnit, + RadrootsCoreUnitDimension, +}; + +pub fn dto_roots() -> [RootDescriptor; 9] { + [ + RootDescriptor::new::<RadrootsCoreDiscount>(), + RootDescriptor::new::<RadrootsCoreDiscountScope>(), + RootDescriptor::new::<RadrootsCoreDiscountThreshold>(), + RootDescriptor::new::<RadrootsCoreDiscountValue>(), + RootDescriptor::new::<RadrootsCoreMoney>(), + RootDescriptor::new::<RadrootsCorePercent>(), + RootDescriptor::new::<RadrootsCoreQuantity>(), + RootDescriptor::new::<RadrootsCoreQuantityPrice>(), + RootDescriptor::new::<RadrootsCoreUnit>(), + ] +} + +impl Dto for RadrootsCoreCurrency { + fn describe(_ctx: &mut DescribeCtx) -> TypeRef { + TypeRef::String + } +} + +impl Dto for RadrootsCoreDecimal { + fn describe(_ctx: &mut DescribeCtx) -> TypeRef { + TypeRef::String + } +} + +impl Dto for RadrootsCoreUnitDimension { + fn describe(ctx: &mut DescribeCtx) -> TypeRef { + let def = EnumDef::new( + "RadrootsCoreUnitDimension", + "RadrootsCoreUnitDimension", + EnumRepr::External, + span("crates/core/src/unit.rs", 17), + ) + .with_variant(unit_variant( + "Count", + "count", + "crates/core/src/unit.rs", + 18, + )) + .with_variant(unit_variant("Mass", "mass", "crates/core/src/unit.rs", 19)) + .with_variant(unit_variant( + "Volume", + "volume", + "crates/core/src/unit.rs", + 20, + )); + register(ctx, "RadrootsCoreUnitDimension", TypeDef::Enum(def)) + } +} + +impl Dto for RadrootsCoreUnit { + fn describe(ctx: &mut DescribeCtx) -> TypeRef { + let def = EnumDef::new( + "RadrootsCoreUnit", + "RadrootsCoreUnit", + EnumRepr::External, + span("crates/core/src/unit.rs", 24), + ) + .with_variant(unit_variant("Each", "each", "crates/core/src/unit.rs", 25)) + .with_variant(unit_variant("MassKg", "kg", "crates/core/src/unit.rs", 26)) + .with_variant(unit_variant("MassG", "g", "crates/core/src/unit.rs", 27)) + .with_variant(unit_variant("MassOz", "oz", "crates/core/src/unit.rs", 28)) + .with_variant(unit_variant("MassLb", "lb", "crates/core/src/unit.rs", 29)) + .with_variant(unit_variant("VolumeL", "l", "crates/core/src/unit.rs", 30)) + .with_variant(unit_variant( + "VolumeMl", + "ml", + "crates/core/src/unit.rs", + 31, + )); + register(ctx, "RadrootsCoreUnit", TypeDef::Enum(def)) + } +} + +impl Dto for RadrootsCoreMoney { + fn describe(ctx: &mut DescribeCtx) -> TypeRef { + let def = StructDef::new( + "RadrootsCoreMoney", + "RadrootsCoreMoney", + span("crates/core/src/money.rs", 8), + ) + .with_field(field( + "amount", + "amount", + RadrootsCoreDecimal::describe(ctx), + "crates/core/src/money.rs", + 9, + )) + .with_field(field( + "currency", + "currency", + RadrootsCoreCurrency::describe(ctx), + "crates/core/src/money.rs", + 10, + )); + register(ctx, "RadrootsCoreMoney", TypeDef::Struct(def)) + } +} + +impl Dto for RadrootsCorePercent { + fn describe(ctx: &mut DescribeCtx) -> TypeRef { + let def = StructDef::new( + "RadrootsCorePercent", + "RadrootsCorePercent", + span("crates/core/src/percent.rs", 9), + ) + .with_field(field( + "value", + "value", + RadrootsCoreDecimal::describe(ctx), + "crates/core/src/percent.rs", + 10, + )); + register(ctx, "RadrootsCorePercent", TypeDef::Struct(def)) + } +} + +impl Dto for RadrootsCoreQuantity { + fn describe(ctx: &mut DescribeCtx) -> TypeRef { + let unit = RadrootsCoreUnit::describe(ctx); + let def = StructDef::new( + "RadrootsCoreQuantity", + "RadrootsCoreQuantity", + span("crates/core/src/quantity.rs", 13), + ) + .with_field(field( + "amount", + "amount", + RadrootsCoreDecimal::describe(ctx), + "crates/core/src/quantity.rs", + 14, + )) + .with_field(field( + "unit", + "unit", + unit, + "crates/core/src/quantity.rs", + 16, + )) + .with_field( + field( + "label", + "label", + <Option<String> as Dto>::describe(ctx), + "crates/core/src/quantity.rs", + 18, + ) + .with_presence(FieldPresence::optional_nullable_skip_if_none()), + ); + register(ctx, "RadrootsCoreQuantity", TypeDef::Struct(def)) + } +} + +impl Dto for RadrootsCoreQuantityPrice { + fn describe(ctx: &mut DescribeCtx) -> TypeRef { + let amount = RadrootsCoreMoney::describe(ctx); + let quantity = RadrootsCoreQuantity::describe(ctx); + let def = StructDef::new( + "RadrootsCoreQuantityPrice", + "RadrootsCoreQuantityPrice", + span("crates/core/src/quantity_price.rs", 5), + ) + .with_field(field( + "amount", + "amount", + amount, + "crates/core/src/quantity_price.rs", + 7, + )) + .with_field(field( + "quantity", + "quantity", + quantity, + "crates/core/src/quantity_price.rs", + 9, + )); + register(ctx, "RadrootsCoreQuantityPrice", TypeDef::Struct(def)) + } +} + +impl Dto for RadrootsCoreDiscountScope { + fn describe(ctx: &mut DescribeCtx) -> TypeRef { + let def = EnumDef::new( + "RadrootsCoreDiscountScope", + "RadrootsCoreDiscountScope", + EnumRepr::External, + span("crates/core/src/discount.rs", 9), + ) + .with_variant(unit_variant( + "Bin", + "bin", + "crates/core/src/discount.rs", + 10, + )) + .with_variant(unit_variant( + "OrderTotal", + "order_total", + "crates/core/src/discount.rs", + 11, + )); + register(ctx, "RadrootsCoreDiscountScope", TypeDef::Enum(def)) + } +} + +impl Dto for RadrootsCoreDiscountThreshold { + fn describe(ctx: &mut DescribeCtx) -> TypeRef { + let quantity = RadrootsCoreQuantity::describe(ctx); + let def = EnumDef::new( + "RadrootsCoreDiscountThreshold", + "RadrootsCoreDiscountThreshold", + EnumRepr::Adjacent { + tag: "kind".to_owned(), + content: "amount".to_owned(), + }, + span("crates/core/src/discount.rs", 20), + ) + .with_variant(VariantDef::new( + "BinCount", + "bin_count", + VariantShape::Struct(vec![ + field( + "bin_id", + "bin_id", + String::describe(ctx), + "crates/core/src/discount.rs", + 21, + ), + field( + "min", + "min", + u32::describe(ctx), + "crates/core/src/discount.rs", + 21, + ), + ]), + span("crates/core/src/discount.rs", 21), + )) + .with_variant(VariantDef::new( + "OrderQuantity", + "order_quantity", + VariantShape::Struct(vec![field( + "min", + "min", + quantity, + "crates/core/src/discount.rs", + 22, + )]), + span("crates/core/src/discount.rs", 22), + )); + register(ctx, "RadrootsCoreDiscountThreshold", TypeDef::Enum(def)) + } +} + +impl Dto for RadrootsCoreDiscountValue { + fn describe(ctx: &mut DescribeCtx) -> TypeRef { + let money = RadrootsCoreMoney::describe(ctx); + let percent = RadrootsCorePercent::describe(ctx); + let def = EnumDef::new( + "RadrootsCoreDiscountValue", + "RadrootsCoreDiscountValue", + EnumRepr::Adjacent { + tag: "kind".to_owned(), + content: "amount".to_owned(), + }, + span("crates/core/src/discount.rs", 31), + ) + .with_variant(VariantDef::new( + "MoneyPerBin", + "money_per_bin", + VariantShape::Newtype(money), + span("crates/core/src/discount.rs", 32), + )) + .with_variant(VariantDef::new( + "Percent", + "percent", + VariantShape::Newtype(percent), + span("crates/core/src/discount.rs", 33), + )); + register(ctx, "RadrootsCoreDiscountValue", TypeDef::Enum(def)) + } +} + +impl Dto for RadrootsCoreDiscount { + fn describe(ctx: &mut DescribeCtx) -> TypeRef { + let scope = RadrootsCoreDiscountScope::describe(ctx); + let threshold = RadrootsCoreDiscountThreshold::describe(ctx); + let value = RadrootsCoreDiscountValue::describe(ctx); + let def = StructDef::new( + "RadrootsCoreDiscount", + "RadrootsCoreDiscount", + span("crates/core/src/discount.rs", 39), + ) + .with_field(field( + "scope", + "scope", + scope, + "crates/core/src/discount.rs", + 40, + )) + .with_field(field( + "threshold", + "threshold", + threshold, + "crates/core/src/discount.rs", + 41, + )) + .with_field(field( + "value", + "value", + value, + "crates/core/src/discount.rs", + 42, + )); + register(ctx, "RadrootsCoreDiscount", TypeDef::Struct(def)) + } +} + +fn register(ctx: &mut DescribeCtx, rust_ident: &str, type_def: TypeDef) -> TypeRef { + ctx.register_type(RustTypeId::new("radroots_core", rust_ident), type_def) +} + +fn unit_variant(rust_name: &str, wire_name: &str, file: &str, line: u32) -> VariantDef { + VariantDef::new(rust_name, wire_name, VariantShape::Unit, span(file, line)) +} + +fn field(rust_name: &str, wire_name: &str, ty: TypeRef, file: &str, line: u32) -> FieldDef { + FieldDef::new( + IdentName::new(rust_name), + WireFieldNames::same(wire_name), + TargetFieldNames::new(wire_name, rust_name), + ty, + span(file, line), + ) +} + +fn span(file: &str, line: u32) -> SourceSpan { + SourceSpan::new(file, line, 1) +} + +#[cfg(test)] +mod tests { + use dto_bindgen_core::{TypeDef, build_registry}; + + use super::dto_roots; + + #[test] + fn core_descriptor_roots_build_registry() { + let registry = build_registry(dto_roots()); + + assert!(!registry.has_errors()); + assert_eq!(registry.roots.len(), dto_roots().len()); + assert!(registry.types_by_id.values().any( + |def| matches!(def, TypeDef::Struct(def) if def.export_name == "RadrootsCoreMoney") + )); + assert!( + registry.types_by_id.values().any( + |def| matches!(def, TypeDef::Enum(def) if def.export_name == "RadrootsCoreUnit") + ) + ); + } +} diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs @@ -6,6 +6,8 @@ extern crate alloc; pub mod currency; pub mod decimal; pub mod discount; +#[cfg(feature = "dto-bindgen")] +pub mod dto; pub mod money; pub mod percent; pub mod quantity; diff --git a/crates/events/Cargo.toml b/crates/events/Cargo.toml @@ -14,10 +14,12 @@ readme = "README" [features] default = ["std", "serde"] +dto-bindgen = ["std", "serde", "dep:dto_bindgen_core", "radroots_core/dto-bindgen"] std = ["radroots_core/std"] serde = ["dep:serde", "radroots_core/serde"] [dependencies] +dto_bindgen_core = { workspace = true, optional = true } radroots_core = { workspace = true, default-features = false } hex = { version = "0.4", default-features = false, features = ["alloc"] } serde = { workspace = true, default-features = false, features = [ diff --git a/crates/events/src/dto.rs b/crates/events/src/dto.rs @@ -0,0 +1,327 @@ +use dto_bindgen_core::{ + DescribeCtx, Dto, FieldDef, FieldPresence, IdentName, RootDescriptor, RustTypeId, SourceSpan, + StructDef, TargetFieldNames, TypeDef, TypeRef, WireFieldNames, +}; + +use crate::{ + RadrootsNostrEvent, RadrootsNostrEventPtr, RadrootsNostrEventRef, + listing::{RadrootsListingImage, RadrootsListingImageSize, RadrootsListingProduct}, +}; + +pub fn dto_roots() -> [RootDescriptor; 5] { + [ + RootDescriptor::new::<RadrootsNostrEvent>(), + RootDescriptor::new::<RadrootsNostrEventRef>(), + RootDescriptor::new::<RadrootsNostrEventPtr>(), + RootDescriptor::new::<RadrootsListingProduct>(), + RootDescriptor::new::<RadrootsListingImage>(), + ] +} + +impl Dto for RadrootsNostrEvent { + fn describe(ctx: &mut DescribeCtx) -> TypeRef { + let def = StructDef::new( + "RadrootsNostrEvent", + "RadrootsNostrEvent", + span("crates/events/src/lib.rs", 52), + ) + .with_field(field( + "id", + "id", + String::describe(ctx), + "crates/events/src/lib.rs", + 53, + )) + .with_field(field( + "author", + "author", + String::describe(ctx), + "crates/events/src/lib.rs", + 54, + )) + .with_field(field( + "created_at", + "created_at", + u32::describe(ctx), + "crates/events/src/lib.rs", + 55, + )) + .with_field(field( + "kind", + "kind", + u32::describe(ctx), + "crates/events/src/lib.rs", + 56, + )) + .with_field(field( + "tags", + "tags", + <Vec<Vec<String>> as Dto>::describe(ctx), + "crates/events/src/lib.rs", + 57, + )) + .with_field(field( + "content", + "content", + String::describe(ctx), + "crates/events/src/lib.rs", + 58, + )) + .with_field(field( + "sig", + "sig", + String::describe(ctx), + "crates/events/src/lib.rs", + 59, + )); + register(ctx, "RadrootsNostrEvent", TypeDef::Struct(def)) + } +} + +impl Dto for RadrootsNostrEventRef { + fn describe(ctx: &mut DescribeCtx) -> TypeRef { + let def = StructDef::new( + "RadrootsNostrEventRef", + "RadrootsNostrEventRef", + span("crates/events/src/lib.rs", 64), + ) + .with_field(field( + "id", + "id", + String::describe(ctx), + "crates/events/src/lib.rs", + 65, + )) + .with_field(field( + "author", + "author", + String::describe(ctx), + "crates/events/src/lib.rs", + 66, + )) + .with_field(field( + "kind", + "kind", + u32::describe(ctx), + "crates/events/src/lib.rs", + 67, + )) + .with_field(field( + "d_tag", + "d_tag", + <Option<String> as Dto>::describe(ctx), + "crates/events/src/lib.rs", + 68, + )) + .with_field(field( + "relays", + "relays", + <Option<Vec<String>> as Dto>::describe(ctx), + "crates/events/src/lib.rs", + 69, + )); + register(ctx, "RadrootsNostrEventRef", TypeDef::Struct(def)) + } +} + +impl Dto for RadrootsNostrEventPtr { + fn describe(ctx: &mut DescribeCtx) -> TypeRef { + let def = StructDef::new( + "RadrootsNostrEventPtr", + "RadrootsNostrEventPtr", + span("crates/events/src/lib.rs", 74), + ) + .with_field(field( + "id", + "id", + String::describe(ctx), + "crates/events/src/lib.rs", + 75, + )) + .with_field(field( + "relays", + "relays", + <Option<String> as Dto>::describe(ctx), + "crates/events/src/lib.rs", + 76, + )); + register(ctx, "RadrootsNostrEventPtr", TypeDef::Struct(def)) + } +} + +impl Dto for RadrootsListingProduct { + fn describe(ctx: &mut DescribeCtx) -> TypeRef { + let def = StructDef::new( + "RadrootsListingProduct", + "RadrootsListingProduct", + span("crates/events/src/listing.rs", 81), + ) + .with_field(field( + "key", + "key", + String::describe(ctx), + "crates/events/src/listing.rs", + 82, + )) + .with_field(field( + "title", + "title", + String::describe(ctx), + "crates/events/src/listing.rs", + 83, + )) + .with_field(field( + "category", + "category", + String::describe(ctx), + "crates/events/src/listing.rs", + 84, + )) + .with_field(nullable_field( + "summary", + "summary", + <Option<String> as Dto>::describe(ctx), + "crates/events/src/listing.rs", + 85, + )) + .with_field(nullable_field( + "process", + "process", + <Option<String> as Dto>::describe(ctx), + "crates/events/src/listing.rs", + 86, + )) + .with_field(nullable_field( + "lot", + "lot", + <Option<String> as Dto>::describe(ctx), + "crates/events/src/listing.rs", + 87, + )) + .with_field(nullable_field( + "location", + "location", + <Option<String> as Dto>::describe(ctx), + "crates/events/src/listing.rs", + 88, + )) + .with_field(nullable_field( + "profile", + "profile", + <Option<String> as Dto>::describe(ctx), + "crates/events/src/listing.rs", + 89, + )) + .with_field(nullable_field( + "year", + "year", + <Option<String> as Dto>::describe(ctx), + "crates/events/src/listing.rs", + 90, + )); + register(ctx, "RadrootsListingProduct", TypeDef::Struct(def)) + } +} + +impl Dto for RadrootsListingImageSize { + fn describe(ctx: &mut DescribeCtx) -> TypeRef { + let def = StructDef::new( + "RadrootsListingImageSize", + "RadrootsListingImageSize", + span("crates/events/src/listing.rs", 133), + ) + .with_field(field( + "w", + "w", + u32::describe(ctx), + "crates/events/src/listing.rs", + 134, + )) + .with_field(field( + "h", + "h", + u32::describe(ctx), + "crates/events/src/listing.rs", + 135, + )); + register(ctx, "RadrootsListingImageSize", TypeDef::Struct(def)) + } +} + +impl Dto for RadrootsListingImage { + fn describe(ctx: &mut DescribeCtx) -> TypeRef { + let size = RadrootsListingImageSize::describe(ctx); + let def = StructDef::new( + "RadrootsListingImage", + "RadrootsListingImage", + span("crates/events/src/listing.rs", 126), + ) + .with_field(field( + "url", + "url", + String::describe(ctx), + "crates/events/src/listing.rs", + 127, + )) + .with_field(nullable_field( + "size", + "size", + TypeRef::option(size), + "crates/events/src/listing.rs", + 128, + )); + register(ctx, "RadrootsListingImage", TypeDef::Struct(def)) + } +} + +fn register(ctx: &mut DescribeCtx, rust_ident: &str, type_def: TypeDef) -> TypeRef { + ctx.register_type(RustTypeId::new("radroots_events", rust_ident), type_def) +} + +fn nullable_field( + rust_name: &str, + wire_name: &str, + ty: TypeRef, + file: &str, + line: u32, +) -> FieldDef { + field(rust_name, wire_name, ty, file, line).with_presence(FieldPresence::nullable_required()) +} + +fn field(rust_name: &str, wire_name: &str, ty: TypeRef, file: &str, line: u32) -> FieldDef { + FieldDef::new( + IdentName::new(rust_name), + WireFieldNames::same(wire_name), + TargetFieldNames::new(wire_name, rust_name), + ty, + span(file, line), + ) +} + +fn span(file: &str, line: u32) -> SourceSpan { + SourceSpan::new(file, line, 1) +} + +#[cfg(test)] +mod tests { + use dto_bindgen_core::{TypeDef, build_registry}; + + use super::dto_roots; + + #[test] + fn event_descriptor_roots_build_registry() { + let registry = build_registry(dto_roots()); + + assert!(!registry.has_errors()); + assert_eq!(registry.roots.len(), dto_roots().len()); + assert!(registry.types_by_id.values().any( + |def| matches!(def, TypeDef::Struct(def) if def.export_name == "RadrootsNostrEvent") + )); + assert!( + registry + .types_by_id + .values() + .any(|def| matches!(def, TypeDef::Struct(def) if def.export_name == "RadrootsListingImageSize")) + ); + } +} diff --git a/crates/events/src/lib.rs b/crates/events/src/lib.rs @@ -15,6 +15,8 @@ pub mod contract; pub mod coop; pub mod document; pub mod draft; +#[cfg(feature = "dto-bindgen")] +pub mod dto; pub mod event_head; pub mod farm; pub mod farm_crdt;