lib

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

domain_encode_non_serde.rs (44077B)


      1 #[path = "../src/test_fixtures.rs"]
      2 mod test_fixtures;
      3 
      4 use std::str::FromStr;
      5 
      6 use radroots_core::{
      7     RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCoreQuantity,
      8     RadrootsCoreQuantityPrice, RadrootsCoreUnit,
      9 };
     10 use radroots_events::{
     11     coop::{RadrootsCoop, RadrootsCoopLocation, RadrootsCoopRef},
     12     document::{RadrootsDocument, RadrootsDocumentSubject},
     13     farm::{
     14         RadrootsFarm, RadrootsFarmLocation, RadrootsFarmRef, RadrootsGcsLocation,
     15         RadrootsGeoJsonPoint, RadrootsGeoJsonPolygon,
     16     },
     17     ids::{RadrootsDTag, RadrootsInventoryBinId},
     18     listing::{
     19         RadrootsListing, RadrootsListingAvailability, RadrootsListingBin,
     20         RadrootsListingDeliveryMethod, RadrootsListingLocation, RadrootsListingProduct,
     21     },
     22     plot::{RadrootsPlot, RadrootsPlotLocation, RadrootsPlotRef},
     23     resource_area::{RadrootsResourceArea, RadrootsResourceAreaLocation, RadrootsResourceAreaRef},
     24     resource_cap::{RadrootsResourceHarvestCap, RadrootsResourceHarvestProduct},
     25 };
     26 use radroots_events_codec::coop::encode::{coop_build_tags, coop_ref_tags};
     27 use radroots_events_codec::coop::list_sets::{coop_members_farms_list_set, coop_members_list_set};
     28 use radroots_events_codec::document::encode::document_build_tags;
     29 use radroots_events_codec::error::EventEncodeError;
     30 use radroots_events_codec::farm::encode::{farm_build_tags, farm_ref_tags};
     31 use radroots_events_codec::farm::list_sets::{farm_listings_list_set, farm_members_list_set};
     32 use radroots_events_codec::listing::encode::listing_build_tags;
     33 use radroots_events_codec::listing::tags::{
     34     ListingTagOptions, listing_tags_full, listing_tags_with_options,
     35 };
     36 use radroots_events_codec::plot::encode::{plot_address, plot_build_tags};
     37 use radroots_events_codec::resource_area::encode::{
     38     resource_area_build_tags, resource_area_ref_tags,
     39 };
     40 use radroots_events_codec::resource_area::list_sets::{
     41     resource_area_members_farms_list_set, resource_area_members_plots_list_set,
     42     resource_area_stewards_list_set,
     43 };
     44 use radroots_events_codec::resource_cap::encode::resource_harvest_cap_build_tags;
     45 use test_fixtures::FIXTURE_ALICE_PUBLIC_KEY_HEX;
     46 
     47 const VALID_PUBKEY: &str = FIXTURE_ALICE_PUBLIC_KEY_HEX;
     48 const VALID_FARM_D_TAG: &str = "AAAAAAAAAAAAAAAAAAAAAA";
     49 const VALID_PLOT_D_TAG: &str = "AAAAAAAAAAAAAAAAAAAAAQ";
     50 const VALID_COOP_D_TAG: &str = "BAAAAAAAAAAAAAAAAAAAAA";
     51 const VALID_AREA_D_TAG: &str = "AAAAAAAAAAAAAAAAAAAAAw";
     52 const VALID_CAP_D_TAG: &str = "AAAAAAAAAAAAAAAAAAAABA";
     53 const VALID_DOC_D_TAG: &str = "AAAAAAAAAAAAAAAAAAAAAg";
     54 
     55 fn decimal(value: &str) -> RadrootsCoreDecimal {
     56     RadrootsCoreDecimal::from_str(value).expect("valid decimal")
     57 }
     58 
     59 fn listing_d_tag(raw: &str) -> RadrootsDTag {
     60     raw.parse().unwrap()
     61 }
     62 
     63 fn bin_id(raw: &str) -> RadrootsInventoryBinId {
     64     raw.parse().unwrap()
     65 }
     66 
     67 fn sample_gcs(geohash: &str) -> RadrootsGcsLocation {
     68     RadrootsGcsLocation {
     69         lat: 37.0,
     70         lng: -122.0,
     71         geohash: geohash.to_string(),
     72         point: RadrootsGeoJsonPoint {
     73             r#type: "Point".to_string(),
     74             coordinates: [-122.0, 37.0],
     75         },
     76         polygon: RadrootsGeoJsonPolygon {
     77             r#type: "Polygon".to_string(),
     78             coordinates: vec![vec![
     79                 [-122.0, 37.0],
     80                 [-122.0, 37.0001],
     81                 [-122.0001, 37.0001],
     82                 [-122.0, 37.0],
     83             ]],
     84         },
     85         accuracy: None,
     86         altitude: None,
     87         tag_0: None,
     88         label: None,
     89         area: None,
     90         elevation: None,
     91         soil: None,
     92         climate: None,
     93         gc_id: None,
     94         gc_name: None,
     95         gc_admin1_id: None,
     96         gc_admin1_name: None,
     97         gc_country_id: None,
     98         gc_country_name: None,
     99     }
    100 }
    101 
    102 fn sample_coop() -> RadrootsCoop {
    103     RadrootsCoop {
    104         d_tag: VALID_COOP_D_TAG.to_string(),
    105         name: "Test Coop".to_string(),
    106         about: None,
    107         website: None,
    108         picture: None,
    109         banner: None,
    110         location: Some(RadrootsCoopLocation {
    111             primary: None,
    112             city: None,
    113             region: None,
    114             country: None,
    115             gcs: sample_gcs("9q8yy"),
    116         }),
    117         tags: Some(vec!["regional".to_string()]),
    118     }
    119 }
    120 
    121 fn sample_farm() -> RadrootsFarm {
    122     RadrootsFarm {
    123         d_tag: VALID_FARM_D_TAG.to_string(),
    124         name: "Test Farm".to_string(),
    125         about: None,
    126         website: None,
    127         picture: None,
    128         banner: None,
    129         location: Some(RadrootsFarmLocation {
    130             primary: None,
    131             city: None,
    132             region: None,
    133             country: None,
    134             gcs: Some(sample_gcs("9q8yy")),
    135         }),
    136         tags: Some(vec!["orchard".to_string()]),
    137     }
    138 }
    139 
    140 fn sample_plot() -> RadrootsPlot {
    141     RadrootsPlot {
    142         d_tag: VALID_PLOT_D_TAG.to_string(),
    143         farm: RadrootsFarmRef {
    144             pubkey: VALID_PUBKEY.to_string(),
    145             d_tag: VALID_FARM_D_TAG.to_string(),
    146         },
    147         name: "Plot 1".to_string(),
    148         about: None,
    149         location: Some(RadrootsPlotLocation {
    150             primary: None,
    151             city: None,
    152             region: None,
    153             country: None,
    154             gcs: sample_gcs("9q8yy"),
    155         }),
    156         tags: Some(vec!["orchard".to_string()]),
    157     }
    158 }
    159 
    160 fn sample_listing() -> RadrootsListing {
    161     let quantity =
    162         RadrootsCoreQuantity::new(RadrootsCoreDecimal::from(1u32), RadrootsCoreUnit::Each);
    163     let price_per_canonical_unit = RadrootsCoreQuantityPrice::new(
    164         RadrootsCoreMoney::new(RadrootsCoreDecimal::from(10u32), RadrootsCoreCurrency::USD),
    165         quantity.clone(),
    166     );
    167 
    168     RadrootsListing {
    169         d_tag: listing_d_tag(VALID_DOC_D_TAG),
    170         published_at: None,
    171         farm: RadrootsFarmRef {
    172             pubkey: VALID_PUBKEY.to_string(),
    173             d_tag: VALID_FARM_D_TAG.to_string(),
    174         },
    175         product: RadrootsListingProduct {
    176             key: "nutmeg".to_string(),
    177             title: "Nutmeg".to_string(),
    178             category: "spice".to_string(),
    179             summary: None,
    180             process: None,
    181             lot: None,
    182             location: None,
    183             profile: None,
    184             year: None,
    185         },
    186         primary_bin_id: bin_id("bin-1"),
    187         bins: vec![RadrootsListingBin {
    188             bin_id: bin_id("bin-1"),
    189             quantity,
    190             price_per_canonical_unit,
    191             display_amount: None,
    192             display_unit: None,
    193             display_label: None,
    194             display_price: None,
    195             display_price_unit: None,
    196         }],
    197         resource_area: None,
    198         plot: None,
    199         discounts: None,
    200         inventory_available: Some(decimal("12")),
    201         availability: Some(RadrootsListingAvailability::Window {
    202             start: Some(1),
    203             end: Some(2),
    204         }),
    205         delivery_method: Some(RadrootsListingDeliveryMethod::Shipping),
    206         location: None,
    207         images: None,
    208     }
    209 }
    210 
    211 fn sample_resource_area() -> RadrootsResourceArea {
    212     RadrootsResourceArea {
    213         d_tag: VALID_AREA_D_TAG.to_string(),
    214         name: "Banda Grove".to_string(),
    215         about: None,
    216         location: RadrootsResourceAreaLocation {
    217             primary: None,
    218             city: None,
    219             region: None,
    220             country: None,
    221             gcs: sample_gcs("pmb5v"),
    222         },
    223         tags: Some(vec!["nutmeg".to_string()]),
    224     }
    225 }
    226 
    227 fn sample_resource_cap() -> RadrootsResourceHarvestCap {
    228     RadrootsResourceHarvestCap {
    229         d_tag: VALID_CAP_D_TAG.to_string(),
    230         resource_area: RadrootsResourceAreaRef {
    231             pubkey: VALID_PUBKEY.to_string(),
    232             d_tag: VALID_AREA_D_TAG.to_string(),
    233         },
    234         product: RadrootsResourceHarvestProduct {
    235             key: "nutmeg".to_string(),
    236             category: Some("spice".to_string()),
    237         },
    238         start: 1,
    239         end: 2,
    240         cap_quantity: RadrootsCoreQuantity::new(decimal("1000"), RadrootsCoreUnit::MassG),
    241         display_amount: None,
    242         display_unit: None,
    243         display_label: None,
    244         tags: None,
    245     }
    246 }
    247 
    248 fn sample_document() -> RadrootsDocument {
    249     RadrootsDocument {
    250         d_tag: VALID_DOC_D_TAG.to_string(),
    251         doc_type: "charter".to_string(),
    252         title: "Charter".to_string(),
    253         version: "1.0.0".to_string(),
    254         summary: None,
    255         effective_at: None,
    256         body_markdown: None,
    257         subject: RadrootsDocumentSubject {
    258             pubkey: VALID_PUBKEY.to_string(),
    259             address: Some(format!("30340:{VALID_PUBKEY}:{VALID_FARM_D_TAG}")),
    260         },
    261         tags: Some(vec!["policy".to_string()]),
    262     }
    263 }
    264 
    265 #[test]
    266 fn coop_encode_and_list_set_paths() {
    267     let tags = coop_build_tags(&sample_coop()).expect("coop tags");
    268     assert!(
    269         tags.iter()
    270             .any(|tag| tag.first().map(|v| v.as_str()) == Some("d"))
    271     );
    272     assert!(
    273         tags.iter()
    274             .any(|tag| tag.first().map(|v| v.as_str()) == Some("t"))
    275     );
    276     assert!(
    277         tags.iter()
    278             .any(|tag| tag.first().map(|v| v.as_str()) == Some("g"))
    279     );
    280 
    281     let mut coop = sample_coop();
    282     coop.tags = None;
    283     coop.location = None;
    284     let tags = coop_build_tags(&coop).expect("coop tags without optional fields");
    285     assert!(
    286         !tags
    287             .iter()
    288             .any(|tag| tag.first().map(|v| v.as_str()) == Some("t"))
    289     );
    290     assert!(
    291         !tags
    292             .iter()
    293             .any(|tag| tag.first().map(|v| v.as_str()) == Some("g"))
    294     );
    295 
    296     let mut coop = sample_coop();
    297     coop.d_tag = " ".to_string();
    298     let err = coop_build_tags(&coop).expect_err("empty d_tag");
    299     assert!(matches!(err, EventEncodeError::EmptyRequiredField("d_tag")));
    300 
    301     let mut coop = sample_coop();
    302     coop.name = " ".to_string();
    303     let err = coop_build_tags(&coop).expect_err("empty name");
    304     assert!(matches!(err, EventEncodeError::EmptyRequiredField("name")));
    305 
    306     let mut coop = sample_coop();
    307     coop.location.as_mut().expect("location").gcs.geohash = " ".to_string();
    308     let err = coop_build_tags(&coop).expect_err("empty geohash");
    309     assert!(matches!(
    310         err,
    311         EventEncodeError::EmptyRequiredField("location.gcs.geohash")
    312     ));
    313 
    314     let mut coop = sample_coop();
    315     coop.d_tag = "invalid".to_string();
    316     let err = coop_build_tags(&coop).expect_err("invalid d_tag");
    317     assert!(matches!(err, EventEncodeError::InvalidField("d_tag")));
    318 
    319     let tags = coop_ref_tags(&RadrootsCoopRef {
    320         pubkey: VALID_PUBKEY.to_string(),
    321         d_tag: VALID_COOP_D_TAG.to_string(),
    322     })
    323     .expect("coop ref tags");
    324     assert_eq!(tags.len(), 2);
    325 
    326     let err = coop_ref_tags(&RadrootsCoopRef {
    327         pubkey: " ".to_string(),
    328         d_tag: VALID_COOP_D_TAG.to_string(),
    329     })
    330     .expect_err("empty coop pubkey");
    331     assert!(matches!(
    332         err,
    333         EventEncodeError::EmptyRequiredField("coop.pubkey")
    334     ));
    335 
    336     let err = coop_ref_tags(&RadrootsCoopRef {
    337         pubkey: VALID_PUBKEY.to_string(),
    338         d_tag: " ".to_string(),
    339     })
    340     .expect_err("empty coop d_tag");
    341     assert!(matches!(
    342         err,
    343         EventEncodeError::EmptyRequiredField("coop.d_tag")
    344     ));
    345 
    346     let err = coop_ref_tags(&RadrootsCoopRef {
    347         pubkey: VALID_PUBKEY.to_string(),
    348         d_tag: "invalid".to_string(),
    349     })
    350     .expect_err("invalid coop d_tag");
    351     assert!(matches!(err, EventEncodeError::InvalidField("coop.d_tag")));
    352 
    353     let err = coop_members_list_set("invalid", ["member"]).expect_err("invalid coop id");
    354     assert!(matches!(err, EventEncodeError::InvalidField("coop_id")));
    355 
    356     let err = coop_members_list_set(" ", ["member"]).expect_err("empty coop id");
    357     assert!(matches!(
    358         err,
    359         EventEncodeError::EmptyRequiredField("coop_id")
    360     ));
    361 
    362     let members = coop_members_list_set(VALID_COOP_D_TAG, ["member"]).expect("members list set");
    363     assert_eq!(members.entries.len(), 1);
    364     assert_eq!(members.entries[0].tag, "p");
    365 
    366     let err = coop_members_list_set(VALID_COOP_D_TAG, [" "]).expect_err("empty member entry");
    367     assert!(matches!(
    368         err,
    369         EventEncodeError::EmptyRequiredField("entry.values")
    370     ));
    371 
    372     let member_farms = coop_members_farms_list_set(
    373         VALID_COOP_D_TAG,
    374         vec![RadrootsFarmRef {
    375             pubkey: VALID_PUBKEY.to_string(),
    376             d_tag: VALID_FARM_D_TAG.to_string(),
    377         }],
    378     )
    379     .expect("member farms list set");
    380     assert_eq!(member_farms.entries.len(), 2);
    381     assert_eq!(member_farms.entries[0].tag, "a");
    382     assert_eq!(member_farms.entries[1].tag, "p");
    383 
    384     let member_farms_from_array = coop_members_farms_list_set(
    385         VALID_COOP_D_TAG,
    386         [RadrootsFarmRef {
    387             pubkey: VALID_PUBKEY.to_string(),
    388             d_tag: VALID_FARM_D_TAG.to_string(),
    389         }],
    390     )
    391     .expect("member farms list set array");
    392     assert_eq!(member_farms_from_array.entries.len(), 2);
    393     assert_eq!(member_farms_from_array.entries[0].tag, "a");
    394     assert_eq!(member_farms_from_array.entries[1].tag, "p");
    395 
    396     let err = coop_members_farms_list_set(
    397         VALID_COOP_D_TAG,
    398         vec![RadrootsFarmRef {
    399             pubkey: " ".to_string(),
    400             d_tag: VALID_FARM_D_TAG.to_string(),
    401         }],
    402     )
    403     .expect_err("empty farm pubkey");
    404     assert!(matches!(
    405         err,
    406         EventEncodeError::EmptyRequiredField("farm.pubkey")
    407     ));
    408 
    409     let err = coop_members_farms_list_set(
    410         VALID_COOP_D_TAG,
    411         vec![RadrootsFarmRef {
    412             pubkey: VALID_PUBKEY.to_string(),
    413             d_tag: " ".to_string(),
    414         }],
    415     )
    416     .expect_err("empty farm d_tag");
    417     assert!(matches!(
    418         err,
    419         EventEncodeError::EmptyRequiredField("farm.d_tag")
    420     ));
    421 
    422     let err = coop_members_farms_list_set(
    423         VALID_COOP_D_TAG,
    424         vec![RadrootsFarmRef {
    425             pubkey: VALID_PUBKEY.to_string(),
    426             d_tag: "invalid".to_string(),
    427         }],
    428     )
    429     .expect_err("invalid farm d_tag");
    430     assert!(matches!(err, EventEncodeError::InvalidField("farm.d_tag")));
    431 }
    432 
    433 #[test]
    434 fn farm_encode_and_list_set_paths() {
    435     let tags = farm_build_tags(&sample_farm()).expect("farm tags");
    436     assert!(
    437         tags.iter()
    438             .any(|tag| tag.first().map(|v| v.as_str()) == Some("d"))
    439     );
    440     assert!(
    441         tags.iter()
    442             .any(|tag| tag.first().map(|v| v.as_str()) == Some("t"))
    443     );
    444     assert!(
    445         tags.iter()
    446             .any(|tag| tag.first().map(|v| v.as_str()) == Some("g"))
    447     );
    448 
    449     let mut farm = sample_farm();
    450     farm.tags = None;
    451     farm.location = None;
    452     let tags = farm_build_tags(&farm).expect("farm tags without optional fields");
    453     assert!(
    454         !tags
    455             .iter()
    456             .any(|tag| tag.first().map(|v| v.as_str()) == Some("t"))
    457     );
    458     assert!(
    459         !tags
    460             .iter()
    461             .any(|tag| tag.first().map(|v| v.as_str()) == Some("g"))
    462     );
    463 
    464     let mut farm = sample_farm();
    465     farm.d_tag = " ".to_string();
    466     let err = farm_build_tags(&farm).expect_err("empty d_tag");
    467     assert!(matches!(err, EventEncodeError::EmptyRequiredField("d_tag")));
    468 
    469     let mut farm = sample_farm();
    470     farm.name = " ".to_string();
    471     let err = farm_build_tags(&farm).expect_err("empty name");
    472     assert!(matches!(err, EventEncodeError::EmptyRequiredField("name")));
    473 
    474     let mut farm = sample_farm();
    475     farm.location
    476         .as_mut()
    477         .expect("location")
    478         .gcs
    479         .as_mut()
    480         .expect("gcs")
    481         .geohash = " ".to_string();
    482     let err = farm_build_tags(&farm).expect_err("empty geohash");
    483     assert!(matches!(
    484         err,
    485         EventEncodeError::EmptyRequiredField("location.gcs.geohash")
    486     ));
    487 
    488     let mut farm = sample_farm();
    489     farm.location.as_mut().expect("location").gcs = None;
    490     let tags = farm_build_tags(&farm).expect("farm location without geo");
    491     assert!(
    492         !tags
    493             .iter()
    494             .any(|tag| tag.first().map(|v| v.as_str()) == Some("g"))
    495     );
    496 
    497     let tags = farm_ref_tags(&RadrootsFarmRef {
    498         pubkey: VALID_PUBKEY.to_string(),
    499         d_tag: VALID_FARM_D_TAG.to_string(),
    500     })
    501     .expect("farm ref tags");
    502     assert_eq!(tags.len(), 2);
    503 
    504     let err = farm_ref_tags(&RadrootsFarmRef {
    505         pubkey: " ".to_string(),
    506         d_tag: VALID_FARM_D_TAG.to_string(),
    507     })
    508     .expect_err("empty farm pubkey");
    509     assert!(matches!(
    510         err,
    511         EventEncodeError::EmptyRequiredField("farm.pubkey")
    512     ));
    513 
    514     let err = farm_ref_tags(&RadrootsFarmRef {
    515         pubkey: VALID_PUBKEY.to_string(),
    516         d_tag: " ".to_string(),
    517     })
    518     .expect_err("empty farm d_tag");
    519     assert!(matches!(
    520         err,
    521         EventEncodeError::EmptyRequiredField("farm.d_tag")
    522     ));
    523 
    524     let err = farm_ref_tags(&RadrootsFarmRef {
    525         pubkey: VALID_PUBKEY.to_string(),
    526         d_tag: "invalid".to_string(),
    527     })
    528     .expect_err("invalid farm d_tag");
    529     assert!(matches!(err, EventEncodeError::InvalidField("farm.d_tag")));
    530 
    531     let err = farm_members_list_set("invalid", ["member"]).expect_err("invalid farm id");
    532     assert!(matches!(err, EventEncodeError::InvalidField("farm_id")));
    533 
    534     let err = farm_members_list_set(VALID_FARM_D_TAG, [" "]).expect_err("empty member entry");
    535     assert!(matches!(
    536         err,
    537         EventEncodeError::EmptyRequiredField("entry.values")
    538     ));
    539 
    540     let err = farm_listings_list_set(VALID_FARM_D_TAG, VALID_PUBKEY, [" "])
    541         .expect_err("empty listing id");
    542     assert!(matches!(
    543         err,
    544         EventEncodeError::EmptyRequiredField("listing_id")
    545     ));
    546 
    547     let err = farm_listings_list_set(VALID_FARM_D_TAG, VALID_PUBKEY, ["invalid"])
    548         .expect_err("invalid listing id");
    549     assert!(matches!(err, EventEncodeError::InvalidField("listing_id")));
    550 }
    551 
    552 #[test]
    553 fn plot_encode_paths() {
    554     let tags = plot_build_tags(&sample_plot()).expect("plot tags");
    555     assert!(
    556         tags.iter()
    557             .any(|tag| tag.first().map(|v| v.as_str()) == Some("a"))
    558     );
    559     assert!(
    560         tags.iter()
    561             .any(|tag| tag.first().map(|v| v.as_str()) == Some("p"))
    562     );
    563     assert!(
    564         tags.iter()
    565             .any(|tag| tag.first().map(|v| v.as_str()) == Some("t"))
    566     );
    567     assert!(
    568         tags.iter()
    569             .any(|tag| tag.first().map(|v| v.as_str()) == Some("g"))
    570     );
    571 
    572     let mut plot = sample_plot();
    573     plot.tags = None;
    574     plot.location = None;
    575     let tags = plot_build_tags(&plot).expect("plot tags without optional fields");
    576     assert!(
    577         !tags
    578             .iter()
    579             .any(|tag| tag.first().map(|v| v.as_str()) == Some("t"))
    580     );
    581     assert!(
    582         !tags
    583             .iter()
    584             .any(|tag| tag.first().map(|v| v.as_str()) == Some("g"))
    585     );
    586 
    587     let err = plot_address(" ", VALID_PLOT_D_TAG).expect_err("empty author pubkey");
    588     assert!(matches!(
    589         err,
    590         EventEncodeError::EmptyRequiredField("plot.author_pubkey")
    591     ));
    592 
    593     let err = plot_address(VALID_PUBKEY, " ").expect_err("empty plot d_tag");
    594     assert!(matches!(
    595         err,
    596         EventEncodeError::EmptyRequiredField("plot.d_tag")
    597     ));
    598 
    599     let err = plot_address(VALID_PUBKEY, "invalid").expect_err("invalid plot d_tag");
    600     assert!(matches!(err, EventEncodeError::InvalidField("plot.d_tag")));
    601 
    602     let mut plot = sample_plot();
    603     plot.d_tag = " ".to_string();
    604     let err = plot_build_tags(&plot).expect_err("empty plot d_tag");
    605     assert!(matches!(err, EventEncodeError::EmptyRequiredField("d_tag")));
    606 
    607     let mut plot = sample_plot();
    608     plot.name = " ".to_string();
    609     let err = plot_build_tags(&plot).expect_err("empty plot name");
    610     assert!(matches!(err, EventEncodeError::EmptyRequiredField("name")));
    611 
    612     let mut plot = sample_plot();
    613     plot.farm.pubkey = " ".to_string();
    614     let err = plot_build_tags(&plot).expect_err("empty farm pubkey");
    615     assert!(matches!(
    616         err,
    617         EventEncodeError::EmptyRequiredField("farm.pubkey")
    618     ));
    619 
    620     let mut plot = sample_plot();
    621     plot.farm.d_tag = " ".to_string();
    622     let err = plot_build_tags(&plot).expect_err("empty farm d_tag");
    623     assert!(matches!(
    624         err,
    625         EventEncodeError::EmptyRequiredField("farm.d_tag")
    626     ));
    627 
    628     let mut plot = sample_plot();
    629     plot.farm.d_tag = "invalid".to_string();
    630     let err = plot_build_tags(&plot).expect_err("invalid farm d_tag");
    631     assert!(matches!(err, EventEncodeError::InvalidField("farm.d_tag")));
    632 
    633     let mut plot = sample_plot();
    634     plot.location.as_mut().expect("location").gcs.geohash = " ".to_string();
    635     let err = plot_build_tags(&plot).expect_err("empty geohash");
    636     assert!(matches!(
    637         err,
    638         EventEncodeError::EmptyRequiredField("location.gcs.geohash")
    639     ));
    640 }
    641 
    642 #[test]
    643 fn listing_encode_paths() {
    644     let listing = sample_listing();
    645     let tags = listing_build_tags(&listing).expect("listing tags");
    646     assert!(
    647         tags.iter()
    648             .any(|tag| tag.first().map(|v| v.as_str()) == Some("d"))
    649     );
    650 
    651     let full_tags = listing_tags_full(&listing).expect("listing full tags");
    652     assert!(full_tags.iter().any(|tag| {
    653         tag.first().map(|v| v.as_str()) == Some("inventory")
    654             && tag.get(1).map(|v| v.as_str()) == Some("12")
    655     }));
    656     assert!(full_tags.iter().any(|tag| {
    657         tag.first().map(|v| v.as_str()) == Some("radroots:availability_start")
    658             && tag.get(1).map(|v| v.as_str()) == Some("1")
    659     }));
    660     assert!(full_tags.iter().any(|tag| {
    661         tag.first().map(|v| v.as_str()) == Some("expires_at")
    662             && tag.get(1).map(|v| v.as_str()) == Some("2")
    663     }));
    664     assert!(full_tags.iter().any(|tag| {
    665         tag.first().map(|v| v.as_str()) == Some("delivery")
    666             && tag.get(1).map(|v| v.as_str()) == Some("shipping")
    667     }));
    668 
    669     let with_trade_fields: fn() -> ListingTagOptions = ListingTagOptions::with_trade_fields;
    670     let option_tags =
    671         listing_tags_with_options(&listing, with_trade_fields()).expect("listing option tags");
    672     assert!(option_tags.iter().any(|tag| {
    673         tag.first().map(|v| v.as_str()) == Some("inventory")
    674             && tag.get(1).map(|v| v.as_str()) == Some("12")
    675     }));
    676 
    677     let mut listing_with_display_fallback = sample_listing();
    678     listing_with_display_fallback.bins[0].quantity = listing_with_display_fallback.bins[0]
    679         .quantity
    680         .clone()
    681         .with_label("fallback-label");
    682     listing_with_display_fallback.bins[0].display_amount = Some(decimal("1"));
    683     listing_with_display_fallback.bins[0].display_unit = Some(RadrootsCoreUnit::Each);
    684     listing_with_display_fallback.bins[0].display_label = None;
    685     let display_tags =
    686         listing_tags_with_options(&listing_with_display_fallback, ListingTagOptions::default())
    687             .expect("listing tags with display fallback");
    688     assert!(display_tags.iter().any(|tag| {
    689         tag.first().map(|v| v.as_str()) == Some("radroots:bin")
    690             && tag.last().map(|v| v.as_str()) == Some("fallback-label")
    691     }));
    692 
    693     let mut listing_with_geohash = sample_listing();
    694     listing_with_geohash.location = Some(RadrootsListingLocation {
    695         primary: "Origin".to_string(),
    696         city: None,
    697         region: None,
    698         country: None,
    699         lat: None,
    700         lng: None,
    701         geohash: Some("6gkzwgjzn".to_string()),
    702     });
    703     let decoded_tags = listing_tags_with_options(
    704         &listing_with_geohash,
    705         ListingTagOptions {
    706             include_geohash: false,
    707             include_gps: true,
    708             ..ListingTagOptions::default()
    709         },
    710     )
    711     .expect("listing tags with decoded geohash");
    712     assert!(decoded_tags.iter().any(|tag| {
    713         tag.first().map(|v| v.as_str()) == Some("l") && tag.get(2).map(|v| v.as_str()) == Some("dd")
    714     }));
    715 
    716     let mut listing_with_shared_geohash = sample_listing();
    717     listing_with_shared_geohash.location = Some(RadrootsListingLocation {
    718         primary: "Origin".to_string(),
    719         city: None,
    720         region: None,
    721         country: None,
    722         lat: None,
    723         lng: None,
    724         geohash: Some("6gkzwgjzn".to_string()),
    725     });
    726     let shared_geohash_tags =
    727         listing_tags_with_options(&listing_with_shared_geohash, ListingTagOptions::default())
    728             .expect("listing tags with shared geohash");
    729     assert!(shared_geohash_tags.iter().any(|tag| {
    730         tag.first().map(|v| v.as_str()) == Some("g")
    731             && tag.get(1).map(|v| v.as_str()) == Some("6gkzwgjzn")
    732     }));
    733 
    734     let mut listing_without_coordinates = sample_listing();
    735     listing_without_coordinates.location = Some(RadrootsListingLocation {
    736         primary: "Origin".to_string(),
    737         city: None,
    738         region: None,
    739         country: None,
    740         lat: None,
    741         lng: None,
    742         geohash: None,
    743     });
    744     let no_coordinates_tags =
    745         listing_tags_with_options(&listing_without_coordinates, ListingTagOptions::default())
    746             .expect("listing tags without coordinates");
    747     assert!(
    748         !no_coordinates_tags
    749             .iter()
    750             .any(|tag| tag.first().map(|v| v.as_str()) == Some("L"))
    751     );
    752     assert!(
    753         !no_coordinates_tags
    754             .iter()
    755             .any(|tag| tag.first().map(|v| v.as_str()) == Some("g"))
    756     );
    757 
    758     let mut listing_with_blank_optionals = sample_listing();
    759     listing_with_blank_optionals.product.summary = Some(" ".to_string());
    760     listing_with_blank_optionals.product.process = Some("null".to_string());
    761     listing_with_blank_optionals.product.location = Some(" ".to_string());
    762     listing_with_blank_optionals.product.profile = Some("null".to_string());
    763     listing_with_blank_optionals.product.year = Some(" ".to_string());
    764     listing_with_blank_optionals.location = Some(RadrootsListingLocation {
    765         primary: " ".to_string(),
    766         city: Some(" ".to_string()),
    767         region: Some("null".to_string()),
    768         country: Some(" ".to_string()),
    769         lat: None,
    770         lng: None,
    771         geohash: None,
    772     });
    773     let blank_optional_tags =
    774         listing_tags_with_options(&listing_with_blank_optionals, ListingTagOptions::default())
    775             .expect("listing tags with blank optional values");
    776     assert!(
    777         !blank_optional_tags
    778             .iter()
    779             .any(|tag| tag.first().map(|v| v.as_str()) == Some("summary"))
    780     );
    781     assert!(
    782         !blank_optional_tags
    783             .iter()
    784             .any(|tag| tag.first().map(|v| v.as_str()) == Some("location"))
    785     );
    786 
    787     let mut listing_no_gps = sample_listing();
    788     listing_no_gps.location = Some(RadrootsListingLocation {
    789         primary: "Origin".to_string(),
    790         city: None,
    791         region: None,
    792         country: None,
    793         lat: Some(37.0),
    794         lng: Some(-122.0),
    795         geohash: None,
    796     });
    797     let no_gps_tags = listing_tags_with_options(
    798         &listing_no_gps,
    799         ListingTagOptions {
    800             include_gps: false,
    801             ..ListingTagOptions::default()
    802         },
    803     )
    804     .expect("listing tags without gps labels");
    805     assert!(
    806         !no_gps_tags
    807             .iter()
    808             .any(|tag| tag.first().map(|v| v.as_str()) == Some("L"))
    809     );
    810 
    811     let mut listing_without_availability = sample_listing();
    812     listing_without_availability.availability = None;
    813     let no_availability_tags =
    814         listing_tags_with_options(&listing_without_availability, with_trade_fields())
    815             .expect("listing tags without availability");
    816     assert!(
    817         !no_availability_tags
    818             .iter()
    819             .any(|tag| { tag.first().map(|v| v.as_str()) == Some("radroots:availability_start") })
    820     );
    821 
    822     let mut listing_pickup = sample_listing();
    823     listing_pickup.delivery_method = Some(RadrootsListingDeliveryMethod::Pickup);
    824     let pickup_tags = listing_tags_with_options(&listing_pickup, with_trade_fields())
    825         .expect("listing tags with pickup delivery");
    826     assert!(pickup_tags.iter().any(|tag| {
    827         tag.first().map(|v| v.as_str()) == Some("delivery")
    828             && tag.get(1).map(|v| v.as_str()) == Some("pickup")
    829     }));
    830 
    831     let mut listing_local = sample_listing();
    832     listing_local.delivery_method = Some(RadrootsListingDeliveryMethod::LocalDelivery);
    833     let local_tags = listing_tags_with_options(&listing_local, with_trade_fields())
    834         .expect("listing tags with local delivery");
    835     assert!(local_tags.iter().any(|tag| {
    836         tag.first().map(|v| v.as_str()) == Some("delivery")
    837             && tag.get(1).map(|v| v.as_str()) == Some("local_delivery")
    838     }));
    839 
    840     let mut listing_other_delivery = sample_listing();
    841     listing_other_delivery.delivery_method = Some(RadrootsListingDeliveryMethod::Other {
    842         method: "courier".to_string(),
    843     });
    844     let other_delivery_tags =
    845         listing_tags_with_options(&listing_other_delivery, with_trade_fields())
    846             .expect("listing tags with other delivery");
    847     assert!(other_delivery_tags.iter().any(|tag| {
    848         tag.first().map(|v| v.as_str()) == Some("delivery")
    849             && tag.get(1).map(|v| v.as_str()) == Some("other")
    850             && tag.get(2).map(|v| v.as_str()) == Some("courier")
    851     }));
    852 
    853     let mut invalid = sample_listing();
    854     invalid.bins[0].display_price = None;
    855     invalid.bins[0].display_price_unit = Some(RadrootsCoreUnit::Each);
    856     let err = listing_tags_with_options(&invalid, ListingTagOptions::default())
    857         .expect_err("missing display price");
    858     assert!(matches!(
    859         err,
    860         EventEncodeError::EmptyRequiredField("bin.display_price")
    861     ));
    862 
    863     let mut invalid = sample_listing();
    864     invalid.bins[0].display_price = Some(RadrootsCoreMoney::new(
    865         decimal("10"),
    866         RadrootsCoreCurrency::USD,
    867     ));
    868     invalid.bins[0].display_price_unit = None;
    869     let err = listing_tags_with_options(&invalid, ListingTagOptions::default())
    870         .expect_err("missing display price unit");
    871     assert!(matches!(
    872         err,
    873         EventEncodeError::EmptyRequiredField("bin.display_price_unit")
    874     ));
    875 
    876     let mut listing_with_display_price = sample_listing();
    877     listing_with_display_price.bins[0].display_price = Some(RadrootsCoreMoney::new(
    878         decimal("10"),
    879         RadrootsCoreCurrency::USD,
    880     ));
    881     listing_with_display_price.bins[0].display_price_unit = Some(RadrootsCoreUnit::Each);
    882     let display_price_tags =
    883         listing_tags_with_options(&listing_with_display_price, ListingTagOptions::default())
    884             .expect("listing tags with display price");
    885     assert!(display_price_tags.iter().any(|tag| {
    886         tag.first().map(|v| v.as_str()) == Some("radroots:price")
    887             && tag.get(6).map(|v| v.as_str()) == Some("10")
    888             && tag.get(7).map(|v| v.as_str()) == Some("each")
    889     }));
    890 
    891     let mut invalid = listing_with_display_price.clone();
    892     invalid.bins[0].display_price = Some(RadrootsCoreMoney::new(
    893         decimal("10"),
    894         RadrootsCoreCurrency::EUR,
    895     ));
    896     let err = listing_tags_with_options(&invalid, ListingTagOptions::default())
    897         .expect_err("display price currency mismatch");
    898     assert!(matches!(
    899         err,
    900         EventEncodeError::EmptyRequiredField("bin.display_price")
    901     ));
    902 
    903     let mut invalid = sample_listing();
    904     invalid.bins[0].display_amount = None;
    905     invalid.bins[0].display_unit = Some(RadrootsCoreUnit::Each);
    906     let err = listing_tags_with_options(&invalid, ListingTagOptions::default())
    907         .expect_err("missing display amount");
    908     assert!(matches!(
    909         err,
    910         EventEncodeError::EmptyRequiredField("bin.display_amount")
    911     ));
    912 
    913     let mut invalid = sample_listing();
    914     invalid.bins[0].quantity = RadrootsCoreQuantity::new(decimal("1"), RadrootsCoreUnit::MassKg);
    915     invalid.bins[0].price_per_canonical_unit = RadrootsCoreQuantityPrice::new(
    916         RadrootsCoreMoney::new(decimal("10"), RadrootsCoreCurrency::USD),
    917         RadrootsCoreQuantity::new(decimal("1"), RadrootsCoreUnit::MassG),
    918     );
    919     let err = listing_tags_with_options(&invalid, ListingTagOptions::default())
    920         .expect_err("non-canonical bin quantity");
    921     assert!(matches!(
    922         err,
    923         EventEncodeError::EmptyRequiredField("bin.quantity")
    924     ));
    925 
    926     let mut invalid = sample_listing();
    927     invalid.bins[0].price_per_canonical_unit = RadrootsCoreQuantityPrice::new(
    928         RadrootsCoreMoney::new(decimal("10"), RadrootsCoreCurrency::USD),
    929         RadrootsCoreQuantity::new(decimal("2"), RadrootsCoreUnit::Each),
    930     );
    931     let err = listing_tags_with_options(&invalid, ListingTagOptions::default())
    932         .expect_err("price must be per canonical unit");
    933     assert!(matches!(
    934         err,
    935         EventEncodeError::EmptyRequiredField("bin.price_per_canonical_unit")
    936     ));
    937 
    938     let mut invalid = sample_listing();
    939     invalid.bins[0].price_per_canonical_unit = RadrootsCoreQuantityPrice::new(
    940         RadrootsCoreMoney::new(decimal("10"), RadrootsCoreCurrency::USD),
    941         RadrootsCoreQuantity::new(decimal("1"), RadrootsCoreUnit::MassG),
    942     );
    943     let err = listing_tags_with_options(&invalid, ListingTagOptions::default())
    944         .expect_err("non-convertible bin total price");
    945     assert!(matches!(
    946         err,
    947         EventEncodeError::EmptyRequiredField("bin.price_per_canonical_unit")
    948     ));
    949 
    950     let mut invalid = sample_listing();
    951     invalid.farm.d_tag = " ".to_string();
    952     let err = listing_build_tags(&invalid).expect_err("empty listing farm d_tag");
    953     assert!(matches!(
    954         err,
    955         EventEncodeError::EmptyRequiredField("farm.d_tag")
    956     ));
    957 
    958     let mut invalid = sample_listing();
    959     invalid.bins.clear();
    960     let err =
    961         listing_tags_with_options(&invalid, ListingTagOptions::default()).expect_err("empty bins");
    962     assert!(matches!(err, EventEncodeError::EmptyRequiredField("bins")));
    963 }
    964 
    965 #[test]
    966 fn resource_area_encode_and_list_set_paths() {
    967     let tags = resource_area_build_tags(&sample_resource_area()).expect("resource area tags");
    968     assert!(
    969         tags.iter()
    970             .any(|tag| tag.first().map(|v| v.as_str()) == Some("d"))
    971     );
    972     assert!(
    973         tags.iter()
    974             .any(|tag| tag.first().map(|v| v.as_str()) == Some("g"))
    975     );
    976     assert!(
    977         tags.iter()
    978             .any(|tag| tag.first().map(|v| v.as_str()) == Some("t"))
    979     );
    980 
    981     let mut area = sample_resource_area();
    982     area.tags = None;
    983     let tags = resource_area_build_tags(&area).expect("resource area tags without optional tags");
    984     assert!(
    985         !tags
    986             .iter()
    987             .any(|tag| tag.first().map(|v| v.as_str()) == Some("t"))
    988     );
    989 
    990     let mut area = sample_resource_area();
    991     area.d_tag = " ".to_string();
    992     let err = resource_area_build_tags(&area).expect_err("empty d_tag");
    993     assert!(matches!(err, EventEncodeError::EmptyRequiredField("d_tag")));
    994 
    995     let mut area = sample_resource_area();
    996     area.name = " ".to_string();
    997     let err = resource_area_build_tags(&area).expect_err("empty name");
    998     assert!(matches!(err, EventEncodeError::EmptyRequiredField("name")));
    999 
   1000     let mut area = sample_resource_area();
   1001     area.location.gcs.geohash = " ".to_string();
   1002     let err = resource_area_build_tags(&area).expect_err("empty geohash");
   1003     assert!(matches!(
   1004         err,
   1005         EventEncodeError::EmptyRequiredField("location.gcs.geohash")
   1006     ));
   1007 
   1008     let tags = resource_area_ref_tags(&RadrootsResourceAreaRef {
   1009         pubkey: VALID_PUBKEY.to_string(),
   1010         d_tag: VALID_AREA_D_TAG.to_string(),
   1011     })
   1012     .expect("resource area ref tags");
   1013     assert_eq!(tags.len(), 2);
   1014 
   1015     let err = resource_area_ref_tags(&RadrootsResourceAreaRef {
   1016         pubkey: " ".to_string(),
   1017         d_tag: VALID_AREA_D_TAG.to_string(),
   1018     })
   1019     .expect_err("empty resource area pubkey");
   1020     assert!(matches!(
   1021         err,
   1022         EventEncodeError::EmptyRequiredField("resource_area.pubkey")
   1023     ));
   1024 
   1025     let err = resource_area_ref_tags(&RadrootsResourceAreaRef {
   1026         pubkey: VALID_PUBKEY.to_string(),
   1027         d_tag: " ".to_string(),
   1028     })
   1029     .expect_err("empty resource area d_tag");
   1030     assert!(matches!(
   1031         err,
   1032         EventEncodeError::EmptyRequiredField("resource_area.d_tag")
   1033     ));
   1034 
   1035     let err = resource_area_ref_tags(&RadrootsResourceAreaRef {
   1036         pubkey: VALID_PUBKEY.to_string(),
   1037         d_tag: "invalid".to_string(),
   1038     })
   1039     .expect_err("invalid resource area d_tag");
   1040     assert!(matches!(
   1041         err,
   1042         EventEncodeError::InvalidField("resource_area.d_tag")
   1043     ));
   1044 
   1045     let err = resource_area_members_farms_list_set(
   1046         "invalid",
   1047         vec![RadrootsFarmRef {
   1048             pubkey: VALID_PUBKEY.to_string(),
   1049             d_tag: VALID_FARM_D_TAG.to_string(),
   1050         }],
   1051     )
   1052     .expect_err("invalid area id");
   1053     assert!(matches!(err, EventEncodeError::InvalidField("area_id")));
   1054 
   1055     let err =
   1056         resource_area_stewards_list_set(VALID_AREA_D_TAG, [" "]).expect_err("empty steward entry");
   1057     assert!(matches!(
   1058         err,
   1059         EventEncodeError::EmptyRequiredField("entry.values")
   1060     ));
   1061 
   1062     let stewards =
   1063         resource_area_stewards_list_set(VALID_AREA_D_TAG, ["steward"]).expect("stewards list set");
   1064     assert_eq!(stewards.entries.len(), 1);
   1065     assert_eq!(stewards.entries[0].tag, "p");
   1066 
   1067     let err = resource_area_members_farms_list_set(
   1068         VALID_AREA_D_TAG,
   1069         vec![RadrootsFarmRef {
   1070             pubkey: " ".to_string(),
   1071             d_tag: VALID_FARM_D_TAG.to_string(),
   1072         }],
   1073     )
   1074     .expect_err("empty farm pubkey");
   1075     assert!(matches!(
   1076         err,
   1077         EventEncodeError::EmptyRequiredField("farm.pubkey")
   1078     ));
   1079 
   1080     let err = resource_area_members_farms_list_set(
   1081         VALID_AREA_D_TAG,
   1082         vec![RadrootsFarmRef {
   1083             pubkey: VALID_PUBKEY.to_string(),
   1084             d_tag: " ".to_string(),
   1085         }],
   1086     )
   1087     .expect_err("empty farm d_tag");
   1088     assert!(matches!(
   1089         err,
   1090         EventEncodeError::EmptyRequiredField("farm.d_tag")
   1091     ));
   1092 
   1093     let err = resource_area_members_farms_list_set(
   1094         VALID_AREA_D_TAG,
   1095         vec![RadrootsFarmRef {
   1096             pubkey: VALID_PUBKEY.to_string(),
   1097             d_tag: "invalid".to_string(),
   1098         }],
   1099     )
   1100     .expect_err("invalid farm d_tag");
   1101     assert!(matches!(err, EventEncodeError::InvalidField("farm.d_tag")));
   1102 
   1103     let err = resource_area_members_plots_list_set(
   1104         VALID_AREA_D_TAG,
   1105         vec![RadrootsPlotRef {
   1106             pubkey: " ".to_string(),
   1107             d_tag: VALID_PLOT_D_TAG.to_string(),
   1108         }],
   1109     )
   1110     .expect_err("empty plot pubkey");
   1111     assert!(matches!(
   1112         err,
   1113         EventEncodeError::EmptyRequiredField("plot.pubkey")
   1114     ));
   1115 
   1116     let err = resource_area_members_plots_list_set(
   1117         VALID_AREA_D_TAG,
   1118         vec![RadrootsPlotRef {
   1119             pubkey: VALID_PUBKEY.to_string(),
   1120             d_tag: " ".to_string(),
   1121         }],
   1122     )
   1123     .expect_err("empty plot d_tag");
   1124     assert!(matches!(
   1125         err,
   1126         EventEncodeError::EmptyRequiredField("plot.d_tag")
   1127     ));
   1128 
   1129     let err = resource_area_members_plots_list_set(
   1130         VALID_AREA_D_TAG,
   1131         vec![RadrootsPlotRef {
   1132             pubkey: VALID_PUBKEY.to_string(),
   1133             d_tag: "invalid".to_string(),
   1134         }],
   1135     )
   1136     .expect_err("invalid plot d_tag");
   1137     assert!(matches!(err, EventEncodeError::InvalidField("plot.d_tag")));
   1138 }
   1139 
   1140 #[test]
   1141 fn resource_harvest_cap_encode_paths() {
   1142     let tags = resource_harvest_cap_build_tags(&sample_resource_cap()).expect("resource cap tags");
   1143     assert!(
   1144         tags.iter()
   1145             .any(|tag| tag.first().map(|v| v.as_str()) == Some("category"))
   1146     );
   1147 
   1148     let mut cap = sample_resource_cap();
   1149     cap.product.category = None;
   1150     let tags = resource_harvest_cap_build_tags(&cap).expect("resource cap tags without category");
   1151     assert!(
   1152         !tags
   1153             .iter()
   1154             .any(|tag| tag.first().map(|v| v.as_str()) == Some("category"))
   1155     );
   1156 
   1157     let mut cap = sample_resource_cap();
   1158     cap.d_tag = " ".to_string();
   1159     let err = resource_harvest_cap_build_tags(&cap).expect_err("empty cap d_tag");
   1160     assert!(matches!(err, EventEncodeError::EmptyRequiredField("d_tag")));
   1161 
   1162     let mut cap = sample_resource_cap();
   1163     cap.d_tag = "invalid".to_string();
   1164     let err = resource_harvest_cap_build_tags(&cap).expect_err("invalid cap d_tag");
   1165     assert!(matches!(err, EventEncodeError::InvalidField("d_tag")));
   1166 
   1167     let mut cap = sample_resource_cap();
   1168     cap.product.key = " ".to_string();
   1169     let err = resource_harvest_cap_build_tags(&cap).expect_err("empty product key");
   1170     assert!(matches!(
   1171         err,
   1172         EventEncodeError::EmptyRequiredField("product.key")
   1173     ));
   1174 
   1175     let mut cap = sample_resource_cap();
   1176     cap.resource_area.pubkey = " ".to_string();
   1177     let err = resource_harvest_cap_build_tags(&cap).expect_err("empty resource_area pubkey");
   1178     assert!(matches!(
   1179         err,
   1180         EventEncodeError::EmptyRequiredField("resource_area.pubkey")
   1181     ));
   1182 
   1183     let mut cap = sample_resource_cap();
   1184     cap.resource_area.d_tag = " ".to_string();
   1185     let err = resource_harvest_cap_build_tags(&cap).expect_err("empty resource_area d_tag");
   1186     assert!(matches!(
   1187         err,
   1188         EventEncodeError::EmptyRequiredField("resource_area.d_tag")
   1189     ));
   1190 
   1191     let mut cap = sample_resource_cap();
   1192     cap.resource_area.d_tag = "invalid".to_string();
   1193     let err = resource_harvest_cap_build_tags(&cap).expect_err("invalid resource_area d_tag");
   1194     assert!(matches!(
   1195         err,
   1196         EventEncodeError::InvalidField("resource_area.d_tag")
   1197     ));
   1198 }
   1199 
   1200 #[test]
   1201 fn document_encode_paths() {
   1202     let tags = document_build_tags(&sample_document()).expect("document tags");
   1203     assert!(
   1204         tags.iter()
   1205             .any(|tag| tag.first().map(|v| v.as_str()) == Some("d"))
   1206     );
   1207     assert!(
   1208         tags.iter()
   1209             .any(|tag| tag.first().map(|v| v.as_str()) == Some("p"))
   1210     );
   1211     assert!(
   1212         tags.iter()
   1213             .any(|tag| tag.first().map(|v| v.as_str()) == Some("a"))
   1214     );
   1215     assert!(
   1216         tags.iter()
   1217             .any(|tag| tag.first().map(|v| v.as_str()) == Some("t"))
   1218     );
   1219 
   1220     let mut document = sample_document();
   1221     document.subject.address = None;
   1222     document.tags = None;
   1223     let tags = document_build_tags(&document).expect("document without optional tags");
   1224     assert!(
   1225         !tags
   1226             .iter()
   1227             .any(|tag| tag.first().map(|v| v.as_str()) == Some("a"))
   1228     );
   1229     assert!(
   1230         !tags
   1231             .iter()
   1232             .any(|tag| tag.first().map(|v| v.as_str()) == Some("t"))
   1233     );
   1234 
   1235     let mut document = sample_document();
   1236     document.d_tag = " ".to_string();
   1237     let err = document_build_tags(&document).expect_err("empty d_tag");
   1238     assert!(matches!(err, EventEncodeError::EmptyRequiredField("d_tag")));
   1239 
   1240     let mut document = sample_document();
   1241     document.doc_type = " ".to_string();
   1242     let err = document_build_tags(&document).expect_err("empty doc_type");
   1243     assert!(matches!(
   1244         err,
   1245         EventEncodeError::EmptyRequiredField("doc_type")
   1246     ));
   1247 
   1248     let mut document = sample_document();
   1249     document.title = " ".to_string();
   1250     let err = document_build_tags(&document).expect_err("empty title");
   1251     assert!(matches!(err, EventEncodeError::EmptyRequiredField("title")));
   1252 
   1253     let mut document = sample_document();
   1254     document.version = " ".to_string();
   1255     let err = document_build_tags(&document).expect_err("empty version");
   1256     assert!(matches!(
   1257         err,
   1258         EventEncodeError::EmptyRequiredField("version")
   1259     ));
   1260 
   1261     let mut document = sample_document();
   1262     document.subject.pubkey = " ".to_string();
   1263     let err = document_build_tags(&document).expect_err("empty subject pubkey");
   1264     assert!(matches!(
   1265         err,
   1266         EventEncodeError::EmptyRequiredField("subject.pubkey")
   1267     ));
   1268 
   1269     let mut document = sample_document();
   1270     document.subject.address = Some(" ".to_string());
   1271     let err = document_build_tags(&document).expect_err("empty subject address");
   1272     assert!(matches!(
   1273         err,
   1274         EventEncodeError::EmptyRequiredField("subject.address")
   1275     ));
   1276 
   1277     let mut document = sample_document();
   1278     document.d_tag = "invalid".to_string();
   1279     let err = document_build_tags(&document).expect_err("invalid d_tag");
   1280     assert!(matches!(err, EventEncodeError::InvalidField("d_tag")));
   1281 }
   1282 
   1283 #[test]
   1284 fn resource_harvest_cap_money_type_sanity() {
   1285     let money = RadrootsCoreMoney::new(decimal("1"), RadrootsCoreCurrency::USD);
   1286     assert_eq!(money.amount.to_string(), "1");
   1287 }