commit 107e8a4f7248720cff7e87d3f865f84d644ef7f0
parent 7840ad9ce4bf788a4b87b00611a50bbc5fe0d5c3
Author: triesap <tyson@radroots.org>
Date: Sun, 4 Jan 2026 12:52:38 +0000
listing: Ignore \"null\" sentinel values in tag encoding
- Treat case-insensitive \"null\" strings as empty in clean_value
- Prevent emitting tags with \"null\" values during listing tag build
- Add regression test covering listing fields, location, and image URL
- Apply clean_value fix consistently across `radroots-events-codec` and trade codec
Diffstat:
5 files changed, 37 insertions(+), 2 deletions(-)
diff --git a/events-codec/src/listing/tags.rs b/events-codec/src/listing/tags.rs
@@ -566,7 +566,7 @@ fn push_tag_value(tags: &mut Vec<Vec<String>>, key: &str, value: &str) {
fn clean_value(value: &str) -> Option<String> {
let trimmed = value.trim();
- if trimmed.is_empty() {
+ if trimmed.is_empty() || trimmed.eq_ignore_ascii_case("null") {
None
} else {
Some(trimmed.to_string())
diff --git a/events-codec/tests/listing.rs b/events-codec/tests/listing.rs
@@ -368,3 +368,32 @@ fn listing_tags_full_includes_status_tag() {
&& t.get(1).map(|s| s.as_str()) == Some("active")
}));
}
+
+#[test]
+fn listing_build_tags_ignores_null_strings() {
+ let mut listing = sample_listing_full("listing-1");
+ listing.product.summary = Some("null".to_string());
+ listing.product.process = Some("null".to_string());
+ listing.product.lot = Some("null".to_string());
+ listing.product.location = Some("null".to_string());
+ listing.product.profile = Some("null".to_string());
+ listing.product.year = Some("null".to_string());
+ listing.location = Some(RadrootsListingLocation {
+ primary: "Moyobamba".to_string(),
+ city: Some("null".to_string()),
+ region: Some("San Martin".to_string()),
+ country: Some("null".to_string()),
+ lat: Some(-6.0346),
+ lng: Some(-76.9714),
+ geohash: None,
+ });
+ listing.images = Some(vec![RadrootsListingImage {
+ url: "null".to_string(),
+ size: None,
+ }]);
+
+ let tags = listing_build_tags(&listing).unwrap();
+ assert!(!tags
+ .iter()
+ .any(|tag| tag.iter().any(|value| value == "null")));
+}
diff --git a/events/bindings/ts/src/types.ts b/events/bindings/ts/src/types.ts
@@ -1,3 +1,5 @@
+import type { RadrootsCoreDecimal, RadrootsCoreDiscount, RadrootsCoreDiscountValue, RadrootsCoreMoney, RadrootsCorePercent, RadrootsCoreQuantity, RadrootsCoreQuantityPrice, RadrootsCoreUnit } from "@radroots/core-bindings";
+
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type JobFeedbackStatus = "payment_required" | "processing" | "error" | "success" | "partial";
diff --git a/trade/bindings/ts/src/types.ts b/trade/bindings/ts/src/types.ts
@@ -1,3 +1,7 @@
+import type { RadrootsListingImage, RadrootsNostrEventPtr, RadrootsPlotRef, RadrootsResourceAreaRef } from "@radroots/events-bindings";
+
+import type { RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreDiscount, RadrootsCoreDiscountValue, RadrootsCoreMoney, RadrootsCoreQuantity, RadrootsCoreQuantityPrice, RadrootsCoreUnit } from "@radroots/core-bindings";
+
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type RadrootsListing = { d_tag: string, farm: RadrootsListingFarmRef, product: RadrootsListingProduct, primary_bin_id: string, bins: Array<RadrootsListingBin>, resource_area?: RadrootsResourceAreaRef | null, plot?: RadrootsPlotRef | null, discounts?: RadrootsCoreDiscount[] | null, inventory_available?: RadrootsCoreDecimal | null, availability?: RadrootsListingAvailability | null, delivery_method?: RadrootsListingDeliveryMethod | null, location?: RadrootsListingLocation | null, images?: RadrootsListingImage[] | null, };
diff --git a/trade/src/listing/codec.rs b/trade/src/listing/codec.rs
@@ -680,7 +680,7 @@ mod tests {
fn clean_value(value: &str) -> Option<String> {
let trimmed = value.trim();
- if trimmed.is_empty() {
+ if trimmed.is_empty() || trimmed.eq_ignore_ascii_case("null") {
None
} else {
Some(trimmed.to_string())