commit a28aa10587e11055d7d1e99b1af880593ae3ddcf
parent d44eb0fe99ed4d8f6e8f3948d3f980c77a64e2b1
Author: triesap <tyson@radroots.org>
Date: Sat, 27 Jun 2026 20:34:38 +0000
trade: expose workflow state DTO
- export RadrootsTradeWorkflowState as a dto_bindgen root
- pin workflow state serde names to approved snake_case wires
- add DTO registry coverage for workflow state discriminants
- verify events and trade all-features check, tests, and clippy
Diffstat:
2 files changed, 76 insertions(+), 3 deletions(-)
diff --git a/crates/trade/src/dto.rs b/crates/trade/src/dto.rs
@@ -7,12 +7,14 @@ use crate::listing::{
model::{RadrootsTradeListingSubtotal, RadrootsTradeListingTotal},
validation::RadrootsTradeListing,
};
+use crate::workflow::RadrootsTradeWorkflowState;
-pub fn dto_roots() -> [RootDescriptor; 3] {
+pub fn dto_roots() -> [RootDescriptor; 4] {
[
RootDescriptor::new::<RadrootsTradeListing>(),
RootDescriptor::new::<RadrootsTradeListingSubtotal>(),
RootDescriptor::new::<RadrootsTradeListingTotal>(),
+ RootDescriptor::new::<RadrootsTradeWorkflowState>(),
]
}
@@ -219,7 +221,9 @@ fn span(file: &str, line: u32) -> SourceSpan {
#[cfg(test)]
mod tests {
- use dto_bindgen_core::{BackendId, Registry, StructDef, TypeDef, TypeRef, build_registry};
+ use dto_bindgen_core::{
+ BackendId, EnumDef, Registry, StructDef, TypeDef, TypeRef, build_registry,
+ };
use super::dto_roots;
@@ -227,6 +231,7 @@ mod tests {
"RadrootsTradeListing",
"RadrootsTradeListingSubtotal",
"RadrootsTradeListingTotal",
+ "RadrootsTradeWorkflowState",
];
#[test]
@@ -268,6 +273,26 @@ mod tests {
);
}
+ #[test]
+ fn trade_workflow_state_uses_approved_wire_vocabulary() {
+ let registry = build_registry(dto_roots());
+ let workflow_state = find_enum(®istry, "RadrootsTradeWorkflowState");
+
+ assert_eq!(
+ enum_wire_names(workflow_state),
+ [
+ "missing",
+ "requested",
+ "revision_proposed",
+ "agreed_pending_rhi",
+ "committed",
+ "declined",
+ "cancelled",
+ "invalid",
+ ]
+ );
+ }
+
fn root_export_names(registry: &Registry) -> Vec<&str> {
registry
.roots
@@ -299,6 +324,24 @@ mod tests {
.expect("descriptor struct")
}
+ fn find_enum<'a>(registry: &'a Registry, export_name: &str) -> &'a EnumDef {
+ registry
+ .types_by_id
+ .values()
+ .find_map(|def| match def {
+ TypeDef::Enum(def) if def.export_name == export_name => Some(def),
+ _ => None,
+ })
+ .expect("descriptor enum")
+ }
+
+ fn enum_wire_names(def: &EnumDef) -> Vec<&str> {
+ def.variants
+ .iter()
+ .map(|variant| variant.wire_name.as_str())
+ .collect()
+ }
+
fn field_ty<'a>(def: &'a StructDef, field_name: &str) -> &'a TypeRef {
&def.fields
.iter()
diff --git a/crates/trade/src/workflow.rs b/crates/trade/src/workflow.rs
@@ -18,8 +18,11 @@ use crate::validation_receipt::{
RadrootsValidationReceiptType,
};
-#[derive(Clone, Debug, PartialEq, Eq)]
+#[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))]
+#[cfg_attr(feature = "dto-bindgen", dto(export))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
+#[derive(Clone, Debug, PartialEq, Eq)]
pub enum RadrootsTradeWorkflowState {
Missing,
Requested,
@@ -296,6 +299,33 @@ mod tests {
const BUYER: &str = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
const SELLER: &str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+ #[cfg(feature = "serde_json")]
+ #[test]
+ fn workflow_state_serializes_as_approved_wire_vocabulary() {
+ for (state, wire_name) in [
+ (RadrootsTradeWorkflowState::Missing, "missing"),
+ (RadrootsTradeWorkflowState::Requested, "requested"),
+ (
+ RadrootsTradeWorkflowState::RevisionProposed,
+ "revision_proposed",
+ ),
+ (
+ RadrootsTradeWorkflowState::AgreedPendingRhi,
+ "agreed_pending_rhi",
+ ),
+ (RadrootsTradeWorkflowState::Committed, "committed"),
+ (RadrootsTradeWorkflowState::Declined, "declined"),
+ (RadrootsTradeWorkflowState::Cancelled, "cancelled"),
+ (RadrootsTradeWorkflowState::Invalid, "invalid"),
+ ] {
+ assert_eq!(serde_json::to_value(&state).unwrap(), wire_name);
+ assert_eq!(
+ serde_json::from_value::<RadrootsTradeWorkflowState>(wire_name.into()).unwrap(),
+ state
+ );
+ }
+ }
+
fn event_id(raw: u8) -> RadrootsEventId {
RadrootsEventId::parse(format!("{raw:064x}")).expect("event id")
}