cli

Command-line interface for Radroots
git clone https://radroots.dev/git/cli.git
Log | Files | Refs | README | LICENSE

commit b1a29e8a80a241d2b8eaa698d594849819849d87
parent 0267db462bacbfcd331f0dc346448453b3662b97
Author: triesap <tyson@radroots.org>
Date:   Tue, 28 Apr 2026 19:56:41 +0000

cli: stabilize order issue output

Diffstat:
Msrc/domain/runtime.rs | 3+++
Msrc/runtime/order.rs | 207++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
2 files changed, 181 insertions(+), 29 deletions(-)

diff --git a/src/domain/runtime.rs b/src/domain/runtime.rs @@ -1492,8 +1492,11 @@ pub struct OrderDraftItemView { #[derive(Debug, Clone, Serialize)] pub struct OrderIssueView { + pub code: String, pub field: String, pub message: String, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub event_ids: Vec<String>, } #[derive(Debug, Clone, Serialize)] diff --git a/src/runtime/order.rs b/src/runtime/order.rs @@ -993,30 +993,131 @@ fn active_order_status_reason( } fn active_order_reducer_issue_view(issue_value: RadrootsActiveOrderReducerIssue) -> OrderIssueView { - let field = match &issue_value { - RadrootsActiveOrderReducerIssue::MissingRequest => "request_event_id", - RadrootsActiveOrderReducerIssue::MultipleRequests { .. } => "request_event_id", - RadrootsActiveOrderReducerIssue::RequestPayloadInvalid { .. } => "request_payload", - RadrootsActiveOrderReducerIssue::RequestOrderIdMismatch { .. } => "order_id", - RadrootsActiveOrderReducerIssue::RequestAuthorMismatch { .. } => "buyer_pubkey", - RadrootsActiveOrderReducerIssue::RequestListingAddressInvalid { .. } => "listing_addr", - RadrootsActiveOrderReducerIssue::RequestSellerListingMismatch { .. } => "seller_pubkey", - RadrootsActiveOrderReducerIssue::DecisionPayloadInvalid { .. } => "decision_payload", - RadrootsActiveOrderReducerIssue::DecisionOrderIdMismatch { .. } => "order_id", - RadrootsActiveOrderReducerIssue::DecisionAuthorMismatch { .. } => "seller_pubkey", - RadrootsActiveOrderReducerIssue::DecisionBuyerMismatch { .. } => "buyer_pubkey", - RadrootsActiveOrderReducerIssue::DecisionSellerMismatch { .. } => "seller_pubkey", - RadrootsActiveOrderReducerIssue::DecisionListingAddressInvalid { .. } => "listing_addr", - RadrootsActiveOrderReducerIssue::DecisionListingMismatch { .. } => "listing_addr", - RadrootsActiveOrderReducerIssue::DecisionRootMismatch { .. } => "root_event_id", - RadrootsActiveOrderReducerIssue::DecisionPreviousMismatch { .. } => "prev_event_id", - RadrootsActiveOrderReducerIssue::DecisionMissingInventoryCommitments { .. } => { - "inventory_commitments" + match issue_value { + RadrootsActiveOrderReducerIssue::MissingRequest => issue_with_code( + "missing_request", + "request_event_id", + "active order reducer reported missing request", + ), + RadrootsActiveOrderReducerIssue::MultipleRequests { event_ids } => issue_with_events( + "multiple_requests", + "request_event_id", + "active order reducer reported multiple request events", + event_ids, + ), + RadrootsActiveOrderReducerIssue::RequestPayloadInvalid { event_id } => issue_with_events( + "invalid_request_payload", + "request_payload", + "active order reducer reported invalid request payload", + vec![event_id], + ), + RadrootsActiveOrderReducerIssue::RequestOrderIdMismatch { event_id } => issue_with_events( + "request_order_id_mismatch", + "order_id", + "active order reducer reported request order id mismatch", + vec![event_id], + ), + RadrootsActiveOrderReducerIssue::RequestAuthorMismatch { event_id } => issue_with_events( + "request_author_mismatch", + "buyer_pubkey", + "active order reducer reported request author mismatch", + vec![event_id], + ), + RadrootsActiveOrderReducerIssue::RequestListingAddressInvalid { event_id } => { + issue_with_events( + "invalid_request_listing_address", + "listing_addr", + "active order reducer reported invalid request listing address", + vec![event_id], + ) } - RadrootsActiveOrderReducerIssue::DecisionMissingReason { .. } => "reason", - RadrootsActiveOrderReducerIssue::ConflictingDecisions { .. } => "decision_event_id", - }; - issue(field, format!("{issue_value:?}")) + RadrootsActiveOrderReducerIssue::RequestSellerListingMismatch { event_id } => { + issue_with_events( + "request_seller_listing_mismatch", + "seller_pubkey", + "active order reducer reported request seller/listing mismatch", + vec![event_id], + ) + } + RadrootsActiveOrderReducerIssue::DecisionPayloadInvalid { event_id } => issue_with_events( + "invalid_decision_payload", + "decision_payload", + "active order reducer reported invalid decision payload", + vec![event_id], + ), + RadrootsActiveOrderReducerIssue::DecisionOrderIdMismatch { event_id } => issue_with_events( + "decision_order_id_mismatch", + "order_id", + "active order reducer reported decision order id mismatch", + vec![event_id], + ), + RadrootsActiveOrderReducerIssue::DecisionAuthorMismatch { event_id } => issue_with_events( + "decision_author_mismatch", + "seller_pubkey", + "active order reducer reported decision author mismatch", + vec![event_id], + ), + RadrootsActiveOrderReducerIssue::DecisionBuyerMismatch { event_id } => issue_with_events( + "decision_buyer_mismatch", + "buyer_pubkey", + "active order reducer reported decision buyer mismatch", + vec![event_id], + ), + RadrootsActiveOrderReducerIssue::DecisionSellerMismatch { event_id } => issue_with_events( + "decision_seller_mismatch", + "seller_pubkey", + "active order reducer reported decision seller mismatch", + vec![event_id], + ), + RadrootsActiveOrderReducerIssue::DecisionListingAddressInvalid { event_id } => { + issue_with_events( + "invalid_decision_listing_address", + "listing_addr", + "active order reducer reported invalid decision listing address", + vec![event_id], + ) + } + RadrootsActiveOrderReducerIssue::DecisionListingMismatch { event_id } => issue_with_events( + "decision_listing_mismatch", + "listing_addr", + "active order reducer reported decision listing mismatch", + vec![event_id], + ), + RadrootsActiveOrderReducerIssue::DecisionRootMismatch { event_id } => issue_with_events( + "decision_root_mismatch", + "root_event_id", + "active order reducer reported decision root mismatch", + vec![event_id], + ), + RadrootsActiveOrderReducerIssue::DecisionPreviousMismatch { event_id } => { + issue_with_events( + "decision_previous_mismatch", + "prev_event_id", + "active order reducer reported decision previous mismatch", + vec![event_id], + ) + } + RadrootsActiveOrderReducerIssue::DecisionMissingInventoryCommitments { event_id } => { + issue_with_events( + "missing_decision_inventory_commitments", + "inventory_commitments", + "active order reducer reported missing decision inventory commitments", + vec![event_id], + ) + } + RadrootsActiveOrderReducerIssue::DecisionMissingReason { event_id } => issue_with_events( + "missing_decision_decline_reason", + "reason", + "active order reducer reported missing decision decline reason", + vec![event_id], + ), + RadrootsActiveOrderReducerIssue::ConflictingDecisions { event_ids } => issue_with_events( + "conflicting_decisions", + "decision_event_id", + "active order reducer reported conflicting decisions", + event_ids, + ), + } } fn order_history_unconfigured( @@ -1216,13 +1317,15 @@ fn order_decision_view_from_resolution( "multiple seller-targeted order request events matched `{}`; refusing to choose an order root", args.key )); - view.issues = vec![issue( + view.issues = vec![issue_with_events( + "multiple_request_candidates", "request_event_id", format!( "matched {} request events for the same order id: {}", requests.len(), event_ids.join(", ") ), + event_ids, )]; view.actions = vec![format!("radroots order status get {}", args.key)]; view @@ -1374,9 +1477,11 @@ fn seller_order_request_resolution_from_receipt( } Err(error) => { skipped_count += 1; - candidate_issues.push(issue( + candidate_issues.push(issue_with_events( + "invalid_request_candidate", "request_event_id", format!("request event `{event_id}` failed seller decision preflight: {error}"), + vec![event_id], )); } } @@ -1946,10 +2051,7 @@ fn summary_for_invalid_file(path: &Path, reason: String) -> OrderSummaryView { item_count: 0, updated_at_unix: modified_unix(path).unwrap_or_default(), job: None, - issues: vec![OrderIssueView { - field: "draft".to_owned(), - message: reason, - }], + issues: vec![issue_with_code("invalid_order_draft", "draft", reason)], } } @@ -2455,9 +2557,56 @@ fn parse_listing_addr(raw: &str) -> Result<RadrootsTradeListingAddress, String> } fn issue(field: impl Into<String>, message: impl Into<String>) -> OrderIssueView { + let field = field.into(); + issue_with_code(validation_issue_code(&field), field, message) +} + +fn issue_with_code( + code: impl Into<String>, + field: impl Into<String>, + message: impl Into<String>, +) -> OrderIssueView { + OrderIssueView { + code: code.into(), + field: field.into(), + message: message.into(), + event_ids: Vec::new(), + } +} + +fn issue_with_events( + code: impl Into<String>, + field: impl Into<String>, + message: impl Into<String>, + mut event_ids: Vec<String>, +) -> OrderIssueView { + event_ids.sort(); + event_ids.dedup(); OrderIssueView { + code: code.into(), field: field.into(), message: message.into(), + event_ids, + } +} + +fn validation_issue_code(field: &str) -> String { + let mut code = String::new(); + let mut previous_separator = false; + for character in field.chars() { + if character.is_ascii_alphanumeric() { + code.push(character.to_ascii_lowercase()); + previous_separator = false; + } else if !previous_separator { + code.push('_'); + previous_separator = true; + } + } + let code = code.trim_matches('_'); + if code.is_empty() { + "validation_failed".to_owned() + } else { + format!("{code}_invalid") } }